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.005';
3             # ABSTRACT: A tiny microservice builder
4              
5 4     4   311666 use 5.008001;
  4         26  
6 4     4   20 use warnings;
  4         8  
  4         108  
7 4     4   18 use strict;
  4         8  
  4         97  
8              
9 4     4   18 use Carp qw(croak);
  4         8  
  4         192  
10 4     4   23 use JSON::MaybeXS ();
  4         8  
  4         95  
11 4     4   1901 use WWW::Form::UrlEncoded qw(parse_urlencoded);
  4         23245  
  4         207  
12 4     4   2016 use Plack::Request;
  4         267054  
  4         507  
13              
14             my %methods_for_package;
15              
16             sub import {
17 3     3   37 my ($class, @methods) = @_;
18 3         7 my $package = caller;
19 3         7 $methods_for_package{$package} = { map { $_ => undef } @methods };
  3         5422  
20             }
21              
22             sub psgi {
23 3     3 0 316 my $package = caller;
24              
25 3         10 my $methods = $methods_for_package{$package};
26 3         14 for my $method (keys %$methods) {
27 4     4   37 my $coderef = do { no strict 'refs'; *{$package.'::'.$method}{CODE} };
  4         11  
  4         2695  
  3         41  
  3         6  
  3         18  
28 3 50       24 croak "method $method not found in $package" unless $coderef;
29 3         10 $methods->{$method} = $coderef;
30             }
31              
32 3         28 my $json = JSON::MaybeXS->new->utf8->allow_nonref;
33              
34             my $response = sub {
35 6     6   18 my ($code, $raw) = @_;
36 6         10 my $body = [ eval { $json->encode($raw) } ];
  6         57  
37 6 50       29 if ($@) {
38 0         0 $code = 500;
39 0         0 $body = [ $json->encode("couldn't encode response: $@") ];
40             }
41              
42 6         60 [ $code, [ 'Content-type' => 'application/json' ], $body ]
43 3         68 };
44              
45             sub {
46 6     6   89658 my ($env) = @_;
47              
48 6 50       14 return $response->(405, "request method must be POST or GET (not $env->{REQUEST_METHOD})") unless grep { $env->{REQUEST_METHOD} eq $_ } qw(POST GET);
  12         57  
49              
50 6         37 my ($method) = $env->{REQUEST_URI} =~ m{^/([^/?]+)};
51 6 50       19 return $response->(400, "method not found in request URL") unless defined $method;
52              
53 6 50       20 return $response->(404, "method not found") unless $methods->{$method};
54              
55 6         12 my $args = {};
56              
57 6 100       25 if ($env->{REQUEST_METHOD} eq 'GET') {
    50          
58 1         9 my $req = Plack::Request->new($env);
59 1         15 %$args = $req->query_parameters->flatten;
60             }
61              
62             elsif ($env->{REQUEST_METHOD} eq 'POST') {
63 5   100     27 my $len = 0+($env->{CONTENT_LENGTH} || 0);
64              
65 5 100       19 if ($len > 0) {
66 2 50       6 return $response->(400, "content type not provided") unless defined $env->{CONTENT_TYPE};
67              
68 2 100       8 if ($env->{CONTENT_TYPE} eq 'application/json') {
    50          
69 1         14 my $nread = $env->{'psgi.input'}->read(my $content, $len);
70 1 50       12 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         7  
73 1 50       4 return $response->(400, $@) if $@;
74             }
75             elsif ($env->{CONTENT_TYPE} eq 'application/x-www-form-urlencoded') {
76 1         7 my $nread = $env->{'psgi.input'}->read(my $content, $len);
77 1 50       11 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       58 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 6 50       184 my @args =
    50          
96             ref $args eq 'ARRAY' ? @$args :
97             ref $args eq 'HASH' ? %$args :
98             ($args);
99              
100 6         13 my $ret = eval { $methods->{$method}->(@args) };
  6         59  
101 6 50       50 return $response->(500, "method call failed: $@") if $@;
102              
103 6         19 return $response->(200, $ret);
104             }
105 3         21 }
106              
107             1;
108             __END__