File Coverage

blib/lib/Net/Async/SPORE/Loader.pm
Criterion Covered Total %
statement 54 79 68.3
branch 4 10 40.0
condition 1 6 16.6
subroutine 12 16 75.0
pod 4 4 100.0
total 75 115 65.2


line stmt bran cond sub pod time code
1             package Net::Async::SPORE::Loader;
2             $Net::Async::SPORE::Loader::VERSION = '0.003';
3 1     1   22703 use strict;
  1         2  
  1         39  
4 1     1   10 use warnings;
  1         13  
  1         40  
5              
6             =head1 NAME
7              
8             Net::Async::SPORE::Loader - loads SPORE API definitions
9              
10             =head1 VERSION
11              
12             Version 0.003
13              
14             =head1 SYNOPSIS
15              
16             my $api = Net::Async::SPORE::Loader->new_from_file(
17             'sample.json',
18             transport => 'Net::Async::HTTP',
19             class => 'Sample::API',
20             );
21             $api->some_request(x => 123, y => 456)->get;
22              
23             =head1 DESCRIPTION
24              
25             This is the API loader class. It'll read in definitions and create classes in memory.
26              
27             =cut
28              
29 1     1   667 use Net::Async::SPORE::Request;
  1         4  
  1         42  
30 1     1   722 use Net::Async::SPORE::Definition;
  1         3  
  1         36  
31              
32 1     1   12 use JSON::MaybeXS;
  1         2  
  1         54  
33 1     1   6 use File::Spec;
  1         2  
  1         176  
34              
35             sub inject_method(&@);
36              
37             =head1 METHODS
38              
39             =cut
40              
41             =head2 new_from_file
42              
43             Instantiate a new API object from the given file.
44              
45             my $api = Net::Async::SPORE::Loader->new_from_file(
46             'sample.json',
47             transport => 'Net::Async::HTTP',
48             class => 'Sample::API',
49             );
50             $api->some_request(x => 123, y => 456)->get;
51              
52             =cut
53              
54             sub new_from_file {
55 1     1 1 1753 my ($class, $file, %args) = @_;
56              
57 1         2 my $api = do {
58 1 50       130 open my $fh, '<', $file or die "$file - $!";
59 1         2 my $txt = do { local $/; <$fh> };
  1         4  
  1         49  
60 1         64 decode_json($txt);
61             };
62 1         10 $class->new($api, %args);
63             }
64              
65             =head2 new
66              
67             Instantiates an API object from a definition provided as a hashref.
68              
69             my $api = Net::Async::SPORE::Loader->new(
70             { ... },
71             transport => 'Net::Async::HTTP',
72             class => 'Sample::API',
73             );
74             $api->some_request(x => 123, y => 456)->get;
75              
76             =cut
77              
78             sub new {
79 1     1 1 6 my ($class, $api, %args) = @_;
80              
81 1   33     5 $args{class} ||= $class->_next_api_class;
82              
83             {
84 1     1   7 no strict 'refs';
  1         3  
  1         791  
  1         2  
85 1         2 push @{$args{class} . '::ISA'}, qw(Net::Async::SPORE::Definition);
  1         25  
86             }
87              
88             inject_method {
89 0     0   0 $api->{'base_url'}
90 1         10 } $args{class} => '_base_url';
91             inject_method {
92 0     0   0 $api->{'headers'}
93 1         6 } $args{class} => '_headers';
94              
95 1         7 $class->apply_methods(
96             %args,
97             spec => $api
98             );
99 1         10 $args{class}->new
100             }
101              
102             =head1 METHODS - Internal
103              
104             You're welcome to use these, but you probably don't need them.
105              
106             =head2 apply_methods
107              
108             Applies the API methods to the target class.
109              
110             =cut
111              
112             sub apply_methods {
113 1     1 1 4 my ($self, %args) = @_;
114 1 50       6 my $spec = delete $args{spec} or die 'need a spec';
115 1 50       4 my $class = delete $args{class} or die 'need a class';
116 1         2 my %methods = %{$spec->{methods}};
  1         4  
117 1         4 for my $method (keys %methods) {
118 2         3 my $method_spec = $methods{$method};
119             inject_method {
120 0     0   0 my ($self, %args) = @_;
121              
122 0 0       0 if(my @missing = grep !exists $args{$_}, @{$method_spec->{required_params}}) {
  0         0  
123 0         0 die "Missing parameters: " . join ',', @missing;
124             }
125              
126             # Start with the path template
127 0         0 my $path = $method_spec->{path};
128              
129             # Apply our parameters
130 0         0 my @param = map {;
131 0         0 $_ => $args{$_}
132 0         0 } @{$method_spec->{required_params}};
133 0         0 push @param, $_ => $args{$_} for grep exists $args{$_}, @{$method_spec->{optional_params}};
  0         0  
134              
135             # We now have enough info to construct the endpoint URI
136 0         0 my $uri = URI->new(
137             $self->_base_url,
138             );
139 0         0 $uri->path(
140             File::Spec->catdir(
141             $uri->path,
142             $path
143             )
144             );
145              
146 0         0 my @hdr;
147             {
148 0         0 my %hdr = %{$self->_headers};
  0         0  
  0         0  
149 0         0 for (sort keys %hdr) {
150 0         0 (my $mapped = $_) =~ s/-/_/g;
151 0         0 push @hdr, "HTTP_$mapped" => $hdr{$_};
152             }
153             }
154              
155 0   0     0 my $rq = Net::Async::SPORE::Request->new(
156             # UPPER CASE FOR THAT FORTRAN FEELING
157             REQUEST_METHOD => $method_spec->{method},
158             SCRIPT_NAME => '',
159             PATH_INFO => $uri->path,
160             REQUEST_URI => $uri->path_query,
161             SERVER_NAME => $uri->host,
162             SERVER_PORT => $uri->port || ($uri->scheme eq 'https' ? 443 : 80),
163             QUERY_STRING => '',
164              
165             # yes, consistency is not a bad thing either
166             payload => '',
167             params => \@param,
168             redirections => [],
169             scheme => $uri->scheme,
170             @hdr,
171             );
172              
173             # Pass this on to whatever our defined handler
174             # is. Middleware may be involved.
175 0         0 $rq->as_request;
176 0         0 $self->_request($rq->as_request);
177 2         21 } $class => $method;
178             }
179             $self
180 1         4 }
181              
182             =head1 FUNCTIONS
183              
184             =head2 inject_method
185              
186             Helper function for adding a method to the given
187             class.
188              
189             inject_method($target_class, $method_name, $code);
190              
191             Will raise an exception if the method is already there.
192              
193             =cut
194              
195             sub inject_method(&@) {
196 4     4 1 10 my ($code, $class, $method) = @_;
197 1     1   15 no strict 'refs';
  1         2  
  1         129  
198 4 50       43 die "Method overlap for $method" if $class->can($method);
199 4         7 *{join '::', $class, $method} = $code;
  4         18  
200             }
201              
202             {
203             my $next_id = 'AA001';
204              
205             =head2 _next_api_class
206              
207             Returns an autogenerated class name.
208              
209             =cut
210              
211 0     0     sub _next_api_class { 'Net::Async::SPORE::API::' . $next_id++ }
212             }
213              
214             1;
215              
216             __END__