File Coverage

blib/lib/HTTP/Thin/UserAgent.pm
Criterion Covered Total %
statement 62 125 49.6
branch 0 12 0.0
condition 1 2 50.0
subroutine 21 37 56.7
pod 2 9 22.2
total 86 185 46.4


line stmt bran cond sub pod time code
1             package HTTP::Thin::UserAgent;
2             $HTTP::Thin::UserAgent::VERSION = '0.016';
3 3     3   63335 use 5.12.1;
  3         12  
4 3     3   17 use warnings;
  3         4  
  3         140  
5              
6             # ABSTRACT: A Thin UserAgent around some useful modules.
7              
8              
9             {
10             package
11             HTTP::Thin::UserAgent::HTTPExceptionWithResponse;
12 3     3   2275 use Moo::Role;
  3         67407  
  3         17  
13             has response => ( is => 'ro' );
14             }
15              
16             {
17             package
18             HTTP::Thin::UserAgent::Error::UnexpectedResponse;
19              
20 3     3   3511 use Moo;
  3         6404  
  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   5952 use Moo;
  3         6  
  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   953 use Moo;
  3         5  
  3         14  
47 3     3   2952 use MooX::late;
  3         75797  
  3         18  
48 3     3   2807 use MooX::ChainedAttributes;
  3         15844  
  3         20  
49 3     3   3707 use HTTP::Thin;
  3         276770  
  3         126  
50 3     3   2620 use JSON::Any;
  3         10406  
  3         13  
51 3     3   18168 use Try::Tiny;
  3         7278  
  3         183  
52 3     3   19 use Scalar::Util qw/weaken/;
  3         5  
  3         144  
53 3     3   14 use Carp qw(confess);
  3         6  
  3         140  
54              
55 3   50 3   15 use constant TRACE => $ENV{TRACE} // 0;
  3         5  
  3         169  
56 3     3   16 use constant UnexpectedResponse => 'HTTP::Thin::UserAgent::Error::UnexpectedResponse';
  3         5  
  3         130  
57 3     3   14 use constant HTTPException => 'HTTP::Thin::UserAgent::HTTP::Throwable::Factory';
  3         6  
  3         3453  
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 "$_" } },
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 0         0 my $e;
108             try {
109 0     0   0 $e = HTTPException->new_exception(
110             $res->code => {
111             additional_headers => [ $res->headers->flatten() ],
112             response => $res,
113             }
114             );
115             }
116             catch {
117 0     0   0 $e = HTTPException->new_exception(
118             {
119             status_code => $res->code,
120             reason => $res->message,
121             additional_headers => [ $res->headers->flatten(), ],
122             response => $res,
123             }
124             );
125 0         0 };
126 0         0 for ($e) { $self->on_error->($e) }
  0         0  
127             }
128              
129 0         0 return $res;
130             }
131              
132             sub as_json {
133 0     0 0 0 my $self = shift;
134              
135 0         0 my $request = $self->request;
136              
137 0         0 $request->header(
138             'Accept' => 'application/json',
139             'Content-Type' => 'application/json',
140             );
141              
142 0 0       0 if ( my $data = shift ) {
143 0         0 $request->content( JSON::Any->encode($data) );
144             }
145              
146 0         0 weaken($self);
147             $self->decoder(
148             sub {
149 0     0   0 my $res = shift;
150 0         0 my $content_type = $res->header('Content-Type');
151             my $data = try {
152 0 0       0 die "Content-Type was $content_type not application/json"
153             unless $content_type =~ m'application/json';
154 0         0 JSON::Any->decode( $res->decoded_content );
155             }
156             catch {
157 0         0 my $error = UnexpectedResponse->new(
158             message => $_,
159             response => $res,
160             );
161 0         0 for ($error) {
162 0         0 $self->on_error->($error);
163             }
164 0         0 };
165             }
166 0         0 );
167 0         0 return $self;
168             }
169              
170 0     0 0 0 sub dump { require Data::Dumper; return Data::Dumper::Dumper(shift) }
  0         0  
171              
172             sub scraper {
173 0     0 0 0 my ( $self, $scraper ) = @_;
174              
175 0         0 weaken($self);
176             $self->decoder(
177             sub {
178 0     0   0 my $res = shift;
179 0         0 my $data = try { $scraper->scrape( $res->decoded_content ) }
180             catch {
181 0         0 my $error = UnexpectedResponse->new(
182             message => $_,
183             response => $res
184             );
185 0         0 for ($error) { $self->on_error->($error); }
  0         0  
186 0         0 };
187 0         0 return $data;
188             }
189 0         0 );
190 0         0 return $self;
191             }
192              
193             sub tree {
194 0     0 0 0 my ($self) = @_;
195 0         0 my $t = HTML::TreeBuilder::XPath->new;
196 0 0       0 $t->store_comments(1) if ( $t->can('store_comments') );
197 0         0 $t->ignore_unknown(0);
198 0         0 $t->parse( $self->content );
199 0         0 return $t;
200             }
201              
202             sub find {
203 0     0 0 0 my ( $self, $exp ) = @_;
204              
205 0 0       0 my $xpath =
206             $exp =~ m!^(?:/|id\()!
207             ? $exp
208             : HTML::Selector::XPath::selector_to_xpath($exp);
209              
210 0     0   0 my @nodes = try { $self->tree->findnodes($xpath) }
211             catch {
212 0     0   0 for ($_) { $self->on_error($_) }
  0         0  
213 0         0 };
214 0 0       0 return unless @nodes;
215 0         0 return \@nodes;
216             }
217              
218             }
219              
220 3     3   19 use parent qw(Exporter);
  3         9  
  3         23  
221 3     3   2374 use Import::Into;
  3         1341  
  3         82  
222 3     3   2407 use HTTP::Request::Common;
  3         12163  
  3         197  
223 3     3   2196 use Web::Scraper;
  3         248365  
  3         21  
224              
225             our @EXPORT = qw(http);
226              
227             sub import {
228 3     3   300 shift->export_to_level(1);
229 3         38 HTTP::Request::Common->import::into( scalar caller );
230 3         754 Web::Scraper->import::into( scalar caller );
231             }
232              
233 0     0 1   sub http { HTTP::Thin::UserAgent::Client->new( request => shift ) }
234              
235             1;
236              
237             __END__