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
1.33 2017-11-30T14:39:09Z
- Resolve anyOf, allOf and oneOf keywords when serving Mock Server and API Document
1.32 2017-11-30T06:08:51Z
- MockServer always returns bytes
1.31 2017-11-29T02:30:47Z
- MockServer support `response => {body => 'bmi', encoding => 'json'}` syntax
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) 19xx name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the
appropriate parts of the General Public License. Of course, the
commands you use may be called something other than `show w' and `show
c'; they could even be mouse-clicks or menu items--whatever suits your
program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here a sample; alter the names:
[](https://travis-ci.org/hitode909/APISchema)
# NAME
APISchema - Schema for API
# SYNOPSIS
# bmi.def
resource figure => {
type => 'object',
description => 'Figure, which includes weight and height',
properties => {
weight => {
type => 'number',
description => 'Weight(kg)',
example => 50,
},
height => {
type => 'number',
description => 'Height(m)',
example => 1.6,
},
},
required => ['weight', 'height'],
};
resource bmi => {
type => 'object',
description => 'Body mass index',
properties => {
value => {
type => 'number',
description => 'bmi value',
example => 19.5,
},
},
required => ['value'],
};
POST '/bmi/' => {
title => 'BMI API',
description => 'This API calculates your BMI.',
eg/bmi.psgi view on Meta::CPAN
Sample Application
=head1 HOW TO USE
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"}
% curl -X POST -H "Content-type: application/json" -d '{"weight": 60}' http://localhost:5000/bmi
{"attribute":"Valiemon::Attributes::Required","position":"/required"}
You can read the document of API at L<http://localhost:5000/doc/>
lib/APISchema.pm view on Meta::CPAN
=head1 NAME
APISchema - Schema for API
=head1 SYNOPSIS
# bmi.def
resource figure => {
type => 'object',
description => 'Figure, which includes weight and height',
properties => {
weight => {
type => 'number',
description => 'Weight(kg)',
example => 50,
},
height => {
type => 'number',
description => 'Height(m)',
example => 1.6,
},
},
required => ['weight', 'height'],
};
resource bmi => {
type => 'object',
description => 'Body mass index',
properties => {
value => {
type => 'number',
description => 'bmi value',
example => 19.5,
},
},
required => ['value'],
};
POST '/bmi/' => {
title => 'BMI API',
description => 'This API calculates your BMI.',
lib/APISchema/Generator/Markdown.pm view on Meta::CPAN
},
);
} @$routes),
join('', map {
my $properties = $resolver->properties($_->definition);
$self->{resource}->($renderer, $resolver, $_, [ map { +{
path => $_,
definition => $properties->{$_},
} } sort keys %$properties ]);
} grep {
( $_->definition->{type} // '' ) ne 'hidden';
} @$resources),
);
}
1;
__DATA__
@@ index
? my ($schema, $toc_text, $routes_text, $resources_text) = @_;
# <?= $schema->title || '' ?>
lib/APISchema/Generator/Markdown.pm view on Meta::CPAN
@@ toc
? my ($routes, $resources) = @_;
- [Routes](#routes)
? for (@$routes) {
- [<?= $_->title ?>](#<?= anchor(route => $_->title) ?>) - <?= methods($_->method) ?> <?= $_->route ?>
? }
- [Resources](#resources)
? for (@$resources) {
? next if ( $_->definition->{type} // '' ) eq 'hidden';
- [<?= $_->title ?>](#<?= anchor(resource => $_->title) ?>)
? }
@@ route
? my ($route, $example, $text) = @_;
### <a name="<?= anchor(route => $route) ?>"></a> <?= $route->title ?>
?= $example->{req}
?= $example->{res}
?= $route->description || ''
lib/APISchema/Generator/Markdown.pm view on Meta::CPAN
@@ request
? my ($route, $req) = @_;
#### Request <?= methods($route->method) ?>
?= $req->{description} // ''
? if (scalar grep { $req->{$_} } qw(header parameter body)) {
|Part|Resource|Content-Type|Encoding|
|----|--------|------------|--------|
? for (qw(header parameter)) {
? next unless $req->{$_};
|<?= $_ ?>|<?= type($req->{$_}) ?>|-|-|
? } # for
? if ($req->{body}) {
? for (sort keys %{$req->{encoding}}) {
|body|<?= type($req->{body}) ?>|<?= content_type($_) ?>|<?= content_type($req->{encoding}->{$_}) ?>|
? } # for
? } # $req->{body}
? } # scalar keys %$req
@@ response
? my ($route, $code, $res) = @_;
#### Response <?= http_status_code($code) ?>
?= $res->{description} // ''
? if (scalar grep { $res->{$_} } qw(header parameter body)) {
|Part|Resource|Content-Type|Encoding|
|----|--------|------------|--------|
? for (qw(header)) {
? next unless $res->{$_};
|<?= $_ ?>|<?= type($res->{$_}) ?>|-|-|
? } # for
? if ($res->{body}) {
? for (sort keys %{$res->{encoding}}) {
|body|<?= type($res->{body}) ?>|<?= content_type($_) ?>|<?= content_type($res->{encoding}->{$_}) ?>|
? } # for
? } # $res->{body}
? } # scalar keys %$res
@@ resource
? my ($r, $resource, $properties) = @_;
### <a name="<?= anchor(resource => $resource) ?>"></a> `<?= $resource->title ?>` : <?= type($resource->definition) ?>
```javascript
<?= pretty_json $r->example($resource->definition) ?>
```
?= $resource->definition->{description} || ''
#### Properties
? if (scalar @$properties) {
|Property|Type|Default|Example|Restrictions|Description|
|--------|----|-------|-------|------------|-----------|
? for my $prop (@$properties) {
? my $def = $prop->{definition};
|`<?= $prop->{path} ?>` |<?= type($def) ?> |<?= code($def->{default}) ?> |<?= code($def->{example}, exists $def->{example}) ?> |<?= restriction($def) ?> |<?= desc($def->{description}) ?> |
? } # $prop
? } # scalar @$properties
lib/APISchema/Generator/Markdown/Formatter.pm view on Meta::CPAN
package APISchema::Generator::Markdown::Formatter;
use 5.014;
use strict;
use warnings;
# core
use Exporter qw(import);
our @EXPORT = qw(type json pretty_json code restriction desc anchor method methods content_type http_status http_status_code);
# cpan
use HTTP::Status qw(status_message);
use URI::Escape qw(uri_escape_utf8);
use JSON::XS ();
my $JSON = JSON::XS->new->canonical(1);
use constant +{
RESTRICTIONS => [qw(required max_items min_items max_length min_length maximum minimum pattern)],
SHORT_DESCRIPTION_LENGTH => 100,
};
sub type ($); # type has recursive call
sub type ($) {
my $def = shift;
my $bar = '|';
if (ref $def) {
for my $type (qw(oneOf anyOf allOf)) {
if (my $union = $def->{$type}) {
return join($bar, map { type($_) } @$union);
}
}
}
my $ref = ref $def ? $def->{'$ref'} : $def;
if ($ref) {
$ref = $ref =~ s!^#/resource/!!r;
my $ref_text = "`$ref`";
my $name = $ref =~ s!/.*$!!r;
$ref_text = sprintf('[%s](#%s)', $ref_text, anchor(resource => $name)) if $name;
return $ref_text;
}
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';
lib/APISchema/Generator/Markdown/Formatter.pm view on Meta::CPAN
return $method;
}
sub methods ($) {
my $method = shift;
return join ', ', map { _code($_) } @$method
if (ref $method || '') eq 'ARRAY';
return _code($method);
}
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;
lib/APISchema/Generator/Markdown/ResourceResolver.pm view on Meta::CPAN
new => 1,
ro => [qw(schema)],
);
sub _foreach_properties($$&) {
my ($name_path, $definition, $callback) = @_;
return unless (ref $definition || '') eq 'HASH';
if ($definition->{items}) {
my $items = $definition->{items};
my $type = ref $items || '';
if ($type eq 'HASH') {
$callback->([@$name_path, '[]'], $items);
} elsif ($type eq 'ARRAY') {
$callback->([@$name_path, "[$_]"], $items->{$_}) for (0..$#$items);
}
}
if ($definition->{properties}) {
my $items = $definition->{properties};
my $type = ref $items || '';
if ($type eq 'HASH') {
$callback->([@$name_path, $_], $items->{$_}) for keys %$items;
}
}
}
sub _property_name (@) {
my @name_path = @_;
return '.' . join '.', @name_path;
}
lib/APISchema/Generator/Markdown/ResourceResolver.pm view on Meta::CPAN
}
my $ref = $definition->{'$ref'};
if ($ref) {
$ref = $ref =~ s/^#//r;
my $def = JSON::Pointer->get($self->schema, $ref);
return ($self->_collect_example($path, $def), 1) if $def;
}
my %result;
my $type = $definition->{type} || '';
_foreach_properties($path, $definition, sub {
my ($example, $exists) = $self->_collect_example(@_);
unless ($exists) {
if (exists $_[1]->{default}) {
$example = $_[1]->{default};
$exists = 1;
}
}
$result{$_[0]->[-1]} = $example if $exists;
});
return (\%result, 1) if $type eq 'object';
if ($type eq 'array') {
return ([ $result{'[]'} ], 1) if $result{'[]'};
my @result;
for (keys %result) {
next unless $_ =~ /\A\[([0-9]+)\]\z/;
$result[$1] = $result{$_};
}
return (\@result, 1);
}
lib/APISchema/Validator.pm view on Meta::CPAN
sub for_response {
my $class = shift;
return $class->_new(@_, fetch_resource_method => 'canonical_response_resource');
}
sub _valid_result { APISchema::Validator::Result->new_valid(@_) }
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) };
lib/APISchema/Validator.pm view on Meta::CPAN
my $resource_root = $schema->get_resource_root;
my $resource_spec = $route->$method(
$resource_root,
$target->{status_code} ? [ $target->{status_code} ] : [],
[ @target_keys ],
);
@target_keys = grep { $resource_spec->{$_} } @target_keys;
my $body_encoding = $resource_spec->{body} && do {
my ($enc, $err) = _resolve_encoding(
$target->{content_type} // '',
$resource_spec->{encoding},
);
if ($err) {
return _error_result(body => $err);
}
$enc;
};
my $encoding = {
body => $body_encoding,
lib/Plack/Middleware/APISchema/RequestValidator.pm view on Meta::CPAN
my $validator = APISchema::Validator->for_request(
validator_class => $self->validator // DEFAULT_VALIDATOR_CLASS,
);
my $result = $validator->validate($route->name => {
header => +{ map {
my $field = lc($_) =~ s/[-]/_/gr;
( $field => $req->header($_) );
} $req->headers->header_field_names },
parameter => $env->{QUERY_STRING},
body => $req->content,
content_type => $req->content_type,
}, $self->schema);
my $errors = $result->errors;
my $status_code = $self->_resolve_status_code($result);
return [
$status_code,
[ 'Content-Type' => 'application/json' ],
[ encode_json_canonical($errors) ],
] if scalar keys %$errors;
lib/Plack/Middleware/APISchema/RequestValidator.pm view on Meta::CPAN
$self->{router} //= do {
my $generator = APISchema::Generator::Router::Simple->new;
$generator->generate_router($self->schema);
};
}
sub _resolve_status_code {
my ($self, $validation_result) = @_;
my $error_message = $validation_result->errors->{body}->{message} // '';
return $error_message =~ m/Wrong content-type/ ? HTTP_UNSUPPORTED_MEDIA_TYPE : HTTP_UNPROCESSABLE_ENTITY;
}
1;
lib/Plack/Middleware/APISchema/ResponseValidator.pm view on Meta::CPAN
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;
if (scalar keys %$errors) {
my $error_cause = join '+', __PACKAGE__, $validator_class;
@$res = (
500,
[ 'Content-Type' => 'application/json', 'X-Error-Cause' => $error_cause ],
[ encode_json_canonical($errors) ],
);
t/APISchema-DSL.t view on Meta::CPAN
};
};
subtest 'Validation should be returned' => sub {
lives_ok {
my $schema = APISchema::DSL::process {
title 'BMI API';
description 'The API to calculate BMI';
resource figure => {
type => 'object',
description => 'Figure, which includes weight and height',
properties => {
weight => {
type => 'number',
description => 'Weight(kg)',
example => 50,
},
height => {
type => 'number',
description => 'Height(m)',
example => 1.6,
},
},
required => ['weight', 'height'],
};
resource bmi => {
type => 'object',
description => 'Body mass index',
properties => {
value => {
type => 'number',
description => 'bmi value',
example => 19.5,
},
},
required => ['value'],
};
POST '/bmi' => {
title => 'BMI API',
description => 'This API calculates your BMI.',
t/APISchema-DSL.t view on Meta::CPAN
on_match => sub { 1 },
};
};
isa_ok $schema, 'APISchema::Schema';
is_deeply [ sort {
$a->title cmp $b->title;
} @{$schema->get_resources} ], [ {
title => 'bmi',
definition => {
type => 'object',
description => 'Body mass index',
properties => {
value => {
type => 'number',
description => 'bmi value',
example => 19.5,
},
},
required => ['value'],
},
}, {
title => 'figure',
definition => {
type => 'object',
description => 'Figure, which includes weight and height',
properties => {
weight => {
type => 'number',
description => 'Weight(kg)',
example => 50,
},
height => {
type => 'number',
description => 'Height(m)',
example => 1.6,
},
},
required => ['weight', 'height'],
},
} ];
is $schema->title, 'BMI API';
is $schema->description, 'The API to calculate BMI';
t/APISchema-DSL.t view on Meta::CPAN
isa_ok $schema, 'APISchema::Schema';
is $schema->title, 'BMI API';
is $schema->description, 'The API to calculate BMI';
is_deeply [ sort {
$a->title cmp $b->title;
} @{$schema->get_resources} ], [ {
title => 'bmi',
definition => {
type => 'object',
description => 'Body mass index',
properties => {
value => {
type => 'number',
description => 'bmi value',
example => 19.5,
},
},
required => ['value'],
},
}, {
title => 'figure',
definition => {
type => 'object',
description => 'Figure, which includes weight and height',
properties => {
weight => {
type => 'number',
description => 'Weight(kg)',
example => 50,
},
height => {
type => 'number',
description => 'Height(m)',
example => 1.6,
},
},
required => ['weight', 'height'],
},
} ];
my $routes = $schema->get_routes;
is scalar @$routes, 1;
t/APISchema-DSL.t view on Meta::CPAN
my $schema = APISchema::DSL::process {
include 't/fixtures/user.def';
};
isa_ok $schema, 'APISchema::Schema';
is $schema->title, decode_utf8('ã¦ã¼ã¶ã¼');
is $schema->description, decode_utf8('ã¦ã¼ã¶ã¼ã®å®ç¾©');
cmp_deeply $schema->get_resource_by_name('user')->{definition}, {
type => 'object',
description => decode_utf8('ã¦ã¼ã¶ã¼'),
properties => {
first_name => {
type => 'string',
description => decode_utf8('å§'),
example => decode_utf8('å°é£¼'),
},
last_name => {
type => 'string',
description => decode_utf8('å'),
example => decode_utf8('å¼¾'),
},
},
required => ['first_name', 'last_name'],
};
}
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-Resource.t view on Meta::CPAN
sub _require : Test(startup => 1) {
my ($self) = @_;
use_ok 'APISchema::Resource';
}
sub instantiate : Tests {
my $resource = APISchema::Resource->new(
title => 'Human',
definition => {
type => 'object',
properties => {
name => { type => 'string' },
age => { type => 'integer' },
},
required => ['name', 'age'],
},
);
cmp_deeply $resource, isa('APISchema::Resource') & methods(
title => 'Human',
definition => {
type => 'object',
properties => {
name => { type => 'string' },
age => { type => 'integer' },
},
required => ['name', 'age'],
},
);
}
t/APISchema-Schema.t view on Meta::CPAN
}
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'],
});
cmp_deeply $schema->get_resource_by_name('user'), isa('APISchema::Resource') & methods(
title => 'user',
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'),
];
t/APISchema-Validator.t view on Meta::CPAN
}
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 {
t/APISchema-Validator.t view on Meta::CPAN
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;
};
t/APISchema-Validator.t view on Meta::CPAN
[ ('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;
};
t/APISchema-Validator.t view on Meta::CPAN
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') ];
};
t/APISchema-Validator.t view on Meta::CPAN
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' => {
t/Plack-App-APISchema-Document.t view on Meta::CPAN
sub serve_document : Tests {
my $schema = t::test::fixtures::prepare_bmi;
my $app = Plack::App::APISchema::Document->new(schema => $schema)->to_app;
subtest 'when valid request' => sub {
test_psgi $app => sub {
my $server = shift;
my $res = $server->(GET '/');
is $res->code, 200;
is $res->header('content-type'), 'text/html; charset=utf-8';
like $res->content, qr{<h3 id="toc_8"><a name="resource-figure"></a> <code>figure</code> : <code>object</code></h3>};
done_testing;
}
};
}
sub mojibake : Tests {
my $schema = t::test::fixtures::prepare_author;
my $app = Plack::App::APISchema::Document->new(schema => $schema)->to_app;
subtest 'when valid request' => sub {
test_psgi $app => sub {
my $server = shift;
my $res = $server->(GET '/');
is $res->code, 200;
is $res->header('content-type'), 'text/html; charset=utf-8';
like $res->content, qr{td>èè
</td>};
done_testing;
}
};
}
sub inheritable : Tests {
my $schema = t::test::fixtures::prepare_bmi;
my $app = t::test::InheritedDocument->new(schema => $schema)->to_app;
subtest 'Document is inheritable' => sub {
test_psgi $app => sub {
my $server = shift;
my $res = $server->(GET '/');
is $res->code, 200;
is $res->header('content-type'), 'text/html; charset=utf-8';
like $res->content, qr{pink};
done_testing;
};
};
}
t/Plack-App-APISchema-MockServer.t view on Meta::CPAN
sub serve_document_bmi : Tests {
my $schema = t::test::fixtures::prepare_bmi;
my $app = Plack::App::APISchema::MockServer->new(schema => $schema)->to_app;
subtest 'when valid request' => sub {
test_psgi $app => sub {
my $server = shift;
my $res = $server->(POST '/bmi');
is $res->code, 200;
is $res->header('content-type'), 'application/json; charset=utf-8';
is $res->content, q!{"value":19.5}!;
}
};
subtest 'when invalid request' => sub {
test_psgi $app => sub {
my $server = shift;
my $res = $server->(POST '/notfound');
is $res->code, 404;
is $res->header('content-type'), 'text/plain; charset=utf-8';
is $res->content, q!not found!;
}
};
}
sub when_encoding_is_specified : Tests {
my $schema = t::test::fixtures::prepare_bmi;
$schema->register_route(
method => 'POST',
route => '/bmi_force_json',
t/Plack-App-APISchema-MockServer.t view on Meta::CPAN
response_resource => {
encoding => 'json',
body => 'bmi',
},
);
my $app = Plack::App::APISchema::MockServer->new(schema => $schema)->to_app;
test_psgi $app => sub {
my $server = shift;
my $res = $server->(POST '/bmi_force_json');
is $res->code, 200;
is $res->header('content-type'), 'application/json; charset=utf-8';
is $res->content, q!{"value":19.5}!;
}
}
sub with_wide_character : Tests {
my $schema = t::test::fixtures::prepare_author;
$schema->register_route(
method => 'GET',
route => '/author',
response_resource => {
t/Plack-App-APISchema-MockServer.t view on Meta::CPAN
body => 'author',
},
);
my $app = Plack::App::APISchema::MockServer->new(schema => $schema)->to_app;
test_psgi $app => sub {
my $server = shift;
my $res = $server->(GET '/author');
is $res->code, 200;
is $res->header('content-type'), 'application/json; charset=utf-8';
is $res->content, q!{"author_name":"èè
"}!;
};
}
sub one_of : Tests {
my $schema = t::test::fixtures::prepare_bmi;
$schema->register_resource(maybe_bmi => {
oneOf => [
{
type => 'object',
'$ref' => '#/resource/bmi',
},
{
type => 'null',
},
],
});
$schema->register_route(
method => 'GET',
route => '/maybe_bmi',
response_resource => {
encoding => 'json',
body => 'maybe_bmi',
},
);
my $app = Plack::App::APISchema::MockServer->new(schema => $schema)->to_app;
test_psgi $app => sub {
my $server = shift;
my $res = $server->(GET '/maybe_bmi');
is $res->code, 200;
is $res->header('content-type'), 'application/json; charset=utf-8';
is $res->content, q!{"value":19.5}!;
};
}
sub status_201 : Tests {
my $schema = t::test::fixtures::prepare_bmi;
$schema->register_route(
method => 'PUT',
route => '/put_bmi',
request_resource => {
t/Plack-Middleware-APISchema-RequestValidator.t view on Meta::CPAN
},
);
$schema->register_route(
method => 'POST',
route => '/bmi_by_parameter',
request_resource => {
parameter => 'figure',
},
);
$schema->register_resource(figure_header => {
type => 'object',
properties => {
'x_weight' => { type => 'number' },
'x_height' => { type => 'number' },
},
required => ['x_weight', 'x_height'],
});
$schema->register_route(
method => 'POST',
route => '/bmi_by_header',
request_resource => {
header => 'figure_header',
},
);
t/Plack-Middleware-APISchema-RequestValidator.t view on Meta::CPAN
cmp_deeply $res->content, json({
body => {
message => "Failed to parse json",
encoding => 'json',
},
});
done_testing;
}
};
subtest 'when content-type is incorrect' => sub {
test_psgi $middleware => sub {
my $server = shift;
my $content_type = 'application/x-www-form-urlencoded';
my $res = $server->(
POST '/bmi',
Content_Type => $content_type,
Content => encode_json({weight => 50, height => 1.6}),
);
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;
my $content_type = 'application/x-www-form-urlencoded';
my $res = $server->(
POST '/bmi_force_json',
Content_Type => $content_type,
Content => encode_json({weight => 50, height => 1.6}),
);
is $res->code, 200;
done_testing;
}
};
subtest 'when content-type is incorrect with strict content-type check' => sub {
test_psgi $middleware => sub {
my $server = shift;
my $content_type = 'application/x-www-form-urlencoded';
my $res = $server->(
POST '/bmi_strict',
Content_Type => $content_type,
Content => encode_json({weight => 50, height => 1.6}),
);
is $res->code, HTTP_UNSUPPORTED_MEDIA_TYPE;
cmp_deeply $res->content, json({
body => { message => "Wrong content-type: $content_type" },
});
done_testing;
}
};
subtest 'when valid request by parameter' => sub {
test_psgi $middleware => sub {
my $server = shift;
my $res = $server->(
POST '/bmi_by_parameter?weight=50&height=1.6',
t/Plack-Middleware-APISchema-RequestValidator.t view on Meta::CPAN
is $res->code, HTTP_UNPROCESSABLE_ENTITY;
cmp_deeply $res->content, json({
header => {
attribute => 'Valiemon::Attributes::Required',
position => '/$ref/required',
message => "Contents do not match resource 'figure_header'",
encoding => 'perl',
actual => {
content_length => 0,
x_weight => 50,
content_type => "application/x-www-form-urlencoded",
host => "localhost",
},
expected => $schema->get_resource_by_name('figure_header')->definition,
},
});
done_testing;
}
};
}
t/Plack-Middleware-APISchema-RequestValidator.t view on Meta::CPAN
Content => encode_json({
first_name => 'Bill',
last_name => [],
}),
);
is $res->code, HTTP_UNPROCESSABLE_ENTITY;
cmp_deeply $res->content, json({
body => {
actual => [],
attribute => 'Valiemon::Attributes::Type',
position => '/$ref/properties/last_name/type',
expected => $schema->get_resource_by_name('user')->definition->{properties}->{last_name},
encoding => 'json',
message => "Contents do not match resource 'user'",
},
});
done_testing;
};
};
}
t/Plack-Middleware-APISchema-ResponseValidator.t view on Meta::CPAN
);
$schema->register_route(
method => 'POST',
route => '/bmi_force_json',
response_resource => {
encoding => 'json',
body => 'bmi',
},
);
$schema->register_resource(bmi_header => {
type => 'object',
properties => {
'x_value' => { type => 'number' },
},
required => ['x_value'],
});
$schema->register_route(
method => 'POST',
route => '/bmi_by_header',
response_resource => {
header => 'bmi_header',
},
);
my $content_type;
my $json;
my $middleware = Plack::Middleware::APISchema::ResponseValidator->new(schema => $schema);
my $header = [];
$middleware->wrap(sub {
[200, [ 'Content-Type' => $content_type, @$header ], [ $json ] ]
});
subtest 'when valid response' => sub {
test_psgi $middleware => sub {
$content_type = 'application/json';
$json = encode_json({value => 19.5});
$header = [];
my $server = shift;
my $res = $server->(POST '/bmi');
is $res->code, 200;
done_testing;
}
};
subtest 'when invalid response' => sub {
test_psgi $middleware => sub {
$content_type = 'application/json';
$json = encode_json({value => 'aaa'});
$header = [];
my $server = shift;
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::Type",
position => '/$ref/properties/value/type',
expected => $schema->get_resource_by_name('bmi')->definition->{properties}->{value},
actual => 'aaa',
message => "Contents do not match resource 'bmi'",
encoding => 'json',
},
});
done_testing;
}
};
subtest 'when wrong content-type' => sub {
test_psgi $middleware => sub {
$content_type = 'text/plain';
$json = encode_json({value => 19.5});
$header = [];
my $server = shift;
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 => { message => "Wrong content-type: text/plain" },
});
done_testing;
}
};
subtest 'when response is not a JSON' => sub {
test_psgi $middleware => sub {
$content_type = 'application/json';
$json = 'aaa';
$header = [];
my $server = shift;
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 => {
message => "Failed to parse json",
encoding => 'json',
},
});
done_testing;
}
};
subtest 'when content-type is incorrect' => sub {
test_psgi $middleware => sub {
$content_type = 'application/x-www-form-urlencoded';
$json = encode_json({value => 19.5});
$header = [];
my $server = shift;
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',
t/Plack-Middleware-APISchema-ResponseValidator.t view on Meta::CPAN
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 {
$content_type = 'application/x-www-form-urlencoded';
$json = encode_json({value => 19.5});
$header = [];
my $server = shift;
my $res = $server->(POST '/bmi_force_json');
is $res->code, 200;
done_testing;
}
};
subtest 'when content-type is incorrect with strict content-type check' => sub {
test_psgi $middleware => sub {
$content_type = 'application/x-www-form-urlencoded';
$json = encode_json({value => 19.5});
my $server = shift;
my $res = $server->(POST '/bmi_strict');
is $res->code, 500;
is $res->header('X-Error-Cause'), 'Plack::Middleware::APISchema::ResponseValidator+Valiemon';
cmp_deeply $res->content, json({
body => { message => "Wrong content-type: $content_type" },
});
done_testing;
}
};
subtest 'other endpoints are not affected' => sub {
test_psgi $middleware => sub {
my $server = shift;
my $res = $server->(GET '/other/');
is $res->code, 200;
}
};
subtest 'when valid response by header' => sub {
test_psgi $middleware => sub {
$content_type = 'application/json';
$json = encode_json({});
$header = [ 'X-Value' => 19.5 ];
my $server = shift;
my $res = $server->(POST '/bmi_by_header');
is $res->code, 200;
done_testing;
}
};
subtest 'when invalid response by header' => sub {
test_psgi $middleware => sub {
$content_type = 'application/json';
$json = encode_json({});
$header = [ 'X-Foo' => 1 ];
my $server = shift;
my $res = $server->(POST '/bmi_by_header');
is $res->code, 500;
is $res->header('X-Error-Cause'), 'Plack::Middleware::APISchema::ResponseValidator+Valiemon';
cmp_deeply $res->content, json({
header => {
attribute => "Valiemon::Attributes::Required",
position => '/$ref/required',
message => "Contents do not match resource 'bmi_header'",
encoding => 'perl',
expected => $schema->get_resource_by_name('bmi_header')->definition,
actual => {
content_type => "application/json",
"x_foo" => 1,
},
},
});
done_testing;
}
};
}
sub status : Tests {
my $schema = t::test::fixtures::prepare_status;
t/Plack-Middleware-APISchema-ResponseValidator.t view on Meta::CPAN
});
subtest 'invalid response with utf8' => sub {
test_psgi $middleware => sub {
my $server = shift;
my $res = $server->(GET '/user');
is $res->code, 500;
cmp_deeply $res->content, json({
body => {
attribute => "Valiemon::Attributes::Type",
position => '/$ref/properties/last_name/type',
expected => $schema->get_resource_by_name('user')->definition->{properties}->{last_name},
actual => [],
message => "Contents do not match resource 'user'",
encoding => 'json',
},
});
done_testing;
};
};
}
t/fixtures/author.def view on Meta::CPAN
resource author => {
type => 'object',
description => 'èè
',
properties => {
author_name => {
type => 'string',
description => 'èè
',
example => 'èè
',
},
},
};
t/fixtures/bmi.def view on Meta::CPAN
title 'BMI API';
description 'The API to calculate BMI';
resource figure => {
type => 'object',
description => 'Figure, which includes weight and height',
properties => {
weight => {
type => 'number',
description => 'Weight(kg)',
example => 50,
},
height => {
type => 'number',
description => 'Height(m)',
example => 1.6,
},
},
required => ['weight', 'height'],
};
resource bmi => {
type => 'object',
description => 'Body mass index',
properties => {
value => {
type => 'number',
description => 'bmi value',
example => 19.5,
},
},
required => ['value'],
};
POST '/bmi' => {
title => 'BMI API',
description => 'This API calculates your BMI.',
t/fixtures/boolean.def view on Meta::CPAN
title 'Object with Boolean';
resource value => {
type => 'object',
description => 'the result',
properties => {
value => {
type => 'boolean',
description => 'The Value',
example => \1,
},
},
required => ['value'],
};
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'],
};
t/fixtures/family.def view on Meta::CPAN
title 'Family API';
description 'The API to define or exaplain a family';
resource person => {
type => 'object',
description => 'A person',
properties => {
name => {
type => 'string',
description => 'The name of the person',
example => 'Alice',
},
age => {
type => 'integer',
description => 'The age of the person',
example => 16,
},
},
required => ['name', 'age'],
};
resource target => {
type => 'object',
description => 'Target of retrieving information',
properties => {
name => { '$ref' => '#/resource/person/properties/name' },
},
};
resource parent => {
'$ref' => '#/resource/target',
description => 'Target of retrieving/defining children',
example => { name => 'Bob' },
};
resource people => {
type => 'array',
description => 'Some people',
items => {
'$ref' => '#/resource/person',
},
example => [ {
name => 'Alice',
age => 16,
}, {
name => 'Charlie',
age => 14,
} ],
};
resource result => {
type => 'object',
description => 'Result of an operation',
properties => {
status => {
enum => [ 'success', 'failure' ],
example => 'success',
},
message => {
type => 'string',
example => 'OK',
},
},
};
PUT '/person' => {
title => 'Person PUT API',
description => 'Define a new person',
destination => {},
request => 'person',
t/fixtures/status.def view on Meta::CPAN
title 'Status';
description 'Return various HTTP status';
resource none => {
type => 'object',
description => 'Empty object',
properties => {},
};
resource ok => {
type => 'string',
description => 'Success',
example => 'Succeeded!',
};
resource error => {
type => 'object',
description => 'Failure',
properties => {
status => {
type => 'number',
example => 400,
},
message => {
type => 'string',
example => 'Bad Request',
},
},
};
GET '/get' => {
title => 'Get API',
description => 'Get something',
destination => {},
request => 'none',
t/fixtures/syntax-error.def view on Meta::CPAN
title 'Incorrect API definition';
description 'API definition with a syntax error';
resource value => {
type => 'number',
description => 'value' # , missing here
example => 1,
};
GET '/value' => {
title => 'Value API',
description => 'Endpoint for test.',
response => 'value',
};
t/fixtures/user.def view on Meta::CPAN
title 'ã¦ã¼ã¶ã¼';
description 'ã¦ã¼ã¶ã¼ã®å®ç¾©';
resource user => {
type => 'object',
description => 'ã¦ã¼ã¶ã¼',
properties => {
first_name => {
type => 'string',
description => 'å§',
example => 'å°é£¼',
},
last_name => {
type => 'string',
description => 'å',
example => 'å¼¾',
},
},
required => ['first_name', 'last_name'],
};