File Coverage

blib/lib/EVDB/API.pm
Criterion Covered Total %
statement 79 152 51.9
branch 20 96 20.8
condition 10 32 31.2
subroutine 11 13 84.6
pod 3 4 75.0
total 123 297 41.4


line stmt bran cond sub pod time code
1             package EVDB::API;
2              
3             =head1 NAME
4              
5             EVDB::API - Perl interface to EVDB public API
6              
7             =head1 SYNOPSIS
8              
9             use EVDB::API;
10            
11             my $evdb = EVDB::API->new(app_key => $app_key);
12            
13             $evdb->login(user => 'harry', password => 'H0gwart$')
14             or die "Can't log in: $EVDB::API::errstr";
15            
16             # call() accepts either an array ref or a hash ref.
17             my $event = $evdb->call('events/get', {id => 'E0-001-000218163-6'})
18             or die "Can't retrieve event: $EVDB::API::errstr";
19            
20             print "Title: $event->{title}\n";
21              
22             my $venue = $evdb->call('venues/get', [id => $event->{venue_id}])
23             or die "Can't retrieve venue: $EVDB::API::errstr";
24            
25             print "Venue: $venue->{name}\n";
26              
27              
28             =head1 DESCRIPTION
29              
30             The EVDB API allows you to build tools and applications that interact with EVDB, the Events & Venues Database. This module provides a Perl interface to that API, including the digest-based authentication infrastructure.
31              
32             See http://api.evdb.com/ for details.
33              
34             =head1 AUTHOR
35              
36             Copyright 2006 Eventful, Inc. All rights reserved.
37              
38             You may distribute under the terms of either the GNU General Public License or the Artistic License, as specified in the Perl README file.
39              
40             =head1 ACKNOWLEDGEMENTS
41              
42             Special thanks to Daniel Westermann-Clark for adding support for "flavors" of
43             plug-in parsers. Visit Podbop.org to see other cool things made by Daniel.
44              
45             =cut
46              
47             require 5.6.0;
48              
49 3     3   2378 use strict;
  3         8  
  3         101  
50 3     3   17 use warnings;
  3         6  
  3         88  
51 3     3   26 no warnings qw(uninitialized);
  3         6  
  3         111  
52              
53 3     3   35 use Carp;
  3         6  
  3         269  
54 3     3   5676 use LWP::UserAgent;
  3         228522  
  3         91  
55 3     3   4870 use HTTP::Request::Common;
  3         6195  
  3         1454  
56 3     3   17 use Digest::MD5 qw(md5_hex);
  3         5  
  3         146  
57 3     3   2487 use Module::Pluggable::Object;
  3         28468  
  3         5389  
58              
59             =head1 VERSION
60              
61             0.99 - August 2006
62              
63             =cut
64              
65             our $VERSION = 0.99;
66              
67             our $VERBOSE = 0;
68             our $DEBUG = 0;
69              
70             our $default_api_server = 'http://api.evdb.com';
71             our $default_flavor = 'rest';
72              
73             our $errcode;
74             our $errstr;
75              
76             =head1 CLASS METHODS
77              
78             =head2 new
79            
80             $evdb = EVDB::API->new(app_key => $app_key);
81              
82             Creates a new API object. Requires a valid app_key as provided by EVDB.
83              
84             You can also specify an API "flavor", such as C, to use a different format.
85              
86             $evdb = EVDB::API->new(app_key => $app_key, flavor => 'yaml');
87              
88             Valid flavors are C, C, and C.
89              
90             =cut
91              
92             sub new
93             {
94 1     1 1 207 my $thing = shift;
95 1   33     9 my $class = ref($thing) || $thing;
96            
97 1         5 my %params = @_;
98 1   33     19 my $self =
      33        
99             {
100             'app_key' => $params{app_key} || $params{app_token},
101             'debug' => $params{debug},
102             'verbose' => $params{verbose},
103             'user_key' => '',
104             'api_root' => $params{api_root} || $default_api_server,
105             };
106            
107 1   33     10 $DEBUG ||= $params{debug};
108 1   33     7 $VERBOSE ||= $params{verbose};
109            
110 1 50       22 print "Creating object in class ($class)...\n" if $VERBOSE;
111            
112 1         3 bless $self, $class;
113            
114 1   33     6 my $flavor = $params{flavor} || $default_flavor;
115 1         7 $self->{parser} = $self->_find_parser($flavor);
116 1 50       8 croak "No parser found for flavor [$flavor]"
117             unless $self->{parser};
118              
119             # Create an LWP user agent for later use.
120 1         30 $self->{user_agent} = LWP::UserAgent->new(
121             agent => "EVDB_API_Perl_Wrapper/$VERSION-$flavor",
122             );
123            
124 1         3878 return $self;
125             }
126              
127             # Attempt to find a parser for the specified API flavor.
128             # Returns the package name if one is found.
129             sub _find_parser
130             {
131 1     1   3 my ($self, $requested_flavor) = @_;
132              
133             # Based on Catalyst::Plugin::ConfigLoader
134 1         12 my $finder = Module::Pluggable::Object->new(
135             search_path => [ __PACKAGE__ ],
136             require => 1,
137             );
138              
139 1         8 my $parser;
140 1         5 foreach my $plugin ($finder->plugins) {
141 3         2878 my $flavor = $plugin->flavor;
142 3 100       12 if ($flavor eq $requested_flavor) {
143 1         3 $parser = $plugin;
144             }
145             }
146              
147 1         28 return $parser;
148             }
149              
150              
151             =head1 OBJECT METHODS
152              
153             =head2 login
154              
155             $evdb->login(user => $username, password => $password);
156             $evdb->login(user => $username, password_md5 => $password_md5);
157              
158             Retrieves an authentication token from the EVDB API server.
159              
160             =cut
161              
162             sub login
163             {
164 0     0 1 0 my $self = shift;
165            
166 0         0 my %args = @_;
167            
168 0         0 $self->{user} = $args{user};
169            
170             # Call login to receive a nonce.
171             # (The nonce is stored in an error structure.)
172 0         0 $self->call('users/login');
173 0 0       0 my $nonce = $self->{response_data}{nonce} or return;
174            
175             # Generate the digested password response.
176 0   0     0 my $password_md5 = $args{password_md5} || md5_hex($args{password});
177 0         0 my $response = md5_hex( $nonce . ":" . $password_md5 );
178            
179             # Send back the nonce and response.
180 0         0 my $params =
181             {
182             nonce => $nonce,
183             response => $response,
184             };
185            
186 0 0       0 my $r = $self->call('users/login', $params) or return;
187            
188             # Store the provided user_key.
189 0   0     0 $self->{user_key} = $r->{user_key} || $r->{auth_token};
190            
191 0         0 return 1;
192             }
193              
194             =head2 call
195              
196             $data = $evdb->call($method, \%arguments, [$force_array]);
197              
198             Calls the specified method with the given arguments and any previous authentication information (including C). Returns a hash reference containing the results.
199              
200             =cut
201              
202             sub call
203             {
204 1     1 1 7 my $self = shift;
205            
206 1         3 my $method = shift;
207 1   50     4 my $args = shift || [];
208 1         1 my $force_array = shift;
209              
210             # Remove any leading slash from the method name.
211 1         3 $method =~ s%^/%%;
212              
213             # If we have no force_array, see if we have one for this method.
214 1 50 33     9 if ($self->{parser}->flavor eq 'rest' and !$force_array) {
215              
216             # The following code is automatically generated. Edit
217             # /main/trunk/evdb/public_api/force_array/force_array.conf
218             # and run
219             # /main/trunk/evdb/public_api/force_array/enforcer
220             # instead.
221             #
222             # BEGIN REPLACE
223 1 50       20 if($method eq 'calendars/latest/stickers') {
    50          
    50          
    50          
    50          
    50          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
224 0         0 $force_array = ['site'];
225             }
226              
227             elsif($method eq 'calendars/tags/cloud') {
228 0         0 $force_array = ['tag'];
229             }
230              
231             elsif($method eq 'demands/get') {
232 0         0 $force_array = ['link', 'comment', 'image', 'tag', 'event', 'member'];
233             }
234              
235             elsif($method eq 'demands/latest/hottest') {
236 0         0 $force_array = ['demand', 'event'];
237             }
238              
239             elsif($method eq 'demands/search') {
240 0         0 $force_array = ['demand', 'event'];
241             }
242              
243             elsif($method eq 'events/get') {
244 1         6 $force_array = ['link', 'comment', 'trackback', 'image', 'parent', 'child', 'tag', 'feed', 'calendar', 'group', 'user', 'relationship', 'performer', 'rrule', 'exrule', 'rdate', 'exdate', 'date', 'category'];
245             }
246              
247             elsif($method eq 'events/recurrence/list') {
248 0         0 $force_array = ['recurrence'];
249             }
250              
251             elsif($method eq 'events/tags/cloud') {
252 0         0 $force_array = ['tag'];
253             }
254              
255             elsif($method eq 'events/validate/hcal') {
256 0         0 $force_array = ['tag', 'event_url', 'venue_url', 'event'];
257             }
258              
259             elsif($method eq 'groups/get') {
260 0         0 $force_array = ['user', 'calendar', 'link', 'comment', 'trackback', 'image', 'tag'];
261             }
262              
263             elsif($method eq 'groups/search') {
264 0         0 $force_array = ['group'];
265             }
266              
267             elsif($method eq 'groups/users/list') {
268 0         0 $force_array = ['user'];
269             }
270              
271             elsif($method eq 'internal/events/submissions/pending') {
272 0         0 $force_array = ['submission'];
273             }
274              
275             elsif($method eq 'internal/events/submissions/set_status') {
276 0         0 $force_array = ['submission'];
277             }
278              
279             elsif($method eq 'internal/events/submissions/status') {
280 0         0 $force_array = ['target'];
281             }
282              
283             elsif($method eq 'internal/submissions/targets') {
284 0         0 $force_array = ['target'];
285             }
286              
287             elsif($method eq 'performers/demands/list') {
288 0         0 $force_array = ['demand'];
289             }
290              
291             elsif($method eq 'performers/get') {
292 0         0 $force_array = ['link', 'comment', 'image', 'tag', 'event', 'demand', 'trackback'];
293             }
294              
295             elsif($method eq 'performers/search') {
296 0         0 $force_array = ['performer'];
297             }
298              
299             elsif($method eq 'users/calendars/get') {
300 0         0 $force_array = ['rule', 'feed'];
301             }
302              
303             elsif($method eq 'users/calendars/list') {
304 0         0 $force_array = ['calendar'];
305             }
306              
307             elsif($method eq 'users/comments/get') {
308 0         0 $force_array = ['comment'];
309             }
310              
311             elsif($method eq 'users/events/recent') {
312 0         0 $force_array = ['event'];
313             }
314              
315             elsif($method eq 'users/get') {
316 0         0 $force_array = ['site', 'im_account', 'event', 'venue', 'performer', 'comment', 'trackback', 'calendar', 'locale', 'link', 'event'];
317             }
318              
319             elsif($method eq 'users/groups/list') {
320 0         0 $force_array = ['group'];
321             }
322              
323             elsif($method eq 'users/search') {
324 0         0 $force_array = ['user'];
325             }
326              
327             elsif($method eq 'users/venues/get') {
328 0         0 $force_array = ['user_venue'];
329             }
330              
331             elsif($method eq 'venues/get') {
332 0         0 $force_array = ['link', 'comment', 'trackback', 'image', 'parent', 'child', 'event', 'tag', 'feed', 'calendar', 'group'];
333             }
334              
335             elsif($method eq 'venues/tags/cloud') {
336 0         0 $force_array = ['tag'];
337             }
338              
339             else {
340 0         0 $force_array = ['event', 'venue', 'comment', 'trackback', 'calendar', 'group', 'user', 'performer', 'member'];
341             }
342              
343             # END REPLACE
344              
345             }
346              
347             # Construct the method URL.
348 1         5 my $url = join '/', $self->{api_root}, $self->{parser}->flavor, $method;
349 1 50       23 print "Calling ($url)...\n" if $VERBOSE;
350            
351             # Pre-process the arguments into a hash (for searching) and an array ref
352             # (to pass on to HTTP::Request::Common).
353 1         3 my $arg_present = {};
354 1 50       7 if (ref($args) eq 'ARRAY')
    50          
355             {
356             # Create a hash of the array values (assumes [foo => 'bar', baz => 1]).
357 0         0 my %arg_present = @{$args};
  0         0  
358 0         0 $arg_present = \%arg_present;
359             }
360             elsif (ref($args) eq 'HASH')
361             {
362             # Migrate the provided hash to an array ref.
363 1         2 $arg_present = $args;
364 1         2 my @args = %{$args};
  1         4  
365 1         2 $args = \@args;
366             }
367             else
368             {
369 0         0 $errcode = 'Missing parameter';
370 0         0 $errstr = 'Missing parameters: The second argument to call() should be an array or hash reference.';
371 0         0 return undef;
372             }
373            
374             # Add the standard arguments to the list.
375 1         2 foreach my $k ('app_key', 'user', 'user_key')
376             {
377 3 100 66     13 if ($self->{$k} and !$arg_present->{$k})
378             {
379 1         2 push @{$args}, $k, $self->{$k};
  1         3  
380             }
381             }
382            
383             # If one of the arguments is a file, set up the Common-friendly
384             # file indicator field and set the content-type.
385 1         2 my $content_type = '';
386 1         2 foreach my $this_field (keys %{$arg_present})
  1         2  
387             {
388             # Any argument with a name that ends in "_file" is a file.
389 1 50       4 if ($this_field =~ /_file$/)
390             {
391 0         0 $content_type = 'form-data';
392 0 0       0 next if ref($arg_present->{$this_field}) eq 'ARRAY';
393 0         0 my $file =
394             [
395             $arg_present->{$this_field},
396             ];
397            
398             # Replace the original argument with the file indicator.
399 0         0 $arg_present->{$this_field} = $file;
400 0         0 my $last_arg = scalar(@{$args}) - 1;
  0         0  
401 0         0 ARG: for my $i (0..$last_arg)
402             {
403 0 0       0 if ($args->[$i] eq $this_field)
404             {
405             # If this is the right arg, replace the item after it.
406 0         0 splice(@{$args}, $i + 1, 1, $file);
  0         0  
407 0         0 last ARG;
408             }
409             }
410             }
411             }
412            
413             # Fetch the data using the POST method.
414 1         3 my $ua = $self->{user_agent};
415            
416 1         7 my $response = $ua->request(POST $url,
417             'Content-type' => $content_type,
418             'Content' => $args,
419             );
420 1 50       466915 unless ($response->is_success)
421             {
422 0         0 $errcode = $response->code;
423 0         0 $errstr = $response->code . ': ' . $response->message;
424 0         0 return undef;
425             }
426            
427 1         25 $self->{response_content} = $response->content();
428 1         19 my $data;
429            
430 1         12 my $ctype = $self->{parser}->ctype;
431 1 50       6 if ($response->header('Content-Type') =~ m/$ctype/i)
432             {
433             # Parse the response into a Perl data structure.
434 1 50       63 if ($self->{parser}->flavor eq 'rest')
435             {
436             # Maintain backwards compatibility.
437 1         3 $self->{response_xml} = $self->{response_content};
438             }
439 1         6 $data = $self->{response_data} = $self->{parser}->parse($self->{response_content}, $force_array);
440            
441             # Check for errors.
442 0 0         if ($data->{string})
443             {
444 0           $errcode = $data->{string};
445 0           $errstr = $data->{string} . ": " .$data->{description};
446 0 0         print "\n", $self->{response_content}, "\n" if $DEBUG;
447 0           return undef;
448             }
449             }
450             else
451             {
452 0           print "Content-type is: ", $response->header('Content-Type'), "\n";
453 0           $data = $self->{response_content};
454             }
455              
456 0           return $data;
457             }
458              
459             # Copied shamelessly from CGI::Minimal.
460             sub url_encode
461             {
462 0     0 0   my $s = shift;
463 0 0         return '' unless defined($s);
464            
465             # Filter out any URL-unfriendly characters.
466 0           $s =~ s/([^-_.a-zA-Z0-9])/"\%".unpack("H",$1).unpack("h",$1)/egs;
  0            
467            
468 0           return $s;
469             }
470              
471             1;
472              
473             __END__