File Coverage

blib/lib/Atto.pm
Criterion Covered Total %
statement 71 75 94.6
branch 21 36 58.3
condition 2 2 100.0
subroutine 12 12 100.0
pod 0 1 0.0
total 106 126 84.1


line stmt bran cond sub pod time code
1             package Atto;
2             $Atto::VERSION = '0.003';
3             # ABSTRACT: A tiny microservice builder
4              
5 3     3   182766 use 5.008_001;
  3         13  
6 3     3   18 use warnings;
  3         6  
  3         123  
7 3     3   18 use strict;
  3         11  
  3         82  
8              
9 3     3   16 use Carp qw(croak);
  3         6  
  3         200  
10 3     3   741 use JSON::MaybeXS ();
  3         1095  
  3         77  
11 3     3   2795 use WWW::Form::UrlEncoded qw(parse_urlencoded);
  3         18324  
  3         170  
12 3     3   2364 use Plack::Request;
  3         185604  
  3         357  
13              
14             my %methods_for_package;
15              
16             sub import {
17 2     2   48 my ($class, @methods) = @_;
18 2         4 my $package = caller;
19 2         5 $methods_for_package{$package} = { map { $_ => undef } @methods };
  2         4076  
20             }
21              
22             sub psgi {
23 2     2 0 21 my $package = caller;
24              
25 2         7 my $methods = $methods_for_package{$package};
26 2         9 for my $method (keys %$methods) {
27 3     3   25 my $coderef = do { no strict 'refs'; *{$package.'::'.$method}{CODE} };
  3         6  
  3         2099  
  2         3  
  2         4  
  2         12  
28 2 50       8 croak "method $method not found in $package" unless $coderef;
29 2         5 $methods->{$method} = $coderef;
30             }
31              
32 2         16 my $json = JSON::MaybeXS->new->allow_nonref;
33              
34             my $response = sub {
35 5     5   10 my ($code, $raw) = @_;
36 5         7 my $body = [ eval { $json->encode($raw) } ];
  5         106  
37 5 50       15 if ($@) {
38 0         0 $code = 500;
39 0         0 $body = [ $json->encode("couldn't encode response: $@") ];
40             }
41              
42 5         51 [ $code, [ 'Content-type' => 'application/json' ], $body ]
43 2         57 };
44              
45             sub {
46 5     5   126012 my ($env) = @_;
47              
48 5 50       12 return $response->(405, "request method must be POST or GET (not $env->{REQUEST_METHOD})") unless grep { $env->{REQUEST_METHOD} eq $_ } qw(POST GET);
  10         34  
49              
50 5         24 my ($method) = $env->{REQUEST_URI} =~ m{^/([^/?]+)};
51 5 50       20 return $response->(400, "method not found in request URL") unless defined $method;
52              
53 5 50       18 return $response->(404, "method not found") unless $methods->{$method};
54              
55 5         12 my $args = {};
56              
57 5 100       28 if ($env->{REQUEST_METHOD} eq 'GET') {
    50          
58 1         11 my $req = Plack::Request->new($env);
59 1         14 %$args = $req->query_parameters->flatten;
60             }
61              
62             elsif ($env->{REQUEST_METHOD} eq 'POST') {
63 4   100     24 my $len = 0+($env->{CONTENT_LENGTH} || 0);
64              
65 4 100       14 if ($len > 0) {
66 2 50       6 return $response->(400, "content type not provided") unless defined $env->{CONTENT_TYPE};
67              
68 2 100       10 if ($env->{CONTENT_TYPE} eq 'application/json') {
    50          
69 1         13 my $nread = $env->{'psgi.input'}->read(my $content, $len);
70 1 50       17 return $response->(400, sprintf("expected %d bytes (from content-length), got %d", $len, $nread)) if $nread != $len;
71              
72 1         2 $args = eval { $json->decode($content) };
  1         8  
73 1 50       4 return $response->(400, $@) if $@;
74             }
75             elsif ($env->{CONTENT_TYPE} eq 'application/x-www-form-urlencoded') {
76 1         5 my $nread = $env->{'psgi.input'}->read(my $content, $len);
77 1 50       10 return $response->(400, sprintf("expected %d bytes (from content-length), got %d", $len, $nread)) if $nread != $len;
78              
79 1         7 %$args = parse_urlencoded($content);
80 1 50       32 return $response->(400, $@) if $@;
81             }
82             else {
83 0         0 return $response->(400, "unknown content type");
84             }
85             }
86             }
87              
88             else {
89 0         0 return $response->(405, "request method must be POST or GET (not $env->{REQUEST_METHOD})");
90             }
91              
92              
93             # XXX prototypes
94              
95 5 50       155 my @args =
    50          
96             ref $args eq 'ARRAY' ? @$args :
97             ref $args eq 'HASH' ? %$args :
98             ($args);
99              
100 5         9 my $ret = eval { $methods->{$method}->(@args) };
  5         26  
101 5 50       36 return $response->(500, "method call failed: $@") if $@;
102              
103 5         15 return $response->(200, $ret);
104             }
105 2         16 }
106              
107             1;
108             __END__