File Coverage

blib/lib/AnyEvent/CouchDB.pm
Criterion Covered Total %
statement 36 108 33.3
branch 0 26 0.0
condition 0 10 0.0
subroutine 12 25 48.0
pod 8 9 88.8
total 56 178 31.4


line stmt bran cond sub pod time code
1             package AnyEvent::CouchDB;
2              
3 2     2   90602 use strict;
  2         5  
  2         93  
4 2     2   12 use warnings;
  2         4  
  2         90  
5             our $VERSION = '1.31';
6              
7 2     2   20003 use JSON;
  2         84001  
  2         12  
8 2     2   2610 use AnyEvent::HTTP;
  2         119579  
  2         223  
9 2     2   1521 use AnyEvent::CouchDB::Database;
  2         8  
  2         75  
10 2     2   98 use AnyEvent::CouchDB::Exceptions;
  2         3  
  2         108  
11 2     2   14878 use URI;
  2         7184  
  2         72  
12 2     2   18 use URI::Escape;
  2         4  
  2         161  
13 2     2   15 use File::Basename;
  2         5  
  2         196  
14 2     2   13 use MIME::Base64;
  2         3  
  2         99  
15              
16 2     2   13 use Exporter;
  2         4  
  2         77  
17 2     2   13 use base 'Exporter';
  2         4  
  2         4210  
18              
19             our @EXPORT = qw(couch couchdb);
20              
21             # exception class shortcuts
22             our $HTTPError = "AnyEvent::CouchDB::Exception::HTTPError";
23             our $JSONError = "AnyEvent::CouchDB::Exception::JSONError";
24              
25             # default JSON encoder
26             our $default_json = JSON->new->allow_nonref->utf8;
27              
28             # arbitrary uri support
29             sub _build_headers {
30 0     0     my ( $self, $options ) = @_;
31 0           my $headers = $options->{headers};
32 0 0         if ( ref($headers) ne 'HASH' ) {
33 0           $headers = {};
34             }
35              
36             # should probably move $options->{type} to $options->{headers}
37 0 0         if ( exists $options->{type} ) {
    0          
38 0           $headers->{'Content-Type'} = $options->{type};
39             }
40             elsif ( !exists $headers->{'Content-Type'} ) {
41 0           $headers->{'Content-Type'} = 'application/json';
42             }
43              
44 0 0         if ( exists $self->{http_auth} ) {
45 0           $headers->{'Authorization'} = $self->{http_auth};
46             }
47              
48 0           return $headers;
49             }
50              
51             # return a condvar and callback
52             #
53             # - The condvar is what most of our methods return.
54             # You can call recv on them to get data back, or
55             # you can call cb on them to assign an asynchronous callback to
56             # run WHEN the data comes back
57             #
58             # - The callback is the code that handles the
59             # generic part of every CouchDB response. This is given
60             # to AnyEvent::HTTP.
61             #
62             sub cvcb {
63 0     0 0   my ($options, $status, $json) = @_;
64 0   0       $status ||= 200;
65 0   0       $json ||= $default_json;
66 0           my $cv = AE::cv;
67 0           AE::now_update();
68              
69             # default success handler sends back decoded json response
70             my $success = sub {
71 0     0     my ($resp) = @_;
72 0 0         $options->{success}->(@_) if ($options->{success});
73 0           $cv->send($resp);
74 0           };
75              
76             # default error handler croaks w/ http headers and response
77             my $error = sub {
78 0     0     my ($headers, $response) = @_;
79 0 0         $options->{error}->(@_) if ($options->{error});
80 0           $cv->croak(
81             $HTTPError->new(
82             message => sprintf("%s - %s - %s", $headers->{Status}, $headers->{Reason}, $headers->{URL}),
83             headers => $headers,
84             body => $response
85             )
86             );
87 0           };
88              
89             my $cb = sub {
90 0     0     my ($body, $headers) = @_;
91 0           my $response;
92 0           eval { $response = $json->decode($body); };
  0            
93 0 0         $cv->croak(
94             $JSONError->new(
95             message => $@,
96             headers => $headers,
97             body => $body
98             )
99             ) if ($@);
100 0 0 0       if ($headers->{Status} >= $status and $headers->{Status} < 400) {
101 0           $success->($response);
102             } else {
103 0           $error->($headers, $body);
104             }
105 0           };
106 0           ($cv, $cb);
107             }
108              
109             sub couch {
110 0     0 1   AnyEvent::CouchDB->new(@_);
111             }
112              
113             sub couchdb {
114 0     0 1   my $db = shift;
115 0 0         if ($db =~ /^https?:/) {
116 0 0         $db .= '/' if ($db !~ /\/$/);
117 0           my $uri = URI->new($db);
118 0           my $name = basename($db);
119 0           AnyEvent::CouchDB::Database->new($name, $uri);
120             } else {
121 0           AnyEvent::CouchDB->new->db($db);
122             }
123             }
124              
125             sub new {
126 0     0 1   my ($class, $uri) = @_;
127 0   0       $uri ||= 'http://localhost:5984/';
128 0           my $self = bless { uri => URI->new($uri) } => $class;
129 0 0         if (my $userinfo = $self->{uri}->userinfo) {
130 0           my $auth = encode_base64($userinfo, '');
131 0           $self->{http_auth} = "Basic $auth";
132             }
133 0           return $self;
134             }
135              
136             sub all_dbs {
137 0     0 1   my ($self, $options) = @_;
138 0           my ($cv, $cb) = cvcb($options);
139 0           http_request(
140             GET => $self->{uri}.'_all_dbs',
141             headers => $self->_build_headers($options),
142             $cb
143             );
144 0           $cv;
145             }
146              
147             sub db {
148 0     0 1   my ($self, $name) = @_;
149 0           my $uri = $self->{uri}->clone;
150 0 0         $uri->path(($uri->path ? $uri->path . $name : $name) . "/");
151 0           AnyEvent::CouchDB::Database->new($name, $uri);
152             }
153              
154             sub info {
155 0     0 1   my ($self, $options) = @_;
156 0           my ($cv, $cb) = cvcb($options);
157 0           http_request(
158             GET => $self->{uri}->as_string,
159             headers => $self->_build_headers($options),
160             $cb
161             );
162 0           $cv;
163             }
164              
165             sub config {
166 0     0 1   my ($self, $options) = @_;
167 0           my ($cv, $cb) = cvcb($options);
168 0           http_request(
169             GET => $self->{uri} . '_config',
170             headers => $self->_build_headers($options),
171             $cb
172             );
173 0           $cv;
174             }
175              
176             sub replicate {
177 0     0 1   my ($self, $source, $target, $options) = @_;
178 0           my ($cv, $cb) = cvcb($options);
179 0           my $replication = {source => $source, target => $target};
180 0 0         if (my $continuous = delete $options->{continuous}) {
181 0           $replication->{continuous} = 1;
182             }
183 0           my $body = $default_json->encode($replication);
184 0           http_request(
185             POST => $self->{uri}.'_replicate',
186             headers => $self->_build_headers($options),
187             body => $body,
188             $cb
189             );
190 0           $cv;
191             }
192              
193             1;
194              
195             __END__