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   819 use 5.006;
  1         6  
  1         41  
2 1     1   5 use strict;
  1         2  
  1         30  
3 1     1   5 use warnings;
  1         1  
  1         45  
4              
5             package Metabase::Client::Simple;
6             # ABSTRACT: a client that submits to Metabase servers
7             our $VERSION = '0.009'; # VERSION
8              
9 1     1   971 use HTTP::Status qw/:constants/;
  1         4961  
  1         663  
10 1     1   14500 use HTTP::Request::Common ();
  1         75334  
  1         35  
11 1     1   13 use JSON 2 ();
  1         30  
  1         25  
12 1     1   1469 use LWP::UserAgent 5.54 (); # keep_alive
  1         46262  
  1         43  
13 1     1   14 use URI;
  1         1  
  1         61  
14              
15             my @valid_args;
16             BEGIN {
17 1     1   3 @valid_args = qw(profile secret uri);
18              
19 1         3 for my $arg (@valid_args) {
20 1     1   8 no strict 'refs';
  1         2  
  1         64  
21 10     10   664 *$arg = sub { $_[0]->{$arg}; }
22 3         2495 }
23             }
24              
25              
26             sub new {
27 3     3 1 15756 my ($class, @args) = @_;
28              
29 9         38 my $args = $class->__validate_args(
30             \@args,
31 3         11 { map { $_ => 1 } @valid_args }
32             );
33              
34             # uri must have a trailing slash
35 3 100       19 $args->{uri} .= "/" unless substr($args->{uri}, -1) eq '/';
36              
37 3         9 my $self = bless $args => $class;
38              
39 3 50       11 unless ( $self->profile->isa('Metabase::User::Profile') ) {
40 0         0 Carp::confess( "'profile' argument for $class must be a Metabase::User::Profile" );
41             }
42 3 50       12 unless ( $self->secret->isa('Metabase::User::Secret') ) {
43 0         0 Carp::confess( "'profile' argument for $class must be a Metabase::User::secret" );
44             }
45              
46 3         12 my $scheme = URI->new( $self->uri )->scheme;
47 3 100       24653 unless ( $self->_ua->is_protocol_supported( $scheme ) ) {
48 1         589 my $msg = "Scheme '$scheme' is not supported by your LWP::UserAgent.\n";
49 1 50       5 if ( $scheme eq 'https' ) {
50 0         0 $msg .= "You must install Crypt::SSLeay or IO::Socket::SSL or use http instead.\n";
51             }
52 1         9 die $msg;
53             }
54              
55 2         110020 return $self;
56             }
57              
58             sub _ua {
59 5     5   1170 my ($self) = @_;
60 5 100       24 if ( ! $self->{_ua} ) {
61 3         92 $self->{_ua} = LWP::UserAgent->new(
62             agent => __PACKAGE__ . "/" . __PACKAGE__->VERSION . " ",
63             env_proxy => 1,
64             keep_alive => 5,
65             );
66             }
67 5         65958 return $self->{_ua};
68             }
69              
70              
71             sub submit_fact {
72 0     0 1 0 my ($self, $fact) = @_;
73              
74 0         0 my $path = sprintf 'submit/%s', $fact->type;
75              
76 0 0       0 $fact->set_creator($self->profile->resource)
77             unless $fact->creator;
78              
79 0         0 my $req_uri = $self->_abs_uri($path);
80              
81 0         0 my $req = HTTP::Request::Common::POST(
82             $req_uri,
83             Content_Type => 'application/json',
84             Accept => 'application/json',
85             Content => JSON->new->ascii->encode($fact->as_struct),
86             );
87 0         0 $req->authorization_basic($self->profile->resource->guid, $self->secret->content);
88              
89 0         0 my $res = $self->_ua->request($req);
90              
91 0 0       0 if ($res->code == HTTP_UNAUTHORIZED) {
92 0 0       0 if ( $self->guid_exists( $self->profile->guid ) ) {
93 0         0 Carp::confess $self->_error( $res => "authentication failed" );
94             }
95 0         0 $self->register; # dies on failure
96             # should now be registered so try again
97 0         0 $res = $self->_ua->request($req);
98             }
99              
100 0 0       0 unless ( $res->is_success ) {
101 0         0 Carp::confess $self->_error( $res => "fact submission failed" );
102             }
103              
104             # This wil be something more informational later, like "accepted" or
105             # "queued," maybe. -- rjbs, 2009-03-30
106 0         0 return 1;
107             }
108              
109              
110             sub guid_exists {
111 0     0 1 0 my ($self, $guid) = @_;
112              
113 0         0 my $path = sprintf 'guid/%s', $guid;
114              
115 0         0 my $req_uri = $self->_abs_uri($path);
116              
117 0         0 my $req = HTTP::Request::Common::HEAD( $req_uri );
118              
119 0         0 my $res = $self->_ua->request($req);
120              
121 0 0       0 return $res->is_success ? 1 : 0;
122             }
123              
124              
125             sub register {
126 0     0 1 0 my ($self) = @_;
127              
128 0         0 my $req_uri = $self->_abs_uri('register');
129              
130 0         0 for my $type ( qw/profile secret/ ) {
131 0 0       0 $self->$type->set_creator( $self->$type->resource)
132             unless $self->$type->creator;
133             }
134              
135 0         0 my $req = HTTP::Request::Common::POST(
136             $req_uri,
137             Content_Type => 'application/json',
138             Accept => 'application/json',
139             Content => JSON->new->ascii->encode([
140             $self->profile->as_struct, $self->secret->as_struct
141             ]),
142             );
143              
144 0         0 my $res = $self->_ua->request($req);
145              
146 0 0       0 unless ($res->is_success) {
147 0         0 Carp::confess $self->_error( $res => "registration failed" );
148             }
149              
150 0         0 return 1;
151             }
152              
153             #--------------------------------------------------------------------------#
154             # private methods
155             #--------------------------------------------------------------------------#
156              
157             # Stolen from ::Fact.
158             # XXX: Should refactor this into something in Fact, which we can then rely on.
159             # -- rjbs, 2009-03-30
160             sub __validate_args {
161 3     3   7 my ($self, $args, $spec) = @_;
162 3 0 33     32 my $hash = (@$args == 1 and ref $args->[0]) ? { %{ $args->[0] } }
  3 50       15  
163             : (@$args == 0) ? { }
164             : { @$args };
165              
166 3         6 my @errors;
167              
168 3         11 for my $key (keys %$hash) {
169 9 50       27 push @errors, qq{unknown argument "$key" when constructing $self}
170             unless exists $spec->{ $key };
171             }
172              
173 3         10 for my $key (grep { $spec->{ $_ } } keys %$spec) {
  9         21  
174 9 50       27 push @errors, qq{missing required argument "$key" when constructing $self}
175             unless defined $hash->{ $key };
176             }
177              
178 3 50       10 Carp::confess(join qq{\n}, @errors) if @errors;
179              
180 3         9 return $hash;
181             }
182              
183             sub _abs_uri {
184 0     0     my ($self, $str) = @_;
185 0           my $req_uri = URI->new($str)->abs($self->uri);
186             }
187              
188             sub _error {
189 0     0     my ($self, $res, $prefix) = @_;
190 0   0       $prefix ||= "unrecognized error";
191 0 0 0       if ( ref($res) && $res->header('Content-Type') eq 'application/json') {
192 0           my $entity = JSON->new->ascii->decode($res->content);
193 0           return "$prefix\: $entity->{error}";
194             } else {
195 0           return "$prefix\: " . $res->message;
196             }
197             }
198              
199             1;
200              
201              
202              
203             =pod
204              
205             =head1 NAME
206              
207             Metabase::Client::Simple - a client that submits to Metabase servers
208              
209             =head1 VERSION
210              
211             version 0.009
212              
213             =head1 SYNOPSIS
214              
215             use Metabase::Client::Simple;
216             use Metabase::User::Profile;
217             use Metabase::User::Secret;
218              
219             my $profile = Metabase::User::Profile->load('user.profile.json');
220             my $secret = Metabase::User::Secret ->load('user.secret.json' );
221              
222             my $client = Metabase::Client::Simple->new({
223             profile => $profile,
224             secret => $secret,
225             uri => 'http://metabase.example.com/',
226             });
227              
228             my $fact = generate_metabase_fact;
229              
230             $client->submit_fact($fact);
231              
232             =head1 DESCRIPTION
233              
234             Metabase::Client::Simple provides is extremely simple, lightweight library for
235             submitting facts to a L web server.
236              
237             =head1 METHODS
238              
239             =head2 new
240              
241             my $client = Metabase::Client::Simple->new(\%arg)
242              
243             This is the object constructor.
244              
245             Valid arguments are:
246              
247             profile - a Metabase::User::Profile object
248             secret - a Metabase::User::Secret object
249             uri - the root URI for the metabase server
250              
251             If you use a C argument with the 'https' scheme, you must have
252             L installed.
253              
254             =head2 submit_fact
255              
256             $client->submit_fact($fact);
257              
258             This method will submit a L object to the
259             client's server. On success, it will return a true value. On failure, it will
260             raise an exception.
261              
262             =head2 guid_exists
263              
264             $client->guid_exists('2f8519c6-24cf-11df-90b1-0018f34ec37c');
265              
266             This method will check whether the given GUID is found on the metabase server.
267             The GUID must be in lower-case, string form. It will return true or false.
268             Note that a server error will also result in a false value.
269              
270             =head2 register
271              
272             $client->register;
273              
274             This method will submit the user credentials to the metabase server. It will
275             be called automatically by C if necessary. You generally won't
276             need to use it. On success, it will return a true value. On failure, it will
277             raise an exception.
278              
279             =for Pod::Coverage profile secret uri
280              
281             =for :stopwords cpan testmatrix url annocpan anno bugtracker rt cpants kwalitee diff irc mailto metadata placeholders metacpan
282              
283             =head1 SUPPORT
284              
285             =head2 Bugs / Feature Requests
286              
287             Please report any bugs or feature requests through the issue tracker
288             at L.
289             You will be notified automatically of any progress on your issue.
290              
291             =head2 Source Code
292              
293             This is open source software. The code repository is available for
294             public review and contribution under the terms of the license.
295              
296             L
297              
298             git clone https://github.com/dagolden/metabase-client-simple.git
299              
300             =head1 AUTHORS
301              
302             =over 4
303              
304             =item *
305              
306             David Golden
307              
308             =item *
309              
310             Ricardo Signes
311              
312             =back
313              
314             =head1 COPYRIGHT AND LICENSE
315              
316             This software is Copyright (c) 2012 by David Golden.
317              
318             This is free software, licensed under:
319              
320             The Apache License, Version 2.0, January 2004
321              
322             =cut
323              
324              
325             __END__