File Coverage

lib/Sailthru/Client.pm
Criterion Covered Total %
statement 176 180 97.7
branch 14 20 70.0
condition n/a
subroutine 33 33 100.0
pod 12 20 60.0
total 235 253 92.8


line stmt bran cond sub pod time code
1             package Sailthru::Client;
2              
3 5     5   263306 use strict;
  5         12  
  5         151  
4 5     5   27 use warnings;
  5         9  
  5         146  
5              
6 5     5   35 use Carp;
  5         10  
  5         356  
7 5     5   4196 use JSON::XS;
  5         36980  
  5         317  
8 5     5   5731 use LWP::UserAgent;
  5         234409  
  5         210  
9 5     5   60 use Digest::MD5 qw( md5_hex );
  5         12  
  5         407  
10 5     5   7915 use Params::Validate qw( :all );
  5         54416  
  5         1414  
11 5     5   739 use Readonly;
  5         3056  
  5         323  
12 5     5   33 use URI;
  5         11  
  5         12752  
13              
14             our $VERSION = '2.002';
15             Readonly my $API_URI => 'https://api.sailthru.com/';
16              
17             #
18             # public api
19             #
20              
21             # args:
22             #
23             # * api_key - scalar
24             # * secret - scalar
25             # * timeout - scalar (optional)
26             sub new {
27 5     5 1 1874 my ( $class, $api_key, $secret, $timeout ) = @_;
28 5         86 my $self = {
29             api_key => $api_key,
30             secret => $secret,
31             ua => LWP::UserAgent->new,
32             };
33 5 50       15892 $self->{ua}->timeout($timeout) if $timeout;
34 5         40 $self->{ua}->default_header( 'User-Agent' => "Sailthru API Perl Client $VERSION" );
35 5         280 return bless $self, $class;
36             }
37              
38             # args:
39             #
40             # * template_name - scalar
41             # * email - scalar
42             # * vars - hashref (optional)
43             # * options - hashref (optional)
44             # * schedule_time - scalar (optional)
45             sub send {
46 2     2 1 2297 my $self = shift;
47 2         79 my @params = validate_pos(
48             @_,
49             { type => SCALAR },
50             { type => SCALAR },
51             { type => HASHREF, default => {} },
52             { type => HASHREF, default => {} },
53             { type => SCALAR, default => undef }
54             );
55 2         12 my ( $template_name, $email, $vars, $options, $schedule_time ) = @params;
56 2         4 my $data = {};
57 2         5 $data->{template} = $template_name;
58 2         4 $data->{email} = $email;
59 2 100       2 $data->{vars} = $vars if keys %{$vars};
  2         9  
60 2 100       3 $data->{options} = $options if keys %{$options};
  2         10  
61 2 100       7 $data->{schedule_time} = $schedule_time if $schedule_time;
62 2         9 return $self->api_post( 'send', $data );
63             }
64              
65             # args:
66             # * send_id - scalar
67             sub get_send {
68 2     2 1 2424 my $self = shift;
69 2         52 my @params = validate_pos( @_, { type => SCALAR } );
70 2         9 my ($send_id) = @params;
71 2         12 return $self->api_get( 'send', { send_id => $send_id } );
72             }
73              
74             # args:
75             # * email - scalar
76             sub get_email {
77 3     3 1 1543 my $self = shift;
78 3         72 my @params = validate_pos( @_, { type => SCALAR } );
79 3         16 my ($email) = @params;
80 3         14 return $self->api_get( 'email', { email => $email } );
81             }
82              
83             # args:
84             # * email - scalar
85             # * vars - hashref (optional)
86             # * lists - hashref (optional)
87             # * templates - hashref (optional)
88             sub set_email {
89 3     3 1 2828 my $self = shift;
90 3         124 my @params = validate_pos(
91             @_,
92             { type => SCALAR },
93             { type => HASHREF, default => {} },
94             { type => HASHREF, default => {} },
95             { type => HASHREF, default => {} }
96             );
97 3         52 my ( $email, $vars, $lists, $templates ) = @params;
98 3         8 my $data = {};
99 3         8 $data->{email} = $email;
100 3 100       5 $data->{vars} = $vars if keys %{$vars};
  3         16  
101 3 100       5 $data->{lists} = $lists if keys %{$lists};
  3         17  
102 3 100       5 $data->{templates} = $templates if keys %{$templates};
  3         13  
103 3         12 return $self->api_post( 'email', $data );
104             }
105              
106             # args:
107             # * name - scalar
108             # * list - scalar
109             # * schedule_time - scalar
110             # * from_name - scalar
111             # * from_email - scalar
112             # * subject - scalar
113             # * content_html - scalar
114             # * content_text - scalar
115             # * options - hashref (optional)
116             sub schedule_blast {
117 3     3 1 4440 my $self = shift;
118 3         185 my @params = validate_pos(
119             @_,
120             { type => SCALAR },
121             { type => SCALAR },
122             { type => SCALAR },
123             { type => SCALAR },
124             { type => SCALAR },
125             { type => SCALAR },
126             { type => SCALAR },
127             { type => SCALAR },
128             { type => HASHREF, default => {} }
129             );
130 3         23 my ( $name, $list, $schedule_time, $from_name, $from_email, $subject, $content_html, $content_text, $options ) =
131             @params;
132             # initialize our data hash by copying the contents of the options hash
133 3         6 my $data = { %{$options} };
  3         10  
134 3         9 $data->{name} = $name;
135 3         6 $data->{list} = $list;
136 3         15 $data->{schedule_time} = $schedule_time;
137 3         8 $data->{from_name} = $from_name;
138 3         5 $data->{from_email} = $from_email;
139 3         7 $data->{subject} = $subject;
140 3         9 $data->{content_html} = $content_html;
141 3         6 $data->{content_text} = $content_text;
142 3         13 return $self->api_post( 'blast', $data );
143             }
144              
145             # args:
146             # * template_name - scalar
147             # * list - scalar
148             # * schedule_time - scalar
149             # * options - hashref (optional)
150             sub schedule_blast_from_template {
151 2     2 1 3257 my $self = shift;
152 2         46 my @params = validate_pos(
153             @_,
154             { type => SCALAR },
155             { type => SCALAR },
156             { type => SCALAR },
157             { type => HASHREF, default => {} },
158             );
159 2         10 my ( $template_name, $list, $schedule_time, $options ) = @params;
160             # initialize our data hash by copying the contents of the options hash
161 2         3 my $data = { %{$options} };
  2         6  
162 2         5 $data->{copy_template} = $template_name;
163 2         4 $data->{list} = $list;
164 2         3 $data->{schedule_time} = $schedule_time;
165 2         7 return $self->api_post( 'blast', $data );
166              
167             }
168              
169             # args:
170             # * blast_id - scalar
171             sub get_blast {
172 2     2 1 1862 my $self = shift;
173 2         45 my @params = validate_pos( @_, { type => SCALAR } );
174 2         9 my ($blast_id) = @params;
175 2         13 return $self->api_get( 'blast', { blast_id => $blast_id } );
176             }
177              
178             # args:
179             # * template_name - scalar
180             sub get_template {
181 2     2 1 1568 my $self = shift;
182 2         34 my @params = validate_pos( @_, { type => SCALAR } );
183 2         10 my ($template_name) = @params;
184 2         12 return $self->api_get( 'template', { template => $template_name } );
185             }
186              
187             # args:
188             # * action - scalar
189             # * data - hashref
190             sub api_get {
191 10     10 1 11582 my $self = shift;
192 10         173 my @params = validate_pos( @_, { type => SCALAR }, { type => HASHREF } );
193 10         42 my ( $action, $data ) = @params;
194 10         64 return $self->_api_request( $action, $data, 'GET' );
195             }
196              
197             # args:
198             # * action - scalar
199             # * data - hashref
200             # * TODO: optional binary_key arg
201             sub api_post {
202 15     15 1 1393 my $self = shift;
203 15         252 my @params = validate_pos( @_, { type => SCALAR }, { type => HASHREF } );
204 15         58 my ( $action, $data ) = @params;
205 15         59 return $self->_api_request( $action, $data, 'POST' );
206             }
207              
208             # args:
209             # * action - scalar
210             # * data - hashref
211             sub api_delete {
212 1     1 1 2291 my $self = shift;
213 1         16 my @params = validate_pos( @_, { type => SCALAR }, { type => HASHREF } );
214 1         4 my ( $action, $data ) = @params;
215 1         4 return $self->_api_request( $action, $data, 'DELETE' );
216             }
217              
218             #
219             # private helper methods
220             #
221              
222             # args:
223             # * action - scalar
224             # * data - hashref
225             # * request_type - scalar
226             sub _api_request {
227 4     4   8 my $self = shift;
228 4         62 my @params = validate_pos( @_, { type => SCALAR }, { type => HASHREF }, { type => SCALAR } );
229 4         16 my ( $action, $data, $request_type ) = @params;
230 4         13 my $payload = $self->_prepare_json_payload($data);
231 4         27 my $action_uri = $API_URI . $action;
232 4         30 my $response = $self->_http_request( $action_uri, $payload, $request_type );
233 4         349 return decode_json( $response->content );
234             }
235              
236             # args:
237             # * uri - scalar
238             # * data - hashref
239             # * method - scalar
240             sub _http_request {
241 1     1   2 my $self = shift;
242 1         19 my @params = validate_pos( @_, { type => SCALAR }, { type => HASHREF }, { type => SCALAR } );
243 1         4 my ( $uri, $data, $method ) = @params;
244 1         8 $uri = URI->new($uri);
245 1         11181 my $response;
246 1 50       29 if ( $method eq 'GET' ) {
    0          
    0          
247 1         12 $uri->query_form($data);
248 1         378 $response = $self->{ua}->get($uri);
249             }
250             elsif ( $method eq 'POST' ) {
251 0         0 $response = $self->{ua}->post( $uri, $data );
252             }
253             elsif ( $method eq 'DELETE' ) {
254 0         0 $uri->query_form($data);
255 0         0 $response = $self->{ua}->delete($uri);
256             }
257             else {
258 0         0 croak "Invalid method: $method";
259             }
260 1         755917 return $response;
261             }
262              
263             # args:
264             # * data - hashref
265             sub _prepare_json_payload {
266 4     4   6 my $self = shift;
267 4         37 my @params = validate_pos( @_, { type => HASHREF } );
268 4         12 my ($data) = @params;
269 4         5 my $payload = {};
270 4         17 $payload->{api_key} = $self->{api_key};
271 4         9 $payload->{format} = 'json';
272             # this gives us nice clean utf8 encoded json text
273 4         52 $payload->{json} = encode_json($data);
274 4         20 $payload->{sig} = $self->_get_signature_hash( $payload, $self->{secret} );
275 4         30 return $payload;
276             }
277              
278             # Every request must also generate a signature hash called sig according to the
279             # following rules:
280             #
281             # * take the string values of every parameter, including api_key
282             # * sort the values alphabetically, case-sensitively (i.e. ordered by Unicode code point)
283             # * concatenate the sorted values, and prepend this string with your shared secret
284             # * generate an MD5 hash of this string and use this as sig
285             # * now generate your URL-encoded query string from your parameters plus sig
286              
287             # args:
288             # * params - hashref
289             # * secret - scalar
290             # NOTE This internal method assumes a single level hash with values for only 'api_key', 'format', and 'json'
291             # NOTE Since we pack everything into the 'json' value this is safe and we do not need to recurse down a nested hash.
292             sub _get_signature_hash {
293 6     6   927 my $self = shift;
294 6         105 my @params = validate_pos( @_, { type => HASHREF }, { type => SCALAR } );
295 6         22 my ( $api_param_hash, $secret ) = @params;
296 6         7 my @api_param_values = values %{$api_param_hash};
  6         25  
297 6         36 my $sig_string = join '', $secret, sort @api_param_values;
298             # assumes utf8 encoded text, works fine because we use encode_json internally
299 6         54 return md5_hex($sig_string);
300             }
301              
302             ### XXX
303             ### DEPRECATED METHODS
304             ### XXX
305              
306             # args:
307             # * email - scalar
308             sub getEmail {
309 1     1 0 1612 my $self = shift;
310 1         16 warnings::warnif( 'deprecated', 'getEmail is deprecated, use get_email instead' );
311 1         163 return $self->get_email(@_);
312             }
313              
314             # args:
315             # * email - scalar
316             # * vars - hashref (optional)
317             # * lists - hashref (optional)
318             # * templates - hashref (optional)
319             sub setEmail {
320 1     1 0 1931 my $self = shift;
321 1         19 warnings::warnif( 'deprecated', 'setEmail is deprecated, use set_email instead' );
322 1         233 return $self->set_email(@_);
323             }
324              
325             # args:
326             # * send_id - scalar
327             sub getSend {
328 1     1 0 1337 my $self = shift;
329 1         38 warnings::warnif( 'deprecated', 'getSend is deprecated, use get_send instead' );
330 1         201 return $self->get_send(@_);
331             }
332              
333             # args:
334             # * name - scalar
335             # * list - scalar
336             # * schedule_time - scalar
337             # * from_name - scalar
338             # * from_email - scalar
339             # * subject - scalar
340             # * content_html - scalar
341             # * content_text - scalar
342             # * options - hashref (optional)
343             sub scheduleBlast {
344 1     1 0 4166 my $self = shift;
345 1         26 warnings::warnif( 'deprecated', 'scheduleBlast is deprecated, use schedule_blast instead' );
346 1         223 return $self->schedule_blast(@_);
347             }
348              
349             # args:
350             # * blast_id - scalar
351             sub getBlast {
352 1     1 0 2764 my $self = shift;
353 1         25 warnings::warnif( 'deprecated', 'getBlast is deprecated, use get_blast instead' );
354 1         249 return $self->get_blast(@_);
355             }
356              
357             sub copyTemplate {
358 2     2 0 4076 my $self = shift;
359 2         70 my @params = validate_pos(
360             @_,
361             { type => SCALAR },
362             { type => SCALAR },
363             { type => SCALAR },
364             { type => SCALAR },
365             { type => SCALAR },
366             { type => SCALAR },
367             { type => HASHREF, default => {} }
368             );
369 2         14 my ( $template, $data_feed, $setup, $subject_line, $schedule_time, $list, $options ) = @params;
370 2         35 warnings::warnif( 'deprecated', 'copyTemplate is deprecated, use schedule_blast_from_template instead' );
371             # initialize our data hash by copying the contents of the options hash
372 2         437 my $data = { %{$options} };
  2         8  
373 2         6 $data->{copy_template} = $template;
374 2         5 $data->{data_feed_url} = $data_feed;
375 2         4 $data->{setup} = $setup;
376 2         5 $data->{name} = $subject_line;
377 2         4 $data->{schedule_time} = $schedule_time;
378 2         4 $data->{list} = $list;
379 2         8 return $self->api_post( 'blast', $data );
380             }
381              
382             # args:
383             # * template_name - scalar
384             sub getTemplate {
385 1     1 0 2014 my $self = shift;
386 1         19 warnings::warnif( 'deprecated', 'getTemplate is deprecated, use get_template instead' );
387 1         272 return $self->get_template(@_);
388             }
389              
390             # args:
391             # * email - scalar
392             # * password - scalar
393             # * include_names - scalar (optional)
394             sub importContacts {
395 2     2 0 4562 my $self = shift;
396 2         45 my @p = validate_pos( @_, { type => SCALAR }, { type => SCALAR }, { type => SCALAR, default => 0 } );
397 2         11 my ( $email, $password, $include_names ) = @p;
398 2         37 warnings::warnif( 'deprecated',
399             'importContacts is deprecated. The contacts API has been discontinued as of August 1st, 2011.' );
400 2         481 my $data = {
401             email => $email,
402             password => $password,
403             include_names => $include_names,
404             };
405 2         8 return $self->api_post( 'contacts', $data );
406             }
407              
408             1;
409              
410             __END__