APISchema

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

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

1.30 2017-11-20T17:00:38Z
   - Empty input could pass validations unexpectedly
   - Add . to lib

1.29 2017-11-20T01:34:11Z
   - Handle boolean on generating document(1.28 was broken)

1.28 2017-11-17T09:49:53Z
   - Handle boolean on generating document

1.27 2017-09-19T09:58:40Z
   - Sort keys when encoding JSON

1.26 2017-08-30T02:20:58Z
   - Expand `FETCH` method to `GET` and `HEAD`

1.25 2017-08-29T02:31:53Z
   - You can inherit Plack::App::APISchema::Document and customize template

1.24 2016-06-10T10:01:57Z
   - Fix document

eg/bmi.psgi  view on Meta::CPAN

        my $generator = APISchema::Generator::Markdown->new;
        my $content = $generator->format_schema($schema);
        [200, ['Content-Type' => 'text/plain; charset=utf-8;'], [$content]];
    };

    mount '/' => $app;
}

__END__

=encoding utf-8

=head1 NAME

BMI Calculator

=head1 NAME

Sample Application

=head1 HOW TO USE

lib/APISchema.pm  view on Meta::CPAN

package APISchema;
use 5.014;
use strict;
use warnings;

our $VERSION = "1.37";

1;
__END__

=encoding utf-8

=head1 NAME

APISchema - Schema for API

=head1 SYNOPSIS

    # bmi.def

    resource figure => {

lib/APISchema/Generator/Markdown.pm  view on Meta::CPAN

    );
    bless {
        renderer => $renderer,
        map {
            ( $_ => $renderer->build_file($_) );
        } qw(index toc route resource request response
             request_example response_example),
    }, $class;
}

sub resolve_encoding ($) {
    my ($resources) = @_;
    $resources = { body => $resources } unless ref $resources;
    my $encoding = $resources->{encoding} // { '' => 'auto' };
    $encoding = { '' => $encoding } unless ref $encoding;
    return { %$resources, encoding => $encoding };
}

sub format_schema {
    my ($self, $schema) = @_;

    my $renderer = $self->{renderer};
    my $routes = $schema->get_routes;
    my $resources = $schema->get_resources;

    my $root = $schema->get_resource_root;

lib/APISchema/Generator/Markdown.pm  view on Meta::CPAN

    return $self->{index}->(
        $renderer,
        $schema,
        $self->{toc}->(
            $renderer,
            $routes,
            $resources,
        ),
        join('', map {
            my $route = $_;
            my $req = resolve_encoding($route->request_resource);
            my $request_resource = $route->canonical_request_resource($root);

            my $codes = $route->responsible_codes;
            my $default_code = $route->default_responsible_code;
            my $response_resource = $route->canonical_response_resource($root, [
                $default_code
            ]);

            my $res = $_->response_resource;
            $res = $_->responsible_code_is_specified
                ? { map { $_ => resolve_encoding($res->{$_}) } @$codes }
                : { '' => resolve_encoding($res) };

            $self->{route}->(
                $renderer,
                $route,
                {
                    req =>  $self->{request_example}->(
                        $renderer,
                        $route,
                        APISchema::Generator::Markdown::ExampleFormatter->new(
                            resolver => $resolver,

lib/APISchema/Generator/Markdown.pm  view on Meta::CPAN

?= $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) ?>
```

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 {

lib/APISchema/Validator.pm  view on Meta::CPAN

        or return $valid;
    my $method = $self->fetch_resource_method;
    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,
        parameter => 'url_parameter',
        header    => 'perl',
    };

    my $validator_class = $self->validator_class;
    load_class $validator_class;
    my $result = APISchema::Validator::Result->new;
    $result->merge($_) for map {
        my $field = $_;
        my $err = _validate($validator_class, map { $_->{$field} } (
            $encoding, $target, $resource_spec,
        ));
        $err ? _error_result($field => {
            %$err,
            encoding => $encoding->{$_},
        }) : _valid_result($field);
    } @target_keys;

    return $result;
}

1;
__END__

lib/Plack/App/APISchema/MockServer.pm  view on Meta::CPAN

    ]);

    my $resolver = APISchema::Generator::Markdown::ResourceResolver->new(schema => $root);

    my $formatter = APISchema::Generator::Markdown::ExampleFormatter->new(
        resolver => $resolver,
        spec     => $response_resource,
    );

    # TODO: serve all headers defined in example
    # TODO: format body with encoding
    return [$default_code, ['Content-Type' => 'application/json; charset=utf-8'], [encode_utf8($formatter->body)]];
}

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

    return $self->{router} if $self->{router};

    my $generator = APISchema::Generator::Router::Simple->new;
    $self->{router} = $generator->generate_router($self->schema);

t/APISchema-Validator.t  view on Meta::CPAN

    );
    return $schema;
}

sub _forced_route ($$) {
    my ($schema, $keys)  =  @_;
    $keys = [qw(header parameter body)] unless defined $keys;
    $schema->register_route(
        route => '/endpoint',
        request_resource => {
            encoding => 'json',
            map { $_ => 'figure' } @$keys
        },
        response_resource => {
            encoding => 'json',
            map { $_ => 'bmi' } @$keys
        },
    );
    return $schema;
}

sub _invalid_encoding_route ($$) {
    my ($schema, $keys)  =  @_;
    $keys = [qw(header parameter body)] unless defined $keys;
    $schema->register_route(
        route => '/endpoint',
        request_resource => {
            encoding => 'hoge',
            map { $_ => 'figure' } @$keys
        },
        response_resource => {
            encoding => 'hoge',
            map { $_ => 'bmi' } @$keys
        },
    );
    return $schema;
}

sub _strict_route ($$) {
    my ($schema, $keys)  =  @_;
    $keys = [qw(header parameter body)] unless defined $keys;
    $schema->register_route(
        route => '/endpoint',
        request_resource => {
            encoding => { 'application/json' => 'json' },
            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;

t/APISchema-Validator.t  view on Meta::CPAN

        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);

t/APISchema-Validator.t  view on Meta::CPAN

            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 {

t/APISchema-Validator.t  view on Meta::CPAN


    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;

t/APISchema-Validator.t  view on Meta::CPAN

    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}),

t/APISchema-Validator.t  view on Meta::CPAN

            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' },

t/APISchema-Validator.t  view on Meta::CPAN

        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 {

t/APISchema-Validator.t  view on Meta::CPAN

    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',

t/APISchema-Validator.t  view on Meta::CPAN

            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,

t/Plack-App-APISchema-MockServer.t  view on Meta::CPAN

         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',
        request_resource => {
            encoding => 'json',
            body => 'figure',
        },
        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 => {
            encoding => 'json',
            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;

t/Plack-App-APISchema-MockServer.t  view on Meta::CPAN

            },
            {
                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;

t/Plack-App-APISchema-MockServer.t  view on Meta::CPAN

        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 => {
            encoding => 'json',
            body => 'figure',
        },
        response_resource => {
            201 => {
                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->(PUT '/put_bmi');
        is $res->content, q!{"value":19.5}!;

t/Plack-Middleware-APISchema-RequestValidator.t  view on Meta::CPAN


    isa_ok $middleware->router, 'Router::Simple';
}

sub request_validator : Tests {
    my $schema = t::test::fixtures::prepare_bmi;
    $schema->register_route(
        method => 'POST',
        route => '/bmi_strict',
        request_resource => {
            encoding => { 'application/json' => 'json' },
            body => 'figure',
        },
    );
    $schema->register_route(
        method => 'POST',
        route => '/bmi_force_json',
        request_resource => {
            encoding => 'json',
            body => 'figure',
        },
    );
    $schema->register_route(
        method => 'POST',
        route => '/bmi_by_parameter',
        request_resource => {
            parameter => 'figure',
        },
    );

t/Plack-Middleware-APISchema-RequestValidator.t  view on Meta::CPAN

                POST '/bmi',
                Content_Type => 'application/json',
                Content => encode_json({}),
            );
            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 => 'json',
                    actual => {},
                    expected => $schema->get_resource_by_name('figure')->definition,
                },
            });
            done_testing;
        }
    };

    subtest 'other endpoints are not affected' => sub {
        test_psgi $middleware => sub {

t/Plack-Middleware-APISchema-RequestValidator.t  view on Meta::CPAN

            my $server = shift;
            my $res = $server->(
                POST '/bmi',
                Content_Type => 'application/json',
                Content => 'aaa',
            );
            is $res->code, HTTP_UNPROCESSABLE_ENTITY;
            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';

t/Plack-Middleware-APISchema-RequestValidator.t  view on Meta::CPAN

                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;

t/Plack-Middleware-APISchema-RequestValidator.t  view on Meta::CPAN

            my $server = shift;
            my $res = $server->(
                POST '/bmi_by_parameter?weight=50',
            );
            is $res->code, HTTP_UNPROCESSABLE_ENTITY;
            cmp_deeply $res->content, json({
                parameter => {
                    attribute => 'Valiemon::Attributes::Required',
                    position => '/$ref/required',
                    message => "Contents do not match resource 'figure'",
                    encoding => 'url_parameter',
                    actual => {
                        weight => 50,
                    },
                    expected => $schema->get_resource_by_name('figure')->definition,
                },
            });
            done_testing;
        }
    };

t/Plack-Middleware-APISchema-RequestValidator.t  view on Meta::CPAN

            my $res = $server->(
                POST '/bmi_by_header',
                X_Weight => 50,
            );
            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

                    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


    isa_ok $middleware->router, 'Router::Simple';
}

sub response_validator : Tests {
    my $schema = t::test::fixtures::prepare_bmi;
    $schema->register_route(
        method => 'POST',
        route => '/bmi_strict',
        response_resource => {
            encoding => { 'application/json' => 'json' },
            body => 'bmi',
        },
    );
    $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'],
    });

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::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});

t/Plack-Middleware-APISchema-ResponseValidator.t  view on Meta::CPAN

            $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',
                    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 {
            $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;
        }
    };

t/Plack-Middleware-APISchema-ResponseValidator.t  view on Meta::CPAN

            $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;
        }
    };

t/Plack-Middleware-APISchema-ResponseValidator.t  view on Meta::CPAN

    });

    subtest 'Status 400 with invalid body' => sub {
        test_psgi $middleware_ng => sub {
            my $server = shift;
            my $res = $server->(GET '/get');
            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;
        };
    };
}

sub response_validator_with_utf8 : Tests {
    my $schema = t::test::fixtures::prepare_user;
    $schema->register_route(
        method => 'GET',

t/Plack-Middleware-APISchema-ResponseValidator.t  view on Meta::CPAN

            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/status.def  view on Meta::CPAN

};

GET '/get' => {
    title => 'Get API',
    description => 'Get something',
    destination => {},
    request => 'none',
    response => {
        200 => {
            body => 'ok',
            encoding => 'perl',
        },
        400 => {
            body => 'error',
            encoding => 'json',
        },
    },
};



( run in 0.306 second using v1.01-cache-2.11-cpan-a5abf4f5562 )