view release on metacpan or search on metacpan
3.36 2020-09-22T09:42:10+0900
- Fix writeOnly handling OpenAPI v3 #191
3.35 2020-08-11T11:32:59+0900
- Add support for v3 object parameters #184
Contributor: SebMourlhou
- Add support for passing in custom spec to $c->openapi->render_spec #189
- Fix handling 404 and 501 in v3 #179
- Fix issue when "nullable" is stored inside JSON::Validator::Ref #183
- Fix $c->openapi->validate helper #187
3.34 2020-08-05T16:36:41+0900
- Can now set custom status code from a security callback #186
Contributor: Stephan Hradek
3.33 2020-06-08T15:28:12+0900
- Forgot to bump JSON::Validator to 4.00
3.32 2020-06-08T10:42:08+0900
- Compatible with JSON::Validator 4.00
- Fix allowing regular requests with "openapi_cors_allowed_origins" #103
2.10 2019-01-25T12:49:55+0900
- Add "plugins" as a documented feature for register()
- Add Mojolicious::Plugin::OpenAPI::SpecRenderer
- Add the possibility to turn off automatic rendering of specification
using OPTIONS and from /:basePath route
- Add EXPERIMENTAL "openapi_routes_added" hook
- Add support for Preflight CORS requests #99
- Fix Simple CORS requests with "GET" and no Content-Type #99
- Fix writing a list of headers back after validated
- Marked $c->openapi->simple_cors as DEPRECATED
2.09 2019-01-21T09:51:56+0900
- Using formats from JSON::Validator 3.04
2.08 2019-01-07T10:00:52+0900
- Fix Data::Validate::IP is an optional module for the test suite #100
- Bumping JSON::Validator to 3.01
2.07 2018-12-15T11:50:30+0900
0.12 2016-08-10T21:16:54+0200
- Add support for $c->render(openapi => $data);
- Started DEPRECATING $c->reply->openapi()
0.11 2016-08-09T13:35:16+0200
- Add support for retrieving the complete API spec
- Improved tutorial
0.10 2016-08-07T22:16:38+0200
- Add $c->openapi->validate()
- Deprecated $c->openapi->invalid_input()
- Fix validating YAML specifications #3 #4
Contributor: Ilya Rassadin
0.09 2016-08-04T09:30:23+0200
- Add basic support for rendering spec as HTML
- Add check for $ref in the right place in the input specification
Contributor: Lari Taskula
0.08 2016-07-29T14:33:14+0200
- Fix "false" must be false and not true
- Make sure 404 is returned as default format and not html
0.04 2016-07-25T15:03:31+0200
- Fix setting default values in JSON::Validator::OpenAPI 0.76
- Fix registering correct HTTP method for action in a class
0.03 2016-07-25T11:25:43+0200
- Add openapi.invalid_input helper
- Add Mojolicious::Plugin::OpenAPI::Guides::Tutorial
- Remove openapi.validate helper
- Remove openapi.input helper
- Will store validated data into $c->validation->output
0.02 2016-06-11T07:32:51-0700
- Improved documentation
- Add support for MOJO_OPENAPI_LOG_LEVEL=error
0.01 2016-06-10T19:34:35-0700
- Add logging of request/response errors
- Add rendering of API spec from base URL
- Exceptions returns structured JSON data instead of HTML
- Making an improved version of Mojolicious::Plugin::Swagger2
t/v2-collectionformat.t
t/v2-defaults.t
t/v2-discriminator.t
t/v2-file.t
t/v2-formats.t
t/v2-headers.t
t/v2-id-prop.t
t/v2-readonly.t
t/v2-swagger.t
t/v2-tutorial.t
t/v2-validate-schema.t
t/v3-basic.t
t/v3-body.t
t/v3-bundle.t
t/v3-defaults.t
t/v3-file.t
t/v3-invalid_file_refs.t
t/v3-invalid_file_refs_no_path.t
t/v3-nullable.t
t/v3-style-array.t
t/v3-style-object.t
lib/Mojolicious/Plugin/OpenAPI.pm view on Meta::CPAN
if (my $class = $config->{version_from_class} // ref $app) {
$self->validator->data->{info}{version} = sprintf '%s', $class->VERSION if $class->VERSION;
}
my $errors = $config->{skip_validating_specification} ? [] : $self->validator->errors;
die @$errors if @$errors;
unless ($app->defaults->{'openapi.base_paths'}) {
$app->helper('openapi.spec' => \&_helper_get_spec);
$app->helper('openapi.valid_input' => \&_helper_valid_input);
$app->helper('openapi.validate' => \&_helper_validate);
$app->helper('reply.openapi' => \&_helper_reply);
$app->hook(before_render => \&_before_render);
$app->renderer->add_handler(openapi => \&_render);
}
$self->{log_level} = $ENV{MOJO_OPENAPI_LOG_LEVEL} || $config->{log_level} || 'warn';
$self->_build_route($app, $config);
# This plugin is required
my @plugins = (Mojolicious::Plugin::OpenAPI::Parameters->new->register($app, $config));
lib/Mojolicious/Plugin/OpenAPI.pm view on Meta::CPAN
return $c->reply->asset($output);
}
push @args, status => $status if $status;
return $c->render(@args, openapi => $output);
}
sub _helper_valid_input {
my $c = shift;
return undef if $c->res->code;
return $c unless my @errors = _helper_validate($c);
$c->stash(status => 400)
->render(data => $c->openapi->build_response_body({errors => \@errors, status => 400}));
return undef;
}
sub _helper_validate {
my $c = shift;
my $self = _self($c);
my @errors = $self->validator->validate_request([@{$c->stash}{qw(openapi.method openapi.path)}],
$c->openapi->build_schema_request);
$c->openapi->coerce_request_parameters(
delete $c->stash->{'openapi.evaluated_request_parameters'});
return @errors;
}
sub _log {
my ($self, $c, $dir) = (shift, shift, shift);
my $log_level = $self->{log_level};
lib/Mojolicious/Plugin/OpenAPI.pm view on Meta::CPAN
my $method_path_status = [@$stash{qw(openapi.method openapi.path)}, $status];
my $op_spec
= $method_path_status->[0] && $self->validator->parameters_for_response($method_path_status);
my @errors;
delete $args->{encoding};
$args->{status} = $status;
$stash->{format} ||= 'json';
if ($op_spec) {
@errors = $self->validator->validate_response($method_path_status,
$c->openapi->build_schema_response);
$c->openapi->coerce_response_parameters(
delete $stash->{'openapi.evaluated_response_parameters'});
$args->{status} = $errors[0]->path eq '/header/Accept' ? 400 : 500 if @errors;
}
elsif (ref $stash->{openapi} eq 'HASH' and ref $stash->{openapi}{errors} eq 'ARRAY') {
$args->{status} ||= $stash->{openapi}{status};
@errors = @{$stash->{openapi}{errors}};
}
else {
lib/Mojolicious/Plugin/OpenAPI.pm view on Meta::CPAN
{
"paths": {
"/pets": {
"get": {
// This datastructure is returned by default
}
}
}
}
=head2 openapi.validate
@errors = $c->openapi->validate;
Used to validate a request. C<@errors> holds a list of
L<JSON::Validator::Error> objects or empty list on valid input.
Note that this helper is only for customization. You probably want
L</openapi.valid_input> in most cases.
=head2 openapi.valid_input
$c = $c->openapi->valid_input;
Returns the L<Mojolicious::Controller> object if the input is valid or
lib/Mojolicious/Plugin/OpenAPI.pm view on Meta::CPAN
for my $route (@$routes) {
...
}
});
This hook is EXPERIMENTAL and subject for change.
=head1 RENDERER
This plugin register a new handler called C<openapi>. The special thing about
this handler is that it will validate the data before sending it back to the
user agent. Examples:
$c->render(json => {foo => 123}); # without validation
$c->render(openapi => {foo => 123}); # with validation
This handler will also use L</renderer> to format the output data. The code
below shows the default L</renderer> which generates JSON data:
$app->plugin(
OpenAPI => {
lib/Mojolicious/Plugin/OpenAPI.pm view on Meta::CPAN
Holds either a L<JSON::Validator::Schema::OpenAPIv2> or a
L<JSON::Validator::Schema::OpenAPIv3> object.
=head1 METHODS
=head2 register
$openapi = $openapi->register($app, \%config);
$openapi = $app->plugin(OpenAPI => \%config);
Loads the OpenAPI specification, validates it and add routes to
L<$app|Mojolicious>. It will also set up L</HELPERS> and adds a
L<before_render|Mojolicious/before_render> hook for auto-rendering of error
documents. The return value is the object instance, which allow you to access
the L</ATTRIBUTES> after you load the plugin.
C<%config> can have:
=head3 coerce
See L<JSON::Validator/coerce> for possible values that C<coerce> can take.
lib/Mojolicious/Plugin/OpenAPI/Cors.pm view on Meta::CPAN
"x-mojo-to": "user#add_post",
"responses": {
"200": { "description": "Add a new post.", "schema": { "type": "object" } }
}
}
}
The special part can be found in the "OPTIONS" request It has the C<x-mojo-to>
key set to "#openapi_plugin_cors_exchange". This will enable
L<Mojolicious::Plugin::OpenAPI::Cors> to take over the route and add a custom
callback to validate the input headers using regular OpenAPI rules and respond
with a "200 OK" and the default headers as listed under
L</openapi.cors_exchange> if the input is valid. The only extra part that needs
to be done in the C<add_post()> action is this:
sub add_post {
my $c = shift->openapi->valid_input or return;
# Need to respond with a "Access-Control-Allow-Origin" header if
# the input "Origin" header was validated
$c->res->headers->access_control_allow_origin($c->req->headers->origin)
if $c->req->headers->origin;
# Do the rest of your custom logic
$c->respond(openapi => {});
}
=head2 Custom exchange
If you need full control, you must pass a callback to
L</openapi.cors_exchange>:
package MyApp::Controller::User;
sub get_user {
# Validate incoming CORS request with _validate_cors()
my $c = shift->openapi->cors_exchange("_validate_cors")->openapi->valid_input or return;
# Will only run this part if both the cors_exchange and valid_input was
# successful.
$c->render(openapi => {user => {}});
}
# This method must return undef on success. Any true value will be used as an error.
sub _validate_cors {
my $c = shift;
my $req_h = $c->req->headers;
my $res_h = $c->res->headers;
# The following "Origin" header check is the same for both simple and
# preflighted.
return "/Origin" unless $req_h->origin =~ m!^https?://whatever.example.com!;
# The following checks are only valid if preflighted...
lib/Mojolicious/Plugin/OpenAPI/Cors.pm view on Meta::CPAN
the "Origin" header in case the default
L</openapi_cors_default_exchange_callback> is used. Examples:
$app->defaults(openapi_cors_allowed_origins => [qr{^https?://whatever.example.com}]);
$c->stash(openapi_cors_allowed_origins => [qr{^https?://whatever.example.com}]);
=head2 openapi_cors_default_exchange_callback
This value holds a default callback that will be used by
L</openapi.cors_exchange>, unless you pass on a C<$callback>. The default
provided by this plugin will simply validate the C<Origin> header against
L</openapi_cors_allowed_origins>.
Here is an example to allow every "Origin"
$app->defaults(openapi_cors_default_exchange_callback => sub {
my $c = shift;
$c->res->headers->header("Access-Control-Allow-Origin" => "*");
return undef;
});
lib/Mojolicious/Plugin/OpenAPI/Cors.pm view on Meta::CPAN
=head1 HELPERS
=head2 openapi.cors_exchange
$c = $c->openapi->cors_exchange($callback);
$c = $c->openapi->cors_exchange("MyApp::cors_validator");
$c = $c->openapi->cors_exchange("_some_controller_method");
$c = $c->openapi->cors_exchange(sub { ... });
$c = $c->openapi->cors_exchange;
Used to validate either a simple CORS request, preflighted CORS request or a
real request. It will be called as soon as the "Origin" request header is seen.
The C<$callback> will be called with the current L<Mojolicious::Controller>
object and must return an error or C<undef()> on success:
my $error = $callback->($c);
The C<$error> must be in one of the following formats:
=over 2
lib/Mojolicious/Plugin/OpenAPI/Guides/OpenAPIv2.pod view on Meta::CPAN
# You might want to introspect the specification for the current route
my $spec = $c->openapi->spec;
unless ($spec->{'x-opening-hour'} == (localtime)[2]) {
return $c->render(openapi => [], status => 498);
}
my $age = $c->param("age");
my $body = $c->req->json;
# $output will be validated by the OpenAPI spec before rendered
my $output = {pets => [{name => "kit-e-cat"}]};
$c->render(openapi => $output);
}
1;
The input will be validated using
L<Mojolicious::Plugin::OpenAPI/openapi.valid_input> while the output is
validated through then L<openapi|Mojolicious::Plugin::OpenAPI/RENDERER>
handler.
=head2 Route names
Routes will get its name from either L</x-mojo-name> or from L</operationId> if
defined in the specification.
The route name can also be used the other way around, to find already defined
routes. This is especially useful for L<Mojolicious::Lite> apps.
lib/Mojolicious/Plugin/OpenAPI/Guides/OpenAPIv3.pod view on Meta::CPAN
# You might want to introspect the specification for the current route
my $spec = $c->openapi->spec;
unless ($spec->{'x-opening-hour'} == (localtime)[2]) {
return $c->render(openapi => [], status => 498);
}
my $age = $c->param("age");
my $body = $c->req->json;
# $output will be validated by the OpenAPI spec before rendered
my $output = {pets => [{name => "kit-e-cat"}]};
$c->render(openapi => $output);
}
1;
The input will be validated using
L<Mojolicious::Plugin::OpenAPI/openapi.valid_input> while the output is
validated through then L<openapi|Mojolicious::Plugin::OpenAPI/RENDERER>
handler.
=head2 Route names
Routes will get its name from either L</x-mojo-name> or from L</operationId> if
defined in the specification.
The route name can also be used the other way around, to find already defined
routes. This is especially useful for L<Mojolicious::Lite> apps.
lib/Mojolicious/Plugin/OpenAPI/Parameters.pm view on Meta::CPAN
application, required by L<Mojolicious::Plugin::OpenAPI>. These helpers can be
redefined in case you have special needs.
=head1 HELPERS
=head2 openapi.build_response_body
$bytes = $c->openapi->build_response_body(Mojo::Asset->new);
$bytes = $c->openapi->build_response_body($data);
Takes validated data and turns it into bytes that will be used as HTTP response
body. This method is useful to override, in case you want to render some other
structure than JSON.
=head2 openapi.build_schema_request
$hash_ref = $c->openapi->build_schema_request;
Builds input data for L<JSON::Validator::Schema::OpenAPIv2/validate_request>.
=head2 openapi.build_schema_response
$hash_ref = $c->openapi->build_schema_response;
Builds input data for L<JSON::Validator::Schema::OpenAPIv2/validate_response>.
=head2 openapi.coerce_request_parameters
$c->openapi->coerce_request_parameters(\@evaluated_parameters);
Used by L<Mojolicious::Plugin::OpenAPI> to write the validated data back to
L<Mojolicious::Controller/req> and
L<Mojolicious::Validator::Validation/output>.
=head2 openapi.coerce_response_parameters
$c->openapi->coerce_response_parameters(\@evaluated_parameters);
Used by L<Mojolicious::Plugin::OpenAPI> to write the validated data to
L<Mojolicious::Controller/res>.
=head2 openapi.parse_request_body
$hash_ref = $c->openapi->parse_request_body;
Returns a structure representing the request body. The default is to parse the
input as JSON:
{content_type => "application/json", exists => !!$c->req->body_size, value => $c->req->json};
t/basic-bundle.t view on Meta::CPAN
use JSON::Validator::Schema::OpenAPIv2;
# This test mimics what Mojolicious::Plugin::OpenAPI does when loading
# a spec from a file that Mojolicious locates with a '..'
# It checks that a $ref to something that's under /responses doesn't
# get picked as remote, or if so that it doesn't make an invalid spec!
my $validator = JSON::Validator::Schema::OpenAPIv2->new;
my $bundlecheck_path
= path(path(__FILE__)->dirname, 'spec', File::Spec->updir, 'spec', 'bundlecheck.json');
my $bundled = $validator->data($bundlecheck_path)->bundle->data;
eval { JSON::Validator->new->load_and_validate_schema($bundled) };
is $@, '', 'bundled schema is valid';
done_testing;
t/basic-custom-validation.t view on Meta::CPAN
use Mojo::Base -strict;
use Test::Mojo;
use Test::More;
use Mojolicious::Lite;
my $age = 43;
get '/custom' => sub {
my $c = shift;
my @errors = $c->openapi->validate;
return $c->render(text => sprintf '%s errors', int @errors) if @errors;
return $c->render(text => 'cool beans');
},
'get_custom';
plugin OpenAPI => {url => 'data://main/custom.json'};
my $t = Test::Mojo->new;
$t->get_ok('/api/custom?i=42')->content_is('cool beans');
$t->get_ok('/api/custom?i=nok')->content_is('1 errors');
t/jv-recursion.t view on Meta::CPAN
'At this moment in spacetime, I do not know how to suppport both a recursive schema and a recusrive data structure',
2;
my ($data, @errors) = ({});
$data->{rec} = $data;
eval {
local $SIG{ALRM} = sub { die 'Recursion!' };
alarm 2;
@errors
= JSON::Validator::Schema::Draft4->new('data://main/spec.json')->validate({top => $data});
};
is $@, '', 'no error';
is_deeply(\@errors, [], 'avoided recursion');
}
note 'This part of the test checks that we don\'t go into an infite loop';
eval {
my $validator = JSON::Validator::Schema::OpenAPIv2->new;
$validator->data('data://main/user.json')->errors;
$validator->data($validator->data)->errors;
t/plugin-spec-renderer-options.t view on Meta::CPAN
->json_is('/get/responses/200/schema/$ref', '#/definitions/SpecResponse')
->json_is('/definitions/DefaultResponse/properties/errors/items/properties/message/type',
'string')->json_is('/definitions/SpecResponse/type', 'object');
$t->options_ok('/api/spec?method=get')->status_is(200)
->json_is('/$schema', 'http://json-schema.org/draft-04/schema#')
->json_is('/title', 'Test spec response')->json_is('/description', '')
->json_is('/operationId', 'Spec')->json_is('/definitions/SpecResponse/type', 'object');
eval {
JSON::Validator->new->load_and_validate_schema($t->tx->res->json);
ok 1, 'api/spec return valid schema';
} or do {
ok 0, "api/spec return valid schema: $@";
};
$t->options_ok('/api/spec?method=post')->status_is(404)
->json_is('/errors/0/message', 'No spec defined.');
$t->options_ok('/api/user/1')->status_is(200)
->json_is('/$schema', 'http://json-schema.org/draft-04/schema#')
t/v2-formats.t view on Meta::CPAN
use lib '.';
use JSON::Validator::Schema::OpenAPIv2;
use JSON::Validator::Util qw(E);
use Test::More;
my $schema = {type => 'object', properties => {v => {type => 'string'}}};
my $validator = JSON::Validator::Schema::OpenAPIv2->new;
sub validate_ok {
my ($data, $schema, @expected) = @_;
my $descr = @expected ? "errors: @expected" : "valid: " . Mojo::JSON::encode_json($data);
my @errors = $validator->data($schema)->validate($data);
is_deeply [map { $_->TO_JSON } sort { $a->path cmp $b->path } @errors],
[map { $_->TO_JSON } sort { $a->path cmp $b->path } @expected], $descr
or Test::More::diag(Mojo::JSON::encode_json(\@errors));
}
{
$schema->{properties}{v}{format} = 'byte';
validate_ok {v => 'amh0aG9yc2Vu'}, $schema;
validate_ok {v => "\0"}, $schema, E('/v', 'Does not match byte format.');
}
{
$schema->{properties}{v}{format} = 'date';
validate_ok {v => '2014-12-09'}, $schema;
validate_ok {v => '0000-00-00'}, $schema, E('/v', 'Month out of range.');
validate_ok {v => '0000-01-00'}, $schema, E('/v', 'Day out of range.');
validate_ok {v => '2014-12-09T20:49:37Z'}, $schema, E('/v', 'Does not match date format.');
validate_ok {v => '0-0-0'}, $schema, E('/v', 'Does not match date format.');
validate_ok {v => '09-12-2014'}, $schema, E('/v', 'Does not match date format.');
validate_ok {v => '09-DEC-2014'}, $schema, E('/v', 'Does not match date format.');
validate_ok {v => '09/12/2014'}, $schema, E('/v', 'Does not match date format.');
}
{
$schema->{properties}{v}{format} = 'date-time';
validate_ok {v => '2014-12-09T20:49:37Z'}, $schema;
validate_ok {v => '0000-00-00T00:00:00Z'}, $schema, E('/v', 'Month out of range.');
validate_ok {v => '0000-01-00T00:00:00Z'}, $schema, E('/v', 'Day out of range.');
validate_ok {v => '20:46:02'}, $schema, E('/v', 'Does not match date-time format.');
}
{
local $schema->{properties}{v}{type} = 'number';
local $schema->{properties}{v}{format} = 'double';
local $TODO = "cannot test double, since input is already rounded";
validate_ok {v => 1.1000000238418599085576943252817727625370025634765626}, $schema;
}
{
local $schema->{properties}{v}{format} = 'email';
validate_ok {v => 'jhthorsen@cpan.org'}, $schema;
validate_ok {v => 'foo'}, $schema, E('/v', 'Does not match email format.');
}
{
local $schema->{properties}{v}{type} = 'number';
local $schema->{properties}{v}{format} = 'float';
validate_ok {v => -1.10000002384186}, $schema;
validate_ok {v => 1.10000002384186}, $schema;
local $TODO = 'No idea how to test floats';
validate_ok {v => 0.10000000000000}, $schema, E('/v', 'Does not match float format.');
}
{
local $TODO = eval 'require Data::Validate::IP;1' ? undef : 'Missing module';
local $schema->{properties}{v}{format} = 'ipv4';
validate_ok {v => '255.100.30.1'}, $schema;
validate_ok {v => '300.0.0.0'}, $schema, E('/v', 'Does not match ipv4 format.');
}
{
local $schema->{properties}{v}{type} = 'integer';
local $schema->{properties}{v}{format} = 'int32';
validate_ok {v => -2147483648}, $schema;
validate_ok {v => 2147483647}, $schema;
validate_ok {v => 2147483648}, $schema, E('/v', 'Does not match int32 format.');
}
if (JSON::Validator::Formats::IV_SIZE >= 8) {
local $schema->{properties}{v}{type} = 'integer';
local $schema->{properties}{v}{format} = 'int64';
validate_ok {v => -9223372036854775808}, $schema;
validate_ok {v => 9223372036854775807}, $schema;
validate_ok {v => 9223372036854775808}, $schema, E('/v', 'Does not match int64 format.');
}
{
local $schema->{properties}{v}{format} = 'password';
validate_ok {v => 'whatever'}, $schema;
}
{
local $schema->{properties}{v}{format} = 'unknown';
validate_ok {v => 'whatever'}, $schema;
}
done_testing;
t/v2-tutorial.t view on Meta::CPAN
# Do not continue on invalid input and render a default 400
# error document.
my $c = shift->openapi->valid_input or return;
# $c->openapi->valid_input copies valid data to validation object,
# and the normal Mojolicious api works as well.
my $input = $c->validation->output;
my $age = $c->param("age"); # same as $input->{age}
my $body = $c->req->json; # same as $input->{body}
# $output will be validated by the OpenAPI spec before rendered
my $output = {pets => [{name => "kit-e-cat"}]};
$c->render(openapi => $output);
}
$ENV{"Myapp/Controller/Pet.pm"} = 1;
HERE
}
__DATA__
@@ myapi.json
t/v3-invalid_file_refs_no_path.t view on Meta::CPAN
plugin OpenAPI => {url => app->home->rel_file('spec/v3-invalid_file_refs_no_path.yaml')};
my $t = Test::Mojo->new;
$t->get_ok('/api')->status_is(200)->json_hasnt('/PCVersion/name')->json_has('/components/schemas')
->content_like(qr!v3-valid_include_yaml!);
eval { die JSON::Validator::Schema::OpenAPIv3->new($t->get_ok('/api')->tx->res->json)->errors->[0] };
like $@, qr/Properties not allowed: components/,
'load_and_validate_schema fails, wrong placement of data';
done_testing;
t/v3-tutorial.t view on Meta::CPAN
# error document.
my $c = shift;
$c = $c->openapi->valid_input or return;
# $c->openapi->valid_input copies valid data to validation object,
# and the normal Mojolicious api works as well.
my $input = $c->validation->output;
my $age = $c->param("age"); # same as $input->{age}
my $body = $c->req->json; # same as $input->{body}
# $output will be validated by the OpenAPI spec before rendered
my $output = {pets => [{name => "kit-e-cat"}]};
$c->render(openapi => $output);
}
$ENV{"Myapp/Controller/Pet.pm"} = 1;
HERE
}
__DATA__
@@ myapi.json