line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
3
|
|
|
3
|
|
4818
|
use strictures; |
|
3
|
|
|
|
|
8
|
|
|
3
|
|
|
|
|
19
|
|
2
|
3
|
|
|
3
|
|
625
|
use 5.14.0; |
|
3
|
|
|
|
|
9
|
|
3
|
|
|
|
|
|
|
|
4
|
|
|
|
|
|
|
package WebService::GoogleAPI::Client; |
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
our $VERSION = '0.25'; # VERSION |
7
|
|
|
|
|
|
|
|
8
|
3
|
|
|
3
|
|
1687
|
use Data::Dump qw/pp/; |
|
3
|
|
|
|
|
16685
|
|
|
3
|
|
|
|
|
272
|
|
9
|
3
|
|
|
3
|
|
31
|
use Moo; |
|
3
|
|
|
|
|
7
|
|
|
3
|
|
|
|
|
28
|
|
10
|
3
|
|
|
3
|
|
2720
|
use WebService::GoogleAPI::Client::UserAgent; |
|
3
|
|
|
|
|
14
|
|
|
3
|
|
|
|
|
39
|
|
11
|
3
|
|
|
3
|
|
2118
|
use WebService::GoogleAPI::Client::Discovery; |
|
3
|
|
|
|
|
11
|
|
|
3
|
|
|
|
|
133
|
|
12
|
3
|
|
|
3
|
|
29
|
use WebService::GoogleAPI::Client::AuthStorage::GapiJSON; |
|
3
|
|
|
|
|
7
|
|
|
3
|
|
|
|
|
86
|
|
13
|
3
|
|
|
3
|
|
1765
|
use WebService::GoogleAPI::Client::AuthStorage::ServiceAccount; |
|
3
|
|
|
|
|
13
|
|
|
3
|
|
|
|
|
113
|
|
14
|
3
|
|
|
3
|
|
25
|
use Carp; |
|
3
|
|
|
|
|
7
|
|
|
3
|
|
|
|
|
190
|
|
15
|
3
|
|
|
3
|
|
21
|
use CHI; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
63
|
|
16
|
3
|
|
|
3
|
|
16
|
use Mojo::Util; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
7241
|
|
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
#TODO- batch requests. The only thing necessary is to send a |
19
|
|
|
|
|
|
|
#multipart request as I wrote in that e-mail. |
20
|
|
|
|
|
|
|
# |
21
|
|
|
|
|
|
|
#TODO- implement auth for service accounts. |
22
|
|
|
|
|
|
|
# |
23
|
|
|
|
|
|
|
#TODO- allow using promises instead of just calling. Also allow |
24
|
|
|
|
|
|
|
# for full access to the tx instead of assuming it's always |
25
|
|
|
|
|
|
|
# returning the res. Perhaps mix in something that delegates the |
26
|
|
|
|
|
|
|
# json method to the res? |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
# ABSTRACT: Google API Discovery and SDK |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
# FROM MCE POD -- |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
has 'debug' => (is => 'rw', default => 0, lazy => 1); |
34
|
|
|
|
|
|
|
has 'ua' => ( |
35
|
|
|
|
|
|
|
handles => [qw/do_autorefresh auth_storage get_access_token scopes user/], |
36
|
|
|
|
|
|
|
is => 'ro', |
37
|
|
|
|
|
|
|
default => |
38
|
|
|
|
|
|
|
sub { WebService::GoogleAPI::Client::UserAgent->new(debug => shift->debug) } |
39
|
|
|
|
|
|
|
, |
40
|
|
|
|
|
|
|
lazy => 1, |
41
|
|
|
|
|
|
|
); |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
sub get_scopes_as_array { |
44
|
0
|
|
|
0
|
0
|
0
|
carp 'get_scopes_as_array has deprecated in favor of the shorter "scopes"'; |
45
|
0
|
|
|
|
|
0
|
return $_[0]->scopes; |
46
|
|
|
|
|
|
|
} |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
has 'chi' => ( |
49
|
|
|
|
|
|
|
is => 'rw', |
50
|
|
|
|
|
|
|
default => sub { |
51
|
|
|
|
|
|
|
CHI->new(driver => 'File', max_key_length => 512, namespace => __PACKAGE__); |
52
|
|
|
|
|
|
|
}, |
53
|
|
|
|
|
|
|
lazy => 1 |
54
|
|
|
|
|
|
|
); |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
has 'discovery' => ( |
57
|
|
|
|
|
|
|
handles => [ |
58
|
|
|
|
|
|
|
qw/ discover_all |
59
|
|
|
|
|
|
|
get_method_details |
60
|
|
|
|
|
|
|
get_api_document service_exists |
61
|
|
|
|
|
|
|
methods_available_for_google_api_id list_api_ids / |
62
|
|
|
|
|
|
|
], |
63
|
|
|
|
|
|
|
is => 'ro', |
64
|
|
|
|
|
|
|
default => sub { |
65
|
|
|
|
|
|
|
my $self = shift; |
66
|
|
|
|
|
|
|
return WebService::GoogleAPI::Client::Discovery->new( |
67
|
|
|
|
|
|
|
debug => $self->debug, |
68
|
|
|
|
|
|
|
ua => $self->ua, |
69
|
|
|
|
|
|
|
chi => $self->chi |
70
|
|
|
|
|
|
|
); |
71
|
|
|
|
|
|
|
}, |
72
|
|
|
|
|
|
|
lazy => 1, |
73
|
|
|
|
|
|
|
); |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
## provides a way of augmenting constructor (new) without overloading it |
76
|
|
|
|
|
|
|
## see https://metacpan.org/pod/distribution/Moose/lib/Moose/Manual/Construction.pod if like me you an new to Moose |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
#NOTE- in terms of implementing google's ADC |
80
|
|
|
|
|
|
|
# (see https://cloud.google.com/docs/authentication/production and also |
81
|
|
|
|
|
|
|
# https://github.com/googleapis/python-cloud-core/blob/master/google/cloud/client.py) |
82
|
|
|
|
|
|
|
# I looked into it and based on that, it seems that every environment has different reqs, |
83
|
|
|
|
|
|
|
# so it's a maintenance liability |
84
|
|
|
|
|
|
|
sub BUILD { |
85
|
2
|
|
|
2
|
0
|
6010
|
my ($self, $params) = @_; |
86
|
|
|
|
|
|
|
|
87
|
2
|
|
|
|
|
8
|
my ($storage, $file); |
88
|
2
|
50
|
|
|
|
15
|
if ($params->{auth_storage}) { |
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
89
|
0
|
|
|
|
|
0
|
$storage = $params->{auth_storage}; |
90
|
|
|
|
|
|
|
} elsif ($file = $params->{gapi_json}) { |
91
|
2
|
|
|
|
|
22
|
$storage = |
92
|
|
|
|
|
|
|
WebService::GoogleAPI::Client::AuthStorage::GapiJSON->new(path => $file); |
93
|
|
|
|
|
|
|
} elsif ($file = $params->{service_account}) { |
94
|
|
|
|
|
|
|
$storage = WebService::GoogleAPI::Client::AuthStorage::ServiceAccount->new( |
95
|
|
|
|
|
|
|
path => $file, |
96
|
|
|
|
|
|
|
scopes => $params->{scopes} |
97
|
0
|
|
|
|
|
0
|
); |
98
|
|
|
|
|
|
|
} elsif ($file = $ENV{GOOGLE_APPLICATION_CREDENTIALS}) { |
99
|
|
|
|
|
|
|
$storage = WebService::GoogleAPI::Client::AuthStorage::ServiceAccount->new( |
100
|
|
|
|
|
|
|
path => $file, |
101
|
|
|
|
|
|
|
scopes => $params->{scopes} |
102
|
0
|
|
|
|
|
0
|
); |
103
|
|
|
|
|
|
|
} |
104
|
2
|
50
|
|
|
|
58
|
$self->auth_storage($storage) if $storage; |
105
|
|
|
|
|
|
|
|
106
|
2
|
50
|
|
|
|
112
|
$self->user($params->{user}) if (defined $params->{user}); |
107
|
|
|
|
|
|
|
} |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
## ASSUMPTIONS: |
111
|
|
|
|
|
|
|
## - no complex parameter checking ( eg required mediaUpload in endpoint gmail.users.messages.send ) so user assumes responsiiblity |
112
|
|
|
|
|
|
|
## TODO: Exceeding a rate limit will cause an HTTP 403 or HTTP 429 Too Many Requests response and your app should respond by retrying with exponential backoff. (https://developers.google.com/gmail/api/v1/reference/quota) |
113
|
|
|
|
|
|
|
## follows the method spec reqeust reference to the api spec schema api_discovery_struct->{schemas}{Message}; |
114
|
|
|
|
|
|
|
## THE METHOD SPEC CONTAINS |
115
|
|
|
|
|
|
|
## 'request' => { |
116
|
|
|
|
|
|
|
## '$ref' => 'Message' |
117
|
|
|
|
|
|
|
## }, |
118
|
|
|
|
|
|
|
## THE SCHEMA api_discovery_struct->{schemas}{Message} CONTAINS |
119
|
|
|
|
|
|
|
## 'id' => 'Message', |
120
|
|
|
|
|
|
|
## 'properties' => { |
121
|
|
|
|
|
|
|
## 'raw' => { |
122
|
|
|
|
|
|
|
## {annotations}{required}[ 'gmail.users.drafts.create','gmail.users.drafts.update','gmail.users.messages.insert','gmail.users.messages.send' ] |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
## NB - uses the ua api_query to execute the server request |
125
|
|
|
|
|
|
|
################################################## |
126
|
|
|
|
|
|
|
sub api_query { |
127
|
0
|
|
|
0
|
1
|
0
|
my ($self, @params_array) = @_; |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
## TODO - find a more elgant idiom to do this - pulled this off top of head for quick imeplementation |
130
|
0
|
|
|
|
|
0
|
my $params = {}; |
131
|
0
|
0
|
0
|
|
|
0
|
if (scalar(@params_array) == 1 && ref($params_array[0]) eq 'HASH') { |
132
|
0
|
|
|
|
|
0
|
$params = $params_array[0]; |
133
|
|
|
|
|
|
|
} else { |
134
|
0
|
|
|
|
|
0
|
$params = {@params_array}; ## what happens if not even count |
135
|
|
|
|
|
|
|
} |
136
|
0
|
0
|
|
|
|
0
|
carp(pp $params) if $self->debug > 10; |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
croak "Missing neccessary scopes to access $params->{api_endpoint_id}" |
139
|
0
|
0
|
|
|
|
0
|
unless $self->has_scope_to_access_api_endpoint($params->{api_endpoint_id}); |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
## used to collect pre-query validation errors - if set we return a response |
142
|
|
|
|
|
|
|
# with 418 I'm a teapot |
143
|
0
|
|
|
|
|
0
|
my @teapot_errors = (); |
144
|
|
|
|
|
|
|
## pre-query validation if api_id parameter is included |
145
|
|
|
|
|
|
|
@teapot_errors = $self->_process_params($params) |
146
|
0
|
0
|
|
|
|
0
|
if (defined $params->{api_endpoint_id}); |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
## either as param or from discovery |
149
|
0
|
0
|
|
|
|
0
|
if (not defined $params->{path}) { |
150
|
0
|
|
|
|
|
0
|
push @teapot_errors, 'path is a required parameter'; |
151
|
0
|
|
|
|
|
0
|
$params->{path} = ''; |
152
|
|
|
|
|
|
|
} |
153
|
|
|
|
|
|
|
push @teapot_errors, |
154
|
|
|
|
|
|
|
"Path '$params->{path}' includes unfilled variable after processing" |
155
|
0
|
0
|
|
|
|
0
|
if ($params->{path} =~ /\{.+\}/xms); |
156
|
|
|
|
|
|
|
## carp and include in 418 TEAPOT ERROR - response body with @teapot errors |
157
|
0
|
0
|
|
|
|
0
|
if (@teapot_errors > 0) { |
158
|
0
|
0
|
|
|
|
0
|
carp(join("\n", @teapot_errors)) if $self->debug; |
159
|
0
|
|
|
|
|
0
|
return Mojo::Message::Response->new( |
160
|
|
|
|
|
|
|
content_type => 'text/plain', |
161
|
|
|
|
|
|
|
code => 418, |
162
|
|
|
|
|
|
|
message => |
163
|
|
|
|
|
|
|
'Teapot Error - Reqeust blocked before submitting to server with pre-query validation errors', |
164
|
|
|
|
|
|
|
body => join("\n", @teapot_errors) |
165
|
|
|
|
|
|
|
); |
166
|
|
|
|
|
|
|
} else { |
167
|
|
|
|
|
|
|
## query looks good - send to user agent to execute |
168
|
0
|
|
|
|
|
0
|
return $self->ua->validated_api_query($params); |
169
|
|
|
|
|
|
|
} |
170
|
|
|
|
|
|
|
} |
171
|
|
|
|
|
|
|
################################################## |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
################################################## |
174
|
|
|
|
|
|
|
## _ensure_api_spec_has_defined_fields is really only used to allow carping without undef warnings if needed |
175
|
|
|
|
|
|
|
sub _ensure_api_spec_has_defined_fields { |
176
|
11
|
|
|
11
|
|
51
|
my ($self, $api_discovery_struct) = @_; |
177
|
|
|
|
|
|
|
## Ensure API Discovery has expected fields defined |
178
|
11
|
|
|
|
|
47
|
foreach my $expected_key ( |
179
|
|
|
|
|
|
|
qw/path title ownerName version id discoveryVersion |
180
|
|
|
|
|
|
|
revision description documentationLink rest/ |
181
|
|
|
|
|
|
|
) { |
182
|
|
|
|
|
|
|
$api_discovery_struct->{$expected_key} = '' |
183
|
110
|
100
|
|
|
|
321
|
unless defined $api_discovery_struct->{$expected_key}; |
184
|
|
|
|
|
|
|
} |
185
|
|
|
|
|
|
|
$api_discovery_struct->{canonicalName} = $api_discovery_struct->{title} |
186
|
11
|
100
|
|
|
|
65
|
unless defined $api_discovery_struct->{canonicalName}; |
187
|
11
|
|
|
|
|
36
|
return $api_discovery_struct; |
188
|
|
|
|
|
|
|
} |
189
|
|
|
|
|
|
|
################################################## |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
################################################## |
192
|
|
|
|
|
|
|
sub _process_params_for_api_endpoint_and_return_errors { |
193
|
0
|
|
|
0
|
|
0
|
warn |
194
|
|
|
|
|
|
|
'_process_params_for_api_endpoint_and_return_errors has been deprecated. Please use _process_params'; |
195
|
0
|
|
|
|
|
0
|
_process_params(@_); |
196
|
|
|
|
|
|
|
} |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
sub _process_params { |
199
|
|
|
|
|
|
|
## nb - api_endpoint is a param - param key values are modified through this sub |
200
|
12
|
|
|
12
|
|
24675
|
my ($self, $params) = @_; |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
croak('this should never happen - this method is internal only!') |
203
|
12
|
50
|
|
|
|
54
|
unless defined $params->{api_endpoint_id}; |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
## $api_discovery_struct requried for service base URL |
206
|
|
|
|
|
|
|
my $api_discovery_struct = $self->_ensure_api_spec_has_defined_fields( |
207
|
12
|
|
|
|
|
441
|
$self->discovery->get_api_document($params->{api_endpoint_id})); |
208
|
|
|
|
|
|
|
## remove trailing '/' from baseUrl |
209
|
11
|
|
|
|
|
122
|
$api_discovery_struct->{baseUrl} =~ s/\/$//sxmg; |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
## if can get discovery data for google api endpoint then continue to perform |
212
|
|
|
|
|
|
|
# detailed checks |
213
|
|
|
|
|
|
|
my $method_discovery_struct = |
214
|
11
|
|
|
|
|
343
|
$self->get_method_details($params->{api_endpoint_id}); |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
#save away original path so we can know if it's fiddled with |
217
|
|
|
|
|
|
|
#later |
218
|
10
|
|
|
|
|
80
|
$method_discovery_struct->{origPath} = $method_discovery_struct->{path}; |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
## allow optional user callback pre-processing of method_discovery_struct |
221
|
|
|
|
|
|
|
$method_discovery_struct = |
222
|
1
|
|
|
|
|
9
|
&{ $params->{cb_method_discovery_modify} }($method_discovery_struct) |
223
|
|
|
|
|
|
|
if (defined $params->{cb_method_discovery_modify} |
224
|
10
|
100
|
66
|
|
|
70
|
&& ref($params->{cb_method_discovery_modify}) eq 'CODE'); |
225
|
|
|
|
|
|
|
|
226
|
10
|
|
|
|
|
52
|
my @teapot_errors = (); ## errors are pushed into this as encountered |
227
|
|
|
|
|
|
|
$params->{method} = $method_discovery_struct->{httpMethod} || 'GET' |
228
|
10
|
100
|
50
|
|
|
75
|
if (not defined $params->{method}); |
229
|
|
|
|
|
|
|
push(@teapot_errors, |
230
|
|
|
|
|
|
|
"method mismatch - you requested a $params->{method} which conflicts with discovery spec requirement for $method_discovery_struct->{httpMethod}" |
231
|
10
|
50
|
|
|
|
408
|
) if ($params->{method} !~ /^$method_discovery_struct->{httpMethod}$/sxim); |
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
## Set default path iff not set by user - NB - will prepend baseUrl later |
234
|
10
|
100
|
|
|
|
60
|
$params->{path} = $method_discovery_struct->{path} unless $params->{path}; |
235
|
10
|
50
|
|
|
|
44
|
push @teapot_errors, 'path is a required parameter' unless $params->{path}; |
236
|
|
|
|
|
|
|
|
237
|
10
|
|
|
|
|
92
|
push @teapot_errors, |
238
|
|
|
|
|
|
|
$self->_interpolate_path_parameters_append_query_params_and_return_errors( |
239
|
|
|
|
|
|
|
$params, $method_discovery_struct); |
240
|
|
|
|
|
|
|
|
241
|
10
|
|
|
|
|
44
|
$params->{path} =~ s/^\///sxmg; ## remove leading '/' from path |
242
|
|
|
|
|
|
|
$params->{path} = "$api_discovery_struct->{baseUrl}/$params->{path}" |
243
|
|
|
|
|
|
|
unless $params->{path} =~ |
244
|
10
|
100
|
|
|
|
175
|
/^$api_discovery_struct->{baseUrl}/ixsmg; ## prepend baseUrl if required |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
## if errors - add detail available in the discovery struct for the method and service to aid debugging |
247
|
10
|
100
|
|
|
|
99
|
push @teapot_errors, |
248
|
|
|
|
|
|
|
qq{ $api_discovery_struct->{title} $api_discovery_struct->{rest} API into $api_discovery_struct->{ownerName} $api_discovery_struct->{canonicalName} $api_discovery_struct->{version} with id $method_discovery_struct->{id} as described by discovery document version $api_discovery_struct->{discoveryVersion} revision $api_discovery_struct->{revision} with documentation at $api_discovery_struct->{documentationLink} \nDescription: $method_discovery_struct->{description}\n} |
249
|
|
|
|
|
|
|
if @teapot_errors; |
250
|
|
|
|
|
|
|
|
251
|
10
|
|
|
|
|
6404
|
return @teapot_errors; |
252
|
|
|
|
|
|
|
} |
253
|
|
|
|
|
|
|
################################################## |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
#small subs to convert between these_types to theseTypes of params |
256
|
|
|
|
|
|
|
#TODO- should probs move this into a Util module |
257
|
43
|
50
|
|
43
|
0
|
110
|
sub camel { shift if @_ > 1; $_[0] =~ s/ _(\w) /\u$1/grx } |
|
43
|
|
|
|
|
210
|
|
258
|
0
|
0
|
|
0
|
0
|
0
|
sub snake { shift if @_ > 1; $_[0] =~ s/([[:upper:]])/_\l$1/grx } |
|
0
|
|
|
|
|
0
|
|
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
################################################## |
261
|
|
|
|
|
|
|
sub _interpolate_path_parameters_append_query_params_and_return_errors { |
262
|
10
|
|
|
10
|
|
36
|
my ($self, $params, $discovery_struct) = @_; |
263
|
10
|
|
|
|
|
31
|
my @teapot_errors = (); |
264
|
|
|
|
|
|
|
|
265
|
10
|
|
|
|
|
23
|
my @get_query_params = (); |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
#create a hash of whatever the expected params may be |
268
|
10
|
|
|
|
|
25
|
my %path_params; |
269
|
10
|
|
|
|
|
352
|
my $param_regex = qr/\{ \+? ([^\}]+) \}/x; |
270
|
10
|
100
|
|
|
|
53
|
if ($params->{path} ne $discovery_struct->{origPath}) { |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
#check if the path was given as a custom path. If it is, just |
273
|
|
|
|
|
|
|
#interpolate things directly, and assume the user is responsible |
274
|
3
|
|
|
|
|
540
|
%path_params = map { $_ => 'custom' } ($params->{path} =~ /$param_regex/xg); |
|
3
|
|
|
|
|
15
|
|
275
|
|
|
|
|
|
|
} else { |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
#label which param names are from the normal path and from the |
278
|
|
|
|
|
|
|
#flat path |
279
|
|
|
|
|
|
|
%path_params = |
280
|
7
|
|
|
|
|
164
|
map { $_ => 'plain' } ($discovery_struct->{path} =~ /$param_regex/xg); |
|
6
|
|
|
|
|
49
|
|
281
|
7
|
100
|
|
|
|
39
|
if ($discovery_struct->{flatPath}) { |
282
|
|
|
|
|
|
|
%path_params = ( |
283
|
|
|
|
|
|
|
%path_params, |
284
|
6
|
|
|
|
|
78
|
map { $_ => 'flat' } ($discovery_struct->{flatPath} =~ /$param_regex/xg) |
|
13
|
|
|
|
|
60
|
|
285
|
|
|
|
|
|
|
); |
286
|
|
|
|
|
|
|
} |
287
|
|
|
|
|
|
|
} |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
#switch the path we're dealing with to the flat path if any of |
291
|
|
|
|
|
|
|
#the parameters match the flat path |
292
|
|
|
|
|
|
|
$params->{path} = $discovery_struct->{flatPath} |
293
|
9
|
|
|
|
|
45
|
if grep { $_ eq 'flat' } |
294
|
10
|
100
|
|
|
|
26
|
map { $path_params{ camel $_} || () } keys %{ $params->{options} }; |
|
17
|
100
|
|
|
|
55
|
|
|
10
|
|
|
|
|
69
|
|
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
#loop through params given, placing them in the path or query, |
298
|
|
|
|
|
|
|
#or leaving them for the request body |
299
|
10
|
|
|
|
|
38
|
for my $param_name (keys %{ $params->{options} }) { |
|
10
|
|
|
|
|
41
|
|
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
#first check if it needs to be interpolated into the path |
302
|
17
|
100
|
66
|
|
|
93
|
if ($path_params{$param_name} || $path_params{ camel $param_name}) { |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
#pull out the value from the hash, and remove the key |
305
|
9
|
|
|
|
|
103
|
my $param_value = delete $params->{options}{$param_name}; |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
#Camelize the param name if not passed in customly, allowing |
308
|
|
|
|
|
|
|
#the user to pass in camelCase or snake_case param names. |
309
|
|
|
|
|
|
|
#This implictly allows for a non camelCased param to be left |
310
|
|
|
|
|
|
|
#alone in a custom param. |
311
|
9
|
50
|
|
|
|
30
|
$param_name = camel $param_name if $path_params{ camel $param_name}; |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
#first deal with any param that doesn't have a plus, b/c |
314
|
|
|
|
|
|
|
#those just get interpolated |
315
|
9
|
|
|
|
|
175
|
$params->{path} =~ s/\{$param_name\}/$param_value/; |
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
#if there's a plus in the path spec, we need more work |
318
|
9
|
100
|
|
|
|
124
|
if ($params->{path} =~ /\{ \+ $param_name \}/x) { |
319
|
4
|
|
|
|
|
20
|
my $pattern = $discovery_struct->{parameters}{$param_name}{pattern}; |
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
#if the given param matches google's pattern for the |
322
|
|
|
|
|
|
|
#param, just interpolate it straight |
323
|
4
|
100
|
|
|
|
151
|
if ($param_value =~ /$pattern/) { |
324
|
1
|
|
|
|
|
17
|
$params->{path} =~ s/\{\+$param_name\}/$param_value/; |
325
|
|
|
|
|
|
|
} else { |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
#N.B. perhaps support passing an arrayref or csv for those |
328
|
|
|
|
|
|
|
#params such as jobs.projects.jobs.delete which have two |
329
|
|
|
|
|
|
|
#dynamic parts. But for now, unnecessary |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
#remove the regexy parts of the pattern to interpolate it |
332
|
|
|
|
|
|
|
#into the path, assuming the user has provided just the |
333
|
|
|
|
|
|
|
#dynamic portion of the param. |
334
|
3
|
|
|
|
|
30
|
$pattern =~ s/^\^ | \$$//gx; |
335
|
3
|
|
|
|
|
15
|
my $placeholder = qr/ \[ \^ \/ \] \+ /x; |
336
|
3
|
|
|
|
|
93
|
$params->{path} =~ s/\{\+$param_name\}/$pattern/x; |
337
|
3
|
|
|
|
|
30
|
$params->{path} =~ s/$placeholder/$param_value/x; |
338
|
|
|
|
|
|
|
push @teapot_errors, "Not enough parameters given for {+$param_name}." |
339
|
3
|
100
|
|
|
|
37
|
if $params->{path} =~ /$placeholder/; |
340
|
|
|
|
|
|
|
} |
341
|
|
|
|
|
|
|
} |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
#skip to the next run, so I don't need an else clause later |
344
|
9
|
|
|
|
|
38
|
next; #I don't like nested if-elses |
345
|
|
|
|
|
|
|
} |
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
#if it's not in the list of params, then it goes in the |
348
|
|
|
|
|
|
|
#request body, and our work here is done |
349
|
8
|
100
|
|
|
|
34
|
next unless $discovery_struct->{parameters}{$param_name}; |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
#it must be a GET type query param, so push the name and value |
352
|
|
|
|
|
|
|
#on our param stack and take it off of the options list |
353
|
3
|
|
|
|
|
14
|
push @get_query_params, $param_name, delete $params->{options}{$param_name}; |
354
|
|
|
|
|
|
|
} |
355
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
#if there are any query params... |
357
|
10
|
100
|
|
|
|
39
|
if (@get_query_params) { |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
#interpolate and escape the get query params built up in our |
360
|
|
|
|
|
|
|
#former for loop |
361
|
3
|
100
|
|
|
|
18
|
$params->{path} .= ($params->{path} =~ /\?/) ? '&' : '?'; |
362
|
3
|
|
|
|
|
49
|
$params->{path} .= Mojo::Parameters->new(@get_query_params); |
363
|
|
|
|
|
|
|
} |
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
#interpolate default value for path params if not given. Needed |
366
|
|
|
|
|
|
|
#for things like the gmail API, where userID is 'me' by default |
367
|
10
|
|
|
|
|
1215
|
for my $param_name ($params->{path} =~ /$param_regex/g) { |
368
|
2
|
|
|
|
|
10
|
my $param_value = $discovery_struct->{parameters}{$param_name}{default}; |
369
|
2
|
50
|
|
|
|
9
|
$params->{path} =~ s/\{$param_name\}/$param_value/ if $param_value; |
370
|
|
|
|
|
|
|
} |
371
|
|
|
|
|
|
|
push @teapot_errors, "Missing a parameter for {$_}." |
372
|
10
|
|
|
|
|
246
|
for $params->{path} =~ /$param_regex/g; |
373
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
#print pp $params; |
375
|
|
|
|
|
|
|
#exit; |
376
|
10
|
|
|
|
|
60
|
return @teapot_errors; |
377
|
|
|
|
|
|
|
} |
378
|
|
|
|
|
|
|
################################################## |
379
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
sub has_scope_to_access_api_endpoint { |
382
|
0
|
|
|
0
|
1
|
|
my ($self, $api_ep) = @_; |
383
|
0
|
|
|
|
|
|
my $method_spec = $self->get_method_details($api_ep); |
384
|
|
|
|
|
|
|
|
385
|
0
|
0
|
|
|
|
|
if (keys %$method_spec) { |
386
|
0
|
|
|
|
|
|
my $configured_scopes = $self->scopes; ## get user scopes arrayref |
387
|
|
|
|
|
|
|
## create a hashindex to facilitate quick lookups |
388
|
0
|
|
|
|
|
|
my %configured_scopes_hash = map { (s/\/$//xr, 1) } @$configured_scopes; |
|
0
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
## NB r switch as per https://www.perlmonks.org/?node_id=613280 to filter |
390
|
|
|
|
|
|
|
#out any trailing '/' |
391
|
0
|
|
|
|
|
|
my $granted = 0; |
392
|
|
|
|
|
|
|
## assume permission not granted until we find a matching scope |
393
|
0
|
|
|
|
|
|
my $required_scope_count = 0; |
394
|
|
|
|
|
|
|
## if the final count of scope constraints = 0 then we will assume permission is granted - this has proven necessary for the experimental Google My Business because scopes are not defined in the current discovery data as at 14/10/18 |
395
|
0
|
|
|
|
|
|
for my $method_scope (map { s/\/$//xr } @{ $method_spec->{scopes} }) { |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
396
|
0
|
|
|
|
|
|
$required_scope_count++; |
397
|
0
|
0
|
|
|
|
|
$granted = 1 if defined $configured_scopes_hash{$method_scope}; |
398
|
0
|
0
|
|
|
|
|
last if $granted; |
399
|
|
|
|
|
|
|
} |
400
|
0
|
0
|
|
|
|
|
$granted = 1 if $required_scope_count == 0; |
401
|
0
|
|
|
|
|
|
return $granted; |
402
|
|
|
|
|
|
|
} else { |
403
|
0
|
|
|
|
|
|
return 0; |
404
|
|
|
|
|
|
|
} |
405
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
} |
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
#TODO: Consider rename to return_fetched_google_v1_apis_discovery_structure |
410
|
|
|
|
|
|
|
# |
411
|
|
|
|
|
|
|
#TODO - handle auth required error and resubmit request with OAUTH headers if response indicates |
412
|
|
|
|
|
|
|
# access requires auth ( when exceed free access limits ) |
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
1; |
416
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
__END__ |