File Coverage

blib/lib/Metabase/Client/Simple.pm
Criterion Covered Total %
statement 53 94 56.3
branch 12 36 33.3
condition 4 14 28.5
subroutine 12 17 70.5
pod 4 4 100.0
total 85 165 51.5


line stmt bran cond sub pod time code
1 1     1   578 use 5.006;
  1         2  
2 1     1   3 use strict;
  1         1  
  1         17  
3 1     1   3 use warnings;
  1         1  
  1         40  
4              
5             package Metabase::Client::Simple;
6             # ABSTRACT: a client that submits to Metabase servers
7              
8             our $VERSION = '0.012';
9              
10 1     1   3 use JSON::MaybeXS;
  1         1  
  1         61  
11 1     1   558 use HTTP::Tiny 0.056; # can_ssl
  1         32018  
  1         27  
12 1     1   453 use URI;
  1         2923  
  1         38  
13              
14             my @valid_args;
15              
16             BEGIN {
17 1     1   3 @valid_args = qw(profile secret uri);
18              
19 1         2 for my $arg (@valid_args) {
20 1     1   4 no strict 'refs';
  1         3  
  1         40  
21 10     10   380 *$arg = sub { $_[0]->{$arg}; }
22 3         739 }
23             }
24              
25             #pod =method new
26             #pod
27             #pod my $client = Metabase::Client::Simple->new(\%arg)
28             #pod
29             #pod This is the object constructor.
30             #pod
31             #pod Valid arguments are:
32             #pod
33             #pod profile - a Metabase::User::Profile object
34             #pod secret - a Metabase::User::Secret object
35             #pod uri - the root URI for the metabase server
36             #pod
37             #pod If you use a C argument with the 'https' scheme, you must have
38             #pod L and L installed. You may also
39             #pod require L.
40             #pod
41             #pod =cut
42              
43             sub new {
44 3     3 1 4867 my ( $class, @args ) = @_;
45              
46 3         7 my $args = $class->__validate_args( \@args, { map { $_ => 1 } @valid_args } );
  9         20  
47              
48             # uri must have a trailing slash
49 3 100       12 $args->{uri} .= "/" unless substr( $args->{uri}, -1 ) eq '/';
50              
51 3         5 my $self = bless $args => $class;
52              
53 3 50       6 unless ( $self->profile->isa('Metabase::User::Profile') ) {
54 0         0 Carp::confess("'profile' argument for $class must be a Metabase::User::Profile");
55             }
56 3 50       6 unless ( $self->secret->isa('Metabase::User::Secret') ) {
57 0         0 Carp::confess("'profile' argument for $class must be a Metabase::User::secret");
58             }
59              
60 3         5 my $scheme = URI->new( $self->uri )->scheme;
61 3         6676 my ( $can_ssl, $reason ) = HTTP::Tiny::can_ssl();
62 3 50 33     33489 unless ( $scheme eq 'http' || ( $scheme eq 'https' && $can_ssl ) ) {
      66        
63 1         3 my $msg = "Scheme '$scheme' is not supported.\n";
64 1 50       17 if ( $scheme eq 'https' ) {
65 0         0 $msg .= $reason;
66             }
67 1         6 die $msg;
68             }
69              
70 2         8 return $self;
71             }
72              
73             sub _ua {
74 2     2   663 my ($self) = @_;
75 2 100       7 if ( !$self->{_ua} ) {
76             $self->{_ua} =
77 1         17 HTTP::Tiny->new( agent => __PACKAGE__ . "/" . __PACKAGE__->VERSION . " ", );
78             }
79 2         76 return $self->{_ua};
80             }
81              
82             #pod =method submit_fact
83             #pod
84             #pod $client->submit_fact($fact);
85             #pod
86             #pod This method will submit a L object to the
87             #pod client's server. On success, it will return a true value. On failure, it will
88             #pod raise an exception.
89             #pod
90             #pod =cut
91              
92             sub submit_fact {
93 0     0 1 0 my ( $self, $fact ) = @_;
94              
95 0         0 my $path = sprintf 'submit/%s', $fact->type;
96              
97 0 0       0 $fact->set_creator( $self->profile->resource )
98             unless $fact->creator;
99              
100 0         0 my $req_uri = $self->_abs_uri($path);
101              
102 0         0 my $auth = $self->_ua->_uri_escape(
103             join( ":", $self->profile->resource->guid, $self->secret->content ) );
104              
105 0         0 $req_uri->userinfo($auth);
106              
107 0         0 my @req = (
108             $req_uri,
109             {
110             headers => {
111             Content_Type => 'application/json',
112             Accept => 'application/json',
113             },
114             content => JSON::MaybeXS->new( { ascii => 1 } )->encode( $fact->as_struct ),
115             },
116             );
117              
118 0         0 my $res = $self->_ua->post(@req);
119              
120 0 0       0 if ( $res->{status} == 401 ) {
121 0 0       0 if ( $self->guid_exists( $self->profile->guid ) ) {
122 0         0 Carp::confess $self->_error( $res => "authentication failed" );
123             }
124 0         0 $self->register; # dies on failure
125             # should now be registered so try again
126 0         0 $res = $self->_ua->post(@req);
127             }
128              
129 0 0       0 unless ( $res->{success} ) {
130 0         0 Carp::confess $self->_error( $res => "fact submission failed" );
131             }
132              
133             # This will be something more informational later, like "accepted" or
134             # "queued," maybe. -- rjbs, 2009-03-30
135 0         0 return 1;
136             }
137              
138             #pod =method guid_exists
139             #pod
140             #pod $client->guid_exists('2f8519c6-24cf-11df-90b1-0018f34ec37c');
141             #pod
142             #pod This method will check whether the given GUID is found on the metabase server.
143             #pod The GUID must be in lower-case, string form. It will return true or false.
144             #pod Note that a server error will also result in a false value.
145             #pod
146             #pod =cut
147              
148             sub guid_exists {
149 0     0 1 0 my ( $self, $guid ) = @_;
150              
151 0         0 my $path = sprintf 'guid/%s', $guid;
152              
153 0         0 my $req_uri = $self->_abs_uri($path);
154              
155 0         0 my $res = $self->_ua->head($req_uri);
156              
157 0         0 return $res->{success};
158             }
159              
160             #pod =method register
161             #pod
162             #pod $client->register;
163             #pod
164             #pod This method will submit the user credentials to the metabase server. It will
165             #pod be called automatically by C if necessary. You generally won't
166             #pod need to use it. On success, it will return a true value. On failure, it will
167             #pod raise an exception.
168             #pod
169             #pod =cut
170              
171             sub register {
172 0     0 1 0 my ($self) = @_;
173              
174 0         0 my $req_uri = $self->_abs_uri('register');
175              
176 0         0 for my $type (qw/profile secret/) {
177 0 0       0 $self->$type->set_creator( $self->$type->resource )
178             unless $self->$type->creator;
179             }
180              
181 0         0 my @req = (
182             $req_uri,
183             {
184             headers => {
185             Content_Type => 'application/json',
186             Accept => 'application/json',
187             },
188             content => JSON::MaybeXS->new( { ascii => 1 } )
189             ->encode( [ $self->profile->as_struct, $self->secret->as_struct ] ),
190             }
191             );
192              
193 0         0 my $res = $self->_ua->post(@req);
194              
195 0 0       0 unless ( $res->{success} ) {
196 0         0 Carp::confess $self->_error( $res => "registration failed" );
197             }
198              
199 0         0 return 1;
200             }
201              
202             #--------------------------------------------------------------------------#
203             # private methods
204             #--------------------------------------------------------------------------#
205              
206             # Stolen from ::Fact.
207             # XXX: Should refactor this into something in Fact, which we can then rely on.
208             # -- rjbs, 2009-03-30
209             sub __validate_args {
210 3     3   5 my ( $self, $args, $spec ) = @_;
211             my $hash =
212 3 0 33     19 ( @$args == 1 and ref $args->[0] ) ? { %{ $args->[0] } }
  3 50       8  
213             : ( @$args == 0 ) ? {}
214             : {@$args};
215              
216 3         3 my @errors;
217              
218 3         7 for my $key ( keys %$hash ) {
219             push @errors, qq{unknown argument "$key" when constructing $self}
220 9 50       15 unless exists $spec->{$key};
221             }
222              
223 3         5 for my $key ( grep { $spec->{$_} } keys %$spec ) {
  9         11  
224             push @errors, qq{missing required argument "$key" when constructing $self}
225 9 50       14 unless defined $hash->{$key};
226             }
227              
228 3 50       6 Carp::confess( join qq{\n}, @errors ) if @errors;
229              
230 3         4 return $hash;
231             }
232              
233             sub _abs_uri {
234 0     0     my ( $self, $str ) = @_;
235 0           my $req_uri = URI->new($str)->abs( $self->uri );
236             }
237              
238             sub _error {
239 0     0     my ( $self, $res, $prefix ) = @_;
240 0   0       $prefix ||= "unrecognized error";
241 0 0 0       if ( ref($res) && $res->{headers}{'content-type'} eq 'application/json' ) {
242 0           my $entity = JSON::MaybeXS->new( { ascii => 1 } )->decode( $res->{content} );
243 0           return "$prefix\: $entity->{error}";
244             }
245             else {
246 0           return "$prefix\: " . $res->{reason};
247             }
248             }
249              
250             1;
251              
252             __END__