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