File Coverage

blib/lib/Net/YAR.pm
Criterion Covered Total %
statement 66 389 16.9
branch 8 208 3.8
condition 5 109 4.5
subroutine 22 68 32.3
pod 20 31 64.5
total 121 805 15.0


line stmt bran cond sub pod time code
1             package Net::YAR;
2              
3             =head1 NAME
4              
5             Net::YAR - Perl interface to the YAR (Yet Another Registrar) API
6              
7             =cut
8              
9 1     1   1098 use strict;
  1         2  
  1         38  
10 1     1   5 use Carp qw(croak confess);
  1         2  
  1         58  
11 1     1   4384 use LWP::UserAgent;
  1         107467  
  1         33  
12 1     1   11 use HTTP::Request;
  1         1  
  1         22  
13 1     1   5 use HTTP::Headers;
  1         1  
  1         31  
14 1         12975 use vars qw(
15             $AUTOLOAD
16             $VERSION
17             $JSON_ENCODE
18             $JSON_DECODE
19             $DEFAULT_RETRY_MAX
20             $DEFAULT_RETRY_INTERVAL
21 1     1   5 );
  1         1  
22              
23             $VERSION = sprintf "%d.%03d", q$Revision: 1.83 $ =~ /(\d+)/g;
24             $DEFAULT_RETRY_MAX = 3; # Retry up to this many times when network problems occur.
25             $DEFAULT_RETRY_INTERVAL = 15; # Seconds to wait after response failure before trying again.
26              
27             sub new {
28 1     1 1 353 my $class = shift;
29 1   50     10 my $args = shift || {};
30 1         12 return bless {%$args}, $class;
31             }
32              
33 1 50 33 1 1 2 sub api_user { my $self = shift; $self->{'api_user'} || $self->{'user'} || croak "Missing api_user" }
  1         256  
34 1 50 33 1 1 428 sub api_pass { my $self = shift; $self->{'api_pass'} || $self->{'pass'} || croak "Missing api_pass" }
  1         153  
35 1 50 33 1 1 371 sub api_host { my $self = shift; $self->{'api_host'} || $self->{'host'} || croak "Missing api_host" }
  1         100  
36 0 0 0 0 1 0 sub api_port { my $self = shift; $self->{'api_port'} || $self->{'port'} || ($self->use_ssl ? 443 : 80) }
  0 0       0  
37 0 0 0 0 1 0 sub api_path { my $self = shift; $self->{'api_path'} || $self->{'path'} || '/cgi/yar' }
  0         0  
38 0 0 0 0 1 0 sub use_ssl { my $self = shift; $self->{'use_ssl'} || $self->{'ssl'} || 1 }
  0         0  
39 0 0   0 1 0 sub ssl_verify_hostname { shift->{'ssl_verify_hostname'} || 0 }
40             sub log_obj {
41 0     0 1 0 my $self = shift;
42 0 0       0 if (! $self->{'log_obj'}) {
43 0 0       0 if (my $file = $self->log_file) {
44 0         0 require IO::File;
45 0 0       0 if (my $io = new IO::File ">>$file") {
46 0         0 $io->autoflush(1);
47 0         0 $self->{'log_obj'} = $io;
48             }
49             }
50             }
51 0         0 return $self->{'log_obj'};
52             }
53 0 0   0 1 0 sub log_file { shift->{'log_file'} || undef }
54              
55              
56             sub serialize_type {
57             return shift->{'serialize_type'} ||=
58 1         1406 eval { require JSON } ? 'json'
59 1         420 : eval { require YAML::Syck } ? 'yaml'
60 1         336 : eval { require YAML } ? 'yaml'
61 1         274 : eval { require XML::Simple } ? 'xml'
62 1 50 33 1 1 6 : eval { require Data::URIEncode } ? 'uri'
  1 50       1021  
    50          
    50          
    50          
63             : die "Can't find a module that can encode and decode (need one of JSON, YAML::Syck, YAML, Data::URIEncode, XML::Simple)";
64             }
65              
66             ###----------------------------------------------------------------###
67              
68             sub play_method {
69 0     0 1   my ($self, $meth, $args) = @_;
70 0   0       $args ||= {};
71              
72             ### get connection details - these die if not initialized in new
73 0           my $user = $self->api_user;
74 0           my $pass = $self->api_pass;
75 0           my $host = $self->api_host;
76 0           my $port = $self->api_port;
77 0           my $path = $self->api_path;
78              
79             ### setup the request
80 0           local $args->{'method'} = $meth;
81 0 0         die "Invalid method $meth" if $meth !~ /^[\w\.-]+$/;
82 0           my $request = eval { $self->serialize_request($args) };
  0            
83 0 0         if (! $request) {
84 0           return Net::YAR::Fault->new({
85             type => 'serialize_error',
86             method => $meth,
87             serialize_error => $@,
88             serialize_args => $args,
89             });
90             }
91              
92             ### send the request
93 0           my $resp;
94 0 0         my $proto = $self->use_ssl ? 'https' : 'http';
95 0           my $url = "$proto://$host:$port$path/$meth";
96 0           my @head;
97 0 0         if (! $args->{'authentication'}) {
98 0           my $auth = "$user/$pass";
99 0           $auth =~ s|([^\w.\-\:/])|sprintf('%%%02X', ord $1)|eg;
  0            
100 0           push @head, (Cookie => "authentication=$auth;");
101             }
102              
103 0           eval {
104 0           my $req = HTTP::Request->new('POST', $url, HTTP::Headers->new(@head), $request);
105             #warn $req->as_string;
106              
107 0           my $log_obj = $self->log_obj;
108 0 0         if ($log_obj) {
109 0   0       my $id = $args->{'domain'} || $args->{'contact_id'} || $args->{'user_id'} || "";
110 0 0         $id = join ", ", @$id if ref($id) eq 'ARRAY';
111 0           $log_obj->print(scalar(localtime).": REQUEST: $meth - $id\n",$request,"\n");
112             }
113              
114 0 0         my $lwp_args = ref($args->{'lwp_args'}) eq 'HASH' ? $args->{'lwp_args'} : ref($args->{'lwp_args'}) eq 'ARRAY' ? {@{$args->{'lwp_args'}}} : {};
  0 0          
115 0 0         local $lwp_args->{'ssl_opts'} = {} if ! $lwp_args->{'ssl_opts'};
116 0 0         local $lwp_args->{'ssl_opts'}->{'verify_hostname'} = $self->ssl_verify_hostname if ! exists $lwp_args->{'ssl_opts'}->{'verify_hostname'};
117 0 0         my $retries = defined $args->{'retry_max'} ? $args->{'retry_max'} : $DEFAULT_RETRY_MAX;
118 0 0         my $interval= defined $args->{'retry_interval'} ? $args->{'retry_interval'} : $DEFAULT_RETRY_INTERVAL;
119 0 0         $interval = 5 if $interval < 5;
120 0           while (1) {
121 0           $resp = LWP::UserAgent->new(%$lwp_args)->request($req);
122 0 0 0       last if $resp && $resp->is_success;
123 0 0         if ($retries-->0) {
124 0 0         if ($log_obj) {
125 0           $log_obj->print(scalar(localtime).": FAILED RESPONSE (".(eval { $resp->code }).") with $retries retries left:\n".(eval { $resp->content })."\n\n");
  0            
  0            
126             }
127 0           sleep $interval;
128 0           next;
129             }
130 0           last;
131             }
132              
133 0 0         if ($log_obj) {
134 0           $log_obj->print(scalar(localtime).": RESPONSE (".(eval { $resp->code })."):\n".(eval { $resp->content })."\n\n");
  0            
  0            
135             }
136             };
137 0 0 0       if (! $resp || ! $resp->is_success) {
138 0 0         return Net::YAR::Fault->new({
    0          
139             type => 'request_error',
140             method => $meth,
141             request => $request,
142             request_url => $url,
143             request_error => $@,
144             request_code => ($resp ? $resp->code : ''),
145             request_message => ($resp ? $resp->message : ''),
146             });
147             }
148              
149             ### parse the result
150 0           return $self->parse_response({
151             method => $meth,
152             content => $resp->content,
153             response => $resp,
154             request => $request,
155             });
156              
157             }
158              
159             sub serialize_request {
160 0     0 1   my ($self, $args) = @_;
161              
162             ### what type of data serialization should we use
163 0   0       my $type = $args->{'serialize'} || $args->{'serialize_type'} || $self->serialize_type;
164              
165             ### prepare the request
166 0           my $request;
167 0 0         if ($type eq 'yaml') {
    0          
    0          
    0          
168 0 0         if (eval { require YAML::Syck }) {
  0            
169 0           $request = YAML::Syck::Dump({request => $args});
170             } else {
171 0           require YAML;
172 0           $request = YAML::Dump({request => $args});
173             }
174             } elsif ($type eq 'json') {
175 0           require JSON;
176 0 0 0       $JSON_ENCODE ||= JSON->VERSION > 1.98 ? 'encode' : 'objToJSon';
177 0 0         if ($JSON_ENCODE eq 'encode') {
178 0           $request = JSON->new->encode({request => $args});
179             } else {
180 0           $request = JSON->new->objToJSon({request => $args}, {autoconv => 0});
181             }
182              
183             } elsif ($type eq 'xml') {
184 0           require XML::Simple;
185 0           $request = XML::Simple::XMLout({request => $args},
186             XMLDecl => 1,
187             KeepRoot => 1,
188             KeyAttr => [],
189             NoAttr => 1,
190             SuppressEmpty => undef,
191             GroupTags => {
192             nameservers => 'nameserver',
193             nameservers_add => 'nameserver_add',
194             nameservers_remove => 'nameserver_remove',
195             where => 'item',
196             select => 'item',
197             group_by => 'item',
198             order_by => 'item',
199             fields => 'field',
200             });
201              
202             } elsif ($type eq 'uri') {
203 0           require Data::URIEncode;
204 0           $request = Data::URIEncode::complex_to_query({request => $args});
205              
206             } else {
207 0           confess "Not sure how to encode or decode that type ($type)";
208             }
209              
210 0           return $request;
211             }
212              
213             sub parse_response {
214 0     0 1   my ($self, $args) = @_;
215              
216 0           my $content = $args->{'content'};
217              
218 0           my $response;
219 0           eval {
220 0           my $data;
221 0 0         if (!$content) {
    0          
    0          
    0          
    0          
222 0           $data = { response => { type => "error", error => { code => "empty"} } };
223             } elsif ($content =~ /\A \s* <\?xml /sx) {
224 0           require XML::Simple;
225 0           my $hash = XML::Simple::XMLin($content,
226             SuppressEmpty => '',
227             KeyAttr => [],
228             NoAttr => 1,
229             GroupTags => {
230             nameservers => 'nameserver',
231             rows => 'row',
232             tlds => 'tld',
233             fields => 'field',
234             });
235 0           foreach (qw(nameservers rows tlds)) {
236 0 0 0       next if ! $hash->{'data'} || ! $hash->{'data'}->{$_} || ref $hash->{'data'}->{$_} ne 'HASH';
      0        
237 0           $hash->{'data'}->{$_} = [$hash->{'data'}->{$_}];
238             }
239 0           $data = {response => $hash};
240              
241             } elsif ($content =~ /\A \s* \{ /sx) {
242 0           require JSON;
243 0 0 0       $JSON_DECODE ||= JSON->VERSION > 1.98 ? 'decode' : 'jsonToObj';
244 0           local $JSON::UnMapping = 1;
245 0           $data = JSON->new->$JSON_DECODE($content);
246              
247             } elsif ($content =~ /\A ---\s+ /sx) {
248 0 0         if (eval {require YAML::Syck}) {
  0            
249 0           $data = (YAML::Syck::Load($content))[0];
250             } else {
251 0           require YAML;
252 0           $data = (YAML::Load($content))[0];
253             }
254              
255             } elsif ($content =~ /\A [\w\.]+= /sx) {
256             eval {
257 0           require Data::URIEncode;
258 0 0         } or do {
259 0           require Carp;
260 0           require Data::Dumper;
261 0           Carp::confess(Data::Dumper::Dumper([$args, error => $@]));
262             };
263 0           $data = Data::URIEncode::query_to_complex($content);
264             } else {
265 0           die 'unknown_serialization';
266             }
267              
268 0   0       $response = $data->{'response'} || confess "Invalid response";
269             };
270              
271             ### store for later
272 0   0       my $obj_args = $response || {
273             type => 'parse_error',
274             parse_error => $@,
275             };
276 0           $obj_args->{'request'} = $args->{'request'};
277 0           $obj_args->{'response'} = $content;
278 0           $obj_args->{'method'} = $args->{'method'};
279              
280             ### return the appropriate object
281 0 0 0       if (! $obj_args->{'type'} || $obj_args->{'type'} eq 'error' || $obj_args->{'type'} eq 'parse_error') {
      0        
282 0           return Net::YAR::Fault->new($obj_args);
283             } else {
284 0           return Net::YAR::Response->new($obj_args);
285             }
286             }
287              
288             ###----------------------------------------------------------------###
289             ### dynamically handle all of the available YAR namespaces and methods
290              
291 0     0     sub DESTROY {}
292              
293             sub AUTOLOAD {
294 0     0     my $self = shift;
295              
296 0 0         my $method = $AUTOLOAD =~ /::([\w.]+)$/ ? $1 : '';
297              
298             ### magically add _all capability to all searches
299 0 0         if ($method =~ /^(\w+_search)_all$/) {
    0          
    0          
300 0           return $self->_all_search($1, @_);
301              
302             }
303             ### magically add _iter capability to all searches
304             elsif ($method =~ /^(\w+_search)_iter$/) {
305 0           return $self->_iter_search($1, @_);
306              
307             }
308             ### handle all $yar->domain_register style commands
309             elsif ($method =~ /^ (domain|contact|user|util|[^\W_]+) _+ (\w+) $/x) {
310 0           my $yar_method = "$1.$2";
311 0           my $failovers = eval {
312 0 0         my $fails = $_[0]->{'failover'} or die "No failover";
313 0           my $mod = ucfirst($1).ucfirst($2);
314 0           require "Net/YAR/$mod.pm";
315 0 0         if (my $default = UNIVERSAL::can("Net::YAR::$mod","lwp_args_yar_default")) {
316 0   0       $_[0]->{'lwp_args'} ||= $default->();
317             }
318 0 0         $fails = [$fails] if "ARRAY" ne ref $fails;
319 0           my @code_refs = ();
320 0           foreach my $try (@$fails) {
321 0 0         if (my $code = UNIVERSAL::can("Net::YAR::$mod",$method."_$try")) {
322 0           push @code_refs, $code;
323             }
324             }
325 0 0         return \@code_refs if @code_refs;
326 0           die "Net::Server::$mod - Unable to locate any failover for @$fails";
327             };
328              
329 0           my $resp;
330 0 0         if (eval { $resp = $self->play_method($yar_method, @_); 1 }) {
  0            
  0            
331 0 0         if (!$resp) {
332             # Normal YAR request failed.
333 0 0         if ($failovers) {
334             # Try Net::Yar::$mod->method_$try for each failover
335 0           foreach my $code (@$failovers) {
336 0 0         if (my $new_resp = eval { $code->($self, $resp, @_) }) {
  0            
337 0           $resp = $new_resp;
338 0           last;
339             }
340 0           else { warn "FAILOVER CRASHED: $@"; }
341             }
342             }
343 0           $@ = "";
344             }
345 0 0         $@ = $resp if ! $resp;
346 0           return $resp;
347             }
348              
349             ### handle the yar errors
350 0           my $err = $@;
351 0 0 0       die $err if ! UNIVERSAL::can($err, 'type') || $err->type ne 'invalid_method';
352             }
353              
354             ### die with normal invalid method error
355 0           my $pkg = ref $self;
356 0           croak "Can't locate object method \"$method\" via package \"$pkg\"";
357             }
358              
359             ###----------------------------------------------------------------###
360             ### use the standard YAR API search method's pagination features
361             ### to return all results for a given search
362              
363             sub _all_search {
364 0     0     my ($self, $meth, $args) = @_;
365              
366 0   0       my $N_ROWS = $args->{'rows_per_page'} || 10000;
367 0           my $ROWS = [];
368 0           my $RESP = Net::YAR::Response->new({method => "${meth}_all", type => 'success', data => {rows => $ROWS}});
369 0           my $begin = time;
370              
371 0 0 0       if (my $UNIQ_KEY = lc($args->{'unique_key'} || '')) {
372 0           my %UNIQ;
373 0           my $PAGE = 1;
374 0           my $N_EXTRA = 10;
375              
376             ### if we have a unique key - use extra_rows to allow for adds and deletes during the query
377 0           while (1) {
378 0           local $args->{'page'} = $PAGE;
379 0           local $args->{'rows_per_page'} = $N_ROWS;
380 0           local $args->{'extra_rows'} = $N_EXTRA;
381 0           local $args->{'return_sql'};
382              
383             ### get the current page
384 0           $RESP->data->{'n_requests'}++;
385 0           my $resp = $self->$meth($args);
386 0 0         if (! $resp) { # as soon as we get an error - return it
387 0           $resp->data->{'rows'} = $RESP->data->{'rows'};
388 0           return $resp;
389             }
390              
391 0           my $_rows = $resp->data->{'rows'};
392 0           my $i = 0;
393 0           my $found = 0;
394 0           foreach my $row (@$_rows) {
395 0 0         return Net::YAR::Fault->new({
396             method => "${meth}_all",
397             type => 'error',
398             code => 'invalid_key',
399             key => $UNIQ_KEY,
400             response => $resp,
401             }) if ! exists $row->{$UNIQ_KEY};
402              
403 0 0         if (! $UNIQ{$row->{$UNIQ_KEY}}++) {
404 0           push @$ROWS, $row; # not found yet - add it
405             } else {
406 0 0         $found++ if ++$i < $N_EXTRA;
407             }
408             }
409              
410             ### allow for us to modify what there is left to query
411 0 0         if ($PAGE == 1) {
412 0           $RESP->data->{'rows_per_page'} = $resp->data->{'rows_per_page'};
413 0           $RESP->data->{'n_rows_estimated'} = $resp->data->{'n_rows'};
414 0           $RESP->{'request'} = $resp->{'request'};
415 0           $RESP->{'response'} = "--discarded--\n";
416             }
417              
418 0 0 0       if (@$_rows && ! $found && $PAGE > 1) { # didn't find any of the extra records - go back one page
    0 0        
419 0           $PAGE -= 1;
420 0           next;
421             } elsif (@$_rows > $N_ROWS) { # had extra rows - loop to the next page
422 0           $PAGE += 1;
423 0           next;
424             } else { # all done
425 0           $RESP->data->{'n_pages'} = $PAGE;
426 0           $RESP->data->{'n_rows'} = @$ROWS;
427 0           last;
428             }
429             }
430             } else {
431 0           my $PAGE = 0;
432 0           my $N_PAGES = 1;
433 0           while (++$PAGE <= $N_PAGES) {
434 0           local $args->{'page'} = $PAGE;
435 0           local $args->{'rows_per_page'} = $N_ROWS;
436 0           local $args->{'extra_rows'};
437 0           local $args->{'return_sql'};
438              
439             ### get the current page
440 0           $RESP->data->{'n_requests'}++;
441 0           my $resp = $self->$meth($args);
442 0 0         if (! $resp) { # as soon as we get an error - return it
443 0           $resp->data->{'rows'} = $RESP->data->{'rows'};
444 0           return $resp;
445             }
446              
447 0           push @$ROWS, @{ $resp->data->{'rows'} };
  0            
448              
449             ### allow for us to modify what there is left to query
450 0 0         if ($PAGE == 1) {
451 0   0       $N_ROWS = $RESP->data->{'rows_per_page'} = $resp->data->{'rows_per_page'} || return Net::YAR::Fault->new({
452             method => "${meth}_all",
453             type => 'error',
454             code => 'missing_rows_per_page',
455             response => $resp,
456             });
457 0   0       $N_PAGES = $RESP->data->{'n_pages'} = $resp->data->{'n_pages'} || 0;
458 0           $RESP->data->{'n_rows_estimated'} = $resp->data->{'n_rows'};
459 0           $RESP->{'request'} = $resp->{'request'};
460 0           $RESP->{'response'} = "--discarded--\n";
461             }
462             }
463 0           $RESP->data->{'n_rows'} = @$ROWS;
464             }
465 0           $RESP->data->{'elapsed'} = time - $begin;
466              
467 0           return $RESP;
468             }
469              
470             ###----------------------------------------------------------------###
471             ### use the standard YAR API search method's pagination features to
472             ### obtain all results for a given search one iteration at a time
473              
474             sub _iter_search {
475 0     0     my ($self, $meth, $args) = @_;
476              
477 0   0       $args->{'rows_per_page'} ||= 10_000;
478 0   0       $args->{'page'} ||= 1;
479 0   0       $args->{'extra_rows'} ||= 10;
480 0 0         if (!$args->{'unique_key'}) {
481 0 0         if (my $o = $args->{'order_by'}) {
482 0 0         $o = [ $o ] unless ref $o eq "ARRAY";
483 0           foreach my $field (@$o) {
484 0 0         if ($field =~ /^(\w+)$/) {
485 0           $args->{'unique_key'} = $1;
486 0           last;
487             }
488             }
489             }
490             }
491 0 0         croak "Arg [unique_key] could not be determined for ITER [$meth]" if !$args->{'unique_key'};
492 0           my $response = $self->$meth($args);
493 0 0         return $response if !$response;
494 0           my %uniq = ();
495 0           my $tie_obj = undef;
496 0 0         eval {
497 0           require DB_File;
498 0           require Fcntl;
499 0   0       my $unique_hash_file = $args->{'unique_hash_file'} || "/tmp/iter_[$args->{'unique_key'}]_$$.db";
500 0 0         $tie_obj = tie(%uniq, "DB_File", $unique_hash_file, Fcntl::O_RDWR()|Fcntl::O_CREAT(), 0666) or die "$unique_hash_file: tie: $!";
501             # Anonymous file backend so it will disappear once the process dies
502             # but it's actually still on disk instead of wasting precious memory.
503 0           unlink $unique_hash_file;
504             } or warn "DB_File anonymous file tie failed: $@";
505 0           my $iter = {
506             %$response,
507             yar => $self,
508             request => $args,
509             response => $response,
510             method => $meth."_iter",
511             curr => 0,
512             i => 0,
513             tie_obj => $tie_obj,
514             uniq => \%uniq,
515             };
516 0           return bless $iter, "Net::YAR::Iter";
517             }
518              
519              
520             ###----------------------------------------------------------------###
521             ### provide older shortcuts for common util operations
522              
523 0     0 1   sub noop { shift->play_method('util.noop', @_) }
524 0     0 1   sub balance { shift->play_method('util.balance', @_) }
525              
526             ###----------------------------------------------------------------###
527             ### allow for $yar->util->noop type method calls
528              
529 0     0 0   sub contact { shift->new_chain_proxy('contact' ) }
530 0     0 0   sub csr { shift->new_chain_proxy('csr' ) }
531 0     0 0   sub domain { shift->new_chain_proxy('domain' ) }
532 0     0 0   sub domainchange { shift->new_chain_proxy('domainchange') }
533 0     0 0   sub invoice { shift->new_chain_proxy('invoice' ) }
534 0     0 1   sub offer { shift->new_chain_proxy('offer' ) }
535 0     0 1   sub order { shift->new_chain_proxy('order' ) }
536 0     0 0   sub package { shift->new_chain_proxy('package' ) }
537 0     0 0   sub service { shift->new_chain_proxy('service' ) }
538 0     0 1   sub user { shift->new_chain_proxy('user' ) }
539 0     0 0   sub util { shift->new_chain_proxy('util' ) }
540 0     0 1   sub whois { shift->new_chain_proxy('whois' ) }
541 0     0 0   sub host { shift->new_chain_proxy('host' ) }
542 0     0 0   sub dns { shift->new_chain_proxy('dns' ) }
543              
544             sub new_chain_proxy {
545 0     0 0   my ($self, $type) = @_;
546 0           return Net::YAR::_ChainProxy->new({yar => $self, type => $type});
547             }
548              
549             {
550             package Net::YAR::_ChainProxy;
551              
552 1     1   12 use strict;
  1         3  
  1         46  
553 1     1   4 use Carp qw(croak confess);
  1         3  
  1         87  
554 1     1   6 use vars qw($AUTOLOAD);
  1         2  
  1         311  
555              
556             sub new {
557 0   0 0     my $class = shift || __PACKAGE__;
558 0   0       my $args = shift || {};
559 0 0         croak "Missing yar" if ! $args->{'yar'};
560 0 0 0       croak "Missing or invalid type" if ! $args->{'type'} || $args->{'type'} !~ /^\w+$/;
561 0           return bless $args, $class;
562             }
563              
564 0     0     sub DESTROY {}
565              
566             sub AUTOLOAD {
567 0     0     my $self = shift;
568              
569 0   0       my $yar = $self->{'yar'} || croak __PACKAGE__." object modified since new - missing yar";
570 0   0       my $type = $self->{'type'} || croak __PACKAGE__." object modified since new - missing type";
571              
572 0 0         my $method = $AUTOLOAD =~ /::(\w+)$/ ? $1 : '';
573              
574 0           my $yar_method = $type .'_'. $method;
575              
576 0           return $yar->$yar_method(@_);
577             }
578             }
579              
580             ###----------------------------------------------------------------###
581             ### All returns from a YAR call should be wrapped in a Net::YAR::Response
582              
583             {
584             package Net::YAR::Response;
585              
586 1     1   10 use strict;
  1         2  
  1         26  
587 1     1   4 use Carp qw(croak confess);
  1         1  
  1         76  
588             use overload
589 0     0   0 'bool' => sub { ! shift->is_fault },
590 1         15 '""' => \&as_string,
591 1     1   5 fallback => 1;
  1         2  
592              
593             sub new {
594 0   0 0     my $class = shift || confess "Missing class";
595 0   0       my $args = shift || confess "Missing args";
596 0           my $self = bless {%$args}, $class;
597 0   0       $self->{'data'} ||= {};
598 0           return $self;
599             }
600              
601 0 0   0     sub type { shift->{'type'} || 'undefined' }
602 0 0   0     sub code { shift->{'code'} || '' }
603 0 0   0     sub time { shift->{'time'} || '' }
604 0 0   0     sub data { shift->{'data'} || {} }
605 0 0   0     sub method { shift->{'method'} || 'unknown' }
606 0 0   0     sub request { shift->{'request'} || '' }
607 0 0   0     sub response { shift->{'response'} || '' }
608             sub as_string {
609 0     0     my $self = shift;
610 0 0         return ref($self) ." ". ($self->type eq 'error' ? $self->code : $self->type) ." (called with method ".$self->method.")";
611             }
612 0     0     sub is_fault { 0 }
613             }
614              
615             {
616             package Net::YAR::Fault;
617              
618 1     1   304 use strict;
  1         2  
  1         27  
619 1     1   4 use base qw(Net::YAR::Response);
  1         1  
  1         603  
620              
621 0     0     sub is_fault { 1 }
622             }
623              
624             {
625             package Net::YAR::Iter;
626              
627 1     1   4 use strict;
  1         2  
  1         28  
628 1     1   3 use Carp qw(croak confess);
  1         2  
  1         42  
629 1     1   4 use base qw(Net::YAR::Response);
  1         1  
  1         650  
630              
631             sub next {
632 0     0     my $self = shift;
633              
634 0           my $response = $self->response;
635 0 0         if ($self->{'i'} < scalar @{ $response->data->{'rows'} }) {
  0            
636             # Still more entries left since last query
637             # so just return the next one in line.
638 0           $self->{'curr'}++;
639 0           my $result = $response->data->{'rows'}->[$self->{'i'}++];
640 0           my $key = $self->request->{'unique_key'};
641 0 0         return Net::YAR::Fault->new({
642             method => $self->method,
643             type => 'error',
644             code => 'invalid_key',
645             key => $key,
646             response => $response,
647             }) if ! exists $result->{$key};
648 0 0         $result->{$key} = "" if !defined $result->{$key};
649 0 0         if ($self->{'uniq'}->{$result->{$key}}) {
650 0           $self->{'curr'}--;
651 0           return $self->next;
652             }
653 0           $self->{'uniq'}->{$result->{$key}} = 1;
654 0           return $result;
655             }
656              
657 0 0         if ($self->{'i'} >= $self->data->{'rows_per_page'}) {
658             # Hit the end of this page, but there is probably more, so query the next page
659 0           $self->{'i'} = 0;
660 0           $self->request->{'page'}++;
661 0           my $meth = $self->method;
662 0           $response = $self->{response} = $self->{'yar'}->$meth($self->request);
663 0 0         if ($response) {
664             # Recursive call
665 0           return $self->next;
666             }
667             # Return whatever the failure is.
668 0           return $response;
669             }
670              
671             # Exhausted all rows
672 0           return Net::YAR::Fault->new({
673             method => $self->method,
674             type => 'eos',
675             code => 'eos',
676             key => 'key',
677             response => "--discarded--\n",
678             });
679             }
680              
681             }
682              
683             ###----------------------------------------------------------------###
684              
685             1;
686              
687             __END__