APISchema

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

    - Determine the status code from the detail of failure (aereal++)

1.02 2015-10-06T07:12:24Z
    - Prettify the output of examples (aereal++)

1.01 2015-09-04T09:32:37Z
    - Recognize HTTP PATCH method (aereal++)

1.00 2015-08-27T10:48:03Z
    - Support schema which contains unicode characters
    - Enable `use utf8` in DSL file

0.03 2015-08-18T02:06:45Z
    - Validation error has more details. expected: expected schema, actual: actual input data

0.02 2015-03-16T02:35:36Z
    - Add a status_code parameter to P::MW::AS::RequestValidator

0.01 2015-02-18T10:41:05Z

    - original version

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


sub process (&) {
    my $dsl = shift;

    my $schema = APISchema::Schema->new;

    local $_directive->{include} = sub {
        my ($file) = @_;
        -r $_[0] or Carp::croak(sprintf 'No such file: %s', $file);
        my $content = file($file)->slurp;
        my $with_utf8 = "use utf8;\n" . $content;
        eval $with_utf8;
        Carp::croak($@) if $@;
    };
    local $_directive->{title} = sub {
        $schema->title(@_);
    };
    local $_directive->{description} = sub {
        $schema->description(@_);
    };

    my @filters;

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

package APISchema::Generator::Markdown::ExampleFormatter;
use 5.014;
use strict;
use warnings;

# lib
use APISchema::Generator::Markdown::Formatter qw(json);

# cpan
use URI::Escape qw(uri_escape_utf8);
use Class::Accessor::Lite (
    new => 1,
    ro => [qw(resolver spec)],
);

sub example {
    my $self = shift;
    return $self->resolver->example(@_);
}

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

    my $parameter = $self->spec->{parameter} or return '';
    my $resource = $parameter->definition or return '';
    my $example = $self->example($resource);

    return '' unless defined $example;
    return '' unless (ref $example) eq 'HASH';
    return '' unless scalar keys %$example;

    return '?' . join '&', map {
        # TODO multiple values?
        sprintf '%s=%s', map { uri_escape_utf8 $_ } $_, $example->{$_};
    } sort keys %$example;
}

sub body {
    my ($self) = @_;
    my $body = $self->spec->{body} or return '';
    my $resource = $body->definition or return '';
    my $example = $self->example($resource);

    return '' unless defined $example;

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

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

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


sub code ($;$) {
    my ($text, $exists) = @_;
    return $exists ? '`null`' : '' unless defined $text;
    return _code json $text;
}

sub anchor ($$) {
    my ($label, $obj) = @_;
    my $name = ref $obj ? $obj->title : $obj;
    return sprintf '%s-%s', $label, uri_escape_utf8($name);
}

sub restriction ($) {
    my $def = shift;
    return '' unless (ref $def) eq 'HASH';

    my @result = ();
    for my $r (sort @{+RESTRICTIONS}) {
        next unless defined $def->{$r};

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

package APISchema::JSON;
use strict;
use warnings;

use Exporter 'import';
our @EXPORT = qw(encode_json_canonical);

use JSON::XS;

my $json = JSON::XS->new->utf8->canonical(1);

sub encode_json_canonical {
    my ($value) = @_;
    $json->encode($value);
}

1;

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

# cpan
use JSON::XS qw(decode_json);
use URL::Encode qw(url_params_mixed);
use Class::Accessor::Lite ( new => 1 );

sub perl {
    my ($self, $body) = @_;
    return $body;
}

my $JSON = JSON::XS->new->utf8;
sub json {
    my ($self, $body) = @_;
    return $JSON->decode($body);
}

sub url_parameter {
    my ($self, $body) = @_;
    return undef unless defined $body;
    return url_params_mixed($body, 1);
}

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

package Plack::App::APISchema::Document;
use strict;
use warnings;
use parent qw(Plack::Component);
use Plack::Util::Accessor qw(schema);
use Text::Markdown::Hoedown qw(markdown);
use Text::MicroTemplate qw(encoded_string);
use Text::MicroTemplate::DataSection qw(render_mt);
use Encode qw(encode_utf8);

use APISchema::Generator::Markdown;

sub call {
    my ($self, $env) = @_;

    my $generator = APISchema::Generator::Markdown->new;
    my $markdown = $generator->format_schema($self->schema);

    my $body = markdown(

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

                | Text::Markdown::Hoedown::HOEDOWN_EXT_AUTOLINK
                | Text::Markdown::Hoedown::HOEDOWN_EXT_FENCED_CODE
                | Text::Markdown::Hoedown::HOEDOWN_EXT_NO_INTRA_EMPHASIS
            )
    );

    my $renderer = Text::MicroTemplate::DataSection->new(package => ref $self);
    my $title = $self->schema->title || '';
    my $html = $renderer->render_mt('template.mt', $title, $body);

    return [200, ['Content-Type' => 'text/html; charset=utf-8'], [encode_utf8 $html]];
}

1;
__DATA__
@@ template.mt
? my ($title, $body) = @_;
<!DOCTYPE html>
<html>
  <head>
    <title><?= $title ?></title>

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

package Plack::App::APISchema::MockServer;
use strict;
use warnings;
use parent qw(Plack::Component);
use Plack::Util::Accessor qw(schema);
use Plack::Request;
use Encode qw(encode_utf8);

use APISchema::JSON;

use APISchema::Generator::Router::Simple;
use APISchema::Generator::Markdown::ResourceResolver;
use APISchema::Generator::Markdown::ExampleFormatter;

sub call {
    my ($self, $env) = @_;

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

script/generate_markdown_document.pl  view on Meta::CPAN

#!/usr/bin/env perl
use strict;
use warnings;
use Encode qw(encode_utf8);

BEGIN {
    # cpan
    use Path::Class qw(file);
    my $Root = file(__FILE__)->dir->parent->resolve->absolute;
    unshift @INC, $Root->subdir('lib').q();
}

# lib
use APISchema::DSL;

script/generate_markdown_document.pl  view on Meta::CPAN

  <file>    API definition file.
EOM
    exit;
}

my $schema = APISchema::DSL::process {
    include $ARGV[0];
};

my $generator = APISchema::Generator::Markdown->new;
print encode_utf8 $generator->format_schema($schema);

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

package t::APISchema::Generator::Router::Simple;
use lib '.';
use t::test;
use Encode qw(decode_utf8);

sub _require : Test(startup => 1) {
    my ($self) = @_;

    BEGIN{ use_ok 'APISchema::DSL'; }
}

sub no_global : Tests {
    dies_ok {
        filter {};

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

    };
}

sub with_unicode : Tests {
    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.t  view on Meta::CPAN

package t::APISchema::Generator::Markdown;
use lib '.';
use t::test;
use t::test::fixtures;
use utf8;

use APISchema::DSL;

sub _require : Test(startup => 1) {
    my ($self) = @_;

    use_ok 'APISchema::Generator::Markdown';
}

sub instantiate : Tests {

t/APISchema-Generator-Markdown.t  view on Meta::CPAN

            };
        };

        my $generator = APISchema::Generator::Markdown->new;
        my $markdown = $generator->format_schema($schema);

        like $markdown, qr{\Q- [Fetch](#route-Fetch) - `GET`, `HEAD` /\E}, 'FETCH expanded to GET and HEAD';
    };
}

sub generate_utf8 : Tests {
    subtest 'Simple' => sub {
        my $schema = t::test::fixtures::prepare_user;

        my $generator = APISchema::Generator::Markdown->new;
        my $markdown = $generator->format_schema($schema);

        like $markdown, qr!{\n   "first_name" : "小飼",\n   "last_name" : "弾"\n}!;
        like $markdown, qr!\Q|`.last_name` |`string` | |`"弾"` | |名 |\E!;
    };
}

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

            my $res = $server->(
                POST '/bmi',
                Content_Type => 'application/json',
                Content => encode_json({weight => 50, height => 1.6}),
            );
            is $res->code, 200;
            done_testing;
        }
    };

    subtest 'when valid utf8 request' => sub {
        test_psgi $middleware => sub {
            my $server = shift;
            my $res = $server->(
                POST '/bmi',
                Content_Type => 'application/json; charset=UTF-8',
                Content => encode_json({weight => 50, height => 1.6}),
            );
            is $res->code, 200;
            done_testing;
        }

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

                        host           => "localhost",
                    },
                    expected => $schema->get_resource_by_name('figure_header')->definition,
                },
            });
            done_testing;
        }
    };
}

sub request_validator_with_utf8 : Tests {
    my $schema = t::test::fixtures::prepare_user;
    $schema->register_route(
        method => 'POST',
        route => '/user',
        request_resource => {
            body => 'user',
        },
    );

    my $middleware = Plack::Middleware::APISchema::RequestValidator->new(schema => $schema);

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

            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',
        route => '/user',
        response_resource => {
            body => 'user',
        },
    );
    my $middleware = Plack::Middleware::APISchema::ResponseValidator->new(schema => $schema);
    $middleware->wrap(sub {
        [200, [ 'Content-Type' => 'application/json; charset=utf-8' ], [ encode_json({ first_name => 'Bill', last_name => []}) ]  ]
    });

    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 => [],



( run in 1.298 second using v1.01-cache-2.11-cpan-49f99fa48dc )