File Coverage

blib/lib/Metabase/Client/Simple.pm
Criterion Covered Total %
statement 59 100 59.0
branch 13 38 34.2
condition 1 8 12.5
subroutine 14 19 73.6
pod 4 4 100.0
total 91 169 53.8


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