cure-google-api-client 0.8.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +178 -0
  3. data/Gemfile +9 -0
  4. data/LICENSE +202 -0
  5. data/README.md +218 -0
  6. data/Rakefile +41 -0
  7. data/google-api-client.gemspec +43 -0
  8. data/lib/cacerts.pem +2183 -0
  9. data/lib/compat/multi_json.rb +19 -0
  10. data/lib/google/api_client.rb +750 -0
  11. data/lib/google/api_client/auth/compute_service_account.rb +28 -0
  12. data/lib/google/api_client/auth/file_storage.rb +59 -0
  13. data/lib/google/api_client/auth/installed_app.rb +126 -0
  14. data/lib/google/api_client/auth/jwt_asserter.rb +126 -0
  15. data/lib/google/api_client/auth/key_utils.rb +93 -0
  16. data/lib/google/api_client/auth/pkcs12.rb +41 -0
  17. data/lib/google/api_client/auth/storage.rb +102 -0
  18. data/lib/google/api_client/auth/storages/file_store.rb +58 -0
  19. data/lib/google/api_client/auth/storages/redis_store.rb +54 -0
  20. data/lib/google/api_client/batch.rb +326 -0
  21. data/lib/google/api_client/charset.rb +33 -0
  22. data/lib/google/api_client/client_secrets.rb +179 -0
  23. data/lib/google/api_client/discovery.rb +19 -0
  24. data/lib/google/api_client/discovery/api.rb +310 -0
  25. data/lib/google/api_client/discovery/media.rb +77 -0
  26. data/lib/google/api_client/discovery/method.rb +363 -0
  27. data/lib/google/api_client/discovery/resource.rb +156 -0
  28. data/lib/google/api_client/discovery/schema.rb +117 -0
  29. data/lib/google/api_client/environment.rb +42 -0
  30. data/lib/google/api_client/errors.rb +65 -0
  31. data/lib/google/api_client/gzip.rb +28 -0
  32. data/lib/google/api_client/logging.rb +32 -0
  33. data/lib/google/api_client/media.rb +259 -0
  34. data/lib/google/api_client/railtie.rb +18 -0
  35. data/lib/google/api_client/reference.rb +27 -0
  36. data/lib/google/api_client/request.rb +350 -0
  37. data/lib/google/api_client/result.rb +255 -0
  38. data/lib/google/api_client/service.rb +233 -0
  39. data/lib/google/api_client/service/batch.rb +110 -0
  40. data/lib/google/api_client/service/request.rb +144 -0
  41. data/lib/google/api_client/service/resource.rb +40 -0
  42. data/lib/google/api_client/service/result.rb +162 -0
  43. data/lib/google/api_client/service/simple_file_store.rb +151 -0
  44. data/lib/google/api_client/service/stub_generator.rb +61 -0
  45. data/lib/google/api_client/service_account.rb +21 -0
  46. data/lib/google/api_client/version.rb +26 -0
  47. data/spec/google/api_client/auth/storage_spec.rb +122 -0
  48. data/spec/google/api_client/auth/storages/file_store_spec.rb +40 -0
  49. data/spec/google/api_client/auth/storages/redis_store_spec.rb +70 -0
  50. data/spec/google/api_client/batch_spec.rb +248 -0
  51. data/spec/google/api_client/client_secrets_spec.rb +53 -0
  52. data/spec/google/api_client/discovery_spec.rb +708 -0
  53. data/spec/google/api_client/gzip_spec.rb +98 -0
  54. data/spec/google/api_client/media_spec.rb +178 -0
  55. data/spec/google/api_client/request_spec.rb +29 -0
  56. data/spec/google/api_client/result_spec.rb +207 -0
  57. data/spec/google/api_client/service_account_spec.rb +169 -0
  58. data/spec/google/api_client/service_spec.rb +618 -0
  59. data/spec/google/api_client/simple_file_store_spec.rb +133 -0
  60. data/spec/google/api_client_spec.rb +352 -0
  61. data/spec/spec_helper.rb +66 -0
  62. metadata +339 -0
@@ -0,0 +1,19 @@
1
+ require 'multi_json'
2
+
3
+ if !MultiJson.respond_to?(:load) || [
4
+ Kernel,
5
+ defined?(ActiveSupport::Dependencies::Loadable) && ActiveSupport::Dependencies::Loadable
6
+ ].compact.include?(MultiJson.method(:load).owner)
7
+ module MultiJson
8
+ class <<self
9
+ alias :load :decode
10
+ end
11
+ end
12
+ end
13
+ if !MultiJson.respond_to?(:dump)
14
+ module MultiJson
15
+ class <<self
16
+ alias :dump :encode
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,750 @@
1
+ # Copyright 2010 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require 'faraday'
17
+ require 'multi_json'
18
+ require 'compat/multi_json'
19
+ require 'stringio'
20
+ require 'retriable'
21
+
22
+ require 'google/api_client/version'
23
+ require 'google/api_client/logging'
24
+ require 'google/api_client/errors'
25
+ require 'google/api_client/environment'
26
+ require 'google/api_client/discovery'
27
+ require 'google/api_client/request'
28
+ require 'google/api_client/reference'
29
+ require 'google/api_client/result'
30
+ require 'google/api_client/media'
31
+ require 'google/api_client/service_account'
32
+ require 'google/api_client/batch'
33
+ require 'google/api_client/gzip'
34
+ require 'google/api_client/charset'
35
+ require 'google/api_client/client_secrets'
36
+ require 'google/api_client/railtie' if defined?(Rails)
37
+
38
+ module Google
39
+
40
+ ##
41
+ # This class manages APIs communication.
42
+ class APIClient
43
+ include Google::APIClient::Logging
44
+
45
+ ##
46
+ # Creates a new Google API client.
47
+ #
48
+ # @param [Hash] options The configuration parameters for the client.
49
+ # @option options [Symbol, #generate_authenticated_request] :authorization
50
+ # (:oauth_1)
51
+ # The authorization mechanism used by the client. The following
52
+ # mechanisms are supported out-of-the-box:
53
+ # <ul>
54
+ # <li><code>:two_legged_oauth_1</code></li>
55
+ # <li><code>:oauth_1</code></li>
56
+ # <li><code>:oauth_2</code></li>
57
+ # <li><code>:google_app_default</code></li>
58
+ # </ul>
59
+ # @option options [Boolean] :auto_refresh_token (true)
60
+ # The setting that controls whether or not the api client attempts to
61
+ # refresh authorization when a 401 is hit in #execute. If the token does
62
+ # not support it, this option is ignored.
63
+ # @option options [String] :application_name
64
+ # The name of the application using the client.
65
+ # @option options [String | Array | nil] :scope
66
+ # The scope(s) used when using google application default credentials
67
+ # @option options [String] :application_version
68
+ # The version number of the application using the client.
69
+ # @option options [String] :user_agent
70
+ # ("{app_name} google-api-ruby-client/{version} {os_name}/{os_version}")
71
+ # The user agent used by the client. Most developers will want to
72
+ # leave this value alone and use the `:application_name` option instead.
73
+ # @option options [String] :host ("www.googleapis.com")
74
+ # The API hostname used by the client. This rarely needs to be changed.
75
+ # @option options [String] :port (443)
76
+ # The port number used by the client. This rarely needs to be changed.
77
+ # @option options [String] :discovery_path ("/discovery/v1")
78
+ # The discovery base path. This rarely needs to be changed.
79
+ # @option options [String] :ca_file
80
+ # Optional set of root certificates to use when validating SSL connections.
81
+ # By default, a bundled set of trusted roots will be used.
82
+ # @options options[Hash] :force_encoding
83
+ # Experimental option. True if response body should be force encoded into the charset
84
+ # specified in the Content-Type header. Mostly intended for compressed content.
85
+ # @options options[Hash] :faraday_options
86
+ # Pass through of options to set on the Faraday connection
87
+ def initialize(options={})
88
+ logger.debug { "#{self.class} - Initializing client with options #{options}" }
89
+
90
+ # Normalize key to String to allow indifferent access.
91
+ options = options.inject({}) do |accu, (key, value)|
92
+ accu[key.to_sym] = value
93
+ accu
94
+ end
95
+ # Almost all API usage will have a host of 'www.googleapis.com'.
96
+ self.host = options[:host] || 'www.googleapis.com'
97
+ self.port = options[:port] || 443
98
+ self.discovery_path = options[:discovery_path] || '/discovery/v1'
99
+
100
+ # Most developers will want to leave this value alone and use the
101
+ # application_name option.
102
+ if options[:application_name]
103
+ app_name = options[:application_name]
104
+ app_version = options[:application_version]
105
+ application_string = "#{app_name}/#{app_version || '0.0.0'}"
106
+ else
107
+ logger.warn { "#{self.class} - Please provide :application_name and :application_version when initializing the client" }
108
+ end
109
+
110
+ proxy = options[:proxy] || Object::ENV["http_proxy"]
111
+
112
+ self.user_agent = options[:user_agent] || (
113
+ "#{application_string} " +
114
+ "google-api-ruby-client/#{Google::APIClient::VERSION::STRING} #{ENV::OS_VERSION}".strip + " (gzip)"
115
+ ).strip
116
+ # The writer method understands a few Symbols and will generate useful
117
+ # default authentication mechanisms.
118
+ self.authorization =
119
+ options.key?(:authorization) ? options[:authorization] : :oauth_2
120
+ if !options['scope'].nil? and self.authorization.respond_to?(:scope=)
121
+ self.authorization.scope = options['scope']
122
+ end
123
+ self.auto_refresh_token = options.fetch(:auto_refresh_token) { true }
124
+ self.key = options[:key]
125
+ self.user_ip = options[:user_ip]
126
+ self.retries = options.fetch(:retries) { 0 }
127
+ self.expired_auth_retry = options.fetch(:expired_auth_retry) { true }
128
+ @discovery_uris = {}
129
+ @discovery_documents = {}
130
+ @discovered_apis = {}
131
+ ca_file = options[:ca_file] || File.expand_path('../../cacerts.pem', __FILE__)
132
+ self.connection = Faraday.new do |faraday|
133
+ faraday.response :charset if options[:force_encoding]
134
+ faraday.response :gzip
135
+ faraday.options.params_encoder = Faraday::FlatParamsEncoder
136
+ faraday.ssl.ca_file = ca_file
137
+ faraday.ssl.verify = true
138
+ faraday.proxy proxy
139
+ faraday.adapter Faraday.default_adapter
140
+ if options[:faraday_option].is_a?(Hash)
141
+ options[:faraday_option].each_pair do |option, value|
142
+ faraday.options.send("#{option}=", value)
143
+ end
144
+ end
145
+ end
146
+ return self
147
+ end
148
+
149
+ ##
150
+ # Returns the authorization mechanism used by the client.
151
+ #
152
+ # @return [#generate_authenticated_request] The authorization mechanism.
153
+ attr_reader :authorization
154
+
155
+ ##
156
+ # Sets the authorization mechanism used by the client.
157
+ #
158
+ # @param [#generate_authenticated_request] new_authorization
159
+ # The new authorization mechanism.
160
+ def authorization=(new_authorization)
161
+ case new_authorization
162
+ when :oauth_1, :oauth
163
+ require 'signet/oauth_1/client'
164
+ # NOTE: Do not rely on this default value, as it may change
165
+ new_authorization = Signet::OAuth1::Client.new(
166
+ :temporary_credential_uri =>
167
+ 'https://www.google.com/accounts/OAuthGetRequestToken',
168
+ :authorization_uri =>
169
+ 'https://www.google.com/accounts/OAuthAuthorizeToken',
170
+ :token_credential_uri =>
171
+ 'https://www.google.com/accounts/OAuthGetAccessToken',
172
+ :client_credential_key => 'anonymous',
173
+ :client_credential_secret => 'anonymous'
174
+ )
175
+ when :two_legged_oauth_1, :two_legged_oauth
176
+ require 'signet/oauth_1/client'
177
+ # NOTE: Do not rely on this default value, as it may change
178
+ new_authorization = Signet::OAuth1::Client.new(
179
+ :client_credential_key => nil,
180
+ :client_credential_secret => nil,
181
+ :two_legged => true
182
+ )
183
+ when :google_app_default
184
+ require 'googleauth'
185
+ new_authorization = Google::Auth.get_application_default
186
+
187
+ when :oauth_2
188
+ require 'signet/oauth_2/client'
189
+ # NOTE: Do not rely on this default value, as it may change
190
+ new_authorization = Signet::OAuth2::Client.new(
191
+ :authorization_uri =>
192
+ 'https://accounts.google.com/o/oauth2/auth',
193
+ :token_credential_uri =>
194
+ 'https://accounts.google.com/o/oauth2/token'
195
+ )
196
+ when nil
197
+ # No authorization mechanism
198
+ else
199
+ if !new_authorization.respond_to?(:generate_authenticated_request)
200
+ raise TypeError,
201
+ 'Expected authorization mechanism to respond to ' +
202
+ '#generate_authenticated_request.'
203
+ end
204
+ end
205
+ @authorization = new_authorization
206
+ return @authorization
207
+ end
208
+
209
+ ##
210
+ # Default Faraday/HTTP connection.
211
+ #
212
+ # @return [Faraday::Connection]
213
+ attr_accessor :connection
214
+
215
+ ##
216
+ # The setting that controls whether or not the api client attempts to
217
+ # refresh authorization when a 401 is hit in #execute.
218
+ #
219
+ # @return [Boolean]
220
+ attr_accessor :auto_refresh_token
221
+
222
+ ##
223
+ # The application's API key issued by the API console.
224
+ #
225
+ # @return [String] The API key.
226
+ attr_accessor :key
227
+
228
+ ##
229
+ # The IP address of the user this request is being performed on behalf of.
230
+ #
231
+ # @return [String] The user's IP address.
232
+ attr_accessor :user_ip
233
+
234
+ ##
235
+ # The user agent used by the client.
236
+ #
237
+ # @return [String]
238
+ # The user agent string used in the User-Agent header.
239
+ attr_accessor :user_agent
240
+
241
+ ##
242
+ # The API hostname used by the client.
243
+ #
244
+ # @return [String]
245
+ # The API hostname. Should almost always be 'www.googleapis.com'.
246
+ attr_accessor :host
247
+
248
+ ##
249
+ # The port number used by the client.
250
+ #
251
+ # @return [String]
252
+ # The port number. Should almost always be 443.
253
+ attr_accessor :port
254
+
255
+ ##
256
+ # The base path used by the client for discovery.
257
+ #
258
+ # @return [String]
259
+ # The base path. Should almost always be '/discovery/v1'.
260
+ attr_accessor :discovery_path
261
+
262
+ ##
263
+ # Number of times to retry on recoverable errors
264
+ #
265
+ # @return [FixNum]
266
+ # Number of retries
267
+ attr_accessor :retries
268
+
269
+ ##
270
+ # Whether or not an expired auth token should be re-acquired
271
+ # (and the operation retried) regardless of retries setting
272
+ # @return [Boolean]
273
+ # Auto retry on auth expiry
274
+ attr_accessor :expired_auth_retry
275
+
276
+ ##
277
+ # Returns the URI for the directory document.
278
+ #
279
+ # @return [Addressable::URI] The URI of the directory document.
280
+ def directory_uri
281
+ return resolve_uri(self.discovery_path + '/apis')
282
+ end
283
+
284
+ ##
285
+ # Manually registers a URI as a discovery document for a specific version
286
+ # of an API.
287
+ #
288
+ # @param [String, Symbol] api The API name.
289
+ # @param [String] version The desired version of the API.
290
+ # @param [Addressable::URI] uri The URI of the discovery document.
291
+ # @return [Google::APIClient::API] The service object.
292
+ def register_discovery_uri(api, version, uri)
293
+ api = api.to_s
294
+ version = version || 'v1'
295
+ @discovery_uris["#{api}:#{version}"] = uri
296
+ discovered_api(api, version)
297
+ end
298
+
299
+ ##
300
+ # Returns the URI for the discovery document.
301
+ #
302
+ # @param [String, Symbol] api The API name.
303
+ # @param [String] version The desired version of the API.
304
+ # @return [Addressable::URI] The URI of the discovery document.
305
+ def discovery_uri(api, version=nil)
306
+ api = api.to_s
307
+ version = version || 'v1'
308
+ return @discovery_uris["#{api}:#{version}"] ||= (
309
+ resolve_uri(
310
+ self.discovery_path + '/apis/{api}/{version}/rest',
311
+ 'api' => api,
312
+ 'version' => version
313
+ )
314
+ )
315
+ end
316
+
317
+ ##
318
+ # Manually registers a pre-loaded discovery document for a specific version
319
+ # of an API.
320
+ #
321
+ # @param [String, Symbol] api The API name.
322
+ # @param [String] version The desired version of the API.
323
+ # @param [String, StringIO] discovery_document
324
+ # The contents of the discovery document.
325
+ # @return [Google::APIClient::API] The service object.
326
+ def register_discovery_document(api, version, discovery_document)
327
+ api = api.to_s
328
+ version = version || 'v1'
329
+ if discovery_document.kind_of?(StringIO)
330
+ discovery_document.rewind
331
+ discovery_document = discovery_document.string
332
+ elsif discovery_document.respond_to?(:to_str)
333
+ discovery_document = discovery_document.to_str
334
+ else
335
+ raise TypeError,
336
+ "Expected String or StringIO, got #{discovery_document.class}."
337
+ end
338
+ @discovery_documents["#{api}:#{version}"] =
339
+ MultiJson.load(discovery_document)
340
+ discovered_api(api, version)
341
+ end
342
+
343
+ ##
344
+ # Returns the parsed directory document.
345
+ #
346
+ # @return [Hash] The parsed JSON from the directory document.
347
+ def directory_document
348
+ return @directory_document ||= (begin
349
+ response = self.execute!(
350
+ :http_method => :get,
351
+ :uri => self.directory_uri,
352
+ :authenticated => false
353
+ )
354
+ response.data
355
+ end)
356
+ end
357
+
358
+ ##
359
+ # Returns the parsed discovery document.
360
+ #
361
+ # @param [String, Symbol] api The API name.
362
+ # @param [String] version The desired version of the API.
363
+ # @return [Hash] The parsed JSON from the discovery document.
364
+ def discovery_document(api, version=nil)
365
+ api = api.to_s
366
+ version = version || 'v1'
367
+ return @discovery_documents["#{api}:#{version}"] ||= (begin
368
+ response = self.execute!(
369
+ :http_method => :get,
370
+ :uri => self.discovery_uri(api, version),
371
+ :authenticated => false
372
+ )
373
+ response.data
374
+ end)
375
+ end
376
+
377
+ ##
378
+ # Returns all APIs published in the directory document.
379
+ #
380
+ # @return [Array] The list of available APIs.
381
+ def discovered_apis
382
+ @directory_apis ||= (begin
383
+ document_base = self.directory_uri
384
+ if self.directory_document && self.directory_document['items']
385
+ self.directory_document['items'].map do |discovery_document|
386
+ Google::APIClient::API.new(
387
+ document_base,
388
+ discovery_document
389
+ )
390
+ end
391
+ else
392
+ []
393
+ end
394
+ end)
395
+ end
396
+
397
+ ##
398
+ # Returns the service object for a given service name and service version.
399
+ #
400
+ # @param [String, Symbol] api The API name.
401
+ # @param [String] version The desired version of the API.
402
+ #
403
+ # @return [Google::APIClient::API] The service object.
404
+ def discovered_api(api, version=nil)
405
+ if !api.kind_of?(String) && !api.kind_of?(Symbol)
406
+ raise TypeError,
407
+ "Expected String or Symbol, got #{api.class}."
408
+ end
409
+ api = api.to_s
410
+ version = version || 'v1'
411
+ return @discovered_apis["#{api}:#{version}"] ||= begin
412
+ document_base = self.discovery_uri(api, version)
413
+ discovery_document = self.discovery_document(api, version)
414
+ if document_base && discovery_document
415
+ Google::APIClient::API.new(
416
+ document_base,
417
+ discovery_document
418
+ )
419
+ else
420
+ nil
421
+ end
422
+ end
423
+ end
424
+
425
+ ##
426
+ # Returns the method object for a given RPC name and service version.
427
+ #
428
+ # @param [String, Symbol] rpc_name The RPC name of the desired method.
429
+ # @param [String, Symbol] api The API the method is within.
430
+ # @param [String] version The desired version of the API.
431
+ #
432
+ # @return [Google::APIClient::Method] The method object.
433
+ def discovered_method(rpc_name, api, version=nil)
434
+ if !rpc_name.kind_of?(String) && !rpc_name.kind_of?(Symbol)
435
+ raise TypeError,
436
+ "Expected String or Symbol, got #{rpc_name.class}."
437
+ end
438
+ rpc_name = rpc_name.to_s
439
+ api = api.to_s
440
+ version = version || 'v1'
441
+ service = self.discovered_api(api, version)
442
+ if service.to_h[rpc_name]
443
+ return service.to_h[rpc_name]
444
+ else
445
+ return nil
446
+ end
447
+ end
448
+
449
+ ##
450
+ # Returns the service object with the highest version number.
451
+ #
452
+ # @note <em>Warning</em>: This method should be used with great care.
453
+ # As APIs are updated, minor differences between versions may cause
454
+ # incompatibilities. Requesting a specific version will avoid this issue.
455
+ #
456
+ # @param [String, Symbol] api The name of the service.
457
+ #
458
+ # @return [Google::APIClient::API] The service object.
459
+ def preferred_version(api)
460
+ if !api.kind_of?(String) && !api.kind_of?(Symbol)
461
+ raise TypeError,
462
+ "Expected String or Symbol, got #{api.class}."
463
+ end
464
+ api = api.to_s
465
+ return self.discovered_apis.detect do |a|
466
+ a.name == api && a.preferred == true
467
+ end
468
+ end
469
+
470
+ ##
471
+ # Verifies an ID token against a server certificate. Used to ensure that
472
+ # an ID token supplied by an untrusted client-side mechanism is valid.
473
+ # Raises an error if the token is invalid or missing.
474
+ #
475
+ # @deprecated Use the google-id-token gem for verifying JWTs
476
+ def verify_id_token!
477
+ require 'jwt'
478
+ require 'openssl'
479
+ @certificates ||= {}
480
+ if !self.authorization.respond_to?(:id_token)
481
+ raise ArgumentError, (
482
+ "Current authorization mechanism does not support ID tokens: " +
483
+ "#{self.authorization.class.to_s}"
484
+ )
485
+ elsif !self.authorization.id_token
486
+ raise ArgumentError, (
487
+ "Could not verify ID token, ID token missing. " +
488
+ "Scopes were: #{self.authorization.scope.inspect}"
489
+ )
490
+ else
491
+ check_cached_certs = lambda do
492
+ valid = false
493
+ for _key, cert in @certificates
494
+ begin
495
+ self.authorization.decoded_id_token(cert.public_key)
496
+ valid = true
497
+ rescue JWT::DecodeError, Signet::UnsafeOperationError
498
+ # Expected exception. Ignore, ID token has not been validated.
499
+ end
500
+ end
501
+ valid
502
+ end
503
+ if check_cached_certs.call()
504
+ return true
505
+ end
506
+ response = self.execute!(
507
+ :http_method => :get,
508
+ :uri => 'https://www.googleapis.com/oauth2/v1/certs',
509
+ :authenticated => false
510
+ )
511
+ @certificates.merge!(
512
+ Hash[MultiJson.load(response.body).map do |key, cert|
513
+ [key, OpenSSL::X509::Certificate.new(cert)]
514
+ end]
515
+ )
516
+ if check_cached_certs.call()
517
+ return true
518
+ else
519
+ raise InvalidIDTokenError,
520
+ "Could not verify ID token against any available certificate."
521
+ end
522
+ end
523
+ return nil
524
+ end
525
+
526
+ ##
527
+ # Generates a request.
528
+ #
529
+ # @option options [Google::APIClient::Method] :api_method
530
+ # The method object or the RPC name of the method being executed.
531
+ # @option options [Hash, Array] :parameters
532
+ # The parameters to send to the method.
533
+ # @option options [Hash, Array] :headers The HTTP headers for the request.
534
+ # @option options [String] :body The body of the request.
535
+ # @option options [String] :version ("v1")
536
+ # The service version. Only used if `api_method` is a `String`.
537
+ # @option options [#generate_authenticated_request] :authorization
538
+ # The authorization mechanism for the response. Used only if
539
+ # `:authenticated` is `true`.
540
+ # @option options [TrueClass, FalseClass] :authenticated (true)
541
+ # `true` if the request must be signed or somehow
542
+ # authenticated, `false` otherwise.
543
+ #
544
+ # @return [Google::APIClient::Reference] The generated request.
545
+ #
546
+ # @example
547
+ # request = client.generate_request(
548
+ # :api_method => 'plus.activities.list',
549
+ # :parameters =>
550
+ # {'collection' => 'public', 'userId' => 'me'}
551
+ # )
552
+ def generate_request(options={})
553
+ options = {
554
+ :api_client => self
555
+ }.merge(options)
556
+ return Google::APIClient::Request.new(options)
557
+ end
558
+
559
+ ##
560
+ # Executes a request, wrapping it in a Result object.
561
+ #
562
+ # @param [Google::APIClient::Request, Hash, Array] params
563
+ # Either a Google::APIClient::Request, a Hash, or an Array.
564
+ #
565
+ # If a Google::APIClient::Request, no other parameters are expected.
566
+ #
567
+ # If a Hash, the below parameters are handled. If an Array, the
568
+ # parameters are assumed to be in the below order:
569
+ #
570
+ # - (Google::APIClient::Method) api_method:
571
+ # The method object or the RPC name of the method being executed.
572
+ # - (Hash, Array) parameters:
573
+ # The parameters to send to the method.
574
+ # - (String) body: The body of the request.
575
+ # - (Hash, Array) headers: The HTTP headers for the request.
576
+ # - (Hash) options: A set of options for the request, of which:
577
+ # - (#generate_authenticated_request) :authorization (default: true) -
578
+ # The authorization mechanism for the response. Used only if
579
+ # `:authenticated` is `true`.
580
+ # - (TrueClass, FalseClass) :authenticated (default: true) -
581
+ # `true` if the request must be signed or somehow
582
+ # authenticated, `false` otherwise.
583
+ # - (TrueClass, FalseClass) :gzip (default: true) -
584
+ # `true` if gzip enabled, `false` otherwise.
585
+ # - (FixNum) :retries -
586
+ # # of times to retry on recoverable errors
587
+ #
588
+ # @return [Google::APIClient::Result] The result from the API, nil if batch.
589
+ #
590
+ # @example
591
+ # result = client.execute(batch_request)
592
+ #
593
+ # @example
594
+ # plus = client.discovered_api('plus')
595
+ # result = client.execute(
596
+ # :api_method => plus.activities.list,
597
+ # :parameters => {'collection' => 'public', 'userId' => 'me'}
598
+ # )
599
+ #
600
+ # @see Google::APIClient#generate_request
601
+ def execute!(*params)
602
+ if params.first.kind_of?(Google::APIClient::Request)
603
+ request = params.shift
604
+ options = params.shift || {}
605
+ else
606
+ # This block of code allows us to accept multiple parameter passing
607
+ # styles, and maintaining some backwards compatibility.
608
+ #
609
+ # Note: I'm extremely tempted to deprecate this style of execute call.
610
+ if params.last.respond_to?(:to_hash) && params.size == 1
611
+ options = params.pop
612
+ else
613
+ options = {}
614
+ end
615
+
616
+ options[:api_method] = params.shift if params.size > 0
617
+ options[:parameters] = params.shift if params.size > 0
618
+ options[:body] = params.shift if params.size > 0
619
+ options[:headers] = params.shift if params.size > 0
620
+ options.update(params.shift) if params.size > 0
621
+ request = self.generate_request(options)
622
+ end
623
+
624
+ request.headers['User-Agent'] ||= '' + self.user_agent unless self.user_agent.nil?
625
+ request.headers['Accept-Encoding'] ||= 'gzip' unless options[:gzip] == false
626
+ request.headers['Content-Type'] ||= ''
627
+ request.parameters['key'] ||= self.key unless self.key.nil?
628
+ request.parameters['userIp'] ||= self.user_ip unless self.user_ip.nil?
629
+
630
+ connection = options[:connection] || self.connection
631
+ request.authorization = options[:authorization] || self.authorization unless options[:authenticated] == false
632
+
633
+ tries = 1 + (options[:retries] || self.retries)
634
+ attempt = 0
635
+
636
+ Retriable.retriable :tries => tries,
637
+ :on => [TransmissionError],
638
+ :on_retry => client_error_handler,
639
+ :interval => lambda {|attempts| (2 ** attempts) + rand} do
640
+ attempt += 1
641
+
642
+ # This 2nd level retriable only catches auth errors, and supports 1 retry, which allows
643
+ # auth to be re-attempted without having to retry all sorts of other failures like
644
+ # NotFound, etc
645
+ Retriable.retriable :tries => ((expired_auth_retry || tries > 1) && attempt == 1) ? 2 : 1,
646
+ :on => [AuthorizationError],
647
+ :on_retry => authorization_error_handler(request.authorization) do
648
+ result = request.send(connection, true)
649
+
650
+ case result.status
651
+ when 200...300
652
+ result
653
+ when 301, 302, 303, 307
654
+ request = generate_request(request.to_hash.merge({
655
+ :uri => result.headers['location'],
656
+ :api_method => nil
657
+ }))
658
+ raise RedirectError.new(result.headers['location'], result)
659
+ when 401
660
+ raise AuthorizationError.new(result.error_message || 'Invalid/Expired Authentication', result)
661
+ when 400, 402...500
662
+ raise ClientError.new(result.error_message || "A client error has occurred", result)
663
+ when 500...600
664
+ raise ServerError.new(result.error_message || "A server error has occurred", result)
665
+ else
666
+ raise TransmissionError.new(result.error_message || "A transmission error has occurred", result)
667
+ end
668
+ end
669
+ end
670
+ end
671
+
672
+ ##
673
+ # Same as Google::APIClient#execute!, but does not raise an exception for
674
+ # normal API errros.
675
+ #
676
+ # @see Google::APIClient#execute
677
+ def execute(*params)
678
+ begin
679
+ return self.execute!(*params)
680
+ rescue TransmissionError => e
681
+ return e.result
682
+ end
683
+ end
684
+
685
+ protected
686
+
687
+ ##
688
+ # Resolves a URI template against the client's configured base.
689
+ #
690
+ # @api private
691
+ # @param [String, Addressable::URI, Addressable::Template] template
692
+ # The template to resolve.
693
+ # @param [Hash] mapping The mapping that corresponds to the template.
694
+ # @return [Addressable::URI] The expanded URI.
695
+ def resolve_uri(template, mapping={})
696
+ @base_uri ||= Addressable::URI.new(
697
+ :scheme => 'https',
698
+ :host => self.host,
699
+ :port => self.port
700
+ ).normalize
701
+ template = if template.kind_of?(Addressable::Template)
702
+ template.pattern
703
+ elsif template.respond_to?(:to_str)
704
+ template.to_str
705
+ else
706
+ raise TypeError,
707
+ "Expected String, Addressable::URI, or Addressable::Template, " +
708
+ "got #{template.class}."
709
+ end
710
+ return Addressable::Template.new(@base_uri + template).expand(mapping)
711
+ end
712
+
713
+
714
+ ##
715
+ # Returns on proc for special processing of retries for authorization errors
716
+ # Only 401s should be retried and only if the credentials are refreshable
717
+ #
718
+ # @param [#fetch_access_token!] authorization
719
+ # OAuth 2 credentials
720
+ # @return [Proc]
721
+ def authorization_error_handler(authorization)
722
+ can_refresh = authorization.respond_to?(:refresh_token) && auto_refresh_token
723
+ Proc.new do |exception, tries|
724
+ next unless exception.kind_of?(AuthorizationError)
725
+ if can_refresh
726
+ begin
727
+ logger.debug("Attempting refresh of access token & retry of request")
728
+ authorization.fetch_access_token!
729
+ next
730
+ rescue Signet::AuthorizationError
731
+ end
732
+ end
733
+ raise exception
734
+ end
735
+ end
736
+
737
+ ##
738
+ # Returns on proc for special processing of retries as not all client errors
739
+ # are recoverable. Only 401s should be retried (via authorization_error_handler)
740
+ #
741
+ # @return [Proc]
742
+ def client_error_handler
743
+ Proc.new do |exception, tries|
744
+ raise exception if exception.kind_of?(ClientError)
745
+ end
746
+ end
747
+
748
+ end
749
+
750
+ end