Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/strict_message'
Browse files Browse the repository at this point in the history
closes #15
  • Loading branch information
bbrtj committed Sep 6, 2024
2 parents dcc88fe + 15f694e commit 7ced193
Show file tree
Hide file tree
Showing 11 changed files with 121 additions and 49 deletions.
29 changes: 27 additions & 2 deletions lib/Form/Tiny/Error.pm
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,19 @@ sub default_error
return 'Unknown error';
}

sub get_error
{
my ($self) = @_;

return $self->error;
}

sub as_string
{
my ($self) = @_;

my $field = $self->field // 'general';
my $error = $self->error;
my $error = $self->get_error;
return "$field - $error";
}

Expand Down Expand Up @@ -79,12 +86,30 @@ sub as_string

package Form::Tiny::Error::IsntStrict;

use parent -norequire, 'Form::Tiny::Error';
use Moo;
use Types::Standard qw(Str);

extends 'Form::Tiny::Error';

has 'extra_field' => (
is => 'ro',
isa => Str,
required => 1,
);

sub default_error
{
return 'input data has unexpected fields';
}

sub get_error
{
my ($self) = @_;

my $field = $self->extra_field;
my $error = $self->error;
return "$field: $error";
}
}

{
Expand Down
4 changes: 2 additions & 2 deletions lib/Form/Tiny/Form.pm
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ sub add_error
if $error->has_field;

# unwrap nested form errors
$error = $error->error
$error = $error->get_error
if $error->isa('Form::Tiny::Error::NestedFormError');

push @{$self->errors}, $error;
Expand All @@ -258,7 +258,7 @@ sub errors_hash

my %ret;
for my $error (@{$self->errors}) {
push @{$ret{$error->field // ''}}, $error->error;
push @{$ret{$error->field // ''}}, $error->get_error;
}

return \%ret;
Expand Down
2 changes: 1 addition & 1 deletion lib/Form/Tiny/Manual/Cookbook.pod
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ C<message> and translate errors in C<after_error> hook:
my ($self, $error) = @_;

$error->set_error(
do_translate($error->error)
do_translate($error->get_error)
);
);

Expand Down
3 changes: 2 additions & 1 deletion lib/Form/Tiny/Meta.pm
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,8 @@ reference of parameters, which are the attributes specified above.
my $error = $meta->build_error($class => %params);
Builds an error of class C<"Form::Tiny::Class::$class"> and uses C<%params> to
contsruct it. If a custom message was defined for this type, it will be used.
contsruct it. If a custom message was defined for this type, it will be used
instead of any error passed in C<%params>.
=head3 run_hooks_for
Expand Down
13 changes: 12 additions & 1 deletion lib/Form/Tiny/Path.pm
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,21 @@ sub clone
return $self->new(path => [@{$self->path}], meta => [@{$self->meta}]);
}

sub prepend
{
my ($self, $meta, $key) = @_;
$key //= $array_marker
if $meta eq 'ARRAY';

unshift @{$self->path}, $key;
unshift @{$self->meta}, $meta;
return $self;
}

sub append
{
my ($self, $meta, $key) = @_;
$key = $array_marker
$key //= $array_marker
if $meta eq 'ARRAY';

push @{$self->path}, $key;
Expand Down
57 changes: 39 additions & 18 deletions lib/Form/Tiny/Plugin/Strict.pm
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use strict;
use warnings;

use Form::Tiny::Error;
use Form::Tiny::Path;

use parent 'Form::Tiny::Plugin';

Expand All @@ -29,31 +30,44 @@ has '_strict_blueprint' => (
sub _check_recursive
{
my ($self, $data, $blueprint) = @_;
return 0 unless defined $blueprint;

my $ref = ref $blueprint;

if ($ref eq 'ARRAY') {
return 0 unless ref $data eq 'ARRAY';

foreach my $value (@$data) {
return 0
unless $self->_check_recursive($value, $blueprint->[0]);
return [{path => Form::Tiny::Path->empty, error => 'unexpected'}]
unless defined $blueprint;

if (ref $blueprint eq 'ARRAY') {
return [{path => Form::Tiny::Path->empty, error => 'not an array'}]
unless ref $data eq 'ARRAY';

my @errors;
foreach my $key (0 .. $#$data) {
my $err = $self->_check_recursive($data->[$key], $blueprint->[0]);
push @errors, map {
$_->{path}->prepend(ARRAY => $key);
$_;
} @$err;
}

return \@errors;
}
elsif ($ref eq 'HASH') {
return 0 unless ref $data eq 'HASH';
elsif (ref $blueprint eq 'HASH') {
return [{path => Form::Tiny::Path->empty, error => 'not an object'}]
unless ref $data eq 'HASH';

my @errors;
for my $key (keys %$data) {
return 0
unless $self->_check_recursive($data->{$key}, $blueprint->{$key});
my $err = $self->_check_recursive($data->{$key}, $blueprint->{$key});
push @errors, map {
$_->{path}->prepend(HASH => $key);
$_;
} @$err;
}

return \@errors;
}
else {
# we're at leaf and no error occured - we're good.
}

return 1;
return [];
}

sub _check_strict
Expand All @@ -67,9 +81,16 @@ sub _check_strict
unless $self->is_dynamic;
}

my $strict = $self->_check_recursive($input, $blueprint);
if (!$strict) {
$obj->add_error($self->build_error(IsntStrict =>));
my @unexpected_field;
my $errors = $self->_check_recursive($input, $blueprint, \@unexpected_field);
foreach my $err (@$errors) {
$obj->add_error(
$self->build_error(
'IsntStrict',
extra_field => $err->{path}->join,
error => $err->{error},
)
);
}

return $input;
Expand Down
7 changes: 4 additions & 3 deletions t/031-adding-errors.t
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,14 @@ use Test::Exception;
my $form = TestForm->new(input => {});
ok !$form->valid;
is scalar @{$form->errors}, 3;
is $form->errors->[0]->error, 'error 1';
is $form->errors->[0]->get_error, 'error 1';
is $form->errors->[1]->field, 'field';
is $form->errors->[1]->error, 'error 2';
is $form->errors->[2]->error, 'error 3';
is $form->errors->[1]->get_error, 'error 2';
is $form->errors->[2]->get_error, 'error 3';

dies_ok {
$form->add_error(does_not_exist => 'error');
};

done_testing;

26 changes: 14 additions & 12 deletions t/060-strictness.t
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,29 @@ my @data = (
[1, {nested => {name => 1}}],
[1, {nested => {second => {name => 1}}}],
[1, {nested_form => {optional => "yes", int => 1}}],
[0, {nested => "not really"}],
[0, {nested => ["not really"]}],
[0, {nested => {second => 1}}],
[0, {nested_form => {int => 5, nothere => 1}}],
[0, {int => 3, arg2 => 15}],
[0, {arg2 => "more data"}],
[0, {not => {nested => "more data"}}],
[0, {nested => "not really"}, 'general - nested: not an object'],
[0, {nested => ["not really"]}, 'general - nested: not an object'],
[0, {nested => {second => 1}}, 'general - nested.second: not an object'],
[0, {nested_form => {int => 5, nothere => 1}}, 'nested_form - nothere: unexpected'],
[0, {int => 3, arg2 => 15}, 'general - arg2: unexpected'],
[0, {arg2 => "more data"}, 'general - arg2: unexpected'],
[0, {not => {nested => "more data"}}, 'general - not.nested: unexpected'],
[0, {array => [{}, {value => 'x'}]}, 'general - array.1.value: unexpected'],
[0, {not => {'*' => {test => 1}}}, 'general - not.\\*.test: unexpected'],
[0, {'is\\' => {test => 1}}, 'general - is\\\\.test: unexpected'],
);

for my $aref (@data) {
my ($result, $input) = @$aref;
my ($result, $input, $error) = @$aref;
my $form = TestForm->new(input => $input);
is !!$form->valid, !!$result, "validation output ok";
if ($form->valid && $result) {
is_deeply($form->fields, $input, "fields do match");
}
elsif (!$result) {
for (@{$form->errors}) {
isa_ok($_, "Form::Tiny::Error::IsntStrict");
}
note Dumper($form->errors) if @{$form->errors} > 1;
is scalar @{$form->errors}, 1, 'error count ok';
isa_ok($form->errors->[0], "Form::Tiny::Error::IsntStrict");
is '' . $form->errors->[0], $error, 'error string ok';
}
else {
note Dumper($form->errors);
Expand Down
3 changes: 2 additions & 1 deletion t/190-hooks.t
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,12 @@ for my $aref (@data) {
}
for my $error (@{$form->errors}) {
is($error->field, "name.*", "error namespace valid");
is($error->error, "error got overwritten", "error message ok");
is($error->get_error, "error got overwritten", "error message ok");
}

note Dumper($input);
note Dumper($form->errors);
}

done_testing();

11 changes: 6 additions & 5 deletions t/250-messages.t
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@ for my $error (@{$form->errors}) {

for ($error->field) {
if (/no_message/) {
$no_message_error = $error->error;
$no_message_error = $error->get_error;
}
elsif (/plain_message/) {
isnt $error->error, $no_message_error, 'error message is not default';
like $error->error, qr/just a string/, 'error message ok';
isnt $error->get_error, $no_message_error, 'error message is not default';
like $error->get_error, qr/just a string/, 'error message ok';
}
elsif (/stringified_message/) {
isnt $error->error, $no_message_error, 'error message is not default';
like $error->error, qr/it stringifies/, 'error message ok';
isnt $error->get_error, $no_message_error, 'error message is not default';
like $error->get_error, qr/it stringifies/, 'error message ok';
}
}
}
Expand All @@ -66,3 +66,4 @@ dies_ok {
};

done_testing();

15 changes: 12 additions & 3 deletions t/251-form-messages.t
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,22 @@ subtest 'testing invalid format message' => sub {
};

subtest 'testing strict message' => sub {
$form->set_input({required => 1, loose => 1});
$form->set_input({required => 1, loose => 1, nested => {loose => 1}});

ok !$form->valid, 'validation failed ok';
is_deeply $form->errors_hash, {
'' => ['strictmsg']

# ensure deterministic order of errors
my $errors = $form->errors_hash;
@{$errors->{''}} = sort @{$errors->{''}};

is_deeply $errors, {
'' => [
'loose: strictmsg',
'nested: strictmsg',
]
},
'errors ok';
};

done_testing();

0 comments on commit 7ced193

Please sign in to comment.