APISchema

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

   - 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

MANIFEST  view on Meta::CPAN

README.md
cpanfile
eg/bmi.psgi
lib/APISchema.pm
lib/APISchema/DSL.pm
lib/APISchema/Generator/Markdown.pm
lib/APISchema/Generator/Markdown/ExampleFormatter.pm
lib/APISchema/Generator/Markdown/Formatter.pm
lib/APISchema/Generator/Markdown/ResourceResolver.pm
lib/APISchema/Generator/Router/Simple.pm
lib/APISchema/JSON.pm
lib/APISchema/Resource.pm
lib/APISchema/Route.pm
lib/APISchema/Schema.pm
lib/APISchema/Validator.pm
lib/APISchema/Validator/Decoder.pm
lib/APISchema/Validator/Result.pm
lib/Plack/App/APISchema/Document.pm
lib/Plack/App/APISchema/MockServer.pm
lib/Plack/Middleware/APISchema/RequestValidator.pm
lib/Plack/Middleware/APISchema/ResponseValidator.pm
minil.toml
script/generate_markdown_document.pl
t/APISchema-DSL.t
t/APISchema-Generator-Markdown-Formatter.t
t/APISchema-Generator-Markdown.t
t/APISchema-Generator-Router-Simple.t
t/APISchema-JSON.t
t/APISchema-Resource.t
t/APISchema-Route.t
t/APISchema-Schema.t
t/APISchema-Validator.t
t/APISchema.t
t/Plack-App-APISchema-Document.t
t/Plack-App-APISchema-MockServer.t
t/Plack-Middleware-APISchema-RequestValidator.t
t/Plack-Middleware-APISchema-ResponseValidator.t
t/fixtures/author.def

META.json  view on Meta::CPAN

         }
      },
      "runtime" : {
         "requires" : {
            "Class::Accessor::Lite" : "0",
            "Class::Accessor::Lite::Lazy" : "0",
            "Class::Load" : "0",
            "HTML::Escape" : "0",
            "HTTP::Message" : "0",
            "Hash::Merge::Simple" : "0",
            "JSON::Pointer" : "0",
            "JSON::XS" : "0",
            "List::MoreUtils" : "0",
            "Path::Class" : "0",
            "Plack" : "0",
            "Router::Simple" : "0",
            "Text::Markdown::Hoedown" : "0",
            "Text::MicroTemplate" : "0",
            "Text::MicroTemplate::DataSection" : "0",
            "Text::MicroTemplate::Extended" : "0",
            "URI::Escape" : "0",
            "URL::Encode" : "0",
            "Valiemon" : "0.04",
            "perl" : "5.014"
         }
      },
      "test" : {
         "requires" : {
            "HTTP::Request::Common" : "0",
            "Path::Class" : "0",
            "Test::Class" : "0",
            "Test::Deep" : "0",
            "Test::Deep::JSON" : "0",
            "Test::Fatal" : "0",
            "Test::More" : "0.98"
         }
      }
   },
   "provides" : {
      "APISchema" : {
         "file" : "lib/APISchema.pm",
         "version" : "1.37"
      },

META.json  view on Meta::CPAN

      },
      "APISchema::Generator::Markdown::Formatter" : {
         "file" : "lib/APISchema/Generator/Markdown/Formatter.pm"
      },
      "APISchema::Generator::Markdown::ResourceResolver" : {
         "file" : "lib/APISchema/Generator/Markdown/ResourceResolver.pm"
      },
      "APISchema::Generator::Router::Simple" : {
         "file" : "lib/APISchema/Generator/Router/Simple.pm"
      },
      "APISchema::JSON" : {
         "file" : "lib/APISchema/JSON.pm"
      },
      "APISchema::Resource" : {
         "file" : "lib/APISchema/Resource.pm"
      },
      "APISchema::Route" : {
         "file" : "lib/APISchema/Route.pm"
      },
      "APISchema::Schema" : {
         "file" : "lib/APISchema/Schema.pm"
      },

META.json  view on Meta::CPAN

      }
   },
   "version" : "1.37",
   "x_authority" : "cpan:HITODE",
   "x_contributors" : [
      "Mohammad S Anwar <mohammad.anwar@yahoo.com>",
      "Takumi Akiyama <t.akiym@gmail.com>",
      "Tomohiro Nishimura <tomohiro68@gmail.com>",
      "aereal <aereal@aereal.org>"
   ],
   "x_serialization_backend" : "JSON::PP version 2.27400"
}

META.yml  view on Meta::CPAN

---
abstract: 'Schema for API'
author:
  - 'hitode909 <hitode909@gmail.com>'
build_requires:
  HTTP::Request::Common: '0'
  Path::Class: '0'
  Test::Class: '0'
  Test::Deep: '0'
  Test::Deep::JSON: '0'
  Test::Fatal: '0'
  Test::More: '0.98'
configure_requires:
  Module::Build::Tiny: '0.035'
dynamic_config: 0
generated_by: 'Minilla/v3.0.14, CPAN::Meta::Converter version 2.150010'
license: perl
meta-spec:
  url: http://module-build.sourceforge.net/META-spec-v1.4.html
  version: '1.4'

META.yml  view on Meta::CPAN

  APISchema::Generator::Markdown:
    file: lib/APISchema/Generator/Markdown.pm
  APISchema::Generator::Markdown::ExampleFormatter:
    file: lib/APISchema/Generator/Markdown/ExampleFormatter.pm
  APISchema::Generator::Markdown::Formatter:
    file: lib/APISchema/Generator/Markdown/Formatter.pm
  APISchema::Generator::Markdown::ResourceResolver:
    file: lib/APISchema/Generator/Markdown/ResourceResolver.pm
  APISchema::Generator::Router::Simple:
    file: lib/APISchema/Generator/Router/Simple.pm
  APISchema::JSON:
    file: lib/APISchema/JSON.pm
  APISchema::Resource:
    file: lib/APISchema/Resource.pm
  APISchema::Route:
    file: lib/APISchema/Route.pm
  APISchema::Schema:
    file: lib/APISchema/Schema.pm
  APISchema::Validator:
    file: lib/APISchema/Validator.pm
  APISchema::Validator::Decoder:
    file: lib/APISchema/Validator/Decoder.pm

META.yml  view on Meta::CPAN

    file: lib/Plack/Middleware/APISchema/RequestValidator.pm
  Plack::Middleware::APISchema::ResponseValidator:
    file: lib/Plack/Middleware/APISchema/ResponseValidator.pm
requires:
  Class::Accessor::Lite: '0'
  Class::Accessor::Lite::Lazy: '0'
  Class::Load: '0'
  HTML::Escape: '0'
  HTTP::Message: '0'
  Hash::Merge::Simple: '0'
  JSON::Pointer: '0'
  JSON::XS: '0'
  List::MoreUtils: '0'
  Path::Class: '0'
  Plack: '0'
  Router::Simple: '0'
  Text::Markdown::Hoedown: '0'
  Text::MicroTemplate: '0'
  Text::MicroTemplate::DataSection: '0'
  Text::MicroTemplate::Extended: '0'
  URI::Escape: '0'
  URL::Encode: '0'

cpanfile  view on Meta::CPAN

requires 'perl', '5.008001';

requires 'Router::Simple';
requires 'Plack';
requires 'JSON::XS';
requires 'JSON::Pointer';
requires 'Path::Class';
requires 'Class::Load';
requires 'Class::Accessor::Lite';
requires 'Class::Accessor::Lite::Lazy';
requires 'List::MoreUtils';
requires 'Hash::Merge::Simple';
requires 'URL::Encode';
requires 'HTML::Escape';
requires 'Text::MicroTemplate';
requires 'Text::MicroTemplate::Extended';

cpanfile  view on Meta::CPAN

requires 'HTTP::Message';
requires 'Valiemon', '0.04';
requires 'URI::Escape';

on 'test' => sub {
    requires 'Path::Class';
    requires 'Test::More', '0.98';
    requires 'Test::Class';
    requires 'Test::Deep';
    requires 'Test::Fatal';
    requires 'Test::Deep::JSON';
    requires 'HTTP::Request::Common';
};

eg/bmi.psgi  view on Meta::CPAN

use lib glob '../modules/*/lib/';

use Plack::Builder;
use Plack::Request;
use APISchema::DSL;
use APISchema::Generator::Router::Simple;
use Plack::App::APISchema::Document;
use Plack::App::APISchema::MockServer;
use APISchema::Generator::Markdown;

use JSON qw(decode_json encode_json);

my $schema = APISchema::DSL::process {
    include '../t/fixtures/bmi.def';
};

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

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

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;

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


sub json ($) {
    my $x = shift;
    if (ref $x eq 'SCALAR') {
        if ($$x eq 1) {
            $x = 'true';
        } elsif ($$x eq 0) {
            $x = 'false';
        }
    } elsif (ref $x) {
        $x = $JSON->encode($x);
    } else {
        $x = $JSON->encode([$x]);
        $x =~ s/^\[(.*)\]$/$1/;
    }
    return $x;
}

my $PRETTY_JSON = JSON::XS->new->canonical(1)->indent(1)->pretty(1);
sub pretty_json ($) {
    my $x = shift;
    if (ref $x) {
        $x = $PRETTY_JSON->encode($x);
    } else {
        $x = $PRETTY_JSON->encode([$x]);
        $x =~ s/^\[\s*(.*)\s*\]\n$/$1/;
    }
    return $x;
}

sub _code ($) {
    my $text = shift;
    return '' unless defined $text;
    if ($text =~ /[`|]/) {
        $text =~ s/[|]/&#124;/g;

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

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

# cpan
use JSON::Pointer;
use Class::Accessor::Lite (
    new => 1,
    ro  => [qw(schema)],
);

sub _foreach_properties($$&) {
    my ($name_path, $definition, $callback) = @_;
    return unless (ref $definition || '') eq 'HASH';

    if ($definition->{items}) {

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

    return '.' . join '.', @name_path;
}

sub _collect_properties {
    my ($self, $path, $definition) = @_;
    return {} unless (ref $definition || '') eq 'HASH';

    my $ref = $definition->{'$ref'};
    if ($ref) {
        $ref = $ref =~ s/^#//r;
        my $def = JSON::Pointer->get($self->schema, $ref);
        return $self->_collect_properties($path, $def)
            if $def && $ref !~ qr!^/resource/[^/]+$!;

        $definition = +{
            %$definition,
            description => $definition->{description} // $def->{description},
        };
    }

    my $result = { _property_name(@$path) => $definition };

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

    my ($self, $path, $definition) = @_;
    return ($definition->{example}, 1) if exists $definition->{example};

    if (my $union = $definition->{oneOf} || $definition->{anyOf} || $definition->{allOf}) {
        return ($self->_collect_example($path, $union->[0]), 1);
    }

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

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

package APISchema::Validator::Decoder;
use strict;
use warnings;

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

1;

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) = @_;

    my $req = Plack::Request->new($env);

lib/Plack/Middleware/APISchema/RequestValidator.pm  view on Meta::CPAN

package Plack::Middleware::APISchema::RequestValidator;
use strict;
use warnings;

use parent qw(Plack::Middleware);
use HTTP::Status qw(:constants);
use Plack::Util::Accessor qw(schema validator);
use Plack::Request;
use APISchema::Generator::Router::Simple;
use APISchema::Validator;
use APISchema::JSON;

use constant DEFAULT_VALIDATOR_CLASS => 'Valiemon';

sub call {
    my ($self, $env) = @_;
    my $req = Plack::Request->new($env);

    my ($matched, $route) = $self->router->routematch($env);
    $matched or return $self->app->($env);

lib/Plack/Middleware/APISchema/ResponseValidator.pm  view on Meta::CPAN

package Plack::Middleware::APISchema::ResponseValidator;
use strict;
use warnings;

use parent qw(Plack::Middleware);
use Plack::Util ();
use Plack::Util::Accessor qw(schema validator);
use Plack::Response;
use APISchema::Generator::Router::Simple;
use APISchema::Validator;
use APISchema::JSON;

use constant DEFAULT_VALIDATOR_CLASS => 'Valiemon';

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

    Plack::Util::response_cb($self->app->($env), sub {
        my $res = shift;

        my ($matched, $route) = $self->router->routematch($env);

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

package t::APISchema::JSON;
use lib '.';
use t::test;

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

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

sub _encode_json_canonical : Tests {
    is APISchema::JSON::encode_json_canonical({b => 2, c => 3, a => 1}), '{"a":1,"b":2,"c":3}', 'keys are sorted';
    is APISchema::JSON::encode_json_canonical({nested => {b => 2, c => 3, a => 1}}), '{"nested":{"a":1,"b":2,"c":3}}', 'nested keys are sorted';
}

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

package t::Plack::Middleware::APISchema::ResponseValidator;
use lib '.';
use t::test;
use t::test::fixtures;
use JSON::XS qw(encode_json);

sub _require : Test(startup => 1) {
    use_ok 'APISchema::Validator';
}

sub instantiate : Tests {
    subtest 'For request' => sub {
        my $validator = APISchema::Validator->for_request;
        isa_ok $validator, 'APISchema::Validator';
        is $validator->validator_class, 'Valiemon';

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

package t::Plack::Middleware::APISchema::RequestValidator;
use lib '.';
use t::test;
use t::test::fixtures;
use Plack::Test;
use HTTP::Request::Common;
use HTTP::Status qw(:constants);
use JSON::XS qw(encode_json);

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

    use_ok 'Plack::Middleware::APISchema::RequestValidator';
}

sub instantiate : Tests {
    my $schema = APISchema::Schema->new;
    my $middleware = Plack::Middleware::APISchema::RequestValidator->new(schema => $schema);

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

    };

    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 request is not a JSON' => sub {
        test_psgi $middleware => sub {
            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 => {

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

package t::Plack::Middleware::APISchema::ResponseValidator;
use lib '.';
use t::test;
use t::test::fixtures;
use Plack::Test;
use Plack::Request;
use HTTP::Request::Common;
use JSON::XS qw(encode_json);

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

    use_ok 'Plack::Middleware::APISchema::ResponseValidator';
}

sub instantiate : Tests {
    my $schema = APISchema::Schema->new;
    my $middleware = Plack::Middleware::APISchema::ResponseValidator->new(schema => $schema);

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 => { 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 => {

t/test.pm  view on Meta::CPAN


    my $code = qq[
        package $package;
        use strict;
        use warnings;

        use parent qw(Test::Class);
        use Test::More;
        use Test::Fatal qw(lives_ok dies_ok exception);
        use Test::Deep;
        use Test::Deep::JSON;

        END { $package->runtests }
    ];

    eval $code;
    die $@ if $@;
}

1;



( run in 1.598 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )