Code Monkey home page Code Monkey logo

raisin's Introduction

NAME

Raisin - a REST API micro framework for Perl.

SYNOPSIS

use strict;
use warnings;

use utf8;

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

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

plugin 'Swagger', enable => 'CORS';
api_format 'json';

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 => { name => 'start', type => Int, default => 0, desc => 'Pager (start)' },
        optional => { name => '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 $count = $params->{count} > $max_count ? $max_count : $params->{count};

        my @slice = @users[$start .. $count];
        { 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 => { name => 'name', type => Str, desc => 'User name' },
        requires => { name => 'password', type => Str, desc => 'User password' },
        optional => { name => 'email', type => Str, default => undef, regex => qr/.+\@.+/, desc => 'User email' },
    );
    post sub {
        my $params = shift;

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

        { success => 1 }
    };

    desc 'Actions on the user';
    params requires => { name => '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;
            { success => delete $USERS{ $params->{id} } };
        };
    };

    summary 'NOP';
    get nop => sub { };
};

desc 'Echo API endpoint';
resource echo => sub {
    params(optional => { name => 'data0', type => Any, default => "ёй" });
    get sub { shift };
};

run;

DESCRIPTION

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

FUNCTIONS

API DESCRIPTION

resource

Adds a route to an application.

resource user => sub { ... };

route_param

Define a route parameter as a namespace route_param.

route_param id => sub { ... };

del, get, patch, post, put

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

get sub { 'GET' };

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

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

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

desc

Can be applied to resource or any of the HTTP method to add a verbose explanation for an operation or for a resource.

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

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

summary

Can be applied to any of the HTTP method to add a short summary of what the operation does.

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

tags

A list of tags for API documentation control. Tags can be used for logical grouping of operations by resources or any other qualifier.

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

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

params

Here you can define validations and coercion options for your parameters. Can be applied to any HTTP method and/or route_param to describe parameters.

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

params(
    requires => { name => '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 doesn't specified by API user. E.g. URI is asked without an extension (json, yaml) or Accept header isn't specified.

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::Plugin::Format::JSON and Raisin::Plugin::Format::YAML.

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', enable => 'CORS';

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;

run

Returns the PSGI application.

INSIDE ROUTE

req

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

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

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

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.

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:

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

route string parameters will 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 your "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 requirements, Raisin will start returning only the declared params.

Application:

api_format 'json';

params
    required => { name => 'id', type => Int };
    optional => { name => '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 doesn't contain parameters that has undef value. If you want to return all parameters you can use the include_missing function.

Application:

api_format 'json';

params
    required => { name => 'id', type => Int },
    optional => { name => '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 a value and can be an optional. optional parameters can have a default value.

params(
    requires => { name => 'name', type => Str },
    optional => { name => '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

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

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

Before and after 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 3 and 4 only happen if validation succeeds.

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.

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::Plugin::Format.

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

Exports log subroutine.

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

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{ёй}

Swagger

Swagger compatible API documentations.

plugin 'Swagger';

Documentation will be available on http://<url>/api-docs 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;
};

Also see Plack::Builder, Plack::App::URLMap.

EXAMPLES

Raisin comes with three instance in example directory:

ROADMAP

  • Versioning support;
  • Nested params declaration;
  • 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 - [email protected]

LICENSE

This module and all the modules in this package are governed by the same license as Perl itself.

raisin's People

Contributors

dwburke avatar dotandimet avatar vlet avatar kimcognito avatar

Watchers

 avatar

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.