File Coverage

blib/lib/API/Drip/Request.pm
Criterion Covered Total %
statement 47 104 45.1
branch 0 24 0.0
condition 0 9 0.0
subroutine 16 23 69.5
pod 2 2 100.0
total 65 162 40.1


line stmt bran cond sub pod time code
1             package API::Drip::Request;
2              
3 1     1   50799 use v5.14;
  1         4  
4 1     1   4 use strict;
  1         2  
  1         17  
5 1     1   3 use warnings;
  1         7  
  1         31  
6              
7 1     1   284 use Params::ValidationCompiler qw( validation_for );
  1         17396  
  1         60  
8 1     1   452 use Types::Standard qw( Str HashRef CodeRef);
  1         59476  
  1         15  
9 1     1   1279 use YAML;
  1         5616  
  1         47  
10 1     1   8 use File::Spec;
  1         2  
  1         20  
11 1     1   303 use File::HomeDir;
  1         3988  
  1         49  
12 1     1   340 use Readonly;
  1         2938  
  1         48  
13 1     1   10 use Carp;
  1         3  
  1         90  
14 1     1   468 use LWP::UserAgent;
  1         36190  
  1         47  
15 1     1   560 use HTTP::Request::Common;
  1         1805  
  1         77  
16 1     1   447 use JSON;
  1         7273  
  1         7  
17 1     1   164 use URI;
  1         2  
  1         25  
18 1     1   413 use Data::Printer;
  1         24828  
  1         8  
19              
20             Readonly our %DEFAULTS => (
21             DRIP_TOKEN => undef,
22             DRIP_ID => undef,
23             DRIP_URI => 'https://api.getdrip.com/v2',
24             DRIP_AGENT => 'API::Drip',
25             DRIP_DEBUG => 0,
26             );
27              
28             =head1 NAME
29              
30             API::Drip::Request - Perl interface to api.getdrip.com
31              
32             =head1 VERSION
33              
34             Version 0.04
35              
36             =cut
37              
38             our $VERSION = '0.04';
39              
40             =head1 SYNOPSIS
41              
42             use API::Drip::Request;
43              
44             my $drip = API::Drip::Request->new();
45              
46             $drip->do_request( POST => 'subscribers', { subscribers => [ { email => 'foo@example.com', ... }] } );
47              
48             =head1 DESCRIPTION
49              
50             Low-level perl interface to the Drip API as specified at https://www.getdrip.com/docs/rest-api
51              
52             All of the methods in this module will throw exceptions on error.
53              
54             =head1 SUBROUTINES/METHODS
55              
56             =head2 new()
57              
58             Creates the API::Drip::Request object. See L</"CONFIGURATION"> for accepted parameters.
59              
60             Also accepts:
61              
62             =over
63              
64             =item debugger
65              
66             A codref that should accept a list of diagnostic strings and log them somewhere
67             useful for debugging purposes. Only used when DRIP_DEBUG is true.
68              
69             =back
70              
71             =cut
72              
73             my $config_validator = validation_for(
74             params => {
75             DRIP_CLIENT_CONF => { type => Str(), optional => 1 },
76             map { $_ => { type => Str(), optional => 1 } } keys %DEFAULTS,
77             debugger => { type => CodeRef(), optional => 1 },
78             }
79             );
80              
81             sub new {
82 0     0 1   my $class = shift;
83 0           my %OPT = $config_validator->(@_);
84              
85 0           my $self = _load_conf( \%OPT );
86              
87             # At this point, all configuration values should be set
88 0           foreach my $key ( keys %DEFAULTS ) {
89 0 0         confess "Missing configuration $key" unless defined $self->{$key};
90             }
91              
92 0           $self->{debugger} = _mk_debugger( $self, %OPT );
93              
94 0           bless $self, $class;
95 0           return $self;
96             }
97              
98             sub _mk_debugger {
99 0     0     my ($self, %OPT) = @_;
100              
101 0 0   0     unless ($self->{DRIP_DEBUG}) { return sub {}; }
  0            
102 0 0         if ( $OPT{debugger} ) { return $OPT{debugger} }
  0            
103              
104 0 0   0     return sub { warn join "\n", map { ref($_) ? np $_ : $_ } @_ };
  0            
  0            
105             }
106              
107             =head2 do_request
108              
109             Accepts the following positional parameters:
110              
111             =over
112              
113             =item HTTP Method (required)
114              
115             May be 'GET', 'POST', 'DELETE', 'PATCH', etc..
116              
117             =item Endpoint (requird)
118              
119             Specifies the path of the REST enpoint you want to query. Include everything after the account ID. For example, "subscribers", "subscribers/$subscriber_id/campaign_subscriptions", etc...
120              
121             =item Content (optional)
122              
123             Perl hashref of data that will be sent along with the request.
124              
125             =back
126              
127             On success, returns a Perl data structure corresponding to the data returned
128             from the server. Some operations (DELETE), do not return any data and may
129             return undef on success. On error, this method will die() with the
130             HTTP::Response object.
131              
132             =cut
133              
134             my $request_validator = validation_for( params => [ {type => Str()}, {type => Str()}, {type => HashRef(), optional => 1} ] );
135             sub do_request {
136 0     0 1   my $self = shift;
137 0           my ($method, $endpoint, $content) = $request_validator->(@_);
138              
139 0           my $uri = URI->new($self->{DRIP_URI});
140 0           $uri->path_segments( $uri->path_segments, $self->{DRIP_ID}, split( '/', $endpoint) );
141              
142 0           $self->{debugger}->( 'Requesting: ' . $uri->as_string );
143 0           my $request = HTTP::Request->new( $method => $uri->as_string, );
144 0 0         if ( ref($content) ) {
145 0           $request->content_type('application/vnd.api+json');
146 0           $request->content( encode_json( $content ) );
147             }
148 0           $request->authorization_basic( $self->{DRIP_TOKEN}, '' );
149              
150 0   0       $self->{agent} //= LWP::UserAgent->new( agent => $self->{DRIP_AGENT} );
151 0           my $result = $self->{agent}->request( $request );
152              
153 0 0         unless ( $result->is_success ) {
154 0           $self->{debugger}->("Request failed", $result->content);
155 0           die $result;
156             }
157              
158 0 0         if ( $result->code == 204 ) {
159 0           $self->{debugger}->("Success, no content");
160 0           return undef;
161             }
162 0           my $decoded = eval {decode_json( $result->content )};
  0            
163 0 0         if ( $@ ) {
164 0           $self->{debugger}->('Failed to decode JSON:', $@, $result->content);
165 0           die $result;
166             }
167 0           return $decoded;
168             }
169              
170              
171             =head1 CONFIGURATION
172              
173             Configuration data may be passed in through a number of different ways, which are searched in the following order of preference:
174              
175             =over
176              
177             =item 1. As direct paramteters to new().
178              
179             =item 2. As environment variables.
180              
181             =item 3. As elments of the first YAML configuration file that is found and readable in the following locations:
182              
183             =over
184              
185             =item 1. The location specified by the DRIP_CLIENT_CONF parameter supplied to new().
186              
187             =item 2. The location specified by $ENV{DRIP_CLIENT_CONF}.
188              
189             =item 3. $ENV{HOME}/.drip.conf
190              
191             =back
192              
193             =back
194              
195             The following configuration data is accepted:
196              
197             =over
198              
199             =item * DRIP_TOKEN (required)
200              
201             This is the user token assigned to you by drip. When you are logged in, look for "API Token" at https://www.getdrip.com/user/edit
202              
203             =item * DRIP_ID (required)
204              
205             This is the numeric user id assigned to you by drip. When logged in, find it in your settings under Account->General Info.
206              
207             =item * DRIP_URI (optional)
208              
209             This defaults to https://api.getdrip.com/v2. You probably shouldn't change this.
210              
211             =item * DRIP_AGENT (optional)
212              
213             Defaults to "API::Drip". Specifies the HTTP Agent header.
214              
215             =item * DRIP_DEBUG (optional)
216              
217             Defaults to 0. Set to a true value to enable debugging.
218              
219             =cut
220              
221             sub _load_conf {
222 0     0     my $OPT = shift();
223 0           my $conf = {};
224              
225             KEY:
226 0           foreach my $key ( keys %DEFAULTS ) {
227 0 0         next KEY if defined $OPT->{$key};
228              
229 0 0         if ( defined $ENV{$key} ) { $conf->{$key} = $ENV{$key}; next KEY; }
  0            
  0            
230              
231 0   0       state $YAML_CONF //= _load_yaml_conf( $OPT );
232 0 0         if ( defined $YAML_CONF->{$key} ) { $conf->{$key} = $YAML_CONF->{$key}; next KEY; }
  0            
  0            
233              
234 0           $conf->{$key} = $DEFAULTS{$key};
235             }
236 0           return $conf;
237             }
238              
239             sub _load_yaml_conf {
240 0     0     my $OPT = shift();
241              
242             FILE:
243 0           foreach my $location( $OPT->{DRIP_CLIENT_CONF}, $ENV{DRIP_CLIENT_CONF}, File::Spec->catfile( File::HomeDir->my_home, '.drip.conf' )) {
244 1     1   895 no warnings 'uninitialized';
  1         2  
  1         84  
245 0 0 0       next FILE unless -f $location && -r _;
246 0           return YAML::LoadFile $location;
247             }
248             }
249              
250             =head1 AUTHOR
251              
252             Dan Wright, C<< <Dan at DWright.Org> >>
253              
254             =head1 BUGS
255              
256             Please report any bugs or feature requests to C<bug-api-drip at rt.cpan.org>, or through
257             the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=API-Drip>. I will be notified, and then you'll
258             automatically be notified of progress on your bug as I make changes.
259              
260              
261              
262              
263             =head1 SUPPORT
264              
265             You can find documentation for this module with the perldoc command.
266              
267             perldoc API::Drip::Request
268              
269              
270             You can also look for information at:
271              
272             =over 4
273              
274             =item * RT: CPAN's request tracker (report bugs here)
275              
276             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=API-Drip>
277              
278             =item * AnnoCPAN: Annotated CPAN documentation
279              
280             L<http://annocpan.org/dist/API-Drip>
281              
282             =item * CPAN Ratings
283              
284             L<http://cpanratings.perl.org/d/API-Drip>
285              
286             =item * Search CPAN
287              
288             L<http://search.cpan.org/dist/API-Drip/>
289              
290             =back
291              
292              
293             =head1 ACKNOWLEDGEMENTS
294              
295             This code is written to help support my day job and is being released open
296             source thanks to pair Networks, Inc.
297              
298             =head1 LICENSE AND COPYRIGHT
299              
300             Copyright 2017 Dan Wright.
301              
302             This program is free software; you can redistribute it and/or modify it
303             under the terms of either: the GNU General Public License as published
304             by the Free Software Foundation; or the Artistic License.
305              
306             See L<http://dev.perl.org/licenses/> for more information.
307              
308              
309             =cut
310              
311             1; # End of API::Drip::Request