File Coverage

blib/lib/WWW/Snooze/Request.pm
Criterion Covered Total %
statement 60 104 57.6
branch 4 8 50.0
condition 0 12 0.0
subroutine 15 20 75.0
pod 5 5 100.0
total 84 149 56.3


line stmt bran cond sub pod time code
1             package WWW::Snooze::Request;
2              
3 3     3   27602 use strict;
  3         10  
  3         99  
4 3     3   16 use warnings;
  3         5  
  3         64  
5 3     3   69 use 5.010;
  3         10  
  3         129  
6              
7 3     3   1621 use WWW::Snooze::Serialize::JSON;
  3         17  
  3         87  
8              
9 3     3   6420 use URI;
  3         42019  
  3         123  
10 3     3   8816 use LWP::UserAgent;
  3         208377  
  3         110  
11 3     3   9559 use JSON;
  3         55634  
  3         21  
12              
13             our $AUTOLOAD;
14              
15             sub new {
16 5     5 1 627 my $class = shift;
17 5         8 my $uri = shift;
18 5         23 my %args = @_;
19              
20 5         38 bless {
21             base => $uri,
22             parts => [],
23             args => {},
24             headers => undef,
25             serializer => WWW::Snooze::Serialize::JSON->new(),
26             %args
27             }, $class;
28             }
29              
30             sub AUTOLOAD {
31 2     2   1737 my $self = shift;
32 2         5 my $name = $AUTOLOAD;
33 2         10 $name =~ s/WWW::Snooze::Request:://;
34 2         9 return $self->_add_element($name, @_);
35             }
36              
37 0     0   0 sub DESTROY {}
38              
39             sub _add_element {
40 4     4   1228 my $self = shift;
41 4         9 my $name = shift;
42              
43             # TODO parse for multiple prototypes
44 4         6 my $parts;
45 4         6 push(@{$parts}, $name);
  4         44  
46 4         9 my $arg = shift;
47 4 100       12 push(@{$parts}, $arg) if ($arg);
  1         4  
48              
49             # TODO Combine argument hashes? It doesn't make sense to do:
50             # $api->stories(100, owner => 'foo')->tasks(52, sort_by => 1)
51 4         11 my %args = @_;
52              
53 4         10 return WWW::Snooze::Request->new(
54             $self->{base},
55 4         11 parts => [@{$self->{parts}}, @{$parts}],
  4         20  
56             headers => $self->_headers,
57             args => \%args,
58             serializer => $self->_serializer
59             );
60             }
61              
62             # Private-ish functions to avoid namespace collisions
63 10     10   55 sub _serializer { shift->{serializer}; }
64 4     4   20 sub _headers { shift->{headers}; }
65 6     6   32 sub _args { shift->{args}; }
66              
67             sub _build_url {
68 6     6   878 my $self = shift;
69 6         34 my $uri = URI->new($self->{base});
70              
71 6         10711 my @parts = $uri->path_segments();
72 6         413 push(@parts, @{$self->{parts}});
  6         22  
73              
74             # Add extension to last element
75 6 50       19 if (my $ext = $self->_serializer->extension()) {
76 6         11 my $last = pop @parts;
77 6         12 $last .= $ext;
78 6         34 push @parts, $last;
79             }
80              
81             # Rebuild parts and query string
82 6         21 $uri->path_segments(@parts);
83 6         482 $uri->query_form($self->_args);
84              
85 6         332 return $uri->as_string();
86             }
87              
88             sub _request {
89 1     1   585 my $self = shift;
90 1         3 my $method = shift;
91 1         2 my $data = shift;
92              
93 1 50       23 die 'Bad HTTP request method'
94             unless (grep($_ eq $method, (qw/GET POST PUT DELETE/)));
95              
96 0           my $h = LWP::UserAgent->new();
97 0           $h->agent(
98             sprintf(
99             'Snooze/%s',
100             $WWW::Snooze::VERSION
101             )
102             );
103              
104 0           my $req = HTTP::Request->new(
105             $method,
106             $self->_build_url,
107             $self->_headers
108             );
109 0           $req->content_type($self->_serializer->content_type());
110              
111             # Set content if available
112 0 0         if (ref $data eq 'HASH') {
113 0           $req->content(
114             $self->_serializer->encode($data)
115             );
116             }
117 0           return $h->request($req);
118             }
119              
120             sub get {
121 0     0 1   my $self = shift;
122 0           my $res = $self->_request('GET', @_);
123 0           given ($res->code) {
124 0           when (200) {
125 0           return $self->_serializer->decode(
126             $res->content()
127             );
128             }
129 0   0       when ($_ > 200 and $_ < 300) {
130 0           return $res->content();
131             }
132 0           default { return undef; }
  0            
133             }
134             }
135              
136             sub post {
137 0     0 1   my $self = shift;
138             # TODO post to url with query string?
139 0           my $res = $self->_request('POST', @_);
140 0           given ($res->code) {
141 0           when (201) {
142 0           return $self->_serializer->decode(
143             $res->content()
144             );
145             }
146 0   0       when ($_ >= 200 and $_ < 300) {
147 0           return $res->content();
148             }
149 0           default { return undef; }
  0            
150             }
151             }
152              
153             sub put {
154 0     0 1   my $self = shift;
155             # TODO post to url with query string?
156 0           my $res = $self->_request('PUT', @_);
157 0           given ($res->code) {
158 0           when (204) {
159 0           return 1;
160             }
161 0   0       when ($_ >= 200 and $_ < 300) {
162 0           return 1;
163             }
164 0           default { return 0; }
  0            
165             }
166             }
167              
168             sub delete {
169 0     0 1   my $self = shift;
170             # TODO post to url with query string?
171 0           my $res = $self->_request('DELETE', @_);
172 0           given ($res->code) {
173 0           when (204) {
174 0           return 1;
175             }
176 0   0       when ($_ >= 200 and $_ < 300) {
177 0           return 1;
178             }
179 0           default { return 0; }
  0            
180             }
181             }
182              
183             1;
184             =head1 NAME
185              
186             WWW::Snooze::Request - Main request object featuring autoloading
187              
188             =head1 METHODS
189              
190              
191             =head2 new(%args)
192              
193             =over 4
194              
195             =item headers
196              
197             Override headers with an instance of L
198              
199             =item serializer
200              
201             Override serializer with and instance of L
202              
203             =back
204              
205             =head2 get([\%data])
206              
207             =head2 delete([\%data])
208              
209             =head2 post([\%data])
210              
211             =head2 put([\%data])
212              
213             Perform HTTP operation on URL, %data is encoded using the serializer.
214              
215              
216             =head1 AUTOMATIC METHODS
217              
218             The request object uses autoloading method names to build the request. Calling a
219             method on the request object will add that method name on to the URL stack and
220             return a new request object with the new stack.
221              
222             =head2 [$element]($id, %query_string)
223              
224             Automatic methods can be called with an C argument, or C if there is
225             no id, and named parameters which are encoded to a query string
226              
227             my $r = WWW::Snooze::Request->new('http://example.com');
228            
229             $r->foo();
230             # Request URL would be http://example.com/foo.json
231            
232             $r->foo(42)->bar;
233             # http://example.com/foo/42/bar.json
234            
235             $r->foo(undef, foo => 'bar');
236             # http://example.com/foo?foo=bar
237              
238             =head2 _add_element($name, $id, %query_string)
239              
240             Automatic methods are built using this private function, however you can also
241             revert to calling this directly in the case of a namespace collision with an
242             element or a poorly named element.
243              
244             $r->_add_element('poorly named');
245             # http://example.com/poorly%20named
246            
247             $r->_add_element('foo', 42, foo => bar);
248             # http://example.com/foo/42.json?foo=bar
249              
250             =head1 ATTRIBUTES
251              
252             Privately scoped to avoid namespace collision
253              
254             =head2 _args()
255              
256             Return query string arguments added
257              
258             =head2 _serializer()
259              
260             Return the serializer
261              
262             =head2 _headers()
263              
264             Return the HTTP::Headers object
265              
266              
267             =head1 AUTHOR
268              
269             Anthony Johnson Eaj@ohess.orgE