File Coverage

blib/lib/WWW/GoDaddy/REST.pm
Criterion Covered Total %
statement 25 27 92.5
branch n/a
condition n/a
subroutine 9 9 100.0
pod n/a
total 34 36 94.4


line stmt bran cond sub pod time code
1             package WWW::GoDaddy::REST;
2              
3 4     4   147538 use warnings;
  4         7  
  4         128  
4 4     4   19 use strict;
  4         5  
  4         128  
5              
6             #<<< NO perltidy - must be all on one line
7 4     4   2000 use version; our $VERSION = version->new('0.9');
  4         7010  
  4         22  
8             #>>>
9 4     4   426 use Carp qw(confess);
  4         8  
  4         332  
10 4     4   2353 use English qw( -no_match_vars );
  4         11793  
  4         27  
11 4     4   3192 use File::Slurp qw( slurp );
  4         22110  
  4         290  
12 4     4   2476 use LWP::UserAgent;
  4         138503  
  4         136  
13 4     4   32 use HTTP::Request;
  4         6  
  4         81  
14 4     4   301840 use Moose;
  0            
  0            
15             use WWW::GoDaddy::REST::Resource;
16             use WWW::GoDaddy::REST::Schema;
17             use WWW::GoDaddy::REST::Util qw(abs_url json_encode json_decode is_json );
18              
19             my $JSON_MIME_TYPE = 'application/json';
20              
21             has 'url' => (
22             is => 'rw',
23             isa => 'Str',
24             required => 1,
25             documentation => 'Base url of the REST service'
26             );
27              
28             has 'basic_username' => (
29             is => 'rw',
30             isa => 'Str',
31             required => 0,
32             documentation => 'Username portion if using basic auth'
33             );
34              
35             has 'basic_password' => (
36             is => 'rw',
37             isa => 'Str',
38             required => 0,
39             documentation => 'Password portion if using basic auth'
40             );
41              
42             has 'user_agent' => (
43             is => 'rw',
44             isa => 'Object',
45             required => 1,
46             default => \&default_user_agent,
47             );
48              
49             has 'schemas_file' => (
50             is => 'rw',
51             isa => 'Str',
52             required => 0,
53             documentation => 'Optional, cached copy of the schemas JSON to avoid HTTP round trip'
54             );
55              
56             has 'schemas' => (
57             is => 'rw',
58             isa => 'ArrayRef[WWW::GoDaddy::REST::Schema]',
59             required => 1,
60             lazy => 1,
61             builder => '_build_schemas'
62             );
63              
64             has 'raise_http_errors' => (
65             is => 'rw',
66             isa => 'Bool',
67             required => 1,
68             default => 1
69             );
70              
71             sub BUILD {
72             my ( $self, $params ) = @_;
73              
74             if ( defined $params->{basic_username} or defined $params->{basic_password} ) {
75             if ( !defined $params->{basic_username} ) {
76             confess 'Attribute (basic_username) is required if basic_password is provided';
77             }
78             if ( !defined $params->{basic_password} ) {
79             confess 'Attribute (basic_password) is required if basic_username is provided';
80             }
81             }
82              
83             if ( defined $params->{schemas_file} and !-e $params->{schemas_file} ) {
84             confess 'Attribute (schemas_file) must be a file that exists: ' . $params->{schemas_file};
85             }
86              
87             }
88              
89             sub query {
90             my $self = shift;
91             my $type = shift;
92             return $self->schema($type)->query(@_);
93             }
94              
95             sub query_by_id {
96             my $self = shift;
97             my $type = shift;
98             return $self->schema($type)->query_by_id(@_);
99             }
100              
101             sub create {
102             my $self = shift;
103             my $type = shift;
104             return $self->schema($type)->create(@_);
105             }
106              
107             sub schema {
108             my $self = shift;
109             my $name = shift;
110              
111             my @schemas = @{ $self->schemas() };
112              
113             return WWW::GoDaddy::REST::Schema->registry_lookup($name);
114             }
115              
116             sub schemas_url {
117             my $self = shift;
118             my $specific_name = shift || '';
119              
120             my $base = $self->url;
121             my $path = sprintf( 'schemas/%s', $specific_name );
122             return abs_url( $base, $path );
123             }
124              
125             sub http_request_schemas_json {
126             my $self = shift;
127              
128             my $request = $self->build_http_request( 'GET', $self->schemas_url );
129             my $response = $self->user_agent->request($request);
130             my $content = $response->content;
131             if ( !$response->is_success && $self->raise_http_errors ) {
132             die($content);
133             }
134             return $content;
135             }
136              
137             sub http_request_as_resource {
138             my ( $self, $method, $url, $content ) = @_;
139             my ( $struct_from_json, $http_response ) = $self->http_request( $method, $url, $content );
140              
141             my $resource = WWW::GoDaddy::REST::Resource->new_subclassed(
142             { client => $self,
143             fields => $struct_from_json,
144             http_response => $http_response
145             }
146             );
147              
148             if ( !$http_response->is_success && $self->raise_http_errors ) {
149             if ($EXCEPTIONS_BEING_CAUGHT) {
150             die($resource);
151             }
152             else {
153             die( $resource->to_string );
154             }
155             }
156              
157             return $resource;
158             }
159              
160             sub http_request {
161             my $self = shift;
162             my ( $method, $uri, $perl_data ) = @_;
163              
164             $uri = abs_url( $self->url, $uri );
165              
166             my $headers = undef;
167              
168             my $content;
169             if ( defined $perl_data ) {
170             $content = eval { json_encode($perl_data) };
171             if ($@) {
172             confess "$@:\n$perl_data";
173             }
174             $headers = [ 'Content-type' => $JSON_MIME_TYPE ];
175             }
176              
177             my $request = $self->build_http_request( $method, $uri, $headers, $content );
178              
179             my $response = $self->user_agent->request($request);
180             my $response_text = $response->content;
181              
182             my $content_data;
183             if ($response_text) {
184             $content_data = eval { json_decode($response_text) };
185             if ($@) {
186             confess "$@:\n$response_text";
187             }
188             }
189             else {
190             $content_data = undef;
191             }
192             return wantarray ? ( $content_data, $response ) : $content_data;
193             }
194              
195             sub build_http_request {
196             my $self = shift;
197             my @params = @_;
198              
199             my $request = HTTP::Request->new(@params);
200             if ( defined $self->basic_username or defined $self->basic_password ) {
201             $request->authorization_basic( $self->basic_username, $self->basic_password );
202             }
203             return $request;
204             }
205              
206             sub default_user_agent {
207             my $ua = LWP::UserAgent->new( env_proxy => 1 );
208             $ua->default_headers->push_header( 'Accept' => $JSON_MIME_TYPE );
209             return $ua;
210             }
211              
212             sub _build_schemas {
213             my $self = shift;
214              
215             my $schema_json;
216             if ( $self->schemas_file ) {
217             $schema_json = slurp( $self->schemas_file );
218             }
219             else {
220             $schema_json = $self->http_request_schemas_json;
221             }
222              
223             my $struct = eval { json_decode($schema_json) };
224             if ($@) {
225             confess "$@:\n$schema_json";
226             }
227             foreach my $schema_struct ( @{ $struct->{data} } ) {
228             my $schema = WWW::GoDaddy::REST::Resource->new_subclassed(
229             { client => $self, fields => $schema_struct } );
230             my $key = $schema->link('self');
231             WWW::GoDaddy::REST::Schema->registry_add( $schema->id => $schema );
232             WWW::GoDaddy::REST::Schema->registry_add( $key => $schema );
233             }
234              
235             return [ WWW::GoDaddy::REST::Schema->registry_list ];
236             }
237              
238             1;
239              
240             =head1 NAME
241              
242             WWW::GoDaddy::REST - Work with services conforming to the GDAPI spec
243              
244             =head1 SYNOPSIS
245              
246             use WWW::GoDaddy::REST;
247              
248             my $client = WWW::GoDaddy::REST->new({
249             url => 'https://example.com/v1',
250             basic_username => 'theuser',
251             basic_password => 'notsosecret'
252             });
253              
254             # see docs for WWW::GoDaddy::REST::Resource for more info
255             my $auto = $client->query_by_id('autos',$vehicle_id_number);
256              
257             print $auto->f('make'); # get a field
258             print $auto->f('model','S'); # set a field
259             $saved_auto = $auto->save();
260              
261             my $resource = $auto->follow_link('link_name');
262             my $resource = $auto->do_action('drive', { lat => ..., lon => ...});
263              
264             my $new = $client->create('autos', { 'make' => 'Tesla', 'model' => 'S' });
265              
266             $auto->delete();
267              
268             my @autos = $client->query('autos',{ 'make' => 'tesla' });
269              
270             =head1 DESCRIPTION
271              
272             This client makes it easy to code against a REST API that is created using
273             the Go Daddy (r) API Specification (GDAPI) L<https://github.com/godaddy/gdapi>.
274              
275             You will typically only need three pieces of information:
276             - base url of the api (this must include the version number)
277             - username
278             - password
279              
280             =head1 SEARCHING AND FILTERS
281              
282             There are two methods that deal with searching: C<query> and C<query_by_id>.
283              
284             =head2 SEARCH BY ID
285              
286             Example:
287              
288             # GET /v1/how_the_schema_defines/the_resource/url/id
289             $resource = $client->query_by_id('the_schema','the_id');
290              
291             # GET /v1/how_the_schema_defines/the_resource/url/id?other=param
292             $resource = $client->query_by_id('the_schema','the_id', { other => 'param' });
293              
294             =head2 SEARCH WITH FILTER
295              
296             Filters are hash references. The first level key is the field
297             name that you are searching on. The value of the field is an array
298             reference that has a list of hash references.
299              
300             Full Syntax Example:
301              
302             @items = $client->query( 'the_schema_name',
303             {
304             'your_field_name' => [
305             {
306             'modifier' => 'your modifier like "eq" or "ne"',
307             'value' => 'your search value'
308             },
309             {
310             #...
311             },
312             ],
313             'another_field' => ...
314             }
315             );
316              
317             Now there are shortcuts as well.
318              
319             Single Field Equality Example:
320              
321             @items = $client->query( 'the_schema_name',
322             { 'your_field_name' => 'your search value' }
323             );
324              
325             Assumed Equality Example:
326              
327             @items = $client->query( 'the_schema_name',
328             {
329             'your_field_name' => [
330             {
331             # notice the missing 'modifier' key
332             'value' => 'your search value',
333             }
334             ],
335             'another_field' => 'equality search too'
336             }
337             );
338              
339             Pass Through to query_by_id VS Search
340              
341             $resource = $client->query( 'the_schema_name', 'id' );
342              
343             =head1 ATTRIBUTES
344              
345             Attributes can be provided in the C<new> method and have corresponding
346             methods to get/set the values.
347              
348             =over 4
349              
350             =item url
351              
352             Base URL for the web service. This must include the version portion of the
353             URL as well.
354              
355             Trailing slash can be present or left out.
356              
357             Example:
358              
359             $c = WWW::GoDaddy::REST->new( {
360             url => 'https://example.com/v1'
361             } );
362              
363             =item basic_username
364              
365             The username or key you were assigned for the web service.
366              
367             Example:
368              
369             $c = WWW::GoDaddy::REST->new( {
370             url => '...',
371             basic_username => 'me',
372             basic_password => '...'
373             } );
374              
375             NOTE: not all web services authenticate using HTTP Basic Auth. In this case,
376             you will need to provide your own C<user_agent> with default headers to
377             accomplish authentication on your own.
378              
379             =item basic_password
380              
381             The password or secret you were assigned for the web service.
382              
383             Example:
384              
385             $c = WWW::GoDaddy::REST->new( {
386             url => '...',
387             basic_username => '...',
388             basic_password => 'very_secret'
389             } );
390              
391             NOTE: not all web services authenticate using HTTP Basic Auth. In this case,
392             you will need to provide your own C<user_agent> with default headers to
393             accomplish authentication on your own.
394              
395             =item user_agent
396              
397             The instance of L<LWP::UserAgent> that is used for all HTTP(S) interraction.
398              
399             This has a sane default if you do not provide an instance yourself.
400              
401             You may override this if you wish in the constructor or later on at runtime.
402              
403             See the C<default_user_agent> in L<"CLASS METHODS">.
404              
405             Example:
406              
407             $ua = LWP::UserAgent->new();
408             $ua->default_headers->push_header(
409             'Authorization' => 'MyCustom ASDFDAFFASFASFSAFSDFAS=='
410             );
411             $c = WWW::GoDaddy::REST->new({
412             url => '...',
413             user_agent => $ua
414             });
415              
416             =item schemas_file
417              
418             Optional path to a file containing the JSON for all of the schemas for this web
419             service (from the schemas collection). If you would like to avoid a round trip
420             to the server at runtime, this is the way to do it.
421              
422             Example:
423              
424             $c = WWW::GoDaddy::REST->new({
425             url => '...',
426             schemas_file => '/my/app/schema.json'
427             });
428              
429             See the GDAPI Specification for more information about schemas and collections.
430             L<https://github.com/godaddy/gdapi/blob/master/specification.md>
431              
432             =item raise_http_errors
433              
434             Boolean value that indicates whether a C<die()> will occur in the
435             event of a non successful HTTP response (4xx 5xx etc).
436              
437             It defaults to True. Set to a false value if you wish to check
438             the HTTP response code in the resultant resource on your own.
439              
440             =back
441              
442             =head1 METHODS
443              
444             =over 4
445              
446             =item query
447              
448             Search for a list of resources given a schema name and a filter.
449              
450             If the second parameter is a scalar, is assumes you are not searching but rather
451             trying to load a specific resource.
452              
453             See the documentation for C<query_by_id>.
454              
455             In scalar context, this returns a L<Collection|WWW::GoDaddy::REST::Collection>
456             object.
457              
458             In list context, this returns a list of L<Resource|WWW::GoDaddy::REST::Resource>
459             objects (or subclasses).
460              
461              
462             Example:
463              
464             @items = $client->query('schema_name',{ 'field' => 'value' });
465             $collection = $client->query('schema_name',{ 'field' => 'value' });
466              
467             See L<"SEARCHING AND FILTERS"> for more information.
468              
469             =item query_by_id
470              
471             Search for a single instance of a resource by its primary id. Optionally
472             specify a hash for additional query params to append to the resource URL.
473              
474             This returns a L<Resource|WWW::GoDaddy::REST::Resource> (or a subclass).
475              
476             Example:
477              
478             # GET /v1/how_the_schema_defines/the_resource/url/the_id
479             $resource = $client->query_by_id('the_schema','the_id');
480              
481             # GET /v1/how_the_schema_defines/the_resource/url/the_id?other=param
482             $resource = $client->query_by_id('the_schema','the_id', { other => 'param' });
483              
484             =item create
485              
486             Given a schema and a resource (or hashref), a POST will be made
487             against the collection url of the schema to create the resource.
488              
489             This returns a L<WWW::GoDaddy::REST::Resource> (or a subclass).
490              
491             Example:
492              
493             $car = $client->create('autos', { 'make' => 'Tesla', 'model' => 'S' });
494              
495             =item schema
496              
497             Given a schema name, return a L<WWW::GoDaddy::REST::Schema> object or
498             undef if it is not found.
499              
500             Example:
501              
502             $schema_resource = $client->schema('the_schema');
503              
504             =item schemas_url
505              
506             If no schema name is provided, return the schema collection url where you can
507             retrieve the collection of all schemas.
508              
509             If a schema name is provided, return the URL where you can retrieve the schema
510             with the given name.
511              
512             Example:
513              
514             $c = WWW::GoDaddy::REST->new({url => 'http://example.com/v1/'});
515             $c->schemas_url(); # http://example.com/v1/schemas/
516             $c->schemas_url('error'); # http://example.com/v1/schemas/error
517              
518             =item http_request
519              
520             Perform the HTTP request and return a hashref of the decoded JSON response.
521              
522             If this is called in list context, it returns the decoded JSON response and
523             the associated L<HTTP::Response> object.
524              
525             This takes the following parameters (similar but not the same as L<HTTP::Request>):
526             - HTTP method
527             - URL relative to the web service base C<url>
528             - Optional hashref of data to send as JSON content
529              
530             The url provided will be rooted to the base url, C<url>.
531              
532             Example:
533              
534             $c = WWW::GoDaddy::REST->new({
535             url => 'http://example.com/v1/'
536             });
537              
538             # GET http://example.com/v1/servers/Asdf
539             $data_hashref = $c->http_request('GET','/servers/Asdf')
540              
541             ($hash,$http_response) = $c->http_request('GET','/servers/Asdf');
542              
543             =item http_request_as_resource
544              
545             Perform the HTTP request and return a L<WWW::GoDaddy::REST::Resource> instance.
546              
547             This takes the following parameters (similar but not the same as L<HTTP::Request>):
548             - HTTP method
549             - URL relative to the web service base C<url>
550             - Optional hashref of data to send as JSON content
551              
552             The url provided will be rooted to the base url, C<url>.
553              
554             The url provided will be rooted to the base url, C<url>.
555              
556             Example:
557              
558             $c = WWW::GoDaddy::REST->new({
559             url => 'http://example.com/v1/'
560             });
561              
562             # GET http://example.com/v1/servers/Asdf
563             $resource = $c->http_request_as_resource('GET','/servers/Asdf')
564              
565             =item http_request_schemas_json
566              
567             Retrieve the JSON string for the schemas collection.
568              
569             Example:
570              
571             $c = WWW::GoDaddy::REST->new({
572             url => 'http://example.com/v1/'
573             });
574              
575             $schemas_json = $c->http_request_schemas_json();
576             # write this out to a file for later use
577             # with the 'schemas_file' parameter for example
578              
579              
580             =item build_http_request
581              
582             Given parameters for a L<HTTP::Request> object, return an instance
583             of this object with certain defaults filled in.
584              
585             As of this writing the defaults filled in are:
586              
587             - HTTP basic auth headers if auth is provided
588              
589             Unlike other methods such as C<http_request>, the C<url> is not rooted
590             to the base url.
591              
592             Example:
593              
594            
595             $c = WWW::GoDaddy::REST->new({
596             url => 'http://example.com/v1/'
597             });
598              
599             $request = $c->build_http_request('GET','http://example.com/v1/test');
600              
601             =back
602              
603             =head1 CLASS METHODS
604              
605             =over 4
606              
607             =item default_user_agent
608              
609             Generate a default L<LWP::UserAgent>. See C<user_agent>.
610              
611             Example:
612              
613             $ua = WWW::GoDaddy::REST->default_user_agent();
614             $ua->default_headers->push('X-Custom' => 'thing');
615             $c = WWW::GoDaddy::REST->new({
616             user_agent => $ua,
617             url => '...'
618             });
619              
620             =back
621              
622             =head1 SEE ALSO
623              
624             C<gdapi-shell> command line program.
625              
626             =head1 AUTHOR
627              
628             David Bartle, C<< <davidb@mediatemple.net> >>
629              
630             =head1 COPYRIGHT & LICENSE
631              
632             Copyright (c) 2014 Go Daddy Operating Company, LLC
633              
634             Permission is hereby granted, free of charge, to any person obtaining a
635             copy of this software and associated documentation files (the "Software"),
636             to deal in the Software without restriction, including without limitation
637             the rights to use, copy, modify, merge, publish, distribute, sublicense,
638             and/or sell copies of the Software, and to permit persons to whom the
639             Software is furnished to do so, subject to the following conditions:
640              
641             The above copyright notice and this permission notice shall be included in
642             all copies or substantial portions of the Software.
643              
644             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
645             IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
646             FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
647             THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
648             LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
649             FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
650             DEALINGS IN THE SOFTWARE.
651              
652             =cut