File Coverage

blib/lib/Persevere/Client.pm
Criterion Covered Total %
statement 36 150 24.0
branch 0 64 0.0
condition 0 23 0.0
subroutine 12 20 60.0
pod 7 8 87.5
total 55 265 20.7


line stmt bran cond sub pod time code
1             package Persevere::Client;
2              
3 1     1   42813 use warnings;
  1         3  
  1         27  
4 1     1   5 use strict;
  1         2  
  1         36  
5 1     1   1099 use JSON;
  1         18818  
  1         6  
6 1     1   1471 use LWP::UserAgent;
  1         58575  
  1         42  
7 1     1   13 use HTTP::Request qw(GET HEAD POST PUT DELETE);
  1         3  
  1         25  
8 1     1   6 use HTTP::Status;
  1         3  
  1         379  
9 1     1   6 use HTTP::Headers;
  1         2  
  1         43  
10 1     1   6 use HTTP::Response;
  1         2  
  1         19  
11 1     1   1132 use HTTP::Cookies;
  1         15626  
  1         64  
12 1     1   841 use Persevere::Client::Class;
  1         4  
  1         54  
13 1     1   12 use Carp qw(confess carp);
  1         2  
  1         183  
14 1     1   1691 use Encode qw(encode);
  1         16705  
  1         1627  
15              
16             =head1 NAME
17              
18             Persevere::Client - A Simple to use Interface to Persevere the JSON Database
19              
20             =head1 VERSION
21              
22             Version 0.31
23              
24             =cut
25              
26             our $VERSION = '0.31';
27              
28             sub new{
29 0     0 1   my $class = shift;
30 0 0         my %opt = @_ == 1 ? %{$_[0]} : @_;
  0            
31 0           my %self;
32 0           $self{module_version} = $VERSION;
33 0 0         if ($opt{uri}){
34 0           $self{uri} = $opt{uri};
35 0 0         $self{uri} .= '/' unless $self{uri} =~ m{/$};
36             }else{
37 0   0       $self{uri} = ($opt{scheme} || 'http') . '://' .
      0        
      0        
38             ($opt{host} || 'localhost') . ':' .
39             ($opt{port} || '8080') . '/';
40             }
41 0   0       $self{json} = ($opt{json} || JSON->new->utf8->allow_blessed);
42 0   0       $self{ua} = ($opt{ua} || LWP::UserAgent->new(agent => ($self{agent} || "Persevere::Client/$VERSION")));
43 0 0         if (defined $opt{query_timeout}){
44 0           $self{query_timeout} = $opt{query_timeout};
45             }else{
46 0           $self{query_timeout} = 30;
47             }
48             # Throw this in an eval so other ua's don't croak here?
49 0           $self{ua}->timeout($self{query_timeout});
50 0 0         if (defined $opt{defaultSourceClass}){
51 0           $self{defaultSourceClass} = $opt{defaultSourceClass};
52             }
53              
54 0   0       $self{auth_type} = ($opt{auth_type} || "basic");
55 0 0 0       if (!( ($self{auth_type} eq "json-rpc") || ($self{auth_type} eq "basic") || ($self{auth_type} eq "none") )){
    0          
56 0           confess "Invalid auth type. Choices are json-rpc, basic, or none";
57             }elsif (!($self{auth_type} eq "none")){
58 0   0       $self{username} = $opt{username} || confess "A username must be provided if auth_type is not set to none";
59 0   0       $self{password} = $opt{password} || confess "A password must be provided if auth_type is not set to none";
60 0 0         if ($self{auth_type} eq "json-rpc"){
    0          
61             # Not Implemented yet
62             # $self{ua}->cookie_jar(HTTP::Cookies->new);
63             # my $auth_string = '{"method":"authenticate", "params":[ "' . $self{username} . '":"' . $self{password} . '"], "id":"call0"}';
64             # my $authin = $self{ua}->(HTTP::Request->new(POST, $self{uri} . "/Class/User", undef, $auth_string ));
65             # my $authin = $self{req}->('POST', $self{uri} . "/Class/User", undef, $auth_string);
66             # print $authin->{status_line} . "\n";
67             }elsif ($self{auth_type} eq "basic"){
68 0           $self{ua}->default_headers->authorization_basic($self{username}, $self{password});
69             }
70             }
71              
72 0           $self{ua}->default_headers->push_header('Accept' => "application/json");
73              
74 0 0         if (defined $opt{debug}){
75 0           $self{debug} = $opt{debug};
76             }else{
77 0           $self{debug} = 0;
78             }
79            
80 0 0         if (defined $opt{showwarnings}){
81 0           $self{showwarnings} = $opt{showwarnings};
82             }else{
83 0           $self{showwarnings} = 1;
84             }
85              
86 0 0         if (defined $opt{exist_is_error}){
87 0           $self{exist_is_error} = $opt{exist_is_error};
88             }else{
89 0           $self{exist_is_error} = 0;
90             }
91              
92 0           return bless \%self, $class;
93             }
94              
95             sub testConnection{
96 0     0 1   my $self = shift;
97 0           my $testpath = $self->{uri} . "status";
98 0           my $testresponse = $self->req('GET', $testpath, undef, undef, 1);
99 0 0         if (!($testresponse->{success})){
100 0           return 0;
101             }else{
102 0           return 1;
103             }
104             }
105              
106             sub serverInfo{
107 0     0 1   my $self = shift;
108 0           my $inforesponse = $self->req('GET', "$self->{uri}status", undef, undef, 1);
109 0 0         if ($self->testConnection){
110 0           return $inforesponse;
111             }
112             }
113              
114             sub classExists{
115 0     0 1   my $self = shift;
116 0           my $ClassName = shift;
117 0 0         if (!(defined $ClassName)){
118 0           $self->alert("No class passed to classExists, classExists requires a class name to properly function");
119             }
120 0 0         if ($self->{debug}){
121 0           print "DEBUG (FUNCTION classExists): GET $self->{uri}Class/$ClassName\n";
122             }
123 0           my $classresponse = $self->req('GET', "$self->{uri}Class/$ClassName", undef, undef, 1);
124 0 0         if ($classresponse->{success}){
125 0           return 1;
126             }else{
127 0           return 0;
128             }
129             }
130             # ***** Warning *****
131             # this does not represent how the user interface will behave once implemented
132             # These are just personal notes
133             # ***** Warning *****
134             #sub newUser{
135             # my $self = shift;
136             # my $user = shift;
137             # my $pass = shift;
138             # my $userresponse = $self->req('POST', "$self->{uri}Class/User", undef,
139             # '{"method":"createUser","id":"register","params":["' . $user . '","' . $pass . '"]}');
140             # if ($userresponse->{code} == 204){
141             # return 0;
142             # }else{
143             # if ($self->{debug}){
144             # carp $userresponse->{status_line};
145             # }
146             # return 1;
147             # }
148             #}
149              
150             sub listClassNames{
151 0     0 1   my $self = shift;
152 0           my @classlist;
153 0           my $classresponse = $self->req('GET', "$self->{uri}Class/");
154 0 0         if ($self->{debug}){
155 0           print "DEBUG (FUNCTION listClassNames): GET $self->{uri}Class/\n";
156             }
157 0           my @allclasses = $classresponse->{data};
158 0           my @inside = @{$allclasses[0]};
  0            
159 0           foreach my $item (@inside){
160 0 0         if (defined $item->{core}){
161 0 0         if ($item->{core} == 1){
162 0           next;
163             }else{
164 0           push @classlist, $item->{id};
165             }
166             }else{
167 0           push @classlist, $item->{id};
168             }
169             }
170 0           $classresponse->{data} = \@classlist;
171 0           return $classresponse;
172             }
173              
174             sub req{
175 0     0 1   my $self = shift;
176 0           my $meth = shift;
177 0           my $path = shift;
178 0           my $header = shift;
179 0           my $cont = shift;
180 0           my $nowarn = shift;
181 0           my $noencode = shift;
182 0           my $content;
183 0 0         if (!(defined $nowarn)){
184 0           $nowarn = 0;
185             }
186 0 0         if (!(defined $noencode)){
187 0           $noencode = 0;
188             }
189 0 0         if ($noencode){
    0          
190 0           $content = $cont;
191             }elsif (ref $cont){
192 0           $content = encode('utf-8', $self->{json}->encode($cont));
193             }
194 0           my $dheader; # debug header
195 0 0         if (!(defined $header)){
196 0           $dheader = "";
197             }
198 0 0         if (!(defined $content)){
199 0           $content = "";
200             }
201             # if ($self->{debug}){
202             # print "DEBUG (FUNCTION req): Method: $meth Path: $path Header: $dheader Content: $content NoWarn: $nowarn NoEncode: $noencode\n";
203             # }
204            
205 0           my $res = $self->{ua}->request(HTTP::Request->new($meth, $path, $header, $content));
206 0           my $query = "$meth, $path, $dheader, $content";
207 0           my $auth_status;
208 0 0         if ($res->code == 401){
209 0           $auth_status = 0;
210             }else{
211 0           $auth_status = 1;
212             }
213 0           my $ret = {
214             code => $res->code,
215             status_line => $res->status_line,
216             success => 0,
217             content => $res->content,
218             auth => $auth_status,
219             query => $query
220             };
221 0 0         if ($res->is_success){
222 0           $ret->{success} = 1;
223 0 0         if (!($noencode)){
224 0           $ret->{data} = $self->{json}->decode($res->content);
225             }else{
226 0           $ret->{data} = $res->content;
227             }
228 0 0         $ret->{range} = $res->header('Content-Range') if (defined $res->header('Content-Range'));
229             }else{
230 0 0         if (!($nowarn)){
231 0           $self->alert($res->content);
232             }
233             }
234 0           return $ret;
235             }
236              
237             sub alert {
238 0     0 0   my $self = shift;
239 0           my @message = @_;
240 0 0         if ($self->{showwarnings}){
241 0           carp @message;
242             }
243             }
244              
245             sub class{
246 0     0 1   my $self = shift;
247 0           my $ClassName = shift;
248 0           return Persevere::Client::Class->new(name => $ClassName, client => $self);
249             }
250              
251             =head1 SYNOPSIS
252              
253             This module Is a simple interface to Persevere, the JSON Database.
254              
255             This module provides an interface similar to that of Couchdb::Client
256              
257             View documentation on Persevere::Client::Class for information on how
258             to interact with Persevere Classes.
259              
260             use Persevere::Client;
261              
262             my $persvr = Persevere::Client->new(
263             host => "localhost",
264             port => "8080",
265             auth_type => "basic",
266             username => "user",
267             password => "pass"
268             );
269              
270             die "Unable to connect to $persvr->{uri}\n" if !($persvr->testConnection);
271             my $status;
272             my $statusreq = $persvr->serverInfo;
273             if ($statusreq->{success}){
274             $status = $statusreq->{data};
275             }
276             print "VM: $status->{vm}\nVersion: $status->{version}\n";
277             print "Class File Exists\n" if $persvr->classExists("File");
278             print "Class Garbage Doesn't Exist\n" if (!($persvr->classExists("garbage")));
279             my @class_list;
280             my $classreq = $persvr->listClassNames;
281             if ($classreq->{success}){
282             @class_list = @{$classreq->{data}};
283             }
284              
285             =head1 MEATHODS
286              
287             =over 8
288              
289             =item new
290              
291             Constructor
292              
293             uri - Takes a hash or hashref of options: uri which specifies the server's URI; scheme, host, port which are used if uri isn't provided and default to 'http', 'localhost', and '8080' respectively;
294              
295             json - which defaults to a JSON object with utf8 and allow_blessed turned on but can be replaced with anything with the same interface;
296              
297             ua - which is a LWP::UserAgent object and can also be replaced.
298              
299             agent - Replace the name the defaut LWP::UserAgent reports to the db when it crud's
300              
301             debug - boolean, defaults to false, set to 1 to enable debug messages (show's crud sent to persevere).
302              
303             auth_type - can be set to basic, json-rpc, or none, basic is default, and throws an error without a username and password. json-rpc auth is not yet implemented.
304              
305             query_timeout - how long to wait until timing out on a request, defaults to 30.
306              
307             exist_is_error - return an error if a class we try and create already exists
308              
309             showwarnings - carp warning messages
310              
311             =item testConnection
312              
313             Returns true if a connection can be made to the server, false otherwise.
314              
315             =item req
316              
317             All requests made to the server that do not have a boolean response return a req hash.
318             All req hashes contain:
319             code - http status code
320             status_line - http status_line (this is what you use to debug why a request failed)
321             success - false for failure, true for success
322             content - content of the request
323             auth - false if authentication failed for the query, true if authentication succeeded
324            
325             Successful requests contain:
326             data - decoded json data, when assigning this to a variable its type must be declared. most data will be arrays, with the exception of status.
327             Example:
328             my $postreq = $initialclass->createObjects(\@post_data);
329             if ($postreq->{success}){
330             foreach (@{$postreq->{data}}){
331             print "$_\n";
332             }
333             }else{
334             warn "unable to post data";
335             }
336              
337             range - if applicable returns the range header information for the request.
338              
339             using req hashes provides a uniform approach to dealing with error handling for auth, and failed requests.
340              
341             =item serverInfo
342              
343             Returns a req hash, server metadata is contained in {data}, and is typically something that looks like { id => "status", version => "1.0 beta 2" ... }. It throws an warning if it can't connect.
344              
345             =item classExists
346              
347             Returns true if a class of that name exists, false otherwise.
348              
349             =item listClassNames
350              
351             Returns an req hash, with {data} containing all non core class names that the server knows of.
352              
353             =item class
354              
355             Returns a new Persevere::Client::Class object for a class of that name. Note that the Class does not need to exist yet, and will not be created if it doesn't. The create method will create the class, and is documented in Persevere::Client::Class
356              
357             =back
358              
359             =head1 AUTHOR
360              
361             Nathanael Anderson, C<< >>
362              
363             =head1 BUGS
364              
365             Please report any bugs or feature requests to C, or through
366             the web interface at L. I will be notified, and then you'll
367             automatically be notified of progress on your bug as I make changes.
368              
369             =head1 SUPPORT
370              
371             You can find documentation for this module with the perldoc command.
372              
373             perldoc Persevere::Client
374              
375              
376             You can also look for information at:
377              
378             =over 4
379              
380             =item * RT: CPAN's request tracker
381              
382             L
383              
384             =item * AnnoCPAN: Annotated CPAN documentation
385              
386             L
387              
388             =item * CPAN Ratings
389              
390             L
391              
392             =item * Search CPAN
393              
394             L
395              
396             =back
397              
398              
399             =head1 ACKNOWLEDGEMENTS
400              
401             Thanks to mst in #perl-help on irc.perl.org for looking over the code, and providing feedback
402              
403             =head1 COPYRIGHT & LICENSE
404              
405             Copyright 2009-2011 Nathanael Anderson.
406              
407             This program is free software; you can redistribute it and/or modify it
408             under the same terms as Perl itself.
409              
410              
411             =cut
412              
413             1; # End of Persevere::Client