Giter VIP home page Giter VIP logo

raisin's Introduction

NAME

Raisin - A REST API microframework for Perl.

VERSION

version 0.94

SYNOPSIS

use HTTP::Status qw(:constants);
use List::Util qw(max);
use Raisin::API;
use Types::Standard qw(HashRef Any Int Str);

my %USERS = (
    1 => {
        first_name => 'Darth',
        last_name => 'Wader',
        password => 'deathstar',
        email => '[email protected]',
    },
    2 => {
        first_name => 'Luke',
        last_name => 'Skywalker',
        password => 'qwerty',
        email => '[email protected]',
    },
);

plugin 'Logger', fallback => 1;
app->log( debug => 'Starting Raisin...' );

middleware 'CrossOrigin',
    origins => '*',
    methods => [qw/DELETE GET HEAD OPTIONS PATCH POST PUT/],
    headers => [qw/accept authorization content-type api_key_token/];

plugin 'Swagger';

swagger_setup(
    title => 'A POD synopsis API',
    description => 'An example of API documentation.',
    #terms_of_service => '',

    contact => {
        name => 'Artur Khabibullin',
        url => 'http://github.com/khrt',
        email => '[email protected]',
    },

    license => {
        name => 'Perl license',
        url => 'http://dev.perl.org/licenses/',
    },
);

desc 'Users API';
resource users => sub {
    summary 'List users';
    params(
        optional('start', type => Int, default => 0, desc => 'Pager (start)'),
        optional('count', type => Int, default => 10, desc => 'Pager (count)'),
    );
    get sub {
        my $params = shift;

        my @users
            = map { { id => $_, %{ $USERS{$_} } } }
              sort { $a <=> $b } keys %USERS;

        my $max_count = scalar(@users) - 1;
        my $start = $params->{start} > $max_count ? $max_count : $params->{start};
        my $end = $params->{count} > $max_count ? $max_count : $params->{count};

        my @slice = @users[$start .. $end];
        { data => \@slice }
    };

    summary 'List all users at once';
    get 'all' => sub {
        my @users
            = map { { id => $_, %{ $USERS{$_} } } }
              sort { $a <=> $b } keys %USERS;
        { data => \@users }
    };

    summary 'Create new user';
    params(
        requires('user', type => HashRef, desc => 'User object', group {
            requires('first_name', type => Str, desc => 'First name'),
            requires('last_name', type => Str, desc => 'Last name'),
            requires('password', type => Str, desc => 'User password'),
            optional('email', type => Str, default => undef, regex => qr/.+\@.+/, desc => 'User email'),
        }),
    );
    post sub {
        my $params = shift;

        my $id = max(keys %USERS) + 1;
        $USERS{$id} = $params->{user};

        res->status(HTTP_CREATED);
        { success => 1 }
    };

    desc 'Actions on the user';
    params requires('id', type => Int, desc => 'User ID');
    route_param 'id' => sub {
        summary 'Show user';
        get sub {
            my $params = shift;
            $USERS{ $params->{id} };
        };

        summary 'Delete user';
        del sub {
            my $params = shift;
            delete $USERS{ $params->{id} };
            res->status(HTTP_NO_CONTENT);
            undef;
        };
    };
};

run;

DESCRIPTION

Raisin is a REST API microframework for Perl. It's designed to run on Plack, providing a simple DSL to develop RESTful APIs easily. It was inspired by Grape.

FUNCTIONS

API DESCRIPTION

app

Returns the Raisin app. Seldom needed, because most Raisin::API methods invoke the app directly.

resource

Adds a route to an application. namespace is a synonym for resource.

resource user => sub { ... };

route_param

Defines a route parameter as a resource id which can be anything if type isn't specified for it.

route_param id => sub { ... };

Raisin allows you to nest route_param:

params requires => { name => 'id', type => Int };
route_param id => sub {
    get sub { ... };

    params requires => { name => 'sub_id', type => Int };
    route_param sub_id => sub {
        ...
    };
};

produces

Specifies the content types produced by resource.

produces ['text', 'json'];

The argument is an array reference of strings corresponding to the keys used by register_encoder. This array is compared with the Accept header of the request to decide what content-type will actually be returned from a given invocation of resource.

del, get, patch, post, put, head, options

Shortcuts to add a route restricted to the corresponding HTTP method.

get sub { 'GET' };

del 'all' => sub { 'OK' };

params(
    requires('id', type => Int),
    optional('key', type => Str),
);
get sub { 'GET' };

desc 'Put data';
params(
    required('id', type => Int),
    optional('name', type => Str),
);
put 'all' => sub {
    'PUT'
};

desc

Adds a description to resource or any of the HTTP methods. Useful for OpenAPI as it's shown there as a description of an action.

desc 'Some long explanation about an action';
put sub { ... };

desc 'Some exaplanation about a group of actions',
resource => 'user' => sub { ... }

summary

Same as "desc" but shorter.

summary 'Some summary';
put sub { ... };

tags

Tags can be used for logical grouping of operations by resources or any other qualifier. Using in API description.

tags 'delete', 'user';
delete sub { ... };

By default tags are added automatically based on it's namespace but you always can overwrite it using the function.

entity

Describes response object which will be used to generate OpenAPI description.

entity 'MusicApp::Entity::Album';
get {
    my $albums = $schema->resultset('Album');
    present data => $albums, with => 'MusicApp::Entity::Album';
};

params

Defines validations and coercion options for your parameters. Can be applied to any HTTP method and/or "route_param" to describe parameters.

params(
    requires('name', type => Str),
    optional('start', type => Int, default => 0),
    optional('count', type => Int, default => 10),
);
get sub { ... };

params(
    requires('id', type => Int, desc => 'User ID'),
);
route_param 'id' => sub { ... };

For more see "Validation-and-coercion" in Raisin.

api_default_format

Specifies default API format mode when formatter isn't specified by API user. E.g. if URI is asked without an extension (json, yaml) or Accept header isn't specified the default format will be used.

Default value: YAML.

api_default_format 'json';

See also "API-FORMATS" in Raisin.

api_format

Restricts API to use only specified formatter to serialize and deserialize data.

Already exists Raisin::Encoder::JSON, Raisin::Encoder::YAML, and Raisin::Encoder::Text, but you can always register your own using "register_encoder".

api_format 'json';

See also "API-FORMATS" in Raisin.

api_version

Sets up an API version header.

api_version 1.23;

plugin

Loads a Raisin module. A module options may be specified after the module name. Compatible with Kelp modules.

plugin 'Swagger';

middleware

Adds a middleware to your application.

middleware '+Plack::Middleware::Session' => { store => 'File' };
middleware '+Plack::Middleware::ContentLength';
middleware 'Runtime'; # will be loaded Plack::Middleware::Runtime

mount

Mounts multiple API implementations inside another one. These don't have to be different versions, but may be components of the same API.

In RaisinApp.pm:

package RaisinApp;

use Raisin::API;

api_format 'json';

mount 'RaisinApp::User';
mount 'RaisinApp::Host';

1;

register_decoder

Registers a third-party parser (decoder).

register_decoder(xml => 'My::Parser::XML');

See also Raisin::Decoder.

register_encoder

Registers a third-party formatter (encoder).

register_encoder(xml => 'My::Formatter::XML');

See also Raisin::Encoder.

run

Returns the PSGI application.

CONTROLLER

req

Provides quick access to the Raisin::Request object for the current route.

Use req to get access to request headers, params, env, etc.

use DDP;
p req->headers;
p req->params;
p req->env;

say req->header('X-Header');

See also Plack::Request.

res

Provides quick access to the Raisin::Response object for the current route.

Use res to set up response parameters.

res->status(403);
res->headers(['X-Application' => 'Raisin Application']);

See also Plack::Response.

param

Returns request parameters. Without an argument will return an array of all input parameters. Otherwise it will return the value of the requested parameter.

Returns Hash::MultiValue object.

say param('key'); # -> value
say param(); # -> { key => 'value', foo => 'bar' }

include_missing

Returns all declared parameters even if there is no value for a param.

See "Declared-parameters" in Raisin.

session

Returns psgix.session hash. When it exists, you can retrieve and store per-session data.

# store param
session->{hello} = 'World!';

# read param
say session->{name};

present

Raisin hash a built-in present method, which accepts two arguments: an object to be presented and an options associated with it. The options hash may include with key, which is defined the entity to expose. See Raisin::Entity.

my $artists = $schema->resultset('Artist');

present data => $artists, with => 'MusicApp::Entity::Artist';
present count => $artists->count;

Raisin::Entity supports DBIx::Class and Rose::DB::Object.

For details see examples in examples/music-app and Raisin::Entity.

ALLOWED METHODS

When you add a route for a resource, a route for the OPTIONS method will also be added. The response to an OPTIONS request will include an "Allow" header listing the supported methods.

get 'count' => sub {
    { count => $count };
};

params(
    requires('num', type => Int, desc => 'Value to add to the count.'),
);
put 'count' => sub {
    my $params = shift;
    $count += $params->{num};
    { count: $count };
};


curl -v -X OPTIONS http://localhost:5000/count

> OPTIONS /count HTTP/1.1
> Host: localhost:5000
>
* HTTP 1.0, assume close after body
< HTTP/1.1 204 No Content
< Allow: GET, OPTIONS, PUT

If a request for a resource is made with an unsupported HTTP method, an HTTP 405 (Method Not Allowed) response will be returned.

curl -X DELETE -v http://localhost:3000/count

> DELETE /count HTTP/1.1
> Host: localhost:5000
>
* HTTP 1.0, assume close after body
< HTTP/1.1 405 Method Not Allowed
< Allow: OPTIONS, GET, PUT

PARAMETERS

Request parameters are available through the params HASH. This includes GET, POST and PUT parameters, along with any named parameters you specify in your route strings.

Parameters are automatically populated from the request body on POST and PUT for form input, JSON and YAML content-types.

The request:

curl localhost:5000/data -H Content-Type:application/json -d '{"id": "14"}'

The Raisin endpoint:

post data => sub { param('id') };

Multipart POSTs and PUTs are supported as well.

In the case of conflict between either of:

  • path parameters;
  • GET, POST and PUT parameters;
  • contents of request body on POST and PUT;

Path parameters have precedence.

Query string and body parameters will be merged (see "parameters" in Plack::Request)

Declared parameters

Raisin allows you to access only the parameters that have been declared by you in "params" in Raisin block.

By default you can get all declared parameter as a first argument passed to your route subroutine.

Application:

api_format 'json';

post data => sub {
    my $params = shift;
    { data => $params };
};

Request:

curl -X POST -H "Content-Type: application/json" localhost:5000/signup -d '{"id": 42}'

Response:

{ "data": nil }

Once we add parameters block, Raisin will start return only the declared parameters.

Application:

api_format 'json';

params(
    requires('id', type => Int),
    optional('email', type => Str)
);
post data => sub {
    my $params = shift;
    { data => $params };
};

Request:

curl -X POST -H "Content-Type: application/json" localhost:5000/signup -d '{"id": 42, "key": "value"}'

Response:

{ "data": { "id": 42 } }

By default declared parameters don't contain parameters which have no value. If you want to return all parameters you can use the include_missing function.

Application:

api_format 'json';

params(
    requires('id', type => Int),
    optional('email', type => Str)
);
post data => sub {
    my $params = shift;
    { data => include_missing($params) };
};

Request:

curl -X POST -H "Content-Type: application/json" localhost:5000/signup -d '{"id": 42, "key": "value"}'

Response:

{ "data": { "id": 42, "email": null } }

Validation and coercion

You can define validations and coercion options for your parameters using a "params" in Raisin block.

Parameters can requires value or can be optional. optional parameters can have default value.

params(
    requires('name', type => Str),
    optional('count', type => Int, default => 10),
);
get sub {
    my $params = shift;
    "$params->{count}: $params->{name}";
};

Note that default values will NOT be passed through to any validation options specified.

Available arguments:

  • name
  • type
  • default
  • desc
  • regex
  • in

Nested Parameters

Hash

Use a keyword group to define a group of parameters which is enclosed to the parent HashRef parameter.

params(
    requires('name', type => HashRef, group {
        requires('first_name', type => Str),
        requires('last_name', type => Str),
    })
)

Array

Use ArrayRef[*] types from your compatible type library to define arrays.

requires('list', type => ArrayRef[Int], desc => 'List of integers')

Types

Raisin supports Moo(se)-compatible type constraint so you can use any of the Moose, Moo or Type::Tiny type constraints.

By default Raisin depends on Type::Tiny and it's Types::Standard type contraint library.

You can create your own types as well. See Type::Tiny::Manual and Moose::Manual::Types.

HOOKS

Those blocks can be executed before or/and after every API call, using before, after, before_validation and after_validation.

Callbacks execute in the following order:

  • before
  • before_validation
  • after_validation
  • after

The block applies to every API call

before sub {
    my $self = shift;
    say $self->req->method . "\t" . $self->req->path;
};

after_validation sub {
    my $self = shift;
    say $self->res->body;
};

Steps after_validation and after are executed only if validation succeeds.

Every callback has only one argument as an input parameter which is Raisin object. For more information of available methods see "CONTROLLER" in Raisin.

API FORMATS

By default, Raisin supports YAML, JSON, and TEXT content types. Default format is YAML.

Response format can be determined by Accept header or route extension.

Serialization takes place automatically. So, you do not have to call encode_json in each JSON API implementation.

The response format (and thus the automatic serialization) is determined in the following order:

  • Use the file extension, if specified. If the file is .json, choose the JSON format.
  • Attempt to find an acceptable format from the Accept header.
  • Use the default format, if specified by the default_format option.
  • Default to YAML.

Your API can declare to support only one serializator by using "api_format" in Raisin.

Custom formatters for existing and additional types can be defined with a Raisin::Encoder/Raisin::Decoder.

  • JSON

    Call JSON::encode_json and JSON::decode_json.

  • YAML

    Call YAML::Dump and YAML::Load.

  • Text

    Call Data::Dumper->Dump if output data is not a string.

The order for choosing the format is the following.

  • Use the route extension.
  • Use the value of the Accept header.
  • Fallback to default.

LOGGING

Raisin has a built-in logger and supports for Log::Dispatch. You can enable it by:

plugin 'Logger', outputs => [['Screen', min_level => 'debug']];

Or use Raisin::Logger with a fallback option:

plugin 'Logger', fallback => 1;

The plugin registers a log subroutine to Raisin. Below are examples of how to use it.

app->log(debug => 'Debug!');
app->log(warn => 'Warn!');
app->log(error => 'Error!');

app is a Raisin instance, so you can use $self instead of app where it is possible.

See Raisin::Plugin::Logger.

API DOCUMENTATION

Raisin script

You can see application routes with the following command:

$ raisin examples/pod-synopsis-app/darth.pl
GET     /user
GET     /user/all
POST    /user
GET     /user/:id
DELETE  /user/:id
PUT     /user/:id
GET     /echo

Including parameters:

$ raisin --params examples/pod-synopsis-app/darth.pl
GET     /user
   start Int{0}
   count Int{10}
GET     /user/all
POST    /user
  *name     Str
  *password Str
email    Str
GET     /user/:id
  *id Int
DELETE  /user/:id
  *id Int
PUT     /user/:id
  *id Int
GET     /echo
  *data Any{ั‘ะน}

OpenAPI/Swagger

Swagger compatible API documentations.

plugin 'Swagger';

Documentation will be available on http://<url>/swagger.json URL. So you can use this URL in Swagger UI.

See Raisin::Plugin::Swagger.

MIDDLEWARE

You can easily add any Plack middleware to your application using middleware keyword. See "middleware" in Raisin.

PLUGINS

Raisin can be extended using custom modules. Each new module must be a subclass of the Raisin::Plugin namespace. Modules' job is to initialize and register new methods into the web application class.

For more see "plugin" in Raisin and Raisin::Plugin.

TESTING

See Plack::Test, Test::More and etc.

my $app = Plack::Util::load_psgi("$Bin/../script/raisinapp.pl");

test_psgi $app, sub {
    my $cb  = shift;
    my $res = $cb->(GET '/user');

    subtest 'GET /user' => sub {
        if (!is $res->code, 200) {
            diag $res->content;
            BAIL_OUT 'FAILED!';
        }
        my $got = Load($res->content);
        isdeeply $got, $expected, 'Data!';
    };
};

DEPLOYING

Deploying a Raisin application is done the same way any other Plack application is deployed:

$ plackup -E deployment -s Starman app.psgi

Kelp

use Plack::Builder;
use RaisinApp;
use KelpApp;

builder {
    mount '/' => KelpApp->new->run;
    mount '/api/rest' => RaisinApp->new;
};

Dancer

use Plack::Builder;
use Dancer ':syntax';
use Dancer::Handler;
use RaisinApp;

my $dancer = sub {
    setting appdir => '/home/dotcloud/current';
    load_app 'My::App';
    Dancer::App->set_running_app('My::App');
    my $env = shift;
    Dancer::Handler->init_request_headers($env);
    my $req = Dancer::Request->new(env => $env);
    Dancer->dance($req);
};

builder {
    mount '/' => $dancer;
    mount '/api/rest' => RaisinApp->new;
};

Mojolicious::Lite

use Plack::Builder;
use RaisinApp;

builder {
    mount '/' => builder {
        enable 'Deflater';
        require 'my_mojolicious-lite_app.pl';
    };

    mount '/api/rest' => RaisinApp->new;
};

See also Plack::Builder, Plack::App::URLMap.

EXAMPLES

Raisin comes with three instance in example directory:

ROADMAP

  • Versioning support;
  • Mount API's in any place of resource block;

GITHUB

https://github.com/khrt/Raisin

ACKNOWLEDGEMENTS

This module was inspired both by Grape and Kelp, which was inspired by Dancer, which in its turn was inspired by Sinatra.

AUTHOR

Artur Khabibullin

COPYRIGHT AND LICENSE

This software is copyright (c) 2019 by Artur Khabibullin.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.

raisin's People

Contributors

adamgsg avatar bensinober avatar brunoramoslu avatar davel avatar djzort avatar dotandimet avatar dwburke avatar hidden-primary-net avatar jwrightecs avatar kabanoid avatar kimcognito avatar manwar avatar mkirank avatar mschout avatar renatocron avatar slobo avatar vlet avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

raisin's Issues

Undeclared dependency JSON.pm

Another undeclared dependency:

...
Can't locate JSON.pm in @INC (you may need to install the JSON module) (@INC contains: ... .) at t/behaviour/app-deserialize.t line 7.
BEGIN failed--compilation aborted at t/behaviour/app-deserialize.t line 7.
t/behaviour/app-deserialize.t ......... 
Dubious, test returned 2 (wstat 512, 0x200)
No subtests run 
... (etc) ...

'Unexpected error' In the responses/default/descritption of swagger

Cool project!

I noticed when generating swagger I get an 'Unexpected Error'. How can I document the expected response in Raisin?

 {
      "get": {
        "consumes": [
          "application/x-yaml",
          "application/json"
        ],
        "operationId": "get_users__id",
        "responses": {
          "default": {
            "description": "Unexpected error"
          }
        },
        "tags": [
          "users"
        ],
        "produces": [
          "application/x-yaml",
          "application/json"
        ],
        "summary": "Show user",
        "description": "",
        "parameters": [
          {
            "type": "integer",
            "in": "path",
            "required": true,
            "description": "User ID",
            "format": "int32",
            "name": "id"
          }
        ]
      },

Thanks!

Nested route_param loses params from parent route_param

We ran into this problem today. Seems that using route_param inside another route_param loses the params for the parent route_param.

Here is a test case demonstrating the problem:

subtest 'params nested route_param' => sub {
    resource api => sub {
        params requires => { name => 'id', type => undef };
        route_param id => sub {
            params(
                requires => { name => 'start', type => undef },
                optional => { name => 'count', type => undef },
            );
            get sub { param };

            params(
                requires => { name => 'sub_id', type => undef },
            );
            route_param sub_id => sub {
                get sub { param };
            };
        }
    };

    my $app = Raisin::API->app;
    my $e = $app->routes->routes->[1];

    my %params = map { $_->name => $_ } @{ $e->params };

    ok $params{id}, 'id';
    is $params{id}->named, 1, 'named';
    is $params{id}->required, 1, 'required';

    ok $params{sub_id}, 'sub_id';
    is $params{sub_id}->named, 1, 'named';
    is $params{sub_id}->required, 1, 'required';
};

What actually ends up in %params is only the sub_id param. The id param is lost.

Bug in Exporter call

Raisin::API sub import calls export_to_level which expects 3 parameters:

    MyPackage->export_to_level(
        $where_to_export, $package, @what_to_export
    );

However, Raisin::API only passes two parameters. The end result of this is that the first argument passed to "use Raisin::API" is ignored.

APIDocs - basePath broken?

I just upgraded from 0.26 and now basePath doesn't get rid of the "api-docs/" anymore and stays empty (see line 114 in APIDocs.pm).
This breaks SwaggerUI-compatible doc system (like wordnik) and all routs are now just appended after "api-docs/".

basePath should be (like it used to be) the request url minus the "api-docs/"

I edited line 114 in APIDocs.pm and hardcoded the $base_path but that works only because I have only 1 Raisin API running on my perl.

Please fix asap.

null in body params is not the same as undef

Thanks for this great and simple descriptive API!

I am thinking that the following code:

foreach my $p (@$declared) {
my $name = $p->name;
my $value = $params{$name};
if (not $p->validate(\$value)) {
$p->required ? return : next;
}
$value //= $p->default if defined $p->default;
next if not defined($value);
$self->{'raisin.declared_params'}{$name} = $value;
}

will filter out also defined null values in JSON body, which makes it difficult to define optional params that can be null, meaning a typical PUT or PATCH request to "unset" a value. Say, you have a DB column that needs to be NULLed.
Of course, you still get the "undef"s from req->raisin_parameters, so it can easily be circumvented, but I am still unsure why "undef"s should be removed from declared_params and params?

Is line 34 actually neccessary, is my point.

Tags created with <null> description in swagger file

Raisin automatically creates tags for route parameters and subsequent resources. Unfortunately the swagger file comes out with an empty description - or null which causes an error in the Swagger editor:

This code:

use strict;
use warnings;
use Carp;

use HTTP::Status qw(:constants);

use Data::Dumper;
use Raisin::API;
use Types::Standard qw(HashRef Any Int Str);

plugin 'Logger', fallback => 1;
app->log(debug => 'Start');
api_format 'json';

middleware 'CrossOrigin',
    origins => q(*),
    methods => [qw/DELETE GET HEAD OPTIONS PATCH POST PUT/],
    headers => [qw/accept authorization content-type api_key_token/];
plugin 'Swagger';

swagger_setup(
    title       => 'Test Raisin Api',
    description => 'TestRaisin',
    contact     => {
        name  => 'Conrad Beckert',
        url   => 'http://beccon.de',
        email => '[email protected]',
    },

    license => {
        name => 'Testmich - darf alles',
        url  => 'http://www.testmich.de',
    },
);

desc 'Users API';

resource cm => sub {
    summary 'cm sub';

    params requires('mac', type => Str);
    route_param mac => sub {
        desc 'route param desc';
        summary 'route param summary';
        resource 'foo' => sub {

            desc "foo desc";
            get sub {
                desc "foo get desc";
                my $params = shift;
                my $mac    = $params->{mac};

                res->status(HTTP_OK);
                return { hallo => $mac };
            };
            }
    };

    };

    run;

__END__

produces:

`tags:

  • name: ':mac'
    description: null
    `

Which the Swagger Editor marks as error.

Checking an OAuth2 bearer token

Hey,

I'm looking into using Raisin for implementing a nice API into a legacy system, using OAuth2 bearer tokens. I couldn't find any Raisin-based solution that does something similar, aside from Rack middleware that I include and then manually check the environment at the top of every handler (ugly!).

I'd like to make a proof of concept where I intercept a HTTP request, call a custom handler to check that a bearer token exists and is valid, and then proceed if that is the case.

I'm trying to do this as a plugin. However, I've hit a few walls, most notably that I can register a plugin but I can't actually get any information about the route handler I'm calling it before and I can't register a hook to intercept calls.

Working from a high-level concept of how I'd like it to work inspired by Wine Bouncer:

plugin 'OAuth2';
oauth2_setup(
  validate_bearer_token => sub {
    my ($token, $scopes) = @_;
    # ...
  }
);

summary 'Get user';
oauth2 'users:read', 'admin';
params(
  required('userid', type => Int, desc => 'User ID'),
);
get sub {

};

I'd like to mark up this route with users:read and admin as a list of required token scopes (any one will do). I tried hacking this into the $SETTINGS used by the Swagger plugin, but looks like the ones that aren't known are not actually propagated or stored anywhere.

Next up, I'd like to hook into the request chain, and have my handler fire at before_validation, for instance. However, hooks don't have any context about the route or request, and neither can they stop processing. Bummer! I was thinking to do something like:

before_validation sub {
  my ($self, $route, $request, $response) = @_;
  if ($route->metadata_or_whatever->{oauth2}) {
    # more checks here ...
    return (403, 'Not permitted to access this resource');
  }
};

from the plugin code iself, and just call into the user bearer validator (seen above) to check the token.

Is the idea behind hooks/plugins to be able to accommodate something like this or am I looking at the wrong feature?

app->log() broken

The CPAN version of Raisin, app->log() is broken, it was fixed on this commit: 8c9c140

Could you please release to CPAN a new version?

Need to 'use Raisin::Types' somewhere

If you don't have 'use Raisin::Types' this in your RaisinApp (as in example). It will stuff up.
eg

$ raisin --routes --params api.pl
GET     //user
Can't locate object method "name" via package "Raisin::Types::Integer" (perhaps you forgot to load "Raisin::Types::Integer"?) at .../bin/raisin line 62..

Raisin in 5.10

In 5.10, split in scalar context still split to @, which was removed in 5.12, this affects, at least, Raisin/Middleware/Formatter.pm@65, which throws the deprecation warning when run on 5.10/5.10.1
Use of implicit split to @
is deprecated at /lib/perl5/Raisin/Middleware/Formatter.pm line 65.

Coercion of Type::Tiny types

Is there a way to trigger Type::Tiny to coerce the values?

Potentially like

    params( requires('postcode', type => PostCode, desc => 'Post Code', **coerce => 1** ),
            requires('prodcode', type => ProdCode, desc => 'Product Code'),
            optional('orderdate',type => Str,      desc => 'Order Date', default => 'now')
    );

Sort of like how you might go

has lines => (
is => "ro",
isa => ArrayRef->plus_coercions(LinesFromStr),
coerce => 1,
);

See also http://search.cpan.org/~tobyink/Type-Tiny-1.000006/lib/Type/Tiny/Manual/Coercions.pod

Example shows '/users should be 'user'

If you do

namespace '/user' => sub {

you get '//user' as the route

 raisin --routes api.pl
  GET     //user
  GET     //user/all
  POST    //user
  GET     //user/{id}

PS the example also has a mistake:

    sub {
        get sub {
            my $params = shift;
            %USERS{ $params->{id} };
        };
    };

should be $USERS

Serialize JSON

Ist there an easier way to return Perl objects (blessed or Moose) than to

  • change the lib/Raisin/Encoder/JSON.pm to accomodate allow_blessed->convert_blessed->en/decode
  • implement or inherit a TO_JSON serializing function in each object to be returned.

Returning blessed objects to YAML seems to work out of the box. JSON support for plain hashes is also implemented in Raisin.

But perhaps I'm missing the direct path.

Missing SecurityDefinitionsObject on Raisin::Plugin::Swagger

Hello,

I see no documentation about security object definition, but I see there are some commented lines on the code:

    my %spec = (
        swagger  => '2.0',
        info     => _info_object($app),
        host     => $req->env->{HTTP_HOST},
        basePath => $base_path,
        schemes  => [$req->scheme],
        consumes => \@content_types,
        produces => \@content_types,
        paths    => _paths_object($routes), #R
        definitions => _definitions_object($routes),
        #parameters => undef,
        #responses => undef,
        #securityDefinitions => undef,
        #security => undef,
        tags => _tags_object($self->app),
        #externalDocs => '', # TODO
    );

So is there some plans adding support or thoughts about security definitions on OpenAPI spec?

Personally I'll be needing api_key, but trying to figure it out how to implement it with Raisin :) Code examples on authentication also are very welcome.

Loving this simplicity of Raisin ๐Ÿ‘ Little of coding and you have working REST api

Best regards, Jarkko

How to return 404?

When an object/element/whatever doesnt exist, whats the proper way of returning a 404?

For example, with:
curl localhost:5000/user/:id
If there is no user with that ID, i would like to return a 404 - which i believe is the most correct behaviour for rest

Swagger Schema Expected Response and Actual Response Do Not Match

I've noticed with the music-app example, the response is contained within an object with key "data". But, when interpreting the Swagger Schema ( visually and with swagger-ui tool ), there is no hint that the top-level object with key "data" should ever exist. See below image for swagger-ui schema showing the mismatch in expected schema response data structure and actual server response data structure. Do you have any ideas on how to make schema-expected and actual responses match when using Entities? I have some rough ideas on how to solve this with a change to Raisin source, but could potentially introduce breaking-changes for others. Here is one idea:

Replace Line 57 in Raisin::Middleware::Formatter with:
if ( defined $r->body->{data} and ref $r->body->{data} ) { $r->body($s->serialize($r->body->{data})); } else { $r->body($s->serialize($r->body)); }

Screen Shot 2019-08-01 at 3 18 56 PM

List::Util dependency

I've tried to build deb package for Ubuntu Server 14.04 LTS and ran into problem: pairs function available only in List::Util โ‰ฅ 1.29. I can't upgrade List::Util, because it is a part of perl-base package in Ubuntu.
pairs used only in one place at Raisin/Routes.pm and can be replaced with splice (since temporary array created in any case). I've built patched libraisin-perl and seems like it works for me. May be pairs simplicity is not worth packaging problems on major stable distribution?

The darth.pl example can't post new users, missing max().

The darth.pl example explodes when one tries to post a new user (via the swagger sandbox).

The script seems to be missing a definition for a max() function.

Adding

use List::Util qw(max);

e.g. after line 12 seems to fix it.

I'm using

allons:~ USERNAME$ perl -v

This is perl 5, version 18, subversion 4 (v5.18.4) built for darwin-2level

on a mac running OS X 10.9.5.

I set up my test environment via

allons:~ USERNAME$ cpanm -l ~/tmp/raisin-experiment Raisin

and ran the demo like this:

allons:~ USERNAME$ env PERL5LIB=/Users/USERNAME/tmp/raisin-experiment/lib/perl5 PATH=/Users/USERNAME/tmp/raisin-experiment/bin:$PATH plackup ~/.cpanm/latest-build/Raisin-0.58/examples/pod-synopsis-app/darth.pl

g.

access to json serializer?

This might not be a bug, there doesn't seem to be a way to access the json serializer to configure it (for example, to enable convert_blessed()

route_param regression from 0.86 -> 0.87

This works in 0.86 but not in 0.87

resource 'etd' => sub {

    summary 'Get an estimate';
    desc 'Estimated Time of Delivery';
    route_param 'locale' => sub {
        route_param 'postcode' => sub {

            # query param style
            before_validation \&fix_material;
            params(
                requires( 'locale',   type => Locale,   desc => 'Locale (AU/NZ)' ),
                requires( 'postcode', type => PostCode, desc => 'Post Code' ),
                requires(
                    'material',
                    type => ArrayRef [MaterialCode],
                    desc => 'Material Code(s)'
                ),
                optional(
                    'orderdate',
                    type    => Str,
                    desc    => 'Order Date',
                    default => 'now'
                )
            );
            summary
              'GET one or more products via material code as query parameter';
            get \&guess_etd;

            # url path style
            params(
                requires( 'locale',   type => Locale,   desc => 'Locale (AU/NZ)' ),
                requires( 'postcode', type => PostCode, desc => 'Post Code' ),
                requires(
                    'material',
                    type => MaterialCode,
                    desc => 'Material Code'
                ),
                optional(
                    'orderdate',
                    type    => Str,
                    desc    => 'Order Date',
                    default => 'now'
                )
            );

            route_param 'material' => sub {
                summary 'GET a single product via material code in url';
                get \&guess_etd;
            };    # route_param 'material'

        };    # route_param 'postcode'
    };    # route_param 'locale'

};    # resource 'etd'

The api is as follows

/etd///

or

/etd///?material=&material=

Locale, PostCode, ad MaterialCode are custom constraints.

What doesnt work in 0.87 is the route_param version. The &guess_etd is called BUT the $_[0] is empty rather than containing the params as it does with the material in query query version

the fix_material sub massages the material= so that its always an arrayref even if there is just one value (which is an annoying bug of itself, as a single value will give a scalar and the type will fail)

Disable formatters

Basically, I only want JSON. Route extensions should return 404, and a non-JSON Accept: header should return 406. I don't see any way in docs or code to arrange this.

Buggy log, variables not filled in

When seeing bug #22 I also saw a message like these in the logs:
Param `%s` didn't pass constraint `%s` with value "%s"

This should not come in the log, the placeholders should be replaced by the real values.

Raisin::Logger documentation contradicts itself

The documentation for the ::Logger class presents a contradiction.

The synopsis says:

my $logger = Raisin::Logger->new;
$logger->log(info => 'Hello, world!');

But 'info' isn't an option, according to the log() method entry:

METHODS
log
Accept's two parameters: level and message.

accurate parsing of accept header

By default jquery send this accept header:
application/json, text/javascript, */*; q=0.01

Raisin return "Invalid accept header" error, because it expects only one media type

Boolean and JSON

Using Boolean params with JSON does not work because the validation fails. JSON returns JSON::true/JSON::false for true/false values, so these may not be recognized by the validator.

Idea for solution:
Convert JSON::true/JSON::false values in Bool type parameters to 1/0 before validating them.

Canonical way to read in config file?

Is there a canonical way for Raisin apps to read in their config file?

Many other frameworks provide this type of support, making life easier (or annoying you, if you dont like they way they do it)

Problem with Content-Type

Hello,
The function Raisin::Util::detect_serializer does not support this value of Content-Type:
application/json; charset=UTF-8

Examples of using Raisins built in logging features

I found the documentation for the logging features confusing. And when i looked in examples/ as well as the synopsis pod they are not used.

Could you please add some logging examples in to the examples/ scripts as well as into the synopsis pod of Raisin.pm ? thanks.

Undeclared dependency Test::Exception

t/unit/api.t fails if Test::Exception is not installed:

Can't locate Test/Exception.pm in @INC (you may need to install the Test::Exception module) (@INC contains: /home/cpansand/.cpan/build/2019062010/Raisin-0.83-2/blib/lib /home/cpansand/.cpan/build/2019062010/Raisin-0.83-2/blib/arch /home/cpansand/.cpan/build/2019062010/Type-Tiny-1.004004-1/blib/arch /home/cpansand/.cpan/build/2019062010/Type-Tiny-1.004004-1/blib/lib /home/cpansand/.cpan/build/2019062010/Exporter-Tiny-1.002001-0/blib/arch /home/cpansand/.cpan/build/2019062010/Exporter-Tiny-1.002001-0/blib/lib /home/cpansand/.cpan/build/2019062010/Plack-Middleware-CrossOrigin-0.014-1/blib/arch /home/cpansand/.cpan/build/2019062010/Plack-Middleware-CrossOrigin-0.014-1/blib/lib /home/cpansand/.cpan/build/2019062010/Plack-1.0047-1/blib/arch /home/cpansand/.cpan/build/2019062010/Plack-1.0047-1/blib/lib /home/cpansand/.cpan/build/2019062010/Test-TCP-2.19-1/blib/arch /home/cpansand/.cpan/build/2019062010/Test-TCP-2.19-1/blib/lib /home/cpansand/.cpan/build/2019062010/HTTP-Headers-Fast-0.22-1/blib/arch /home/cpansand/.cpan/build/2019062010/HTTP-Headers-Fast-0.22-1/blib/lib /home/cpansand/.cpan/build/2019062010/HTTP-Entity-Parser-0.21-1/blib/arch /home/cpansand/.cpan/build/2019062010/HTTP-Entity-Parser-0.21-1/blib/lib /home/cpansand/.cpan/build/2019062010/WWW-Form-UrlEncoded-0.26-1/blib/arch /home/cpansand/.cpan/build/2019062010/WWW-Form-UrlEncoded-0.26-1/blib/lib /home/cpansand/.cpan/build/2019062010/Stream-Buffered-0.03-1/blib/arch /home/cpansand/.cpan/build/2019062010/Stream-Buffered-0.03-1/blib/lib /home/cpansand/.cpan/build/2019062010/JSON-MaybeXS-1.004000-1/blib/arch /home/cpansand/.cpan/build/2019062010/JSON-MaybeXS-1.004000-1/blib/lib /home/cpansand/.cpan/build/2019062010/Cpanel-JSON-XS-4.12-1/blib/arch /home/cpansand/.cpan/build/2019062010/Cpanel-JSON-XS-4.12-1/blib/lib /home/cpansand/.cpan/build/2019062010/Hash-MultiValue-0.16-1/blib/arch /home/cpansand/.cpan/build/2019062010/Hash-MultiValue-0.16-1/blib/lib /home/cpansand/.cpan/build/2019062010/HTTP-MultiPartParser-0.02-1/blib/arch /home/cpansand/.cpan/build/2019062010/HTTP-MultiPartParser-0.02-1/blib/lib /home/cpansand/.cpan/build/2019062010/Filesys-Notify-Simple-0.13-1/blib/arch /home/cpansand/.cpan/build/2019062010/Filesys-Notify-Simple-0.13-1/blib/lib /home/cpansand/.cpan/build/2019062010/Test-SharedFork-0.35-1/blib/arch /home/cpansand/.cpan/build/2019062010/Test-SharedFork-0.35-1/blib/lib /home/cpansand/.cpan/build/2019062010/File-ShareDir-1.116-1/blib/arch /home/cpansand/.cpan/build/2019062010/File-ShareDir-1.116-1/blib/lib /home/cpansand/.cpan/build/2019062010/Class-Inspector-1.34-1/blib/arch /home/cpansand/.cpan/build/2019062010/Class-Inspector-1.34-1/blib/lib /home/cpansand/.cpan/build/2019062010/Devel-StackTrace-AsHTML-0.15-1/blib/arch /home/cpansand/.cpan/build/2019062010/Devel-StackTrace-AsHTML-0.15-1/blib/lib /home/cpansand/.cpan/build/2019062010/Devel-StackTrace-2.04-1/blib/arch /home/cpansand/.cpan/build/2019062010/Devel-StackTrace-2.04-1/blib/lib /home/cpansand/.cpan/build/2019062010/Cookie-Baker-0.11-1/blib/arch /home/cpansand/.cpan/build/2019062010/Cookie-Baker-0.11-1/blib/lib /home/cpansand/.cpan/build/2019062010/Test-Time-0.08-1/blib/arch /home/cpansand/.cpan/build/2019062010/Test-Time-0.08-1/blib/lib /home/cpansand/.cpan/build/2019062010/Apache-LogFormat-Compiler-0.35-1/blib/arch /home/cpansand/.cpan/build/2019062010/Apache-LogFormat-Compiler-0.35-1/blib/lib /home/cpansand/.cpan/build/2019062010/Test-Requires-0.10-1/blib/arch /home/cpansand/.cpan/build/2019062010/Test-Requires-0.10-1/blib/lib /home/cpansand/.cpan/build/2019062010/Test-MockTime-0.17-1/blib/arch /home/cpansand/.cpan/build/2019062010/Test-MockTime-0.17-1/blib/lib /home/cpansand/.cpan/build/2019062010/POSIX-strftime-Compiler-0.42-1/blib/arch /home/cpansand/.cpan/build/2019062010/POSIX-strftime-Compiler-0.42-1/blib/lib /home/cpansand/.cpan/build/2019062010/File-ShareDir-Install-0.13-1/blib/arch /home/cpansand/.cpan/build/2019062010/File-ShareDir-Install-0.13-1/blib/lib /home/cpansand/.cpan/build/2019062010/JSON-4.02-1/blib/arch /home/cpansand/.cpan/build/2019062010/JSON-4.02-1/blib/lib /home/cpansand/.cpan/build/2019062010/Type-Tiny-1.004004-1/blib/arch /home/cpansand/.cpan/build/2019062010/Type-Tiny-1.004004-1/blib/lib /home/cpansand/.cpan/build/2019062010/Exporter-Tiny-1.002001-0/blib/arch /home/cpansand/.cpan/build/2019062010/Exporter-Tiny-1.002001-0/blib/lib /home/cpansand/.cpan/build/2019062010/Plack-Middleware-CrossOrigin-0.014-1/blib/arch /home/cpansand/.cpan/build/2019062010/Plack-Middleware-CrossOrigin-0.014-1/blib/lib /home/cpansand/.cpan/build/2019062010/Plack-1.0047-1/blib/arch /home/cpansand/.cpan/build/2019062010/Plack-1.0047-1/blib/lib /home/cpansand/.cpan/build/2019062010/Test-TCP-2.19-1/blib/arch /home/cpansand/.cpan/build/2019062010/Test-TCP-2.19-1/blib/lib /home/cpansand/.cpan/build/2019062010/HTTP-Headers-Fast-0.22-1/blib/arch /home/cpansand/.cpan/build/2019062010/HTTP-Headers-Fast-0.22-1/blib/lib /home/cpansand/.cpan/build/2019062010/HTTP-Entity-Parser-0.21-1/blib/arch /home/cpansand/.cpan/build/2019062010/HTTP-Entity-Parser-0.21-1/blib/lib /home/cpansand/.cpan/build/2019062010/WWW-Form-UrlEncoded-0.26-1/blib/arch /home/cpansand/.cpan/build/2019062010/WWW-Form-UrlEncoded-0.26-1/blib/lib /home/cpansand/.cpan/build/2019062010/Stream-Buffered-0.03-1/blib/arch /home/cpansand/.cpan/build/2019062010/Stream-Buffered-0.03-1/blib/lib /home/cpansand/.cpan/build/2019062010/JSON-MaybeXS-1.004000-1/blib/arch /home/cpansand/.cpan/build/2019062010/JSON-MaybeXS-1.004000-1/blib/lib /home/cpansand/.cpan/build/2019062010/Cpanel-JSON-XS-4.12-1/blib/arch /home/cpansand/.cpan/build/2019062010/Cpanel-JSON-XS-4.12-1/blib/lib /home/cpansand/.cpan/build/2019062010/Hash-MultiValue-0.16-1/blib/arch /home/cpansand/.cpan/build/2019062010/Hash-MultiValue-0.16-1/blib/lib /home/cpansand/.cpan/build/2019062010/HTTP-MultiPartParser-0.02-1/blib/arch /home/cpansand/.cpan/build/2019062010/HTTP-MultiPartParser-0.02-1/blib/lib /home/cpansand/.cpan/build/2019062010/Filesys-Notify-Simple-0.13-1/blib/arch /home/cpansand/.cpan/build/2019062010/Filesys-Notify-Simple-0.13-1/blib/lib /home/cpansand/.cpan/build/2019062010/Test-SharedFork-0.35-1/blib/arch /home/cpansand/.cpan/build/2019062010/Test-SharedFork-0.35-1/blib/lib /home/cpansand/.cpan/build/2019062010/File-ShareDir-1.116-1/blib/arch /home/cpansand/.cpan/build/2019062010/File-ShareDir-1.116-1/blib/lib /home/cpansand/.cpan/build/2019062010/Class-Inspector-1.34-1/blib/arch /home/cpansand/.cpan/build/2019062010/Class-Inspector-1.34-1/blib/lib /home/cpansand/.cpan/build/2019062010/Devel-StackTrace-AsHTML-0.15-1/blib/arch /home/cpansand/.cpan/build/2019062010/Devel-StackTrace-AsHTML-0.15-1/blib/lib /home/cpansand/.cpan/build/2019062010/Devel-StackTrace-2.04-1/blib/arch /home/cpansand/.cpan/build/2019062010/Devel-StackTrace-2.04-1/blib/lib /home/cpansand/.cpan/build/2019062010/Cookie-Baker-0.11-1/blib/arch /home/cpansand/.cpan/build/2019062010/Cookie-Baker-0.11-1/blib/lib /home/cpansand/.cpan/build/2019062010/Test-Time-0.08-1/blib/arch /home/cpansand/.cpan/build/2019062010/Test-Time-0.08-1/blib/lib /home/cpansand/.cpan/build/2019062010/Apache-LogFormat-Compiler-0.35-1/blib/arch /home/cpansand/.cpan/build/2019062010/Apache-LogFormat-Compiler-0.35-1/blib/lib /home/cpansand/.cpan/build/2019062010/Test-Requires-0.10-1/blib/arch /home/cpansand/.cpan/build/2019062010/Test-Requires-0.10-1/blib/lib /home/cpansand/.cpan/build/2019062010/Test-MockTime-0.17-1/blib/arch /home/cpansand/.cpan/build/2019062010/Test-MockTime-0.17-1/blib/lib /home/cpansand/.cpan/build/2019062010/POSIX-strftime-Compiler-0.42-1/blib/arch /home/cpansand/.cpan/build/2019062010/POSIX-strftime-Compiler-0.42-1/blib/lib /home/cpansand/.cpan/build/2019062010/File-ShareDir-Install-0.13-1/blib/arch /home/cpansand/.cpan/build/2019062010/File-ShareDir-Install-0.13-1/blib/lib /home/cpansand/.cpan/build/2019062010/JSON-4.02-1/blib/arch /home/cpansand/.cpan/build/2019062010/JSON-4.02-1/blib/lib /opt/perl-5.28.2t/lib/site_perl/5.28.2/x86_64-linux-thread-multi /opt/perl-5.28.2t/lib/site_perl/5.28.2 /opt/perl-5.28.2t/lib/5.28.2/x86_64-linux-thread-multi /opt/perl-5.28.2t/lib/5.28.2 .) at t/unit/api.t line 6.
BEGIN failed--compilation aborted at t/unit/api.t line 6.
t/unit/api.t .......................... 
Dubious, test returned 2 (wstat 512, 0x200)
No subtests run 

Round trip: Raisin -> Swagger -> codegen Perl Client

Using the samples at Github I set up a rather simple server:

use HTTP::Status qw(:constants);
use List::Util qw(max);
use Raisin::API;
use Types::Standard qw(HashRef Any Int Str);

plugin 'Logger', fallback => 1;
app->log(debug => 'Starting Raisin...');

middleware 'CrossOrigin',
    origins => '*',
    methods => [qw/DELETE GET HEAD OPTIONS PATCH POST PUT/],
    headers => [qw/accept authorization content-type api_key_token/];

plugin 'Swagger';

swagger_setup(
    title       => 'A simple test',
    description => 'Roundtrip Raisin - Swagger - Perl Client',

);

desc 'Users API';
resource foo => sub {
    summary 'foo';
    params(
        requires('foo' , type=> Str ),
    );
    post sub {
        my $params = shift;
        app->log(debug => params->{foo});
        res->status(HTTP_CREATED);
        { success => 1 }
    }

};


run;

Which results in the following Swagger-File:

{
   "security" : [],
   "schemes" : [
      "http"
   ],
   "host" : "localhost:5000",
   "info" : {
      "description" : "Roundtrip Raisin - Swagger - Perl Client",
      "version" : "0.0.1",
      "title" : "A simple test"
   },
   "securityDefinitions" : {},
   "basePath" : "/",
   "definitions" : {},
   "swagger" : "2.0",
   "consumes" : [
      "application/x-yaml",
      "application/json"
   ],
   "tags" : [
      {
         "name" : "foo",
         "description" : "Users API"
      }
   ],
   "paths" : {
      "/foo" : {
         "post" : {
            "consumes" : [
               "application/x-yaml",
               "application/json"
            ],
            "tags" : [
               "foo"
            ],
            "produces" : [
               "application/x-yaml",
               "application/json"
            ],
            "summary" : "foo",
            "responses" : {
               "default" : {
                  "description" : "Unexpected error"
               }
            },
            "operationId" : "post_foo",
            "description" : "",
            "parameters" : [
               {
                  "in" : "formData",
                  "description" : "",
                  "name" : "foo",
                  "type" : "string",
                  "required" : true
               }
            ]
         }
      }
   },
   "produces" : [
      "application/x-yaml",
      "application/json"
   ]
}

Pasting this into the Swagger editor results in the following error message:

Semantic error at paths./foo.post
Operations with Parameters of "in: formData" must include "application/x-www-form-urlencoded" or "multipart/form-data" in their "consumes" property

... and the suggested App call:

curl -X POST "http://localhost:5000/foo" -H "accept: application/json" -H "Content-Type: application/json" -d "foo=bar"
which is of course not valid as we'd expect something JSON we could pick up from POST, such as
curl -X POST "http://localhost:5000/foo" -H "accept: application/json" -H "Content-Type: application/json" -d '{"foo":"bar"}'

The code generated with codegen doen't work either as the server expects formData. The documentation suggests parameter settings as I put in my sample.

Using the full code listing from the Github synposis (user management sample) doesn't result in usable code either.

What's wrong here? An issue with the software or with the documentation?

Inconsistent behavior of present when no rows are found

If the result of a DBIx::Class query given to present returns no rows, the generated JSON is undef, rather than an empty array. Can the behaviour be changed to make it return [], so as to make the behaviour consistent?

Many thanks,
Dave

Swagger file incomplete and with errors

I noticed a difference in the Swagger file genererated when I switch from Yaml to Json format and vice versa. Both seem to be incomplete in the way that they lack the definition of the Album entity causing the Swagger output description to be incomplete ("null")

{
  "artist": "string",
  "name": "string",
  "hash": "string",
  "id": 0,
  "albums": [
    null
  ]
}


YAML:

  • Error Message "required: &2 !!perl/scalar:JSON::PP::Boolean 1" in line 60
  • Missing definition of Album
  • Strange trailing hex codes in the object names. (why are they necessary? Names should be unique anyway)
basePath: /
consumes: &1
  - application/x-yaml
  - application/json
definitions:
  MusicApp::Entity::Artist-302668EB84:
    properties:
      albums:
        items:
          $ref: '#/definitions/MusicApp::Entity::Album-AFC2856845'
        type: array
      artist:
        description: Artist name
        type: string
      hash:
        description: ID*10
        type: string
      id:
        description: ID
        format: int32
        type: integer
      name:
        description: Artist name /shown only if it equals to Nirvana/
        type: string
    required:
      - id
      - name
      - artist
      - hash
      - albums
    type: object
host: localhost:5004
info:
  title: API
  version: 0.0.1
paths:
  /albums:
    get:
      consumes: *1
      description: ''
      operationId: get_albums
      produces: *1
      responses:
        default:
          description: Unexpected error
      summary: List
      tags:
        - albums
  '/albums/{id}':
    get:
      consumes: *1
      description: ''
      operationId: get_albums__id
      parameters:
        - description: ''
          format: int32
          in: path
          name: id
          required: &2 !!perl/scalar:JSON::PP::Boolean 1
          type: integer
      produces: *1
      responses:
        default:
          description: Unexpected error
      summary: ''
      tags:
        - albums
  /artists:
    get:
      consumes: *1
      description: ''
      operationId: get_artists
      produces: *1
      responses:
        200:
          description: List
          schema:
            $ref: '#/definitions/MusicApp::Entity::Artist-302668EB84'
        default:
          description: Unexpected error
      summary: List
      tags:
        - artists
  '/artists/{id}':
    get:
      consumes: *1
      description: ''
      operationId: get_artists__id
      parameters:
        - description: ''
          format: int32
          in: path
          name: id
          required: *2
          type: integer
      produces: *1
      responses:
        200:
          description: ''
          schema:
            $ref: '#/definitions/MusicApp::Entity::Artist-302668EB84'
        default:
          description: Unexpected error
      summary: ''
      tags:
        - artists
produces: *1
schemes:
  - http
security: []
securityDefinitions: {}
swagger: 2.0
tags:
  - description: Artist API
    name: artists
  - description: Albums API
    name: albums

The first complain is about the line 60:

required: &2 !!perl/scalar:JSON::PP::Boolean 1

which is: unknown tag !tag:yaml.org,2002:perl/scalar:JSON::PP::Boolean

We have a definition of the Entity usicApp::Entity::Artist-302668EB84 which references the definition for the nested object Albums which links here

#/definitions/MusicApp::Entity::Album-AFC2856845'

but the corresponding definition for Album doesn't exist.

Json

The Json Representation of the same application causes an additional complains about

Semantic error at definitions.MusicApp::Entity::Artist-302668EB84.properties.albums.items.$ref
$refs must reference a valid location in the document
Jump to line 24

due to the quoting of the object names. (probably comes from the fact that Json requires quoting)

The definition for the Album entity is missing here too.

Proposal for a new hook

I would find useful to add a new hook to be called in case of failed validation, to allow for modification of headers, content body before a standard 400 Bad Request response is returned.
It is clear to me how to add such a hook, but not how to get validation errors that now are logged on STDERR.

Missing Dumper in Raisin/Plugin/Logger.pm

Using Raisin and triggering an error in a route gives Undefined subroutine &Raisin::Plugin::Logger::Dumper called at /usr/local/share/perl/5.24.1/Raisin/Plugin/Logger.pm line 39

Maybe you should add use Data::Dumper to the file, which fixes the problem for me.

route_param and dot in parameter

Parameters captured by route_param can't contain dots.

For example, route '/:name'

params requires => { name => "name", type => Str };
route_param 'name' => sub {
    get sub {
        [ "hello" => shift->{name} ]
    }
}

This is ok

$ curl 'http://0:5000/username'
["hello" : "username"]

but this isn't

$ curl 'http://0:5000/user.name'
Nothing found

multiple values for same paramter / ArrayRef[Str] parameters?

Having a problem with parameters -- if I declare a parameter as ArrayRef[Str], the parser will only see the parameter if there is more than one of them. If I declare a paramter as Str, the parser will not see the parameter unless there is only one.

use strict;
use Data::Dumper;
use warnings;
use FindBin;
use lib ( "$FindBin::Bin/../lib", ".", "$FindBin::Bin/../../Raisin/lib" );

use utf8;

use Raisin::API;
use Types::Standard qw(ArrayRef HashRef Any Int Str);

api_version 0.1;
api_format 'json';

desc 'Items API';
resource items => sub {
    summary 'Query items';
    params(
        optional('a', type => ArrayRef[Str], default => undef,
            desc => 'test arrayref param'),
        optional('s', type => Str, default => undef,
            desc => 'test string')
    );
    get sub {
        my $params = shift;
        my $items = undef;
        my $total = undef;
        print "Params: " . Dumper($params);
    };
};

run;

Queries:

curl 'http://localhost:5000/items?s=s1'

INFO 2016-11-22T15:09:51.790 `a` optional and empty
Params: $VAR1 = {
          's' => 's1'
        };
127.0.0.1 - - [22/Nov/2016:15:09:51 -0800] "GET /items?s=s1 HTTP/1.1" 200 1 "-" "curl/7.47.1
curl 'http://localhost:5000/items?s=s1&s=s2'

INFO 2016-11-22T15:10:32.437 `a` optional and empty
WARN 2016-11-22T15:10:32.439 Param `s` didn't pass constraint `Str` with value "ARRAY(0x2d9fda8)"
Params: $VAR1 = undef;
127.0.0.1 - - [22/Nov/2016:15:10:32 -0800] "GET /items?s=s1&s=s2 HTTP/1.1" 200 1 "-" "curl/7.47.1"
curl 'http://localhost:5000/items?a=a1'

WARN 2016-11-22T15:11:21.090 Param `a` didn't pass constraint `__ANON__` with value "a1"
INFO 2016-11-22T15:11:21.090 `s` optional and empty
Params: $VAR1 = undef;
127.0.0.1 - - [22/Nov/2016:15:11:21 -0800] "GET /items?a=a1 HTTP/1.1" 200 1 "-" "curl/7.47.1"
curl 'http://localhost:5000/items?a=a1&a=a2'

INFO 2016-11-22T15:11:51.596 `s` optional and empty
Params: $VAR1 = {
          'a' => [
                   'a1',
                   'a2'
                 ]
        };
127.0.0.1 - - [22/Nov/2016:15:11:51 -0800] "GET /items?a=a1&a=a2 HTTP/1.1" 200 1 "-" "curl/7.47.1"

Maybe I'm misunderstanding how these parameters should be described?

--pryankster

docs/examples use different resource-notation

The current docs & examples use different notation for resource:
resource somerecource => sub {
and
resource => somerecource => sub {

Apparently only the last is correct as it doesn't give the following parsing-errors when running raisin --routes:
Use of uninitialized value $params{"method"} in uc at /Library/Perl/5.12/Raisin/Routes.pm line 34.
Method and path are required at /Library/Perl/5.12/Raisin.pm line 53.

failed test t/unit/middleware/formatter.t

Just have tried to build Raisin 0.70 on debian jessie and have got error:

$ perl -Iblib/lib t/unit/middleware/formatter.t
...
    not ok 2 - content: yaml, default_format: yaml
    #   Failed test 'content: yaml, default_format: yaml'
    #   at t/unit/middleware/formatter.t line 96.
    #          got: undef
    #     expected: 'yaml'
    # unsupported media type: application/xml
...

Dumping env have revealed cause:

    '_content' => 'YAML Error: Stream does not end with newline character
    #    Code: YAML_PARSE_ERR_NO_FINAL_NEWLINE
    #    Line: 0
    #    Document: 0

Looks like YAML not satisfied with 'key: val' , and wants "\n" at the end.

"using" in an entity without declaring an OpenAPI array

Hello,

I am trying to use Raisin for an API which returns a single row of data, read by calling DBIx::Class's ->find(). I am having trouble making the generated OpenAPI / Swagger schema agree with the generated JSON.

I have two Raisin::Entity classes. One represents the row from DBIx::Class. The other represents the JSON data presented by an endpoint. It refers to the first via expose 'data', using => .... When the Swagger schema is generated, it declared the data is an array, when it is in fact an object represented by a hash.

Is there a way to make Raisin generate a schema matching the returned data?

Many thanks,
Dave

Pod: Hooks

Just a quick suggestion.

In the pod for HOOKS in Raisin.pm, its not mentioned what parameters are passed in.

Thats all :)

Trying to use MooseX:Types or Moose::Util::TypeConstraints fails

When I use MooseX::Types as parameters in Raisin, I'l get an exception as soon as params_requires gets executed with one of these. Int and Str is enough - not to mention subtypes.

Can't locate object method "display_name" via package "Moose::Meta::TypeConstraint" at /usr/share/perl5/MooseX/Types/TypeDecorator.pm line 219 MooseX::Types::TypeDecorator::_try_delegate('MooseX::Types::TypeDecorator=HASH(0x2f83338)', 'display_name') called at /usr/share/perl5/MooseX/Types/TypeDecorator.pm line 184 MooseX::Types::TypeDecorator::AUTOLOAD('MooseX::Types::TypeDecorator=HASH(0x2f83338)') called at /home/cbeckert/Raisin/lib/Raisin/Plugin/Swagger.pm line 351 Raisin::Plugin::Swagger::_param_type_object('Raisin::Param=HASH(0x2ecee70)') called at /home/cbeckert/Raisin/lib/Raisin/Plugin/Swagger.pm line 253 Raisin::Plugin::Swagger::_parameters_object('GET', 'ARRAY(0x1c37870)') called at /home/cbeckert/Raisin/lib/Raisin/Plugin/Swagger.pm line 213 Raisin::Plugin::Swagger::_operation_object('Raisin::Routes::Endpoint=HASH(0x2ececf0)') called at /home/cbeckert/Raisin/lib/Raisin/Plugin/Swagger.pm line 177 Raisin::Plugin::Swagger::_paths_object('ARRAY(0x2063970)') called at /home/cbeckert/Raisin/lib/Raisin/Plugin/Swagger.pm line 94 Raisin::Plugin::Swagger::_spec_20('Raisin::Plugin::Swagger=HASH(0x2ec91c8)') called at /home/cbeckert/Raisin/lib/Raisin/Plugin/Swagger.pm line 23 Raisin::Plugin::Swagger::__ANON__('Raisin=HASH(0x2063a00)') called at /home/cbeckert/Raisin/lib/Raisin.pm line 155 Raisin::psgi('Raisin=HASH(0x2063a00)', 'HASH(0x2ff9ec8)') called at /home/cbeckert/Raisin/lib/Raisin.pm line 93 Raisin::__ANON__('HASH(0x2ff9ec8)') called at /home/cbeckert/perl5/lib/perl5/Plack/Middleware/CrossOrigin.pm line 114 Plack::Middleware::CrossOrigin::call('Plack::Middleware::CrossOrigin=HASH(0x2f8f988)', 'HASH(0x2ff9ec8)') called at /usr/share/perl5/Plack/Component.pm line 50 Plack::Component::__ANON__('HASH(0x2ff9ec8)') called at /home/cbeckert/Raisin/lib/Raisin/Middleware/Formatter.pm line 46 Raisin::Middleware::Formatter::call('Raisin::Middleware::Formatter=HASH(0x2fdf760)', 'HASH(0x2ff9ec8)') called at /usr/share/perl5/Plack/Component.pm line 50 Plack::Component::__ANON__('HASH(0x2ff9ec8)') called at /usr/share/perl5/Plack/Middleware/Lint.pm line 24 Plack::Middleware::Lint::call('Plack::Middleware::Lint=HASH(0x16880a0)', 'HASH(0x2ff9ec8)') called at /usr/share/perl5/Plack/Component.pm line 50 Plack::Middleware::StackTrace::try {...} at /usr/share/perl5/Try/Tiny.pm line 79 eval {...} at /usr/share/perl5/Try/Tiny.pm line 72 Plack::Middleware::StackTrace::call('Plack::Middleware::StackTrace=HASH(0x20636b8)', 'HASH(0x2ff9ec8)') called at /usr/share/perl5/Plack/Component.pm line 50 Plack::Component::__ANON__('HASH(0x2ff9ec8)') called at /usr/share/perl5/Plack/Middleware/AccessLog.pm line 24 Plack::Middleware::AccessLog::call('Plack::Middleware::AccessLog=HASH(0x2eaeac0)', 'HASH(0x2ff9ec8)') called at /usr/share/perl5/Plack/Component.pm line 50 Plack::Component::__ANON__('HASH(0x2ff9ec8)') called at /usr/share/perl5/Plack/Middleware/ContentLength.pm line 10 Plack::Middleware::ContentLength::call('Plack::Middleware::ContentLength=HASH(0x2fe0048)', 'HASH(0x2ff9ec8)') called at /usr/share/perl5/Plack/Component.pm line 50 Plack::Component::__ANON__('HASH(0x2ff9ec8)') called at /usr/share/perl5/Plack/Util.pm line 142 eval {...} at /usr/share/perl5/Plack/Util.pm line 142 Plack::Util::run_app('CODE(0x2fe0000)', 'HASH(0x2ff9ec8)') called at /usr/share/perl5/HTTP/Server/PSGI.pm line 170 HTTP::Server::PSGI::handle_connection('HTTP::Server::PSGI=HASH(0x2fdfeb0)', 'HASH(0x2ff9ec8)', 'IO::Socket::INET=GLOB(0x2fe0168)', 'CODE(0x2fe0000)') called at /usr/share/perl5/HTTP/Server/PSGI.pm line 129 HTTP::Server::PSGI::accept_loop('HTTP::Server::PSGI=HASH(0x2fdfeb0)', 'CODE(0x17cb6d0)') called at /usr/share/perl5/HTTP/Server/PSGI.pm line 55 HTTP::Server::PSGI::run('HTTP::Server::PSGI=HASH(0x2fdfeb0)', 'CODE(0x17cb6d0)') called at /usr/share/perl5/Plack/Handler/HTTP/Server/PSGI.pm line 14 Plack::Handler::HTTP::Server::PSGI::run('Plack::Handler::Standalone=HASH(0x2fe0258)', 'CODE(0x17cb6d0)') called at /usr/share/perl5/Plack/Loader.pm line 84 Plack::Loader::run('Plack::Loader=HASH(0x1479398)', 'Plack::Handler::Standalone=HASH(0x2fe0258)') called at /usr/share/perl5/Plack/Runner.pm line 277 Plack::Runner::run('Plack::Runner=HASH(0x1203150)') called at /usr/bin/plackup line 10

Using Moose::Util::TypeConstraints doesnt help either. A simple Setup such as:

subtype 'bla' ,as 'Str';
...
and then
params requires('mac', type => 'bla');

produces:

Can't locate object method "name" via package "bla" (perhaps you forgot to load "bla"?) at /home/cbeckert/Raisin/lib/Raisin/Plugin/Swagger.pm line 382 Raisin::Plugin::Swagger::_param_type('bla') called at /home/cbeckert/Raisin/lib/Raisin/Plugin/Swagger.pm line 242 Raisin::Plugin::Swagger::_parameters_object('GET', 'ARRAY(0x28aa768)') called at /home/cbeckert/Raisin/lib/Raisin/Plugin/Swagger.pm line 213 Raisin::Plugin::Swagger::_operation_object('Raisin::Routes::Endpoint=HASH(0x27fc9b0)') called at /home/cbeckert/Raisin/lib/Raisin/Plugin/Swagger.pm line 177 Raisin::Plugin::Swagger::_paths_object('ARRAY(0x2cce088)') called at /home/cbeckert/Raisin/lib/Raisin/Plugin/Swagger.pm line 94 Raisin::Plugin::Swagger::_spec_20('Raisin::Plugin::Swagger=HASH(0x3a7b600)') called at /home/cbeckert/Raisin/lib/Raisin/Plugin/Swagger.pm line 23 Raisin::Plugin::Swagger::__ANON__('Raisin=HASH(0x2cce118)') called at /home/cbeckert/Raisin/lib/Raisin.pm line 155 Raisin::psgi('Raisin=HASH(0x2cce118)', 'HASH(0x3ae7560)') called at /home/cbeckert/Raisin/lib/Raisin.pm line 93 Raisin::__ANON__('HASH(0x3ae7560)') called at /home/cbeckert/perl5/lib/perl5/Plack/Middleware/CrossOrigin.pm line 114 Plack::Middleware::CrossOrigin::call('Plack::Middleware::CrossOrigin=HASH(0x3b0fd80)', 'HASH(0x3ae7560)') called at /usr/share/perl5/Plack/Component.pm line 50 Plack::Component::__ANON__('HASH(0x3ae7560)') called at /home/cbeckert/Raisin/lib/Raisin/Middleware/Formatter.pm line 46 Raisin::Middleware::Formatter::call('Raisin::Middleware::Formatter=HASH(0x3b4f9b0)', 'HASH(0x3ae7560)') called at /usr/share/perl5/Plack/Component.pm line 50 Plack::Component::__ANON__('HASH(0x3ae7560)') called at /usr/share/perl5/Plack/Middleware/Lint.pm line 24 Plack::Middleware::Lint::call('Plack::Middleware::Lint=HASH(0x219cf30)', 'HASH(0x3ae7560)') called at /usr/share/perl5/Plack/Component.pm line 50 Plack::Middleware::StackTrace::try {...} at /usr/share/perl5/Try/Tiny.pm line 79 eval {...} at /usr/share/perl5/Try/Tiny.pm line 72 Plack::Middleware::StackTrace::call('Plack::Middleware::StackTrace=HASH(0x251f3b8)', 'HASH(0x3ae7560)') called at /usr/share/perl5/Plack/Component.pm line 50 Plack::Component::__ANON__('HASH(0x3ae7560)') called at /usr/share/perl5/Plack/Middleware/AccessLog.pm line 24 Plack::Middleware::AccessLog::call('Plack::Middleware::AccessLog=HASH(0x38e8658)', 'HASH(0x3ae7560)') called at /usr/share/perl5/Plack/Component.pm line 50 Plack::Component::__ANON__('HASH(0x3ae7560)') called at /usr/share/perl5/Plack/Middleware/ContentLength.pm line 10 Plack::Middleware::ContentLength::call('Plack::Middleware::ContentLength=HASH(0x3b50fb8)', 'HASH(0x3ae7560)') called at /usr/share/perl5/Plack/Component.pm line 50 Plack::Component::__ANON__('HASH(0x3ae7560)') called at /usr/share/perl5/Plack/Util.pm line 142 eval {...} at /usr/share/perl5/Plack/Util.pm line 142 Plack::Util::run_app('CODE(0x3b50f58)', 'HASH(0x3ae7560)') called at /usr/share/perl5/HTTP/Server/PSGI.pm line 170 HTTP::Server::PSGI::handle_connection('HTTP::Server::PSGI=HASH(0x3b50dd8)', 'HASH(0x3ae7560)', 'IO::Socket::INET=GLOB(0x3b0a330)', 'CODE(0x3b50f58)') called at /usr/share/perl5/HTTP/Server/PSGI.pm line 129 HTTP::Server::PSGI::accept_loop('HTTP::Server::PSGI=HASH(0x3b50dd8)', 'CODE(0x38e88c8)') called at /usr/share/perl5/HTTP/Server/PSGI.pm line 55 HTTP::Server::PSGI::run('HTTP::Server::PSGI=HASH(0x3b50dd8)', 'CODE(0x38e88c8)') called at /usr/share/perl5/Plack/Handler/HTTP/Server/PSGI.pm line 14 Plack::Handler::HTTP::Server::PSGI::run('Plack::Handler::Standalone=HASH(0x219cbe8)', 'CODE(0x38e88c8)') called at /usr/share/perl5/Plack/Loader/Restarter.pm line 32 Plack::Loader::Restarter::_fork_and_start('Plack::Loader::Restarter=HASH(0x1cf23a0)', 'Plack::Handler::Standalone=HASH(0x219cbe8)') called at /usr/share/perl5/Plack/Loader/Restarter.pm line 61 Plack::Loader::Restarter::run('Plack::Loader::Restarter=HASH(0x1cf23a0)', 'Plack::Handler::Standalone=HASH(0x219cbe8)') called at /usr/share/perl5/Plack/Runner.pm line 277 Plack::Runner::run('Plack::Runner=HASH(0x1ce9278)') called at /usr/bin/plackup line 10 127.0.0.1 - - [18/Oct/2018:17:25:30 +0200] "GET /swagger HTTP/1.1" 500 23459 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0"

utf8-encoding json-output twice?

Hi,
I get the feeling that when serving json-data, Raisin encodes the outgoing data twice: once when serializing it via encode_json (Plugin/Format/JSON.pm) which as you know encodes it already into utf8 (see JSON docs), and again in Response.pm where you do $self->body(encode 'UTF-8', $body); (line 61).

This results in any non-latin1 characters like the letter รซ (e-with-umlaut) to be output by Raisin as รƒยซ which is typical of a double-encoding of the letter as you can see from the UTF-8 encoding debugging chart here:

http://www.i18nqa.com/debug/utf8-debug.html

I tried commenting out line 61 in Response.pm but that leaves the response body with no content.

Am I doing something wrong or is Raisin, in fact, wrongly encoding twice into utf8?

Many thanks in advance!
Chris.

Can't bump user in sample-app via swagger.

I'm working on an OS X 10.9.5 system, Chrome or Safari, Swagger UI from the demo on the swagger site and using perl-5.18.4 installed via perlbrew and have installed raisin like this:

cpanm -l ~/tmp/raisin-experiment Raisin Log::Dispatch

I can increment and fetch a users bump count from the command line, e.g. using httpie

$ http PUT localhost:5000/api/users/1/bump
HTTP/1.0 200 OK
Content-Length: 15
Content-Type: application/yaml
Date: Tue, 10 Mar 2015 16:40:27 GMT
Server: HTTP::Server::PSGI
X-Framework: Raisin 0.58

---
success: 1

$ http localhost:5000/api/users/1/bump
HTTP/1.0 200 OK
Content-Length: 12
Content-Type: application/yaml
Date: Tue, 10 Mar 2015 16:40:28 GMT
Server: HTTP::Server::PSGI
X-Framework: Raisin 0.58

---
data: 1

The log output of that interaction looks like this:

127.0.0.1 - - [10/Mar/2015:09:40:27 -0700] "PUT /api/users/1/bump HTTP/1.1" 200 15 "-" "HTTPie/0.9.1"
127.0.0.1 - - [10/Mar/2015:09:40:28 -0700] "GET /api/users/1/bump HTTP/1.1" 200 12 "-" "HTTPie/0.9.1"

If, on the other hand, I try to use the API from a swagger sandbox I see the following log message:

127.0.0.1 - - [10/Mar/2015:09:35:58 -0700] "OPTIONS /api/users/1/bump HTTP/1.1" 403 9 "http://petstore.swagger.io/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36"

(note, the method is OPTIONS, not PUT) and the user never gets bumped (a print statement in UseCase::User::bump never gets called and http as above shows that the users bump count is still null.

This seems to be related to CORS and swagger ui issue 44.

If I try to call options myself, I get a 404 (not a 403 as above).

$ http  localhost:5000/api/users/1
HTTP/1.0 200 OK
Content-Length: 90
Content-Type: application/yaml
Date: Tue, 10 Mar 2015 16:59:09 GMT
Server: HTTP::Server::PSGI
X-Framework: Raisin 0.58

---
data:
  bumped: 1
  email: [email protected]
  name: Darth Wader
  password: empire

$ http OPTIONS localhost:5000/api/users/1/bump
HTTP/1.0 404 Not Found
Content-Length: 13
Content-Type: text/plain
Date: Tue, 10 Mar 2015 16:59:15 GMT
Server: HTTP::Server::PSGI
X-Framework: Raisin 0.58

Nothing found

Adding a print statement to Raisin::Plugin::Swagger::build suggests that the add_middleware calls is being executed.

Any suggestions?

g.

APIDocs - description editable pls

The "description" of an API should preferably be editable. Now it's just hardcoded to say "Operations about " (see line 101 in APIDocs.pm).

Many thanks in advance for making this configurable per in a future release.

ArrayRef[HashRef] param

We would like an endpoint to to take a list of objects as an input, ex:

{
  entries: [
   { id: 1, title: 'Entry 1'},
   { id: 2, title: 'Entry 2'}
  ]
}

Naive approach, where we don't validate each object in list would be like this

  params(
    requires(
      entries => (
        type => ArrayRef[HashRef],
        desc => 'Entries',
      )
    ),
  );

This generates following bit in swagger:

{
  "parameters": [
    {
      "required": true,
      "description": "Entries",
      "items": {
        "$ref": "#/definitions/Entries-MD5Hash"
      },
      "in": "body",
      "type": "array",
      "name": "entries"
    }
  ]
}

But there is no definition of Entries-MD5Hash in definitions section in swagger, which makes the swagger.json out of spec.

Ideally, we would be able to define the structure of the HashRef too as part of the definition.

Any suggestions how to deal with this?

Thanks!

Optional params that fail type constraints do not return "Invalid Parameters" response.

It appears that if you have an optional param that has a type constraint (such as Enum from Types::Standard, or enum() from Moose::Util::TypeConstraints, then you will not get an Invalid Parameters response.

If the param is instead changed to requires, then you get the expected Invalid Parameters response.

For example

params(
  optional('foo', type => Enum[qw(bar baz)])
)
get sub {
...
}

If called with foo=invalid, then a 200 OK response is received.

The problem is in Raisin::Request::prepare_params() I have a patch with a test case that I will submit as a PR shortly.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.