APISchema
view release on metacpan - search on metacpan
view release on metacpan or search on metacpan
eg/bmi.psgi view on Meta::CPAN
Start the appliction.
carton exec -- plackup bmi.psgi
Then,
% curl -X POST -H "Content-type: application/json" -d '{"weight": 60, "height": '1.7'}' http://localhost:5000/bmi
{"value":20.7612456747405}
Requests and Reponses to the API are validated by Middlewares.
% curl -X POST -H "Content-type: application/json" -d 'hello' http://localhost:5000/bmi
{"attribute":"Valiemon::Attributes::Required","position":"/required"}
% curl -X POST -H "Content-type: application/json" -d '{"weight": 60, "height": 'a'}' http://localhost:5000/bmi
{"attribute":"Valiemon::Attributes::Required","position":"/required"}
% curl -X POST -H "Content-type: application/json" -d '{"weight": 60, "height": 'a'}' http://localhost:5000/bmi
{"attribute":"Valiemon::Attributes::Required","position":"/required"}
lib/APISchema/Validator.pm view on Meta::CPAN
my $method = $encoding_spec;
return ( undef, {
message => "Unknown decoding method: $method",
content_type => $content_type,
} )
unless APISchema::Validator::Decoder->new->can($method);
return ($method, undef);
}
sub _validate {
my ($validator_class, $decode, $target, $spec) = @_;
my $obj = eval { APISchema::Validator::Decoder->new->$decode($target) };
return { message => "Failed to parse $decode" } if $@;
my $validator = $validator_class->new($spec->definition);
my ($valid, $err) = $validator->validate($obj);
return {
attribute => $err->attribute,
position => $err->position,
expected => $err->expected,
actual => $err->actual,
message => "Contents do not match resource '@{[$spec->title]}'",
} unless $valid;
return; # avoid returning the last conditional value
}
sub validate {
my ($self, $route_name, $target, $schema) = @_;
my @target_keys = @{+TARGETS};
my $valid = _valid_result(@target_keys);
my $route = $schema->get_route_by_name($route_name)
or return $valid;
my $method = $self->fetch_resource_method;
my $resource_root = $schema->get_resource_root;
my $resource_spec = $route->$method(
lib/Plack/Middleware/APISchema/ResponseValidator.pm view on Meta::CPAN
$matched or return;
my $plack_res = Plack::Response->new(@$res);
my $body;
Plack::Util::foreach($res->[2] // [], sub { $body .= $_[0] });
my $validator_class = $self->validator // DEFAULT_VALIDATOR_CLASS;
my $validator = APISchema::Validator->for_response(
validator_class => $validator_class,
);
my $result = $validator->validate($route->name => {
status_code => $res->[0],
header => +{ map {
my $field = lc($_) =~ s/[-]/_/gr;
( $field => $plack_res->header($_) );
} $plack_res->headers->header_field_names },
body => $body,
content_type => scalar $plack_res->content_type,
}, $self->schema);
my $errors = $result->errors;
t/APISchema-Validator.t view on Meta::CPAN
map { $_ => 'figure' } @$keys
},
response_resource => {
encoding => { 'application/json' => 'json' },
map { $_ => 'bmi' } @$keys
},
);
return $schema;
}
sub validate_request : Tests {
subtest 'valid with emtpy schema' => sub {
my $schema = APISchema::Schema->new;
my $validator = APISchema::Validator->for_request;
my $result = $validator->validate('/endpoint' => {
header => { foo => 'bar' },
parameter => 'foo&bar',
body => '{"foo":"bar"}',
content_type => 'application/json',
}, $schema);
ok $result->is_valid;
};
subtest 'valid with empty target' => sub {
my $schema = _simple_route t::test::fixtures::prepare_bmi, [];
my $validator = APISchema::Validator->for_request;
my $result = $validator->validate('/endpoint' => {}, $schema);
ok $result->is_valid;
};
subtest 'valid with some target and schema' => sub {
my $schema = _simple_route t::test::fixtures::prepare_bmi, ['body'];
my $validator = APISchema::Validator->for_request;
my $result = $validator->validate('/endpoint' => {
body => encode_json({weight => 50, height => 1.6}),
content_type => 'application/json',
}, $schema);
ok $result->is_valid;
};
subtest 'invalid with missing property' => sub {
my $schema = _simple_route t::test::fixtures::prepare_bmi, ['body'];
my $validator = APISchema::Validator->for_request;
my $result = $validator->validate('/endpoint' => {
body => encode_json({weight => 50}),
content_type => 'application/json',
}, $schema);
ok !$result->is_valid;
is_deeply [ keys %{$result->errors} ], [ 'body' ];
is_deeply [ map { $_->{attribute} } values %{$result->errors} ],
[ ('Valiemon::Attributes::Required') ];
is_deeply [ map { $_->{encoding} } values %{$result->errors} ],
[ ('json') ];
};
subtest 'invalid without body' => sub {
for my $value ({}, '', undef) {
my $schema = _simple_route t::test::fixtures::prepare_bmi, ['body'];
my $validator = APISchema::Validator->for_request;
my $result = $validator->validate('/endpoint' => {
body => $value,
}, $schema);
ok ! $result->is_valid;
is_deeply [ keys %{$result->errors} ], [ 'body' ];
}
};
subtest 'invalid without parameter' => sub {
for my $value ({}, '', undef) {
my $schema = _simple_route t::test::fixtures::prepare_bmi, ['parameter'];
my $validator = APISchema::Validator->for_request;
my $result = $validator->validate('/endpoint' => {
parameter => $value,
}, $schema);
ok ! $result->is_valid;
is_deeply [ keys %{$result->errors} ], [ 'parameter' ];
}
};
subtest 'invalid with wrong encoding' => sub {
my $schema = _simple_route t::test::fixtures::prepare_bmi, ['body'];
my $validator = APISchema::Validator->for_request;
my $result = $validator->validate('/endpoint' => {
body => encode_json({weight => 50, height => 1.6}),
content_type => 'application/x-www-form-urlencoded',
}, $schema);
ok !$result->is_valid;
is_deeply [ keys %{$result->errors} ], [ 'body' ];
is_deeply [ map { $_->{attribute} } values %{$result->errors} ],
[ ('Valiemon::Attributes::Required') ];
is_deeply [ map { $_->{encoding} } values %{$result->errors} ],
[ ('url_parameter') ];
};
subtest 'invalid with invalid encoding' => sub {
my $schema = _invalid_encoding_route t::test::fixtures::prepare_bmi, ['body'];
my $validator = APISchema::Validator->for_request;
my $result = $validator->validate('/endpoint' => {
body => encode_json({weight => 50, height => 1.6}),
content_type => 'application/json',
}, $schema);
ok !$result->is_valid;
is_deeply [ keys %{$result->errors} ], [ 'body' ];
is_deeply [ map { $_->{message} } values %{$result->errors} ],
[ ('Unknown decoding method: hoge') ];
};
subtest 'valid with forced encoding' => sub {
my $schema = _forced_route t::test::fixtures::prepare_bmi, ['body'];
my $validator = APISchema::Validator->for_request;
my $result = $validator->validate('/endpoint' => {
body => encode_json({weight => 50, height => 1.6}),
content_type => 'application/x-www-form-urlencoded',
}, $schema);
ok $result->is_valid;
};
subtest 'valid with strict content-type check' => sub {
my $schema = _strict_route t::test::fixtures::prepare_bmi, ['body'];
my $validator = APISchema::Validator->for_request;
my $result = $validator->validate('/endpoint' => {
body => encode_json({weight => 50, height => 1.6}),
content_type => 'application/json',
}, $schema);
ok $result->is_valid;
};
subtest 'invalid with wrong content type' => sub {
my $schema = _strict_route t::test::fixtures::prepare_bmi, ['body'];
my $validator = APISchema::Validator->for_request;
my $content_type = 'application/x-www-form-urlencoded';
my $result = $validator->validate('/endpoint' => {
body => encode_json({weight => 50, height => 1.6}),
content_type => $content_type,
}, $schema);
ok !$result->is_valid;
is_deeply [ keys %{$result->errors} ], [ 'body' ];
is_deeply [ map { $_->{message} } values %{$result->errors} ],
[ ("Wrong content-type: $content_type") ];
};
subtest 'valid parameter' => sub {
my $schema = _simple_route t::test::fixtures::prepare_bmi, ['parameter'];
my $validator = APISchema::Validator->for_request;
my $result = $validator->validate('/endpoint' => {
parameter => 'weight=50&height=1.6',
}, $schema);
ok $result->is_valid;
};
subtest 'invalid parameter' => sub {
my $schema = _simple_route t::test::fixtures::prepare_bmi, ['parameter'];
my $validator = APISchema::Validator->for_request;
my $result = $validator->validate('/endpoint' => {
parameter => 'weight=50',
}, $schema);
ok !$result->is_valid;
is_deeply [ map { $_->{attribute} } values %{$result->errors} ],
[ ('Valiemon::Attributes::Required') ];
is_deeply [ map { $_->{encoding} } values %{$result->errors} ],
[ ('url_parameter') ];
};
subtest 'valid header' => sub {
my $schema = _simple_route t::test::fixtures::prepare_bmi, ['header'];
my $validator = APISchema::Validator->for_request;
my $result = $validator->validate('/endpoint' => {
header => { weight => 50, height => 1.6 },
}, $schema);
ok $result->is_valid;
};
subtest 'invalid header' => sub {
my $schema = _simple_route t::test::fixtures::prepare_bmi, ['header'];
my $validator = APISchema::Validator->for_request;
my $result = $validator->validate('/endpoint' => {
header => { weight => 50 },
}, $schema);
ok !$result->is_valid;
is_deeply [ keys %{$result->errors} ], [ 'header' ];
is_deeply [ map { $_->{attribute} } values %{$result->errors} ],
[ ('Valiemon::Attributes::Required') ];
is_deeply [ map { $_->{encoding} } values %{$result->errors} ],
[ ('perl') ];
};
subtest 'all valid' => sub {
my $schema = _simple_route t::test::fixtures::prepare_bmi, ['body', 'parameter', 'header'];
my $validator = APISchema::Validator->for_request;
my $result = $validator->validate('/endpoint' => {
header => { weight => 50, height => 1.6 },
parameter => 'weight=50&height=1.6',
body => encode_json({weight => 50, height => 1.6}),
content_type => 'application/json',
}, $schema);
ok $result->is_valid;
};
subtest 'many invalid' => sub {
my $schema = _simple_route t::test::fixtures::prepare_bmi, ['body', 'parameter', 'header'];
my $validator = APISchema::Validator->for_request;
my $result = $validator->validate('/endpoint' => {
header => { weight => 50 },
parameter => 'weight=50',
body => encode_json({weight => 50}),
content_type => 'application/json',
}, $schema);
ok !$result->is_valid;
is scalar keys %{$result->errors}, 3;
is_deeply [ sort keys %{$result->errors} ],
[ qw(body header parameter) ];
is_deeply [ map { $_->{attribute} } values %{$result->errors} ],
[ ('Valiemon::Attributes::Required') x 3 ];
is_deeply [ sort map { $_->{encoding} } values %{$result->errors} ],
[ ('json', 'perl', 'url_parameter') ];
};
}
sub validate_response : Tests {
subtest 'valid with emtpy schema' => sub {
my $schema = APISchema::Schema->new;
my $validator = APISchema::Validator->for_response;
my $result = $validator->validate('/endpoint' => {
header => { foo => 'bar' },
body => '{"foo":"bar"}',
content_type => 'application/json',
}, $schema);
ok $result->is_valid;
};
subtest 'valid with empty target' => sub {
my $schema = _simple_route t::test::fixtures::prepare_bmi, [];
my $validator = APISchema::Validator->for_response;
my $result = $validator->validate('/endpoint' => {}, $schema);
ok $result->is_valid;
};
subtest 'valid with some target and schema' => sub {
my $schema = _simple_route t::test::fixtures::prepare_bmi, ['body'];
my $validator = APISchema::Validator->for_response;
my $result = $validator->validate('/endpoint' => {
body => encode_json({value => 19.5}),
content_type => 'application/json',
}, $schema);
ok $result->is_valid;
};
subtest 'invalid with missing property' => sub {
my $schema = _simple_route t::test::fixtures::prepare_bmi, ['body'];
my $validator = APISchema::Validator->for_response;
my $result = $validator->validate('/endpoint' => {
body => encode_json({hoge => 'foo'}),
content_type => 'application/json',
}, $schema);
ok !$result->is_valid;
is_deeply [ keys %{$result->errors} ], [ 'body' ];
is_deeply [ map { $_->{attribute} } values %{$result->errors} ],
[ ('Valiemon::Attributes::Required') ];
is_deeply [ map { $_->{encoding} } values %{$result->errors} ],
[ ('json') ];
};
subtest 'invalid with wrong encoding' => sub {
my $schema = _simple_route t::test::fixtures::prepare_bmi, ['body'];
my $validator = APISchema::Validator->for_response;
my $result = $validator->validate('/endpoint' => {
body => encode_json({value => 19.5}),
content_type => 'application/x-www-form-urlencoded',
}, $schema);
ok !$result->is_valid;
is_deeply [ keys %{$result->errors} ], [ 'body' ];
is_deeply [ map { $_->{attribute} } values %{$result->errors} ],
[ ('Valiemon::Attributes::Required') ];
is_deeply [ map { $_->{encoding} } values %{$result->errors} ],
[ ('url_parameter') ];
};
subtest 'invalid with invalid encoding' => sub {
my $schema = _invalid_encoding_route t::test::fixtures::prepare_bmi, ['body'];
my $validator = APISchema::Validator->for_response;
my $result = $validator->validate('/endpoint' => {
body => encode_json({value => 19.5}),
content_type => 'application/json',
}, $schema);
ok !$result->is_valid;
is_deeply [ keys %{$result->errors} ], [ 'body' ];
is_deeply [ map { $_->{message} } values %{$result->errors} ],
[ ('Unknown decoding method: hoge') ];
};
subtest 'valid with forced encoding' => sub {
my $schema = _forced_route t::test::fixtures::prepare_bmi, ['body'];
my $validator = APISchema::Validator->for_response;
my $result = $validator->validate('/endpoint' => {
body => encode_json({value => 19.5}),
content_type => 'application/x-www-form-urlencoded',
}, $schema);
ok $result->is_valid;
};
subtest 'valid with strict content-type check' => sub {
my $schema = _strict_route t::test::fixtures::prepare_bmi, ['body'];
my $validator = APISchema::Validator->for_response;
my $result = $validator->validate('/endpoint' => {
body => encode_json({value => 19.5}),
content_type => 'application/json',
}, $schema);
ok $result->is_valid;
};
subtest 'invalid with wrong content type' => sub {
my $schema = _strict_route t::test::fixtures::prepare_bmi, ['body'];
my $validator = APISchema::Validator->for_response;
my $content_type = 'application/x-www-form-urlencoded';
my $result = $validator->validate('/endpoint' => {
body => encode_json({value => 19.5}),
content_type => $content_type,
}, $schema);
ok !$result->is_valid;
is_deeply [ keys %{$result->errors} ], [ 'body' ];
is_deeply [ map { $_->{message} } values %{$result->errors} ],
[ ("Wrong content-type: $content_type") ];
};
subtest 'valid header' => sub {
my $schema = _simple_route t::test::fixtures::prepare_bmi, ['header'];
my $validator = APISchema::Validator->for_response;
my $result = $validator->validate('/endpoint' => {
header => { value => 19.5 },
}, $schema);
ok $result->is_valid;
};
subtest 'invalid header' => sub {
my $schema = _simple_route t::test::fixtures::prepare_bmi, ['header'];
my $validator = APISchema::Validator->for_response;
my $result = $validator->validate('/endpoint' => {
header => {},
}, $schema);
ok !$result->is_valid;
is_deeply [ keys %{$result->errors} ], [ 'header' ];
is_deeply [ map { $_->{attribute} } values %{$result->errors} ],
[ ('Valiemon::Attributes::Required') ];
is_deeply [ map { $_->{encoding} } values %{$result->errors} ],
[ ('perl') ];
};
subtest 'all valid' => sub {
my $schema = _simple_route t::test::fixtures::prepare_bmi, ['body', 'header'];
my $validator = APISchema::Validator->for_response;
my $result = $validator->validate('/endpoint' => {
header => { value => 19.5 },
body => encode_json({value => 19.5}),
content_type => 'application/json',
}, $schema);
ok $result->is_valid;
};
subtest 'many invalid' => sub {
my $schema = _simple_route t::test::fixtures::prepare_bmi, ['body', 'header'];
my $validator = APISchema::Validator->for_response;
my $result = $validator->validate('/endpoint' => {
header => {},
body => encode_json({}),
content_type => 'application/json',
}, $schema);
ok !$result->is_valid;
is scalar keys %{$result->errors}, 2;
is_deeply [ sort keys %{$result->errors} ],
[ qw(body header) ];
is_deeply [ map { $_->{attribute} } values %{$result->errors} ],
[ ('Valiemon::Attributes::Required') x 2 ];
is_deeply [ sort map { $_->{encoding} } values %{$result->errors} ],
[ ('json', 'perl') ];
};
subtest 'valid referenced resource' => sub {
my $schema = _forced_route t::test::fixtures::prepare_family, ['body'];
my $validator = APISchema::Validator->for_response;
my $result = $validator->validate('Children GET API' => {
body => encode_json([ {
name => 'Alice',
age => 16,
}, {
name => 'Charlie',
age => 14,
} ]),
content_type => 'application/json',
}, $schema);
ok $result->is_valid;
};
subtest 'invalid referenced resource' => sub {
my $schema = _forced_route t::test::fixtures::prepare_family, ['body'];
my $validator = APISchema::Validator->for_response;
my $result = $validator->validate('Children GET API' => {
body => encode_json([ {
name => 'Alice',
age => 16,
}, {
age => 14,
} ]),
content_type => 'application/json',
}, $schema);
ok !$result->is_valid;
};
SKIP: {
skip 'Recursive dereference is not implemented in Valiemon', 2;
subtest 'valid recursively referenced resource' => sub {
my $schema = _forced_route t::test::fixtures::prepare_family, ['body'];
my $validator = APISchema::Validator->for_request;
my $result = $validator->validate('Children GET API' => {
parameter => 'name=Bob',
}, $schema);
ok $result->is_valid;
};
subtest 'invalid recursively referenced resource' => sub {
my $schema = _forced_route t::test::fixtures::prepare_family, ['body'];
my $validator = APISchema::Validator->for_request;
my $result = $validator->validate('Children GET API' => {
parameter => 'person=Bob',
}, $schema);
ok !$result->is_valid;
};
};
}
sub status : Tests {
my $schema = t::test::fixtures::prepare_status;
my $validator = APISchema::Validator->for_response;
subtest 'Status 200 with valid body' => sub {
my $result = $validator->validate('Get API' => {
status_code => 200,
body => '200 OK',
}, $schema);
ok $result->is_valid;
};
subtest 'Status 200 with invalid body' => sub {
my $result = $validator->validate('Get API' => {
status_code => 200,
body => { status => 200, message => 'OK' },
}, $schema);
ok !$result->is_valid;
};
subtest 'Status 400 with valid body' => sub {
my $result = $validator->validate('Get API' => {
status_code => 400,
body => encode_json({ status => 400, message => 'Bad Request' }),
}, $schema);
ok $result->is_valid;
};
subtest 'Status 400 with invalid body' => sub {
my $result = $validator->validate('Get API' => {
status_code => 400,
body => '400 Bad Request',
}, $schema);
ok !$result->is_valid;
};
subtest 'Undefined status' => sub {
my $result = $validator->validate('Get API' => {
status_code => 599,
body => { foo => 'bar' },
}, $schema);
ok $result->is_valid;
};
}
view all matches for this distributionview release on metacpan - search on metacpan
( run in 1.260 second using v1.00-cache-2.02-grep-82fe00e-cpan-24a475fd873 )