1 # Copyright 2010 Google Inc.
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
7 # http://www.apache.org/licenses/LICENSE-2.0
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.
18 require 'google/api_client/discovery'
21 # TODO(bobaman): Document all this stuff.
24 # This class manages communication with a single API.
27 # An error which is raised when there is an unexpected response or other
28 # transport error that prevents an operation from succeeding.
29 class TransmissionError < StandardError
32 def initialize(options={})
34 # TODO: What configuration options need to go here?
36 if !self.authorization.respond_to?(:generate_authenticated_request)
38 'Expected authorization mechanism to respond to ' +
39 '#generate_authenticated_request.'
44 # Returns the parser used by the client.
46 unless @options[:parser]
47 require 'google/api_client/parsers/json_parser'
48 # NOTE: Do not rely on this default value, as it may change
49 @options[:parser] = JSONParser
51 return @options[:parser]
55 # Returns the authorization mechanism used by the client.
57 unless @options[:authorization]
58 require 'signet/oauth_1/client'
59 # NOTE: Do not rely on this default value, as it may change
60 @options[:authorization] = Signet::OAuth1::Client.new(
61 :temporary_credential_uri =>
62 'https://www.google.com/accounts/OAuthGetRequestToken',
64 'https://www.google.com/accounts/OAuthAuthorizeToken',
65 :token_credential_uri =>
66 'https://www.google.com/accounts/OAuthGetAccessToken',
67 :client_credential_key => 'anonymous',
68 :client_credential_secret => 'anonymous'
71 return @options[:authorization]
75 # Returns the HTTP adapter used by the client.
77 return @options[:http_adapter] ||= (begin
78 require 'httpadapter/adapters/net_http'
79 @options[:http_adapter] = HTTPAdapter::NetHTTPRequestAdapter
84 # Returns the URI for the discovery document.
86 # @return [Addressable::URI] The URI of the discovery document.
88 return @options[:discovery_uri] ||= (begin
90 service_id = @options[:service]
91 service_version = @options[:service_version] || 'v1'
92 Addressable::URI.parse(
93 "http://www.googleapis.com/discovery/0.1/describe" +
98 'Missing required configuration value, :discovery_uri.'
104 # Returns the parsed discovery document.
106 # @return [Hash] The parsed JSON from the discovery document.
107 def discovery_document
108 return @discovery_document ||= (begin
109 request = ['GET', self.discovery_uri.to_s, [], []]
110 response = self.transmit_request(request)
111 status, headers, body = response
112 if status == 200 # TODO(bobaman) Better status code handling?
113 merged_body = StringIO.new
115 merged_body.write(chunk)
118 JSON.parse(merged_body.string)
120 raise TransmissionError,
121 "Could not retrieve discovery document at: #{self.discovery_uri}"
127 # Returns a list of services this client instance has performed discovery
128 # for. This may return multiple versions of the same service.
131 # A list of discovered <code>Google::APIClient::Service</code> objects.
132 def discovered_services
133 return @discovered_services ||= (begin
134 service_names = self.discovery_document['data'].keys()
136 for service_name in service_names
137 versions = self.discovery_document['data'][service_name]
138 for service_version in versions.keys()
139 service_description =
140 self.discovery_document['data'][service_name][service_version]
141 services << ::Google::APIClient::Service.new(
153 # Returns the service object for a given service name and service version.
155 # @param [String, Symbol] service_name The service name.
156 # @param [String] service_version The desired version of the service.
158 # @return [Google::APIClient::Service] The service object.
159 def discovered_service(service_name, service_version='v1')
160 if !service_name.kind_of?(String) && !service_name.kind_of?(Symbol)
162 "Expected String or Symbol, got #{service_name.class}."
164 service_name = service_name.to_s
165 for service in self.discovered_services
166 if service.name == service_name &&
167 service.version.to_s == service_version.to_s
175 # Returns the method object for a given RPC name and service version.
177 # @param [String, Symbol] rpc_name The RPC name of the desired method.
178 # @param [String] service_version The desired version of the service.
180 # @return [Google::APIClient::Method] The method object.
181 def discovered_method(rpc_name, service_version='v1')
182 if !rpc_name.kind_of?(String) && !rpc_name.kind_of?(Symbol)
184 "Expected String or Symbol, got #{rpc_name.class}."
186 rpc_name = rpc_name.to_s
187 for service in self.discovered_services
188 # This looks kinda weird, but is not a real problem because there's
189 # almost always only one service, and this is memoized anyhow.
190 if service.version.to_s == service_version.to_s
191 return service.to_h[rpc_name] if service.to_h[rpc_name]
198 # Returns the service object with the highest version number.
200 # <em>Warning</em>: This method should be used with great care. As APIs
201 # are updated, minor differences between versions may cause
202 # incompatibilities. Requesting a specific version will avoid this issue.
204 # @param [String, Symbol] service_name The name of the service.
206 # @return [Google::APIClient::Service] The service object.
207 def latest_service(service_name)
208 if !service_name.kind_of?(String) && !service_name.kind_of?(Symbol)
210 "Expected String or Symbol, got #{service_name.class}."
212 service_name = service_name.to_s
214 for service in self.discovered_services
215 next if service.name != service_name
216 sortable_version = service.version.gsub(/^v/, '').split('.').map do |v|
219 versions[sortable_version] = service
221 return versions[versions.keys.sort.last]
225 # Generates a request.
227 # @param [Google::APIClient::Method, String] api_method
228 # The method object or the RPC name of the method being executed.
229 # @param [Hash, Array] parameters
230 # The parameters to send to the method.
231 # @param [String] body The body of the request.
232 # @param [Hash, Array] headers The HTTP headers for the request.
233 # @param [Hash] options
234 # The configuration parameters for the request.
235 # - <code>:service_version</code> —
236 # The service version. Only used if <code>api_method</code> is a
237 # <code>String</code>. Defaults to <code>'v1'</code>.
238 # - <code>:parser</code> —
239 # The parser for the response.
240 # - <code>:authorization</code> —
241 # The authorization mechanism for the response. Used only if
242 # <code>:signed</code> is <code>true</code>.
243 # - <code>:signed</code> —
244 # <code>true</code> if the request must be signed, <code>false</code>
245 # otherwise. Defaults to <code>true</code>.
247 # @return [Array] The generated request.
248 def generate_request(
249 api_method, parameters={}, body='', headers=[], options={})
252 :parser => self.parser,
253 :service_version => 'v1',
254 :authorization => self.authorization
256 if api_method.kind_of?(String) || api_method.kind_of?(Symbol)
257 api_method = self.discovered_method(
258 api_method.to_s, options[:service_version]
260 elsif !api_method.kind_of?(::Google::APIClient::Service)
262 "Expected String, Symbol, or Google::APIClient::Service, " +
263 "got #{api_method.class}."
266 raise ArgumentError, "API method does not exist."
268 request = api_method.generate_request(parameters, body, headers)
270 request = self.sign_request(request, options[:authorization])
276 # Generates a request and transmits it.
278 # @param [Google::APIClient::Method, String] api_method
279 # The method object or the RPC name of the method being executed.
280 # @param [Hash, Array] parameters
281 # The parameters to send to the method.
282 # @param [String] body The body of the request.
283 # @param [Hash, Array] headers The HTTP headers for the request.
284 # @param [Hash] options
285 # The configuration parameters for the request.
286 # - <code>:service_version</code> —
287 # The service version. Only used if <code>api_method</code> is a
288 # <code>String</code>. Defaults to <code>'v1'</code>.
289 # - <code>:adapter</code> —
291 # - <code>:parser</code> —
292 # The parser for the response.
293 # - <code>:authorization</code> —
294 # The authorization mechanism for the response. Used only if
295 # <code>:signed</code> is <code>true</code>.
296 # - <code>:signed</code> —
297 # <code>true</code> if the request must be signed, <code>false</code>
298 # otherwise. Defaults to <code>true</code>.
300 # @return [Array] The response from the API.
301 def execute(api_method, parameters={}, body='', headers=[], options={})
302 request = self.generate_request(
303 api_method, parameters, body, headers, options
305 return self.transmit_request(
307 options[:adapter] || self.http_adapter
312 # Transmits the request using the current HTTP adapter.
314 # @param [Array] request The request to transmit.
315 # @param [#transmit] adapter The HTTP adapter.
317 # @return [Array] The response from the server.
318 def transmit_request(request, adapter=self.http_adapter)
319 ::HTTPAdapter.transmit(request, adapter)
323 # Signs a request using the current authorization mechanism.
325 # @param [Array] request The request to sign.
326 # @param [#generate_authenticated_request] authorization
327 # The authorization mechanism.
329 # @return [Array] The signed request.
330 def sign_request(request, authorization=self.authorization)
331 return authorization.generate_authenticated_request(
338 require 'google/api_client/version'