Two failing tests, neither of which I can do much about.
[arvados.git] / lib / google / api_client.rb
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 gem 'faraday', '~> 0.8.1'
17 require 'faraday'
18 require 'faraday/utils'
19 require 'multi_json'
20 require 'stringio'
21
22 require 'google/api_client/version'
23 require 'google/api_client/errors'
24 require 'google/api_client/environment'
25 require 'google/api_client/discovery'
26 require 'google/api_client/reference'
27 require 'google/api_client/result'
28 require 'google/api_client/media'
29 require 'google/api_client/service_account'
30 require 'google/api_client/batch'
31
32 module Google
33   # TODO(bobaman): Document all this stuff.
34
35
36   ##
37   # This class manages APIs communication.
38   class APIClient
39     ##
40     # Creates a new Google API client.
41     #
42     # @param [Hash] options The configuration parameters for the client.
43     # @option options [Symbol, #generate_authenticated_request] :authorization
44     #   (:oauth_1)
45     #   The authorization mechanism used by the client.  The following
46     #   mechanisms are supported out-of-the-box:
47     #   <ul>
48     #     <li><code>:two_legged_oauth_1</code></li>
49     #     <li><code>:oauth_1</code></li>
50     #     <li><code>:oauth_2</code></li>
51     #   </ul>
52     # @option options [String] :application_name
53     #   The name of the application using the client.
54     # @option options [String] :application_version
55     #   The version number of the application using the client.
56     # @option options [String] :user_agent
57     #   ("{app_name} google-api-ruby-client/{version} {os_name}/{os_version}")
58     #   The user agent used by the client.  Most developers will want to
59     #   leave this value alone and use the `:application_name` option instead.
60     # @option options [String] :host ("www.googleapis.com")
61     #   The API hostname used by the client. This rarely needs to be changed.
62     # @option options [String] :port (443)
63     #   The port number used by the client. This rarely needs to be changed.
64     # @option options [String] :discovery_path ("/discovery/v1")
65     #   The discovery base path. This rarely needs to be changed.
66     def initialize(options={})
67       # Normalize key to String to allow indifferent access.
68       options = options.inject({}) do |accu, (key, value)|
69         accu[key.to_s] = value
70         accu
71       end
72       # Almost all API usage will have a host of 'www.googleapis.com'.
73       self.host = options["host"] || 'www.googleapis.com'
74       self.port = options["port"] || 443
75       self.discovery_path = options["discovery_path"] || '/discovery/v1'
76
77       # Most developers will want to leave this value alone and use the
78       # application_name option.
79       application_string = (
80         options["application_name"] ? (
81           "#{options["application_name"]}/" +
82           "#{options["application_version"] || '0.0.0'}"
83         ) : ""
84       )
85       self.user_agent = options["user_agent"] || (
86         "#{application_string} " +
87         "google-api-ruby-client/#{VERSION::STRING} " +
88          ENV::OS_VERSION
89       ).strip
90       # The writer method understands a few Symbols and will generate useful
91       # default authentication mechanisms.
92       self.authorization =
93         options.key?("authorization") ? options["authorization"] : :oauth_2
94       self.key = options["key"]
95       self.user_ip = options["user_ip"]
96       @discovery_uris = {}
97       @discovery_documents = {}
98       @discovered_apis = {}
99       return self
100     end
101
102     ##
103     # Returns the authorization mechanism used by the client.
104     #
105     # @return [#generate_authenticated_request] The authorization mechanism.
106     attr_reader :authorization
107
108     ##
109     # Sets the authorization mechanism used by the client.
110     #
111     # @param [#generate_authenticated_request] new_authorization
112     #   The new authorization mechanism.
113     def authorization=(new_authorization)
114       case new_authorization
115       when :oauth_1, :oauth
116         gem 'signet', '~> 0.4.0'
117         require 'signet/oauth_1/client'
118         # NOTE: Do not rely on this default value, as it may change
119         new_authorization = Signet::OAuth1::Client.new(
120           :temporary_credential_uri =>
121             'https://www.google.com/accounts/OAuthGetRequestToken',
122           :authorization_uri =>
123             'https://www.google.com/accounts/OAuthAuthorizeToken',
124           :token_credential_uri =>
125             'https://www.google.com/accounts/OAuthGetAccessToken',
126           :client_credential_key => 'anonymous',
127           :client_credential_secret => 'anonymous'
128         )
129       when :two_legged_oauth_1, :two_legged_oauth
130         gem 'signet', '~> 0.4.0'
131         require 'signet/oauth_1/client'
132         # NOTE: Do not rely on this default value, as it may change
133         new_authorization = Signet::OAuth1::Client.new(
134           :client_credential_key => nil,
135           :client_credential_secret => nil,
136           :two_legged => true
137         )
138       when :oauth_2
139         gem 'signet', '~> 0.4.0'
140         require 'signet/oauth_2/client'
141         # NOTE: Do not rely on this default value, as it may change
142         new_authorization = Signet::OAuth2::Client.new(
143           :authorization_uri =>
144             'https://accounts.google.com/o/oauth2/auth',
145           :token_credential_uri =>
146             'https://accounts.google.com/o/oauth2/token'
147         )
148       when nil
149         # No authorization mechanism
150       else
151         if !new_authorization.respond_to?(:generate_authenticated_request)
152           raise TypeError,
153             'Expected authorization mechanism to respond to ' +
154             '#generate_authenticated_request.'
155         end
156       end
157       @authorization = new_authorization
158       return @authorization
159     end
160
161     ##
162     # The application's API key issued by the API console.
163     #
164     # @return [String] The API key.
165     attr_accessor :key
166
167     ##
168     # The IP address of the user this request is being performed on behalf of.
169     #
170     # @return [String] The user's IP address.
171     attr_accessor :user_ip
172
173     ##
174     # The user agent used by the client.
175     #
176     # @return [String]
177     #   The user agent string used in the User-Agent header.
178     attr_accessor :user_agent
179
180     ##
181     # The API hostname used by the client.
182     #
183     # @return [String]
184     #   The API hostname. Should almost always be 'www.googleapis.com'.
185     attr_accessor :host
186
187     ##
188     # The port number used by the client.
189     #
190     # @return [String]
191     #   The port number. Should almost always be 443.
192     attr_accessor :port
193
194     ##
195     # The base path used by the client for discovery.
196     #
197     # @return [String]
198     #   The base path. Should almost always be '/discovery/v1'.
199     attr_accessor :discovery_path
200
201     ##
202     # Resolves a URI template against the client's configured base.
203     #
204     # @param [String, Addressable::URI, Addressable::Template] template
205     #   The template to resolve.
206     # @param [Hash] mapping The mapping that corresponds to the template.
207     # @return [Addressable::URI] The expanded URI.
208     def resolve_uri(template, mapping={})
209       @base_uri ||= Addressable::URI.new(
210         :scheme => 'https',
211         :host => self.host,
212         :port => self.port
213       ).normalize
214       template = if template.kind_of?(Addressable::Template)
215         template.pattern
216       elsif template.respond_to?(:to_str)
217         template.to_str
218       else
219         raise TypeError,
220           "Expected String, Addressable::URI, or Addressable::Template, " +
221           "got #{template.class}."
222       end
223       return Addressable::Template.new(@base_uri + template).expand(mapping)
224     end
225
226     ##
227     # Returns the URI for the directory document.
228     #
229     # @return [Addressable::URI] The URI of the directory document.
230     def directory_uri
231       return resolve_uri(self.discovery_path + '/apis')
232     end
233
234     ##
235     # Manually registers a URI as a discovery document for a specific version
236     # of an API.
237     #
238     # @param [String, Symbol] api The API name.
239     # @param [String] version The desired version of the API.
240     # @param [Addressable::URI] uri The URI of the discovery document.
241     def register_discovery_uri(api, version, uri)
242       api = api.to_s
243       version = version || 'v1'
244       @discovery_uris["#{api}:#{version}"] = uri
245     end
246
247     ##
248     # Returns the URI for the discovery document.
249     #
250     # @param [String, Symbol] api The API name.
251     # @param [String] version The desired version of the API.
252     # @return [Addressable::URI] The URI of the discovery document.
253     def discovery_uri(api, version=nil)
254       api = api.to_s
255       version = version || 'v1'
256       return @discovery_uris["#{api}:#{version}"] ||= (
257         resolve_uri(
258           self.discovery_path + '/apis/{api}/{version}/rest',
259           'api' => api,
260           'version' => version
261         )
262       )
263     end
264
265     ##
266     # Manually registers a pre-loaded discovery document for a specific version
267     # of an API.
268     #
269     # @param [String, Symbol] api The API name.
270     # @param [String] version The desired version of the API.
271     # @param [String, StringIO] discovery_document
272     #   The contents of the discovery document.
273     def register_discovery_document(api, version, discovery_document)
274       api = api.to_s
275       version = version || 'v1'
276       if discovery_document.kind_of?(StringIO)
277         discovery_document.rewind
278         discovery_document = discovery_document.string
279       elsif discovery_document.respond_to?(:to_str)
280         discovery_document = discovery_document.to_str
281       else
282         raise TypeError,
283           "Expected String or StringIO, got #{discovery_document.class}."
284       end
285       @discovery_documents["#{api}:#{version}"] =
286         MultiJson.load(discovery_document)
287     end
288
289     ##
290     # Returns the parsed directory document.
291     #
292     # @return [Hash] The parsed JSON from the directory document.
293     def directory_document
294       return @directory_document ||= (begin
295         request = self.generate_request(
296           :http_method => :get,
297           :uri => self.directory_uri,
298           :authenticated => false
299         )
300         response = self.transmit(:request => request)
301         if response.status >= 200 && response.status < 300
302           MultiJson.load(response.body)
303         elsif response.status >= 400
304           case response.status
305           when 400...500
306             exception_type = ClientError
307           when 500...600
308             exception_type = ServerError
309           else
310             exception_type = TransmissionError
311           end
312           url = request.to_env(Faraday.default_connection)[:url]
313           raise exception_type,
314             "Could not retrieve directory document at: #{url}"
315         end
316       end)
317     end
318
319     ##
320     # Returns the parsed discovery document.
321     #
322     # @param [String, Symbol] api The API name.
323     # @param [String] version The desired version of the API.
324     # @return [Hash] The parsed JSON from the discovery document.
325     def discovery_document(api, version=nil)
326       api = api.to_s
327       version = version || 'v1'
328       return @discovery_documents["#{api}:#{version}"] ||= (begin
329         request = self.generate_request(
330           :http_method => :get,
331           :uri => self.discovery_uri(api, version),
332           :authenticated => false
333         )
334         response = self.transmit(:request => request)
335         if response.status >= 200 && response.status < 300
336           MultiJson.load(response.body)
337         elsif response.status >= 400
338           case response.status
339           when 400...500
340             exception_type = ClientError
341           when 500...600
342             exception_type = ServerError
343           else
344             exception_type = TransmissionError
345           end
346           url = request.to_env(Faraday.default_connection)[:url]
347           raise exception_type,
348             "Could not retrieve discovery document at: #{url}"
349         end
350       end)
351     end
352
353     ##
354     # Returns all APIs published in the directory document.
355     #
356     # @return [Array] The list of available APIs.
357     def discovered_apis
358       @directory_apis ||= (begin
359         document_base = self.directory_uri
360         if self.directory_document && self.directory_document['items']
361           self.directory_document['items'].map do |discovery_document|
362             Google::APIClient::API.new(
363               document_base,
364               discovery_document
365             )
366           end
367         else
368           []
369         end
370       end)
371     end
372
373     ##
374     # Returns the service object for a given service name and service version.
375     #
376     # @param [String, Symbol] api The API name.
377     # @param [String] version The desired version of the API.
378     #
379     # @return [Google::APIClient::API] The service object.
380     def discovered_api(api, version=nil)
381       if !api.kind_of?(String) && !api.kind_of?(Symbol)
382         raise TypeError,
383           "Expected String or Symbol, got #{api.class}."
384       end
385       api = api.to_s
386       version = version || 'v1'
387       return @discovered_apis["#{api}:#{version}"] ||= begin
388         document_base = self.discovery_uri(api, version)
389         discovery_document = self.discovery_document(api, version)
390         if document_base && discovery_document
391           Google::APIClient::API.new(
392             document_base,
393             discovery_document
394           )
395         else
396           nil
397         end
398       end
399     end
400
401     ##
402     # Returns the method object for a given RPC name and service version.
403     #
404     # @param [String, Symbol] rpc_name The RPC name of the desired method.
405     # @param [String, Symbol] rpc_name The API the method is within.
406     # @param [String] version The desired version of the API.
407     #
408     # @return [Google::APIClient::Method] The method object.
409     def discovered_method(rpc_name, api, version=nil)
410       if !rpc_name.kind_of?(String) && !rpc_name.kind_of?(Symbol)
411         raise TypeError,
412           "Expected String or Symbol, got #{rpc_name.class}."
413       end
414       rpc_name = rpc_name.to_s
415       api = api.to_s
416       version = version || 'v1'
417       service = self.discovered_api(api, version)
418       if service.to_h[rpc_name]
419         return service.to_h[rpc_name]
420       else
421         return nil
422       end
423     end
424
425     ##
426     # Returns the service object with the highest version number.
427     #
428     # @note <em>Warning</em>: This method should be used with great care.
429     # As APIs are updated, minor differences between versions may cause
430     # incompatibilities. Requesting a specific version will avoid this issue.
431     #
432     # @param [String, Symbol] api The name of the service.
433     #
434     # @return [Google::APIClient::API] The service object.
435     def preferred_version(api)
436       if !api.kind_of?(String) && !api.kind_of?(Symbol)
437         raise TypeError,
438           "Expected String or Symbol, got #{api.class}."
439       end
440       api = api.to_s
441       return self.discovered_apis.detect do |a|
442         a.name == api && a.preferred == true
443       end
444     end
445
446     ##
447     # Verifies an ID token against a server certificate. Used to ensure that
448     # an ID token supplied by an untrusted client-side mechanism is valid.
449     # Raises an error if the token is invalid or missing.
450     def verify_id_token!
451       gem 'jwt', '~> 0.1.4'
452       require 'jwt'
453       require 'openssl'
454       @certificates ||= {}
455       if !self.authorization.respond_to?(:id_token)
456         raise ArgumentError, (
457           "Current authorization mechanism does not support ID tokens: " +
458           "#{self.authorization.class.to_s}"
459         )
460       elsif !self.authorization.id_token
461         raise ArgumentError, (
462           "Could not verify ID token, ID token missing. " +
463           "Scopes were: #{self.authorization.scope.inspect}"
464         )
465       else
466         check_cached_certs = lambda do
467           valid = false
468           for key, cert in @certificates
469             begin
470               self.authorization.decoded_id_token(cert.public_key)
471               valid = true
472             rescue JWT::DecodeError, Signet::UnsafeOperationError
473               # Expected exception. Ignore, ID token has not been validated.
474             end
475           end
476           valid
477         end
478         if check_cached_certs.call()
479           return true
480         end
481         request = self.generate_request(
482           :http_method => :get,
483           :uri => 'https://www.googleapis.com/oauth2/v1/certs',
484           :authenticated => false
485         )
486         response = self.transmit(:request => request)
487         if response.status >= 200 && response.status < 300
488           @certificates.merge!(
489             Hash[MultiJson.load(response.body).map do |key, cert|
490               [key, OpenSSL::X509::Certificate.new(cert)]
491             end]
492           )
493         elsif response.status >= 400
494           case response.status
495           when 400...500
496             exception_type = ClientError
497           when 500...600
498             exception_type = ServerError
499           else
500             exception_type = TransmissionError
501           end
502           url = request.to_env(Faraday.default_connection)[:url]
503           raise exception_type,
504             "Could not retrieve certificates from: #{url}"
505         end
506         if check_cached_certs.call()
507           return true
508         else
509           raise InvalidIDTokenError,
510             "Could not verify ID token against any available certificate."
511         end
512       end
513       return nil
514     end
515
516     ##
517     # Generates a request.
518     #
519     # @option options [Google::APIClient::Method, String] :api_method
520     #   The method object or the RPC name of the method being executed.
521     # @option options [Hash, Array] :parameters
522     #   The parameters to send to the method.
523     # @option options [Hash, Array] :headers The HTTP headers for the request.
524     # @option options [String] :body The body of the request.
525     # @option options [String] :version ("v1")
526     #   The service version. Only used if `api_method` is a `String`.
527     # @option options [#generate_authenticated_request] :authorization
528     #   The authorization mechanism for the response. Used only if
529     #   `:authenticated` is `true`.
530     # @option options [TrueClass, FalseClass] :authenticated (true)
531     #   `true` if the request must be signed or somehow
532     #   authenticated, `false` otherwise.
533     #
534     # @return [Faraday::Request] The generated request.
535     #
536     # @example
537     #   request = client.generate_request(
538     #     :api_method => 'plus.activities.list',
539     #     :parameters =>
540     #       {'collection' => 'public', 'userId' => 'me'}
541     #   )
542     def generate_request(options={})
543       # Note: The merge method on a Hash object will coerce an API Reference
544       # object into a Hash and merge with the default options.
545
546       options={
547         :version => 'v1',
548         :authorization => self.authorization,
549         :key => self.key,
550         :user_ip => self.user_ip,
551         :connection => Faraday.default_connection
552       }.merge(options)
553
554       # The Reference object is going to need this to do method ID lookups.
555       options[:client] = self
556       # The default value for the :authenticated option depends on whether an
557       # authorization mechanism has been set.
558       if options[:authorization]
559         options = {:authenticated => true}.merge(options)
560       else
561         options = {:authenticated => false}.merge(options)
562       end
563       reference = Google::APIClient::Reference.new(options)
564       request = reference.to_request
565       if options[:authenticated]
566         request = options[:authorization].generate_authenticated_request(
567           :request => request,
568           :connection => options[:connection]
569         )
570       end
571       return request
572     end
573
574     ##
575     # Signs a request using the current authorization mechanism.
576     #
577     # @param [Hash] options a customizable set of options
578     #
579     # @return [Faraday::Request] The signed or otherwise authenticated request.
580     # @deprecated No longer used internally
581     def generate_authenticated_request(options={})
582       return authorization.generate_authenticated_request(options)
583     end
584
585     ##
586     # Transmits the request using the current HTTP adapter.
587     #
588     # @option options [Array, Faraday::Request] :request
589     #   The HTTP request to transmit.
590     # @option options [String, Symbol] :method
591     #   The method for the HTTP request.
592     # @option options [String, Addressable::URI] :uri
593     #   The URI for the HTTP request.
594     # @option options [Array, Hash] :headers
595     #   The headers for the HTTP request.
596     # @option options [String] :body
597     #   The body for the HTTP request.
598     # @option options [Faraday::Connection] :connection
599     #   The HTTP connection to use.
600     #
601     # @return [Faraday::Response] The response from the server.
602     def transmit(options={})
603       options[:connection] ||= Faraday.default_connection
604       if options[:request]
605         if options[:request].kind_of?(Array)
606           method, uri, headers, body = options[:request]
607         elsif options[:request].kind_of?(Faraday::Request)
608           unless options[:connection]
609             raise ArgumentError,
610               "Faraday::Request used, requires a connection to be provided."
611           end
612           method = options[:request].method.to_s.downcase.to_sym
613           uri = options[:connection].build_url(
614             options[:request].path, options[:request].params
615           )
616           headers = options[:request].headers || {}
617           body = options[:request].body || ''
618         end
619       else
620         method = options[:method] || :get
621         uri = options[:uri]
622         headers = options[:headers] || []
623         body = options[:body] || ''
624       end
625       headers = headers.to_a if headers.kind_of?(Hash)
626       request_components = {
627         :method => method,
628         :uri => uri,
629         :headers => headers,
630         :body => body
631       }
632       # Verify that we have all pieces required to transmit an HTTP request
633       request_components.each do |(key, value)|
634         unless value
635           raise ArgumentError, "Missing :#{key} parameter."
636         end
637       end
638
639       if self.user_agent != nil
640         # If there's no User-Agent header, set one.
641         unless headers.kind_of?(Enumerable)
642           # We need to use some Enumerable methods, relying on the presence of
643           # the #each method.
644           class << headers
645             include Enumerable
646           end
647         end
648         if self.user_agent.kind_of?(String)
649           unless headers.any? { |k, v| k.downcase == 'User-Agent'.downcase }
650             headers = headers.to_a.insert(0, ['User-Agent', self.user_agent])
651           end
652         elsif self.user_agent != nil
653           raise TypeError,
654             "Expected User-Agent to be String, got #{self.user_agent.class}"
655         end
656       end
657
658       request = options[:connection].build_request(
659         method.to_s.downcase.to_sym
660       ) do |req|
661         req.url(Addressable::URI.parse(uri).normalize.to_s)
662         req.headers = Faraday::Utils::Headers.new(headers)
663         req.body = body
664       end
665       request_env = request.to_env(options[:connection])
666       response = options[:connection].app.call(request_env)
667       return response
668     end
669
670     ##
671     # Executes a request, wrapping it in a Result object.
672     #
673     # @param [Google::APIClient::BatchRequest, Hash, Array] params
674     #   Either a Google::APIClient::BatchRequest, a Hash, or an Array.
675     #
676     #   If a Google::APIClient::BatchRequest, no other parameters are expected.
677     #
678     #   If a Hash, the below parameters are handled. If an Array, the
679     #   parameters are assumed to be in the below order:
680     #
681     #   - (Google::APIClient::Method, String) api_method:
682     #     The method object or the RPC name of the method being executed.
683     #   - (Hash, Array) parameters:
684     #     The parameters to send to the method.
685     #   - (String) body: The body of the request.
686     #   - (Hash, Array) headers: The HTTP headers for the request.
687     #   - (Hash) options: A set of options for the request, of which:
688     #     - (String) :version (default: "v1") -
689     #       The service version. Only used if `api_method` is a `String`.
690     #     - (#generate_authenticated_request) :authorization (default: true) -
691     #       The authorization mechanism for the response. Used only if
692     #       `:authenticated` is `true`.
693     #     - (TrueClass, FalseClass) :authenticated (default: true) -
694     #       `true` if the request must be signed or somehow
695     #       authenticated, `false` otherwise.
696     #
697     # @return [Google::APIClient::Result] The result from the API, nil if batch.
698     #
699     # @example
700     #   result = client.execute(batch_request)
701     #
702     # @example
703     #   result = client.execute(
704     #     :api_method => 'plus.activities.list',
705     #     :parameters => {'collection' => 'public', 'userId' => 'me'}
706     #   )
707     #
708     # @see Google::APIClient#generate_request
709     def execute(*params)
710       if params.last.kind_of?(Google::APIClient::BatchRequest) &&
711           params.size == 1
712         batch = params.pop
713         options = batch.options
714         options[:connection] ||= Faraday.default_connection
715         http_request = batch.to_http_request
716         request = nil
717
718         if @authorization
719           method, uri, headers, body = http_request
720           method = method.to_s.downcase.to_sym
721
722           faraday_request = options[:connection].build_request(
723             method.to_s.downcase.to_sym
724           ) do |req|
725             req.url(Addressable::URI.parse(uri).normalize.to_s)
726             req.headers = Faraday::Utils::Headers.new(headers)
727             req.body = body
728           end
729
730           request = {
731             :request => self.generate_authenticated_request(
732               :request => faraday_request,
733               :connection => options[:connection]
734             ),
735             :connection => options[:connection]
736           }
737         else
738           request = {
739             :request => http_request,
740             :connection => options[:connection]
741           }
742         end
743
744         response = self.transmit(request)
745         batch.process_response(response)
746         return nil
747       else
748         # This block of code allows us to accept multiple parameter passing
749         # styles, and maintaining some backwards compatibility.
750         #
751         # Note: I'm extremely tempted to deprecate this style of execute call.
752         if params.last.respond_to?(:to_hash) && params.size == 1
753           options = params.pop
754         else
755           options = {}
756         end
757
758         options[:api_method] = params.shift if params.size > 0
759         options[:parameters] = params.shift if params.size > 0
760         options[:body] = params.shift if params.size > 0
761         options[:headers] = params.shift if params.size > 0
762         options[:client] = self
763         options[:connection] ||= Faraday.default_connection
764         reference = Google::APIClient::Reference.new(options)
765         request = self.generate_request(reference)
766         response = self.transmit(
767           :request => request,
768           :connection => options[:connection]
769         )
770         return Google::APIClient::Result.new(reference, request, response)
771       end
772     end
773
774     ##
775     # Same as Google::APIClient#execute, but raises an exception if there was
776     # an error.
777     #
778     # @see Google::APIClient#execute
779     def execute!(*params)
780       result = self.execute(*params)
781       if result.data?
782         if result.data.respond_to?(:error) &&
783              result.data.error.respond_to?(:message)
784           # You're going to get a terrible error message if the response isn't
785           # parsed successfully as an error.
786           error_message = result.data.error.message
787         elsif result.data['error'] && result.data['error']['message']
788           error_message = result.data['error']['message']
789         end
790       end
791       if result.response.status >= 400
792         case result.response.status
793         when 400...500
794           exception_type = ClientError
795           error_message ||= "A client error has occurred."
796         when 500...600
797           exception_type = ServerError
798           error_message ||= "A server error has occurred."
799         else
800           exception_type = TransmissionError
801           error_message ||= "A transmission error has occurred."
802         end
803         raise exception_type, error_message
804       end
805       return result
806     end
807   end
808 end
809
810 require 'google/api_client/version'