File Coverage

blib/lib/Catalyst/ActionRole/JSV.pm
Criterion Covered Total %
statement 18 18 100.0
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 24 24 100.0


line stmt bran cond sub pod time code
1             package Catalyst::ActionRole::JSV;
2              
3 1     1   99246 use strict;
  1         14  
  1         40  
4 1     1   376 use Moose::Role;
  1         482871  
  1         5  
5 1     1   7875 use namespace::autoclean;
  1         7066  
  1         6  
6 1     1   544 use JSV::Validator;
  1         54225  
  1         49  
7 1     1   326 use Path::Class ();
  1         38287  
  1         23  
8 1     1   393 use JSON::MaybeXS ();
  1         1083  
  1         497  
9              
10              
11             our $VERSION = '0.03';
12             our $JSV;
13             our %SCHEMA = ();
14              
15              
16             after BUILD => sub {
17             $JSV = JSV::Validator->new;
18             };
19              
20              
21             around execute => sub {
22             my $orig = shift;
23             my $self = shift;
24             my ($controller, $c) = @_;
25              
26             my $params;
27             if ($c->req->method =~ /^(POST|PUT)+$/) {
28             $params = $c->req->body_data;
29             }
30             else {
31             $params = $c->req->query_parameters;
32             }
33              
34             my $request_schema;
35             my $json_file = $self->attributes->{JSONSchema}->[0];
36              
37             if (exists $SCHEMA{ $json_file } ) {
38             $request_schema = $SCHEMA{ $json_file };
39             $c->log->debug("load memory json schema: ".$json_file);
40             }
41             else {
42             my $load_schema_json = Path::Class::file($c->config->{home}, $json_file);
43             $request_schema = JSON::MaybeXS::decode_json($load_schema_json->slurp);
44             $SCHEMA{ $json_file } = $request_schema;
45             $c->log->debug("load file json schema: ".$json_file);
46             }
47              
48             # find captureargs and convert integer parameter
49             for my $key (keys %{ $request_schema->{properties} }) {
50             my $prop = $request_schema->{properties}->{$key};
51            
52             # find URL captureargs
53             for my $attr (keys %{ $prop }) {
54             if (lc $attr eq 'captureargs') {
55             $params->{$key} = $c->req->arguments->[$prop->{$attr} - 1];
56             last;
57             }
58             }
59              
60             if (defined $params->{$key} && $prop->{type} eq 'integer' && $params->{$key} =~ /^[0-9]+$/) {
61             $params->{$key} = int $params->{$key};
62             }
63             }
64            
65             my $request_result = $JSV->validate($request_schema, $params);
66              
67             if ($request_result->get_error) {
68             $c->log->debug("json schema validation failed: ".$request_result->errors->[0]->{message});
69              
70             my $expose_stash = $c->config->{'View::JSON'}->{'expose_stash'} || 'json';
71             $c->response->status(400);
72             $c->stash->{$expose_stash} = {message => sprintf("%s: %s", $request_result->errors->[0]->{pointer}, $request_result->errors->[0]->{message})};
73             return;
74             }
75             $c->log->debug("json schema validation success.");
76              
77             my $orig_response = $self->$orig(@_);
78              
79             return $orig_response;
80             };
81              
82             1;
83              
84             __END__
85              
86             =encoding utf-8
87              
88             =head1 NAME
89              
90             Catalyst::ActionRole::JSV - A JSON Schema validator for Catalyst actions
91              
92             =head1 SYNOPSIS
93              
94             package MyApp::Controller::Item;
95             use Moose;
96             use namespace::autoclean;
97              
98             BEGIN { extends 'Catalyst::Controller'; }
99              
100             # RESTful API (Consumes type action) support by Catalyst::Runtime 5.90050 higher
101             __PACKAGE__->config(
102             action => {
103             '*' => {
104             Consumes => 'JSON',
105             Path => '',
106             }
107             }
108             );
109              
110              
111             # Get info on a specific item
112             # GET /item/:item_id
113             sub lookup :GET Args(1) :Does(JSV) :JSONSchema(root/schema/lookup.json) {
114             my ( $self, $c, $item_id ) = @_;
115             my $params = $c->request->parameters;
116             ...
117             }
118              
119              
120             # lookup.json (json schema draft4 validation)
121             {
122             "title": "Lookup item",
123             "type": "object",
124             "properties": {
125             "item_id": {
126             "type": "integer",
127             "minLength": 1,
128             "maxLength": 9,
129             "captureargs": 1 # In the case of URL CaptureArgs
130             },
131             "paramX": {
132             "type": "string",
133             "minLength": 8,
134             "maxLength": 12
135             },
136             },
137             "required": ["item_id"]
138             }
139              
140              
141             =head1 DESCRIPTION
142              
143             Catalyst::ActionRole::JSV is JSON Schema validator for Catalyst actions.
144             Internally use the json schema draft4 validator called JSV.
145              
146              
147             =head2 Error Response
148              
149             On error it returns 400 http response status. The stash key to set the error message is 'View::JSON expose_stash' key.
150             The default key if omitted is 'json'.
151              
152             $c->stash->{'View::JSON expose_stash key'} = {message => 'JSV->validate->get_error'}
153              
154             myapp.yml config
155              
156             name: MyApp
157             View::JSON:
158             expose_stash: 'json'
159              
160              
161             =head1 SEE ALSO
162              
163              
164             =over 2
165              
166             =item L<Catalyst::Controller>
167              
168             =item L<Catalyst::View::JSON>
169              
170             =item L<JSV::Validator>
171              
172             =back
173              
174             Catalyst Advent Calendar 2013 / How to implement a super-simple REST API with Catalyst
175              
176             http://www.catalystframework.org/calendar/2013/26
177            
178              
179             =head1 AUTHOR
180              
181             Masaaki Saito E<lt>masakyst.public@gmail.comE<gt>
182              
183             =head1 COPYRIGHT
184              
185             Copyright 2017- Masaaki Saito
186              
187             =head1 LICENSE
188              
189             This library is free software; you can redistribute it and/or modify
190             it under the same terms as Perl itself.
191              
192             =head1 SEE ALSO
193              
194             =cut