A significant update of the client functionality.

* updated to use v0.3 of the discovery API
* updated to use httpadapter 1.0.0
* added OAuth 2 support to the command line tool
* renamed some switches in the command line tool
* added additional configuration capabilities

git-svn-id: https://google-api-ruby-client.googlecode.com/svn/trunk@128 c1d61fac-ed7f-fcc1-18f7-ff78120a04ef
This commit is contained in:
Bob Aman committed 2011-05-04 11:44:35 +00:00
1 parent 1dee705828
commit 286a7152f2
13 files changed
+1062 -805

No files matched your search

+319 -165
View File
@@ -12,58 +12,125 @@
# See the License for the specific language governing permissions and
# limitations under the License.
require 'httpadapter'
require 'json'
require 'stringio'
require 'google/api_client/errors'
require 'google/api_client/discovery'
module Google
# TODO(bobaman): Document all this stuff.
##
# This class manages communication with a single API.
# This class manages APIs communication.
class APIClient
##
# An error which is raised when there is an unexpected response or other
# transport error that prevents an operation from succeeding.
class TransmissionError < StandardError
end
# Creates a new Google API client.
#
# @param [Hash] options The configuration parameters for the client.
# @option options [Symbol, #generate_authenticated_request] :authorization
# (:oauth_1)
# The authorization mechanism used by the client. The following
# mechanisms are supported out-of-the-box:
# <ul>
# <li><code>:two_legged_oauth_1</code></li>
# <li><code>:oauth_1</code></li>
# <li><code>:oauth_2</code></li>
# </ul>
# @option options [String] :host ("www.googleapis.com")
# The API hostname used by the client. This rarely needs to be changed.
# @option options [String] :user_agent ("google-api-ruby-client/{version}")
# The user agent used by the client. Most developers will want to
# leave this value alone — the API key is the primary mechanism used to
# identify an application.
def initialize(options={})
@options = {
:user_agent => (
'google-api-ruby-client/' + Google::APIClient::VERSION::STRING
)
}.merge(options)
# Force immediate type-checking and short-cut resolution
self.parser
self.authorization
self.http_adapter
# Normalize key to String to allow indifferent access.
options = options.inject({}) do |accu, (key, value)|
accu[key.to_s] = value
accu
end
# Almost all API usage will have a host of 'www.googleapis.com'.
self.host = options["host"] || 'www.googleapis.com'
# Most developers will want to leave this value alone.
self.user_agent = options["user_agent"] || (
'google-api-ruby-client/' + Google::APIClient::VERSION::STRING
)
# This is mostly a default for the sake of convenience.
# Unlike most other options, this one may be nil, so we check for
# the presence of the key rather than checking the value.
if options.has_key?("parser")
self.parser = options["parser"]
else
require 'google/api_client/parsers/json_parser'
# NOTE: Do not rely on this default value, as it may change
self.parser = Google::APIClient::JSONParser
end
# The writer method understands a few Symbols and will generate useful
# default authentication mechanisms.
self.authorization = options["authorization"] || :oauth_2
# The HTTP adapter controls all of the HTTP traffic the client generates.
# By default, Net::HTTP is used, but adding support for other clients
# is trivial.
if options["http_adapter"]
self.http_adapter = options["http_adapter"]
else
require 'httpadapter/adapters/net_http'
# NOTE: Do not rely on this default value, as it may change
self.http_adapter = HTTPAdapter::NetHTTPAdapter.new
end
@discovery_uris = {}
@discovery_documents = {}
@discovered_apis = {}
return self
end
##
# Returns the parser used by the client.
def parser
unless @options[:parser]
require 'google/api_client/parsers/json_parser'
# NOTE: Do not rely on this default value, as it may change
@options[:parser] = JSONParser
#
# @return [#serialize, #parse]
# The parser used by the client. Any object that implements both a
# <code>#serialize</code> and a <code>#parse</code> method may be used.
# If <code>nil</code>, no parsing will be done.
attr_reader :parser
##
# Sets the parser used by the client.
#
# @param [#serialize, #parse] new_parser
# The parser used by the client. Any object that implements both a
# <code>#serialize</code> and a <code>#parse</code> method may be used.
# If <code>nil</code>, no parsing will be done.
def parser=(new_parser)
if new_parser &&
!new_parser.respond_to?(:serialize) &&
!new_parser.respond_to?(:parse)
raise TypeError,
'Expected parser object to respond to #serialize and #parse.'
end
return @options[:parser]
@parser = new_parser
end
##
# Returns the authorization mechanism used by the client.
#
# @return [#generate_authenticated_request] The authorization mechanism.
def authorization
case @options[:authorization]
attr_reader :authorization
##
# Sets the authorization mechanism used by the client.
#
# @param [#generate_authenticated_request] new_authorization
# The new authorization mechanism.
def authorization=(new_authorization)
case new_authorization
when :oauth_1, :oauth
require 'signet/oauth_1/client'
# NOTE: Do not rely on this default value, as it may change
@options[:authorization] = Signet::OAuth1::Client.new(
new_authorization = Signet::OAuth1::Client.new(
:temporary_credential_uri =>
'https://www.google.com/accounts/OAuthGetRequestToken',
:authorization_uri =>
@@ -76,79 +143,149 @@ module Google
when :two_legged_oauth_1, :two_legged_oauth
require 'signet/oauth_1/client'
# NOTE: Do not rely on this default value, as it may change
@options[:authorization] = Signet::OAuth1::Client.new(
new_authorization = Signet::OAuth1::Client.new(
:client_credential_key => nil,
:client_credential_secret => nil,
:two_legged => true
)
when :oauth_2
require 'signet/oauth_2/client'
# NOTE: Do not rely on this default value, as it may change
new_authorization = Signet::OAuth2::Client.new(
:authorization_uri =>
'https://accounts.google.com/o/oauth2/auth',
:token_credential_uri =>
'https://accounts.google.com/o/oauth2/token'
)
when nil
# No authorization mechanism
else
if !@options[:authorization].respond_to?(
:generate_authenticated_request)
if !new_authorization.respond_to?(:generate_authenticated_request)
raise TypeError,
'Expected authorization mechanism to respond to ' +
'#generate_authenticated_request.'
end
end
return @options[:authorization]
end
##
# Sets the authorization mechanism used by the client.
#
# @param [#generate_authenticated_request] new_authorization
# The new authorization mechanism.
def authorization=(new_authorization)
@options[:authorization] = new_authorization
return self.authorization
@authorization = new_authorization
return @authorization
end
##
# Returns the HTTP adapter used by the client.
def http_adapter
return @options[:http_adapter] ||= (begin
require 'httpadapter/adapters/net_http'
@options[:http_adapter] = HTTPAdapter::NetHTTPRequestAdapter
end)
#
# @return [HTTPAdapter]
# The HTTP adapter object. The object must include the
# HTTPAdapter module and conform to its interface.
attr_reader :http_adapter
##
# Returns the HTTP adapter used by the client.
#
# @return [HTTPAdapter]
# The HTTP adapter object. The object must include the
# HTTPAdapter module and conform to its interface.
def http_adapter=(new_http_adapter)
if new_http_adapter.kind_of?(HTTPAdapter)
@http_adapter = new_http_adapter
else
raise TypeError, "Expected HTTPAdapter, got #{new_http_adapter.class}."
end
end
##
# The API hostname used by the client.
#
# @return [String]
# The API hostname. Should almost always be 'www.googleapis.com'.
attr_accessor :host
##
# The user agent used by the client.
#
# @return [String]
# The user agent string used in the User-Agent header.
attr_accessor :user_agent
##
# Returns the URI for the directory document.
#
# @return [Addressable::URI] The URI of the directory document.
def directory_uri
template = Addressable::Template.new(
"https://{host}/discovery/v0.3/directory"
)
return template.expand({
"host" => self.host
})
end
##
# Manually registers a URI as a discovery document for a specific version
# of an API.
#
# @param [String, Symbol] api The service name.
# @param [String] version The desired version of the service.
# @param [Addressable::URI] uri The URI of the discovery document.
def register_discovery_uri(api, version, uri)
api = api.to_s
version = version || 'v1'
@discovery_uris["#{api}:#{version}"] = uri
end
##
# Returns the URI for the discovery document.
#
# @param [String, Symbol] api The service name.
# @param [String] version The desired version of the service.
# @return [Addressable::URI] The URI of the discovery document.
def discovery_uri
return @options[:discovery_uri] ||= (begin
if @options[:service]
service_id = @options[:service]
service_version = @options[:service_version] || 'v1'
Addressable::URI.parse(
"http://www.googleapis.com/discovery/0.1/describe" +
"?api=#{service_id}"
)
else
raise ArgumentError,
'Missing required configuration value, :discovery_uri.'
end
def discovery_uri(api, version=nil)
api = api.to_s
version = version || 'v1'
return @discovery_uris["#{api}:#{version}"] ||= (begin
template = Addressable::Template.new(
"https://{host}/discovery/v0.3/describe/" +
"{api}/{version}"
)
template.expand({
"host" => self.host,
"api" => api,
"version" => version
})
end)
end
##
# Sets the discovery URI for the client.
# Manually registers a pre-loaded discovery document for a specific version
# of an API.
#
# @param [Addressable::URI, #to_str, String] new_discovery_uri
# The new discovery URI.
def discovery_uri=(new_discovery_uri)
@options[:discovery_uri] = Addressable::URI.parse(new_discovery_uri)
# @param [String, Symbol] api The service name.
# @param [String] version The desired version of the service.
# @param [String, StringIO] discovery_document
# The contents of the discovery document.
def register_discovery_document(api, version, discovery_document)
api = api.to_s
version = version || 'v1'
if discovery_document.kind_of?(StringIO)
discovery_document.rewind
discovery_document = discovery_document.string
elsif discovery_document.respond_to?(:to_str)
discovery_document = discovery_document.to_str
else
raise TypeError,
"Expected String or StringIO, got #{discovery_document.class}."
end
@discovery_documents["#{api}:#{version}"] =
JSON.parse(discovery_document)
end
##
# Returns the parsed discovery document.
# Returns the parsed directory document.
#
# @return [Hash] The parsed JSON from the discovery document.
def discovery_document
return @discovery_document ||= (begin
request = ['GET', self.discovery_uri.to_s, [], []]
# @return [Hash] The parsed JSON from the directory document.
def directory_document
return @directory_document ||= (begin
request_uri = self.directory_uri
request = ['GET', request_uri, [], []]
response = self.transmit_request(request)
status, headers, body = response
if status == 200 # TODO(bobaman) Better status code handling?
@@ -160,124 +297,130 @@ module Google
JSON.parse(merged_body.string)
else
raise TransmissionError,
"Could not retrieve discovery document at: #{self.discovery_uri}"
"Could not retrieve discovery document at: #{request_uri}"
end
end)
end
##
# Returns a list of services this client instance has performed discovery
# for. This may return multiple versions of the same service.
# Returns the parsed discovery document.
#
# @return [Array]
# A list of discovered <code>Google::APIClient::Service</code> objects.
def discovered_services
return @discovered_services ||= (begin
service_names = self.discovery_document['data'].keys()
services = []
for service_name in service_names
versions = self.discovery_document['data'][service_name]
for service_version in versions.keys()
service_description =
self.discovery_document['data'][service_name][service_version]
services << ::Google::APIClient::Service.new(
service_name,
service_version,
service_description
# @param [String, Symbol] api The service name.
# @param [String] version The desired version of the service.
# @return [Hash] The parsed JSON from the discovery document.
def discovery_document(api, version=nil)
api = api.to_s
version = version || 'v1'
return @discovery_documents["#{api}:#{version}"] ||= (begin
request_uri = self.discovery_uri(api, version)
request = ['GET', request_uri, [], []]
response = self.transmit_request(request)
status, headers, body = response
if status == 200 # TODO(bobaman) Better status code handling?
merged_body = StringIO.new
body.each do |chunk|
merged_body.write(chunk)
end
merged_body.rewind
JSON.parse(merged_body.string)
else
raise TransmissionError,
"Could not retrieve discovery document at: #{request_uri}"
end
end)
end
##
# Returns all APIs published in the directory document.
#
# @return [Array] The list of available APIs.
def discovered_apis
@directory_apis ||= (begin
document_base = self.directory_uri
if self.directory_document && self.directory_document['items']
self.directory_document['items'].map do |discovery_document|
::Google::APIClient::API.new(
document_base,
discovery_document
)
end
else
[]
end
services
end)
end
##
# Returns the service object for a given service name and service version.
#
# @param [String, Symbol] service_name The service name.
# @param [String] service_version The desired version of the service.
# @param [String, Symbol] api The service name.
# @param [String] version The desired version of the service.
#
# @return [Google::APIClient::Service] The service object.
def discovered_service(service_name, service_version='v1')
if !service_name.kind_of?(String) && !service_name.kind_of?(Symbol)
# @return [Google::APIClient::API] The service object.
def discovered_api(api, version=nil)
if !api.kind_of?(String) && !api.kind_of?(Symbol)
raise TypeError,
"Expected String or Symbol, got #{service_name.class}."
"Expected String or Symbol, got #{api.class}."
end
service_name = service_name.to_s
for service in self.discovered_services
if service.name == service_name &&
service.version.to_s == service_version.to_s
return service
api = api.to_s
version = version || 'v1'
return @discovered_apis["#{api}:#{version}"] ||= begin
document_base = self.discovery_uri(api, version)
discovery_document = self.discovery_document(api, version)
if document_base && discovery_document
::Google::APIClient::API.new(
document_base,
discovery_document
)
else
nil
end
end
return nil
end
##
# Returns the method object for a given RPC name and service version.
#
# @param [String, Symbol] rpc_name The RPC name of the desired method.
# @param [String] service_version The desired version of the service.
# @param [String] version The desired version of the service.
#
# @return [Google::APIClient::Method] The method object.
def discovered_method(rpc_name, service_version='v1')
def discovered_method(rpc_name, api, version=nil)
if !rpc_name.kind_of?(String) && !rpc_name.kind_of?(Symbol)
raise TypeError,
"Expected String or Symbol, got #{rpc_name.class}."
end
rpc_name = rpc_name.to_s
for service in self.discovered_services
# This looks kinda weird, but is not a real problem because there's
# almost always only one service, and this is memoized anyhow.
if service.version.to_s == service_version.to_s
return service.to_h[rpc_name] if service.to_h[rpc_name]
end
api = api.to_s
version = version || 'v1'
service = self.discovered_api(api, version)
if service.to_h[rpc_name]
return service.to_h[rpc_name]
else
return nil
end
return nil
end
##
# Returns the service object with the highest version number.
#
# <em>Warning</em>: This method should be used with great care. As APIs
# are updated, minor differences between versions may cause
# @note <em>Warning</em>: This method should be used with great care.
# As APIs are updated, minor differences between versions may cause
# incompatibilities. Requesting a specific version will avoid this issue.
#
# @param [String, Symbol] service_name The name of the service.
# @param [String, Symbol] api The name of the service.
#
# @return [Google::APIClient::Service] The service object.
def latest_service_version(service_name)
if !service_name.kind_of?(String) && !service_name.kind_of?(Symbol)
# @return [Google::APIClient::API] The service object.
def preferred_version(api)
if !api.kind_of?(String) && !api.kind_of?(Symbol)
raise TypeError,
"Expected String or Symbol, got #{service_name.class}."
"Expected String or Symbol, got #{api.class}."
end
service_name = service_name.to_s
return (self.discovered_services.select do |service|
service.name == service_name
end).sort.last
end
##
# Returns the user agent used by the client.
#
# @return [String]
# The user agent string used in the User-Agent header.
def user_agent
return @options[:user_agent]
end
##
# Sets the user agent used by the client.
#
# @param [String, #to_str] new_user_agent
# The new user agent string to use in the User-Agent header.
def user_agent=(new_user_agent)
unless new_user_agent == nil || new_user_agent.respond_to?(:to_str)
raise TypeError, "Expected String, got #{new_user_agent.class}."
api = api.to_s
# TODO(bobaman): Update to use directory API.
return self.discovered_apis.detect do |a|
a.name == api && a.preferred == true
end
new_user_agent = new_user_agent.to_str unless new_user_agent == nil
@options[:user_agent] = new_user_agent
return self.user_agent
end
##
@@ -291,16 +434,17 @@ module Google
# @param [Hash, Array] headers The HTTP headers for the request.
# @param [Hash] options
# The configuration parameters for the request.
# - <code>:service_version</code> —
# - <code>:version</code> —
# The service version. Only used if <code>api_method</code> is a
# <code>String</code>. Defaults to <code>'v1'</code>.
# - <code>:parser</code> —
# The parser for the response.
# - <code>:authorization</code> —
# The authorization mechanism for the response. Used only if
# <code>:signed</code> is <code>true</code>.
# - <code>:signed</code> —
# <code>true</code> if the request must be signed, <code>false</code>
# <code>:authenticated</code> is <code>true</code>.
# - <code>:authenticated</code> —
# <code>true</code> if the request must be signed or otherwise
# authenticated, <code>false</code>
# otherwise. Defaults to <code>true</code> if an authorization
# mechanism has been set, <code>false</code> otherwise.
#
@@ -316,19 +460,27 @@ module Google
api_method, parameters={}, body='', headers=[], options={})
options={
:parser => self.parser,
:service_version => 'v1',
:version => 'v1',
:authorization => self.authorization
}.merge(options)
# The default value for the :signed option depends on whether an
# The default value for the :authenticated option depends on whether an
# authorization mechanism has been set.
if options[:authorization]
options = {:signed => true}.merge(options)
options = {:authenticated => true}.merge(options)
else
options = {:signed => false}.merge(options)
options = {:authenticated => false}.merge(options)
end
if api_method.kind_of?(String) || api_method.kind_of?(Symbol)
api_method = api_method.to_s
# This method of guessing the API is unreliable. This will fail for
# APIs where the first segment of the RPC name does not match the
# service name. However, this is a fallback mechanism anyway.
# Developers should be passing in a reference to the method, rather
# than passing in a string or symbol. This should raise an error
# in the case of a mismatch.
api = api_method[/^([^.]+)\./, 1]
api_method = self.discovered_method(
api_method.to_s, options[:service_version]
api_method, api, options[:version]
)
elsif !api_method.kind_of?(::Google::APIClient::Method)
raise TypeError,
@@ -339,8 +491,8 @@ module Google
raise ArgumentError, "API method could not be found."
end
request = api_method.generate_request(parameters, body, headers)
if options[:signed]
request = self.sign_request(request, options[:authorization])
if options[:authenticated]
request = self.generate_authenticated_request(:request => request)
end
return request
end
@@ -356,7 +508,7 @@ module Google
# @param [Hash, Array] headers The HTTP headers for the request.
# @param [Hash] options
# The configuration parameters for the request.
# - <code>:service_version</code> —
# - <code>:version</code> —
# The service version. Only used if <code>api_method</code> is a
# <code>String</code>. Defaults to <code>'v1'</code>.
# - <code>:adapter</code> —
@@ -365,9 +517,10 @@ module Google
# The parser for the response.
# - <code>:authorization</code> —
# The authorization mechanism for the response. Used only if
# <code>:signed</code> is <code>true</code>.
# - <code>:signed</code> —
# <code>true</code> if the request must be signed, <code>false</code>
# <code>:authenticated</code> is <code>true</code>.
# - <code>:authenticated</code> —
# <code>true</code> if the request must be signed or otherwise
# authenticated, <code>false</code>
# otherwise. Defaults to <code>true</code>.
#
# @return [Array] The response from the API.
@@ -406,25 +559,26 @@ module Google
include Enumerable
end
end
unless headers.any? { |k, v| k.downcase == 'user-agent' }
headers = headers.to_a.insert(0, ['User-Agent', self.user_agent])
if self.user_agent.kind_of?(String)
unless headers.any? { |k, v| k.downcase == 'User-Agent'.downcase }
headers = headers.to_a.insert(0, ['User-Agent', self.user_agent])
end
elsif self.user_agent != nil
raise TypeError,
"Expected User-Agent to be String, got #{self.user_agent.class}"
end
end
::HTTPAdapter.transmit([method, uri, headers, body], adapter)
adapter.transmit([method, uri, headers, body])
end
##
# Signs a request using the current authorization mechanism.
#
# @param [Array] request The request to sign.
# @param [#generate_authenticated_request] authorization
# The authorization mechanism.
# @param [Hash] options The options to pass through.
#
# @return [Array] The signed request.
def sign_request(request, authorization=self.authorization)
return authorization.generate_authenticated_request(
:request => request
)
# @return [Array] The signed or otherwise authenticated request.
def generate_authenticated_request(options={})
return authorization.generate_authenticated_request(options)
end
end
end
+97 -100
View File
@@ -12,42 +12,37 @@
# See the License for the specific language governing permissions and
# limitations under the License.
require 'json'
require 'addressable/uri'
require 'addressable/template'
require 'google/inflection'
require 'google/api_client/errors'
module Google
class APIClient
##
# An exception that is raised if a method is called with missing or
# invalid parameter values.
class ValidationError < StandardError
end
##
# A service that has been described by a discovery document.
class Service
class API
##
# Creates a description of a particular version of a service.
#
# @param [String] service_name
# @param [String] api
# The identifier for the service. Note that while this frequently
# matches the first segment of all of the service's RPC names, this
# should not be assumed. There is no requirement that these match.
# @param [String] service_version
# @param [String] version
# The identifier for the service version.
# @param [Hash] service_description
# @param [Hash] api_description
# The section of the discovery document that applies to this service
# version.
#
# @return [Google::APIClient::Service] The constructed service object.
def initialize(service_name, service_version, service_description)
@name = service_name
@version = service_version
@description = service_description
# @return [Google::APIClient::API] The constructed service object.
def initialize(document_base, discovery_document)
@document_base = Addressable::URI.parse(document_base)
@discovery_document = discovery_document
metaclass = (class <<self; self; end)
self.resources.each do |resource|
method_name = Google::INFLECTOR.underscore(resource.name).to_sym
@@ -63,31 +58,67 @@ module Google
end
end
##
# Returns the id of the service.
#
# @return [String] The service id.
def id
return @discovery_document['id']
end
##
# Returns the identifier for the service.
#
# @return [String] The service identifier.
attr_reader :name
def name
return @discovery_document['name']
end
##
# Returns the version of the service.
#
# @return [String] The service version.
attr_reader :version
def version
return @discovery_document['version']
end
##
# Returns the parsed section of the discovery document that applies to
# this version of the service.
#
# @return [Hash] The service description.
attr_reader :description
def description
return @discovery_document['description']
end
##
# Returns true if this is the preferred version of this API.
#
# @return [TrueClass, FalseClass]
# Whether or not this is the preferred version of this API.
def preferred
return @discovery_document['preferred']
end
##
# Returns the base URI for this version of the service.
#
# @return [Addressable::URI] The base URI that methods are joined to.
def base
return @base ||= Addressable::URI.parse(self.description['baseUrl'])
attr_reader :document_base
##
# Returns the base URI for this version of the service.
#
# @return [Addressable::URI] The base URI that methods are joined to.
def rest_base
if @discovery_document['restBasePath']
return @rest_base ||= (
self.document_base +
Addressable::URI.parse(@discovery_document['restBasePath'])
).normalize
else
return nil
end
end
##
@@ -95,13 +126,13 @@ module Google
#
# @param [Addressable::URI, #to_str, String] new_base
# The new base URI to use for the service.
def base=(new_base)
@base = Addressable::URI.parse(new_base)
def rest_base=(new_rest_base)
@rest_base = Addressable::URI.parse(new_rest_base)
self.resources.each do |resource|
resource.base = @base
resource.rest_base = @rest_base
end
self.methods.each do |method|
method.base = @base
method.rest_base = @rest_base
end
end
@@ -112,8 +143,8 @@ module Google
# @return [Array] A list of {Google::APIClient::Resource} objects.
def resources
return @resources ||= (
(self.description['resources'] || []).inject([]) do |accu, (k, v)|
accu << ::Google::APIClient::Resource.new(self.base, k, v)
(@discovery_document['resources'] || []).inject([]) do |accu, (k, v)|
accu << ::Google::APIClient::Resource.new(self.rest_base, k, v)
accu
end
)
@@ -126,8 +157,8 @@ module Google
# @return [Array] A list of {Google::APIClient::Method} objects.
def methods
return @methods ||= (
(self.description['methods'] || []).inject([]) do |accu, (k, v)|
accu << ::Google::APIClient::Method.new(self.base, k, v)
(@discovery_document['methods'] || []).inject([]) do |accu, (k, v)|
accu << ::Google::APIClient::Method.new(self.rest_base, k, v)
accu
end
)
@@ -140,12 +171,12 @@ module Google
#
# @example
# # Discover available methods
# method_names = client.discovered_service('buzz').to_h.keys
# method_names = client.discovered_api('buzz').to_h.keys
def to_h
return @hash ||= (begin
methods_hash = {}
self.methods.each do |method|
methods_hash[method.rpc_name] = method
methods_hash[method.rpc_method] = method
end
self.resources.each do |resource|
methods_hash.merge!(resource.to_h)
@@ -154,48 +185,6 @@ module Google
end)
end
##
# Compares two versions of a service.
#
# @param [Object] other The service to compare.
#
# @return [Integer]
# <code>-1</code> if the service is older than <code>other</code>.
# <code>0</code> if the service is the same as <code>other</code>.
# <code>1</code> if the service is newer than <code>other</code>.
# <code>nil</code> if the service cannot be compared to
# <code>other</code>.
def <=>(other)
# We can only compare versions of the same service
if other.kind_of?(self.class) && self.name == other.name
split_version = lambda do |version|
dotted_version = version[/^v?(\d+(.\d+)*)-?(.*?)?$/, 1]
suffix = version[/^v?(\d+(.\d+)*)-?(.*?)?$/, 3]
if dotted_version && suffix
[dotted_version.split('.').map { |v| v.to_i }, suffix]
else
[[-1], version]
end
end
self_sortable, self_suffix = split_version.call(self.version)
other_sortable, other_suffix = split_version.call(other.version)
result = self_sortable <=> other_sortable
if result != 0
return result
# If the dotted versions are equal, check the suffix.
# An omitted suffix should be sorted after an included suffix.
elsif self_suffix == ''
return 1
elsif other_suffix == ''
return -1
else
return self_suffix <=> other_suffix
end
else
return nil
end
end
##
# Returns a <code>String</code> representation of the service's state.
#
@@ -222,10 +211,10 @@ module Google
# The section of the discovery document that applies to this resource.
#
# @return [Google::APIClient::Resource] The constructed resource object.
def initialize(base, resource_name, resource_description)
@base = base
def initialize(rest_base, resource_name, discovery_document)
@rest_base = rest_base
@name = resource_name
@description = resource_description
@discovery_document = discovery_document
metaclass = (class <<self; self; end)
self.resources.each do |resource|
method_name = Google::INFLECTOR.underscore(resource.name).to_sym
@@ -258,20 +247,20 @@ module Google
# Returns the base URI for this resource.
#
# @return [Addressable::URI] The base URI that methods are joined to.
attr_reader :base
attr_reader :rest_base
##
# Updates the hierarchy of resources and methods with the new base.
#
# @param [Addressable::URI, #to_str, String] new_base
# The new base URI to use for the resource.
def base=(new_base)
@base = Addressable::URI.parse(new_base)
def rest_base=(new_rest_base)
@rest_base = Addressable::URI.parse(new_rest_base)
self.resources.each do |resource|
resource.base = @base
resource.rest_base = @rest_base
end
self.methods.each do |method|
method.base = @base
method.rest_base = @rest_base
end
end
@@ -281,8 +270,8 @@ module Google
# @return [Array] A list of {Google::APIClient::Resource} objects.
def resources
return @resources ||= (
(self.description['resources'] || []).inject([]) do |accu, (k, v)|
accu << ::Google::APIClient::Resource.new(self.base, k, v)
(@discovery_document['resources'] || []).inject([]) do |accu, (k, v)|
accu << ::Google::APIClient::Resource.new(self.rest_base, k, v)
accu
end
)
@@ -294,8 +283,8 @@ module Google
# @return [Array] A list of {Google::APIClient::Method} objects.
def methods
return @methods ||= (
(self.description['methods'] || []).inject([]) do |accu, (k, v)|
accu << ::Google::APIClient::Method.new(self.base, k, v)
(@discovery_document['methods'] || []).inject([]) do |accu, (k, v)|
accu << ::Google::APIClient::Method.new(self.rest_base, k, v)
accu
end
)
@@ -310,7 +299,7 @@ module Google
return @hash ||= (begin
methods_hash = {}
self.methods.each do |method|
methods_hash[method.rpc_name] = method
methods_hash[method.rpc_method] = method
end
self.resources.each do |resource|
methods_hash.merge!(resource.to_h)
@@ -337,7 +326,7 @@ module Google
##
# Creates a description of a particular method.
#
# @param [Addressable::URI] base
# @param [Addressable::URI] rest_base
# The base URI for the service.
# @param [String] method_name
# The identifier for the method.
@@ -345,10 +334,10 @@ module Google
# The section of the discovery document that applies to this method.
#
# @return [Google::APIClient::Method] The constructed method object.
def initialize(base, method_name, method_description)
@base = base
def initialize(rest_base, method_name, discovery_document)
@rest_base = rest_base
@name = method_name
@description = method_description
@discovery_document = discovery_document
end
##
@@ -369,15 +358,15 @@ module Google
#
# @return [Addressable::URI]
# The base URI that this method will be joined to.
attr_reader :base
attr_reader :rest_base
##
# Updates the method with the new base.
#
# @param [Addressable::URI, #to_str, String] new_base
# The new base URI to use for the method.
def base=(new_base)
@base = Addressable::URI.parse(new_base)
def rest_base=(new_rest_base)
@rest_base = Addressable::URI.parse(new_rest_base)
@uri_template = nil
end
@@ -385,8 +374,8 @@ module Google
# Returns the RPC name for the method.
#
# @return [String] The RPC name.
def rpc_name
return self.description['rpcName']
def rpc_method
return @discovery_document['rpcMethod']
end
##
@@ -399,8 +388,9 @@ module Google
# a join operation on a URI, but we have to treat these as Strings
# because of the way the discovery document provides the URIs.
# This should be fixed soon.
return @uri_template ||=
Addressable::Template.new(base.to_s + self.description['pathUrl'])
return @uri_template ||= Addressable::Template.new(
self.rest_base + @discovery_document['restPath']
)
end
##
@@ -475,7 +465,7 @@ module Google
if !headers.kind_of?(Array) && !headers.kind_of?(Hash)
raise TypeError, "Expected Hash or Array, got #{headers.class}."
end
method = self.description['httpMethod'] || 'GET'
method = @discovery_document['httpMethod'] || 'GET'
uri = self.generate_uri(parameters)
headers = headers.to_a if headers.kind_of?(Hash)
return [method, uri.to_str, headers, [body]]
@@ -488,7 +478,7 @@ module Google
# @return [Hash] The parameter descriptions.
def parameter_descriptions
@parameter_descriptions ||= (
self.description['parameters'] || {}
@discovery_document['parameters'] || {}
).inject({}) { |h,(k,v)| h[k]=v; h }
end
@@ -498,7 +488,7 @@ module Google
# @return [Array] The parameters.
def parameters
@parameters ||= ((
self.description['parameters'] || {}
@discovery_document['parameters'] || {}
).inject({}) { |h,(k,v)| h[k]=v; h }).keys
end
@@ -552,6 +542,12 @@ module Google
end
parameters.each do |k, v|
if self.parameter_descriptions[k]
enum = self.parameter_descriptions[k]['enum']
if enum && !enum.include?(v)
raise ArgumentError,
"Parameter '#{k}' has an invalid value: #{v}. " +
"Must be one of #{enum.inspect}."
end
pattern = self.parameter_descriptions[k]['pattern']
if pattern
regexp = Regexp.new("^#{pattern}$")
@@ -572,7 +568,8 @@ module Google
# @return [String] The method's state, as a <code>String</code>.
def inspect
sprintf(
"#<%s:%#0x NAME:%s>", self.class.to_s, self.object_id, self.rpc_name
"#<%s:%#0x NAME:%s>",
self.class.to_s, self.object_id, self.rpc_method
)
end
end
+30
View File
@@ -0,0 +1,30 @@
# Copyright 2010 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
module Google
class APIClient
##
# An error which is raised when there is an unexpected response or other
# transport error that prevents an operation from succeeding.
class TransmissionError < StandardError
end
##
# An exception that is raised if a method is called with missing or
# invalid parameter values.
class ValidationError < StandardError
end
end
end
@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
require 'json'
module Google
+3 -2
View File
@@ -12,12 +12,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
module Google
class APIClient
module VERSION
MAJOR = 0
MINOR = 1
TINY = 3
MINOR = 2
TINY = 0
STRING = [MAJOR, MINOR, TINY].join('.')
end
+1
View File
@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
module Google
if defined?(ActiveSupport::Inflector)
INFLECTOR = ActiveSupport::Inflector