view release on metacpan or search on metacpan
Revision history for Perl extension APISchema
1.37 2018-02-08T10:31:50Z
- Allow `example: undef` and serve document
1.36 2017-12-25T12:45:24Z
- show enum values in type column
1.35 2017-12-13T14:30:28Z
- MockServer serves default status codes in definitions
1.34 2017-11-30T16:29:26Z
- Define prototype of type before calling it
lib/APISchema/Generator/Markdown.pm view on Meta::CPAN
use APISchema::Generator::Markdown::ExampleFormatter;
use APISchema::Generator::Markdown::ResourceResolver;
# cpan
use Text::MicroTemplate::DataSection qw();
sub new {
my ($class) = @_;
my $renderer = Text::MicroTemplate::DataSection->new(
escape_func => undef
);
bless {
renderer => $renderer,
map {
( $_ => $renderer->build_file($_) );
} qw(index toc route resource request response
request_example response_example),
}, $class;
}
lib/APISchema/Generator/Markdown/Formatter.pm view on Meta::CPAN
}
return join $bar, map { code($_) } @{$def->{enum}} if $def->{enum};
my $type = $def->{type};
if ($type) {
return sprintf '`%s`', $type unless ref $type;
return join $bar, map { code($_) } @{$type} if ref $type eq 'ARRAY';
}
return 'undefined';
}
sub json ($) {
my $x = shift;
if (ref $x eq 'SCALAR') {
if ($$x eq 1) {
$x = 'true';
} elsif ($$x eq 0) {
$x = 'false';
}
lib/APISchema/Generator/Markdown/Formatter.pm view on Meta::CPAN
}
sub content_type ($) {
my $type = shift;
return '-' unless length($type);
return "`$type`";
}
sub http_status ($) {
my $code = shift;
return undef unless $code;
return join(' ', $code, status_message($code));
}
sub http_status_code {
return _code http_status shift;
}
1;
lib/APISchema/Generator/Markdown/ResourceResolver.pm view on Meta::CPAN
return ([ $result{'[]'} ], 1) if $result{'[]'};
my @result;
for (keys %result) {
next unless $_ =~ /\A\[([0-9]+)\]\z/;
$result[$1] = $result{$_};
}
return (\@result, 1);
}
return (undef, 0);
}
sub properties {
my ($self, $resource) = @_;
return $self->_collect_properties([], $resource);
}
sub example {
my ($self, $resource) = @_;
my ($example) = $self->_collect_example([], $resource);
lib/APISchema/Validator.pm view on Meta::CPAN
sub _error_result { APISchema::Validator::Result->new_error(@_) }
sub _resolve_encoding {
my ($content_type, $encoding_spec) = @_;
# TODO handle charset?
$content_type = $content_type =~ s/\s*;.*$//r;
$encoding_spec //= DEFAULT_ENCODING_SPEC;
if (ref $encoding_spec) {
$encoding_spec = $encoding_spec->{$content_type};
return ( undef, { message => "Wrong content-type: $content_type" } )
unless $encoding_spec;
}
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);
lib/APISchema/Validator/Decoder.pm view on Meta::CPAN
}
my $JSON = JSON::XS->new->utf8;
sub json {
my ($self, $body) = @_;
return $JSON->decode($body);
}
sub url_parameter {
my ($self, $body) = @_;
return undef unless defined $body;
return url_params_mixed($body, 1);
}
1;
lib/APISchema/Validator/Result.pm view on Meta::CPAN
new => 1,
);
sub new_valid {
my ($class, @targets) = @_;
return $class->new(values => { map { ($_ => [1]) } @targets });
}
sub new_error {
my ($class, $target, $err) = @_;
return $class->new(values => { ( $target // '' ) => [ undef, $err] });
}
sub _values { shift->{values} // {} }
sub merge {
my ($self, $other) = @_;
$self->{values} = Hash::Merge::Simple::merge(
$self->_values,
$other->_values,
);
t/APISchema-Generator-Markdown-Formatter.t view on Meta::CPAN
package t::APISchema::Generator::Markdown::Formatter;
use lib '.';
use t::test;
use t::test::fixtures;
use APISchema::Generator::Markdown::Formatter ();
sub _type : Tests {
for my $case (
[{} => 'undefined'],
[{type => 'object'} => '`object`'],
[{type => ['object', 'number']} => '`"object"`|`"number"`'],
[{'$ref' => '#/resource/foo'} => '[`foo`](#resource-foo)'],
[{oneOf => [{ type =>'object'}, {type =>'number'}]} => '`object`|`number`'],
[{type => 'string', enum => ['a', 'b', 'c']} => '`"a"`|`"b"`|`"c"`'],
[{type => 'number', enum => [1, 2, 3]} => '`1`|`2`|`3`'],
) {
is APISchema::Generator::Markdown::Formatter::type($case->[0]), $case->[1], $case->[2] || $case->[1];
}
}
t/APISchema-Schema.t view on Meta::CPAN
}
sub instantiate : Tests {
my $schema = APISchema::Schema->new;
isa_ok $schema, 'APISchema::Schema';
}
sub resource : Tests {
my $schema = APISchema::Schema->new;
is $schema->get_resource_by_name('user'), undef;
cmp_deeply $schema->get_resources, [];
$schema->register_resource('user' => {
type => 'object',
properties => {
name => { type => 'string' },
age => { type => 'integer' },
},
required => ['name', 'age'],
t/APISchema-Schema.t view on Meta::CPAN
definition => {
type => 'object',
properties => {
name => { type => 'string' },
age => { type => 'integer' },
},
required => ['name', 'age'],
},
);
is $schema->get_resource_by_name('not_user'), undef;
cmp_deeply $schema->get_resources, [
$schema->get_resource_by_name('user'),
];
}
sub route : Tests {
subtest 'Basic' => sub {
my $schema = APISchema::Schema->new;
cmp_deeply $schema->get_routes, [];
t/APISchema-Schema.t view on Meta::CPAN
$schema->register_route();
is $schema->get_routes->[7]->title, 'empty_route(1)';
$schema->register_route();
is $schema->get_routes->[8]->title, 'empty_route(2)';
};
}
sub title_description : Tests {
my $schema = APISchema::Schema->new;
is $schema->title, undef;
is $schema->description, undef;
$schema->title('BMI');
is $schema->title, 'BMI';
$schema->description('The API to calculate BMI');
is $schema->description, 'The API to calculate BMI';
}
t/APISchema-Validator.t view on Meta::CPAN
}, $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' ];
}
};
t/Plack-Middleware-APISchema-RequestValidator.t view on Meta::CPAN
);
is $res->code, HTTP_UNPROCESSABLE_ENTITY;
cmp_deeply $res->content, json({
body => {
attribute => 'Valiemon::Attributes::Required',
position => '/$ref/required',
message => "Contents do not match resource 'figure'",
encoding => 'url_parameter',
actual => isa('HASH'),
# XXX: Hash order randomization
# actual => { "{\"weight\":50,\"height\":1.6}" => undef }
expected => $schema->get_resource_by_name('figure')->definition,
},
});
done_testing;
}
};
subtest 'when content-type is incorrect with forced encoding' => sub {
test_psgi $middleware => sub {
my $server = shift;
t/Plack-Middleware-APISchema-ResponseValidator.t view on Meta::CPAN
my $res = $server->(POST '/bmi');
is $res->code, 500;
is $res->header('X-Error-Cause'), 'Plack::Middleware::APISchema::ResponseValidator+Valiemon';
cmp_deeply $res->content, json({
body => {
attribute => 'Valiemon::Attributes::Required',
position => '/$ref/required',
message => "Contents do not match resource 'bmi'",
encoding => 'url_parameter',
actual => {
'{"value":19.5}' => undef,
},
expected => $schema->get_resource_by_name('bmi')->definition,
},
});
done_testing;
}
};
subtest 'when content-type is incorrect with forced encoding' => sub {
test_psgi $middleware => sub {
t/Plack-Middleware-APISchema-ResponseValidator.t view on Meta::CPAN
}
sub status : Tests {
my $schema = t::test::fixtures::prepare_status;
my $middleware_ok = Plack::Middleware::APISchema::ResponseValidator->new(schema => $schema);
$middleware_ok->wrap(sub {
my $env = shift;
my $req = Plack::Request->new($env);
if ($req->parameters->{success}) {
return [ 200, [ 'Content-Type' => 'text/plain', ], [ 'OK' ] ];
} elsif ($req->parameters->{undefined}) {
return [ 599, [ 'Content-Type' => 'application/json', ], [
encode_json({ status => 599, message => 'Something wrong' }),
] ];
} else {
return [ 400, [ 'Content-Type' => 'application/json', ], [
encode_json({ status => 400, message => 'Bad Request' }),
] ];
}
});
t/Plack-Middleware-APISchema-ResponseValidator.t view on Meta::CPAN
my $server = shift;
my $res = $server->(GET '/get');
is $res->code, 400;
done_testing;
};
};
subtest 'Undefined status' => sub {
test_psgi $middleware_ok => sub {
my $server = shift;
my $res = $server->(GET '/get?undefined=1');
is $res->code, 599;
done_testing;
};
};
my $middleware_ng = Plack::Middleware::APISchema::ResponseValidator->new(schema => $schema);
$middleware_ng->wrap(sub {
my $env = shift;
my $req = Plack::Request->new($env);
return [ 400, [ 'Content-Type' => 'text/plain', ], [ 'OK' ] ];
t/fixtures/example-null.def view on Meta::CPAN
title 'Example with null';
resource value => {
type => 'object',
description => 'the result',
properties => {
value => {
type => 'null',
description => 'The Value',
example => undef,
},
},
required => ['value'],
};