File Coverage

blib/lib/Finance/CaVirtex/API.pm
Criterion Covered Total %
statement 165 205 80.4
branch 10 30 33.3
condition 5 12 41.6
subroutine 53 65 81.5
pod 5 34 14.7
total 238 346 68.7


line stmt bran cond sub pod time code
1             package Finance::CaVirtex::API;
2              
3 1     1   26223 use 5.014002;
  1         4  
  1         37  
4 1     1   5 use strict;
  1         2  
  1         27  
5 1     1   6 use warnings;
  1         7  
  1         49  
6              
7             our $VERSION = '0.03';
8              
9 1     1   5 use base qw(Finance::CaVirtex::API::DefaultPackage);
  1         2  
  1         569  
10              
11 1     1   6 use constant DEBUG => 0;
  1         2  
  1         48  
12              
13             # you can use a lower version, but then you are responsible for SSL cert verification code...
14 1     1   1088 use LWP::UserAgent 6;
  1         65212  
  1         35  
15 1     1   10 use URI;
  1         2  
  1         23  
16 1     1   6268 use CGI;
  1         16962  
  1         8  
17 1     1   2013 use JSON;
  1         18756  
  1         6  
18 1     1   923 use MIME::Base64;
  1         756  
  1         66  
19 1     1   864 use Time::HiRes qw(gettimeofday);
  1         2032  
  1         4  
20 1     1   1087 use Digest::SHA qw(hmac_sha256_hex);
  1         4270  
  1         160  
21 1     1   982 use Data::Dumper;
  1         7634  
  1         96  
22              
23 1     1   636 use Finance::CaVirtex::API::Request::OrderBook;
  1         3  
  1         30  
24 1     1   572 use Finance::CaVirtex::API::Request::TradeBook;
  1         3  
  1         30  
25 1     1   688 use Finance::CaVirtex::API::Request::Ticker;
  1         3  
  1         64  
26 1     1   607 use Finance::CaVirtex::API::Request::Balance;
  1         2  
  1         25  
27 1     1   488 use Finance::CaVirtex::API::Request::Transactions;
  1         3  
  1         21  
28 1     1   915 use Finance::CaVirtex::API::Request::TradeHistory;
  1         3  
  1         19  
29 1     1   495 use Finance::CaVirtex::API::Request::OrderHistory;
  1         3  
  1         20  
30 1     1   603 use Finance::CaVirtex::API::Request::Order;
  1         2  
  1         22  
31 1     1   524 use Finance::CaVirtex::API::Request::OrderCancel;
  1         2  
  1         25  
32 1     1   541 use Finance::CaVirtex::API::Request::Withdraw;
  1         3  
  1         31  
33              
34 1     1   5 use constant COMPANY => 'CaVirtex';
  1         2  
  1         41  
35 1     1   5 use constant ERROR_NO_REQUEST => 'No request object to send';
  1         2  
  1         35  
36 1     1   5 use constant ERROR_NOT_READY => 'Not enough information to send a %s request';
  1         1  
  1         43  
37 1     1   5 use constant ERROR_IS_IT_READY => "The request is%s READY to send\n";
  1         1  
  1         61  
38 1     1   6 use constant ERROR_CAVIRTEX => COMPANY . " error: '%s'\n";
  1         2  
  1         62  
39 1     1   5 use constant ERROR_UNKNOWN_STATUS => COMPANY . " returned an unknown status\n";
  1         2  
  1         60  
40              
41 1     1   5 use constant ATTRIBUTES => qw(token secret);
  1         2  
  1         65  
42              
43 1         1685 use constant CLASS_ACTION_MAP => {
44             orderbook => 'Finance::CaVirtex::API::Request::OrderBook',
45             tradebook => 'Finance::CaVirtex::API::Request::TradeBook',
46             ticker => 'Finance::CaVirtex::API::Request::Ticker',
47             balance => 'Finance::CaVirtex::API::Request::Balance',
48             transactions => 'Finance::CaVirtex::API::Request::Transactions',
49             trade_history => 'Finance::CaVirtex::API::Request::TradeHistory',
50             order_history => 'Finance::CaVirtex::API::Request::OrderHistory',
51             order => 'Finance::CaVirtex::API::Request::Order',
52             order_cancel => 'Finance::CaVirtex::API::Request::OrderCancel',
53             withdraw => 'Finance::CaVirtex::API::Request::Withdraw',
54 1     1   5 };
  1         1  
55              
56             sub is_ready_to_send {
57 3     3 0 6 my $self = shift;
58 3         6 my $ready = 0;
59             # here we are checking whether or not to default to '0' (not ready to send) based on this objects settings.
60             # the settings in here are the token and the secret provided to you by CaVirtex.
61             # if we dont have to add a nonce, then just check if its ready...
62 3 50 0     10 if (not $self->private or defined $self->token && defined $self->secret) {
      33        
63 3         9 $ready = $self->request->is_ready_to_send;
64             }
65 3         7 warn sprintf ERROR_IS_IT_READY, ($ready ? '' : ' NOT') if DEBUG;
66              
67 3         11 return $ready;
68             }
69              
70             sub send {
71 3     3 0 6 my $self = shift;
72              
73             # clear any previous response values... because if you wan it, you shoulda put a variable on it.
74 3         8 $self->response(undef);
75 3         8 $self->error(undef);
76 3         9 $self->new_nonce;
77              
78 3 50       9 unless ($self->request) {
79 0         0 $self->error({
80             type => __PACKAGE__,
81             message => ERROR_NO_REQUEST,
82             });
83             }
84             else {
85             # validate that the minimum required request attributes are set here.
86 3 50       10 if (not $self->is_ready_to_send) {
87 0         0 $self->error({
88             type => __PACKAGE__,
89             message => sprintf(ERROR_NOT_READY, ref $self->request),
90             });
91             }
92             else {
93             # make sure we have an request to send...
94 3         22 my $request = $self->http_request(HTTP::Request->new);
95 3         14 $request->method($self->request->request_type);
96 3         31 $request->uri($self->request->url);
97 3         11310 my %query_form = %{$self->request_content};
  3         15  
98             #
99             # This block will be removed once we have basic testing completed.
100             # ...because printing these variables on a live system is not a good idea...
101             #
102             #if ($self->private) {
103             # print Data::Dumper->Dump([\%query_form],['Query Form']);
104             # printf "sorted request values: %s\n", join(', ', $self->sorted_request_values);
105             # printf "Nonce: %s\n", $self->nonce;
106             # printf "Token: %s\n", $self->token;
107             # printf "Path: %s\n", $self->path;
108             #}
109             #
110 3 50       14 if ($self->private) {
111 0         0 $query_form{nonce } = $self->nonce;
112 0         0 $query_form{token } = $self->token;
113 0         0 $query_form{signature} = $self->signature;
114             }
115              
116 3         12 my $uri = URI->new;
117 3         136 $uri->query_form(%query_form);
118 3 50 66     304 if ($self->request->request_type eq 'POST') {
    100          
119 0         0 $request->content($uri->query);
120 0         0 $request->content_type($self->request->content_type);
121             }
122             elsif ($self->request->request_type eq 'GET' and $uri->query) {
123 2         30 $request->uri($request->uri . '?' . $uri->query);
124             }
125            
126 3         238 $request->header(Accept => 'application/json');
127              
128             # create a new user_agent each time...
129 3         222 $self->user_agent(LWP::UserAgent->new);
130 3         9 $self->user_agent->agent('Mozilla/8.0');
131 3         171 $self->user_agent->ssl_opts(verify_hostname => 1);
132              
133 3         69 warn Data::Dumper->Dump([$self->user_agent, $request],[qw(UserAgent Request)]) if DEBUG;
134              
135 3         8 $self->http_response($self->user_agent->request($request));
136 3         12 $self->process_response;
137             }
138             }
139 3         9 return $self->is_success;
140             }
141              
142             sub process_response {
143 3     3 0 5 my $self = shift;
144              
145 3         4 warn sprintf "Content: %s\n", $self->http_response->content if DEBUG;
146              
147 3         4 my $content;
148             eval {
149 3         4 warn Data::Dumper->Dump([$self->http_response],['Response']) if DEBUG;
150 3         9 $content = $self->json->decode($self->http_response->content);
151 0         0 1;
152 3 50       7 } or do {
153 3         87 $content = {};
154 3         283 warn "error: $@\n";
155             };
156 3 50       15 if (exists $content->{status}) {
157 0 0       0 if (lc $content->{status} eq 'ok') {
    0          
158 0 0       0 $self->apirate($content->{apirate}) if exists $content->{apirate};
159 0 0       0 if ($self->request->data_key) {
160             # crutch: there is a call to tradebook that returns the json data keyed on either
161             # 'orders' or 'trades'. As a result we have to allow this to be a hash or potential
162             # keys to search.
163             # once this is standardized on Cavirtex, we will remove these conditions...
164             #
165             # TODO: watch for such a change and then reduce the code below and change Request/TrasdeBook.pm
166 0 0       0 if (ref $self->request->data_key) {
167 0         0 foreach my $key (@{$self->request->data_key}) {
  0         0  
168 0 0       0 if (exists $content->{$key}) {
169 0         0 $self->response($content->{$key});
170 0         0 last;
171             }
172             }
173             }
174             # end crutch, but also, remove the 'else' below...
175             else {
176 0         0 $self->response($content->{$self->request->data_key});
177             }
178             }
179             else {
180 0         0 $self->response($content);
181             }
182             }
183             elsif ($content->{status} eq 'error') {
184 0         0 warn sprintf ERROR_CAVIRTEX, Dumper $content->{message} if DEBUG;
185 0         0 $self->error($content->{message});
186             }
187             else {
188             # we got a response but the result was not 'success' and did not contain an 'error' key...
189             # note: your code should never get here, so I am forcing a warning and Dump of the content...
190 0         0 warn ERROR_UNKNOWN_STATUS;
191 0         0 warn Data::Dumper->Dump([$content],[sprintf 'Invalid %s Response Content', COMPANY]);
192 0         0 $self->error('unknown status');
193             }
194             }
195             else {
196             # we did not get valid content from their server. Assume an unknown HTTP error occurred...
197 3         19 $self->error({
198             type => __PACKAGE__,
199             message => 'no status',
200             });
201             }
202 3         10 return $self->is_success;
203             }
204              
205              
206             # the code below is only here to explain the code above
207 0     0 0 0 sub sorted_request_values { @{$_[0]->request_content}{sort {lc($a) cmp lc($b)} keys $_[0]->request_content} }
  0         0  
  0         0  
208             #sub sorted_request_values {
209             #my $self = shift;
210             #my %content = %{$self->request_content};
211             #return @content{sort {lc($a) cmp lc($b)} keys %content};
212             #}
213              
214             # signature : is a HMAC-SHA256 Hex encoded hash containing the string data input:
215             # nonce, API token, relative API, request path and alphabetically sorted post parameters.
216             # The message must be generated using the Secret Key that was created with your API token.
217             #sub signature { hmac_sha256_hex(map($_[0]->$_, qw(nonce token path sorted_request_values secret))) }
218             sub signature {
219 0     0 0 0 my $self = shift;
220 0         0 return hmac_sha256_hex($self->nonce, $self->token, $self->path, $self->sorted_request_values, $self->secret);
221             }
222              
223 3     3 0 33 sub new_nonce { shift->nonce(sprintf '%d%06d' => gettimeofday) }
224 0     0 0 0 sub path { URI->new(shift->http_request->uri)->path }
225 3     3 0 10 sub request_content { shift->request->request_content }
226 3   66 3 0 42 sub json { shift->{json} ||= JSON->new }
227 6     6 0 16 sub private { shift->request->is_private }
228 6     6 0 16 sub is_success { defined shift->response }
229 0     0 0 0 sub public { not shift->private }
230 2     2 0 9 sub attributes { ATTRIBUTES }
231              
232             # this method makes the action call routines simpler...
233             sub class_action {
234 3     3 0 6 my $self = shift;
235 3         31 my $class = CLASS_ACTION_MAP->{((caller(1))[3] =~ /::(\w+)$/)[0]};
236 3         50 $self->request($class->new(@_));
237 3 50       10 return $self->send ? $self->response : undef;
238             }
239              
240 1     1 0 1190 sub orderbook { class_action(@_) }
241 1     1 0 1509 sub tradebook { class_action(@_) }
242 1     1 0 529 sub ticker { class_action(@_) }
243 0     0 0 0 sub balance { class_action(@_) }
244 0     0 0 0 sub transactions { class_action(@_) }
245 0     0 0 0 sub trade_history { class_action(@_) }
246 0     0 0 0 sub order_history { class_action(@_) }
247 0     0 0 0 sub order { class_action(@_) }
248 0     0 0 0 sub order_cancel { class_action(@_) }
249 0     0 0 0 sub withdraw { class_action(@_) }
250              
251 1     1 1 3 sub token { my $self = shift; $self->get_set(@_) }
  1         5  
252 1     1 1 3 sub secret { my $self = shift; $self->get_set(@_) }
  1         5  
253 3     3 0 5 sub nonce { my $self = shift; $self->get_set(@_) }
  3         31  
254 9     9 1 15 sub error { my $self = shift; $self->get_set(@_) }
  9         30  
255 6     6 0 4685 sub http_response { my $self = shift; $self->get_set(@_) }
  6         20  
256 30     30 1 42 sub request { my $self = shift; $self->get_set(@_) }
  30         90  
257 9     9 0 15 sub response { my $self = shift; $self->get_set(@_) }
  9         24  
258 3     3 0 190 sub http_request { my $self = shift; $self->get_set(@_) }
  3         10  
259 12     12 1 3861 sub user_agent { my $self = shift; $self->get_set(@_) }
  12         40  
260 0     0 0   sub apirate { my $self = shift; $self->get_set(@_) }
  0            
261              
262             1;
263              
264             __END__