File Coverage

blib/lib/HTTP/Thin/UserAgent.pm
Criterion Covered Total %
statement 62 122 50.8
branch 0 12 0.0
condition 1 2 50.0
subroutine 21 35 60.0
pod 2 9 22.2
total 86 180 47.7


line stmt bran cond sub pod time code
1             package HTTP::Thin::UserAgent;
2             $HTTP::Thin::UserAgent::VERSION = '0.014';
3 3     3   63307 use 5.12.1;
  3         11  
4 3     3   15 use warnings;
  3         5  
  3         119  
5              
6             # ABSTRACT: A Thin UserAgent around some useful modules.
7              
8              
9             {
10             package
11             HTTP::Thin::UserAgent::HTTPExceptionWithResponse;
12 3     3   2315 use Moo::Role;
  3         67123  
  3         17  
13             has response => ( is => 'ro' );
14             }
15              
16             {
17             package
18             HTTP::Thin::UserAgent::Error::UnexpectedResponse;
19              
20 3     3   3589 use Moo;
  3         6375  
  3         16  
21             extends qw(Throwable::Error);
22             with qw(HTTP::Thin::UserAgent::HTTPExceptionWithResponse);
23              
24             }
25              
26             {
27             package
28             HTTP::Thin::UserAgent::HTTP::Throwable::Factory;
29 3     3   6265 use Moo;
  3         7  
  3         14  
30              
31             extends qw(HTTP::Throwable::Factory);
32              
33             sub extra_roles {
34 0     0 1 0 return qw(
35             HTTP::Throwable::Role::TextBody
36             HTTP::Thin::UserAgent::HTTPExceptionWithResponse
37             );
38             }
39             }
40              
41             {
42              
43             package
44             HTTP::Thin::UserAgent::Client;
45              
46 3     3   952 use Moo;
  3         5  
  3         15  
47 3     3   3054 use MooX::late;
  3         74900  
  3         22  
48 3     3   2755 use MooX::ChainedAttributes;
  3         15795  
  3         19  
49 3     3   3771 use HTTP::Thin;
  3         277895  
  3         104  
50 3     3   2633 use JSON::Any;
  3         10716  
  3         14  
51 3     3   18106 use Try::Tiny;
  3         7353  
  3         196  
52 3     3   18 use Scalar::Util qw/weaken/;
  3         6  
  3         139  
53 3     3   15 use Carp qw(confess);
  3         4  
  3         148  
54              
55 3   50 3   14 use constant TRACE => $ENV{TRACE} // 0;
  3         6  
  3         173  
56 3     3   16 use constant UnexpectedResponse => 'HTTP::Thin::UserAgent::Error::UnexpectedResponse';
  3         6  
  3         132  
57 3     3   15 use constant HTTPException => 'HTTP::Thin::UserAgent::HTTP::Throwable::Factory';
  3         5  
  3         3249  
58              
59             has ua => (
60             is => 'ro',
61             default => sub { HTTP::Thin->new() },
62             );
63              
64             has request => (
65             is => 'ro',
66             required => 1,
67             );
68              
69             has on_error => (
70             is => 'rw',
71             default => sub { sub { confess $_->message } },
72             chained => 1,
73             );
74              
75             has decoder => (
76             is => 'rw',
77             chained => 1,
78             default => sub {
79             sub { shift->decoded_content }
80             },
81             );
82              
83             sub decoded_content {
84 0     0 0 0 my $self = shift;
85 0         0 return $self->decoder->( $self->response );
86             }
87              
88 0     0 0 0 sub decode { warn 'decode is deprecated, please call decoded_content instead'; shift->decoded_content }
  0         0  
89              
90             has response => (
91             is => 'ro',
92             lazy => 1,
93             builder => '_build_response',
94             handles => { 'content' => 'decoded_content' },
95             );
96              
97             sub _build_response {
98 0     0   0 my $self = shift;
99 0         0 my $ua = $self->ua;
100 0         0 my $request = $self->request;
101              
102 0         0 warn $request->dump if TRACE;
103 0         0 my $res = $ua->request($request);
104 0         0 warn $res->dump if TRACE;
105              
106 0 0       0 if ($res->is_error) {
107              
108 0         0 my $e = HTTPException->new_exception({
109             status_code => $res->code,
110             reason => $res->message,
111             additional_headers => [
112             $res->headers->flatten(),
113             ],
114             response => $res,
115             });
116              
117 0         0 for ($e) {
118 0         0 $self->on_error->($e);
119             }
120             }
121              
122 0         0 return $res;
123             }
124              
125             sub as_json {
126 0     0 0 0 my $self = shift;
127              
128 0         0 my $request = $self->request;
129              
130 0         0 $request->header(
131             'Accept' => 'application/json',
132             'Content-Type' => 'application/json',
133             );
134              
135 0 0       0 if ( my $data = shift ) {
136 0         0 $request->content( JSON::Any->encode($data) );
137             }
138              
139 0         0 weaken($self);
140             $self->decoder(
141             sub {
142 0     0   0 my $res = shift;
143 0         0 my $content_type = $res->header('Content-Type');
144             my $data = try {
145 0 0       0 die "Content-Type was $content_type not application/json"
146             unless $content_type =~ m'application/json';
147 0         0 JSON::Any->decode( $res->decoded_content );
148             }
149             catch {
150 0         0 my $error = UnexpectedResponse->new(
151             message => $_,
152             response => $res,
153             );
154 0         0 for ($error) {
155 0         0 $self->on_error->($error);
156             }
157 0         0 };
158             }
159 0         0 );
160 0         0 return $self;
161             }
162              
163 0     0 0 0 sub dump { require Data::Dumper; return Data::Dumper::Dumper(shift) }
  0         0  
164              
165             sub scraper {
166 0     0 0 0 my ( $self, $scraper ) = @_;
167              
168 0         0 weaken($self);
169             $self->decoder(
170             sub {
171 0     0   0 my $res = shift;
172 0         0 my $data = try { $scraper->scrape( $res->decoded_content ) }
173             catch {
174 0         0 my $error = UnexpectedResponse->new(
175             message => $_,
176             response => $res
177             );
178 0         0 for ($error) { $self->on_error->($error); }
  0         0  
179 0         0 };
180 0         0 return $data;
181             }
182 0         0 );
183 0         0 return $self;
184             }
185              
186             sub tree {
187 0     0 0 0 my ($self) = @_;
188 0         0 my $t = HTML::TreeBuilder::XPath->new;
189 0 0       0 $t->store_comments(1) if ( $t->can('store_comments') );
190 0         0 $t->ignore_unknown(0);
191 0         0 $t->parse( $self->content );
192 0         0 return $t;
193             }
194              
195             sub find {
196 0     0 0 0 my ( $self, $exp ) = @_;
197              
198 0 0       0 my $xpath =
199             $exp =~ m!^(?:/|id\()!
200             ? $exp
201             : HTML::Selector::XPath::selector_to_xpath($exp);
202              
203 0     0   0 my @nodes = try { $self->tree->findnodes($xpath) }
204             catch {
205 0     0   0 for ($_) { $self->on_error($_) }
  0         0  
206 0         0 };
207 0 0       0 return unless @nodes;
208 0         0 return \@nodes;
209             }
210              
211             }
212              
213 3     3   18 use parent qw(Exporter);
  3         9  
  3         23  
214 3     3   2508 use Import::Into;
  3         1403  
  3         90  
215 3     3   2481 use HTTP::Request::Common;
  3         12349  
  3         208  
216 3     3   2193 use Web::Scraper;
  3         248060  
  3         21  
217              
218             our @EXPORT = qw(http);
219              
220             sub import {
221 3     3   309 shift->export_to_level(1);
222 3         40 HTTP::Request::Common->import::into( scalar caller );
223 3         735 Web::Scraper->import::into( scalar caller );
224             }
225              
226 0     0 1   sub http { HTTP::Thin::UserAgent::Client->new( request => shift ) }
227              
228             1;
229              
230             __END__