Nuke it from orbit, it's the only way to be sure

This commit is contained in:
Steven Bazyl 2015-04-16 17:21:38 -07:00
parent 82f69445ad
commit 9b7809174d
72 changed files with 0 additions and 11948 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,19 +0,0 @@
require 'multi_json'
if !MultiJson.respond_to?(:load) || [
Kernel,
defined?(ActiveSupport::Dependencies::Loadable) && ActiveSupport::Dependencies::Loadable
].compact.include?(MultiJson.method(:load).owner)
module MultiJson
class <<self
alias :load :decode
end
end
end
if !MultiJson.respond_to?(:dump)
module MultiJson
class <<self
alias :dump :encode
end
end
end

View File

@ -1,750 +0,0 @@
# 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.
require 'faraday'
require 'multi_json'
require 'compat/multi_json'
require 'stringio'
require 'retriable'
require 'google/api_client/version'
require 'google/api_client/logging'
require 'google/api_client/errors'
require 'google/api_client/environment'
require 'google/api_client/discovery'
require 'google/api_client/request'
require 'google/api_client/reference'
require 'google/api_client/result'
require 'google/api_client/media'
require 'google/api_client/service_account'
require 'google/api_client/batch'
require 'google/api_client/gzip'
require 'google/api_client/charset'
require 'google/api_client/client_secrets'
require 'google/api_client/railtie' if defined?(Rails)
module Google
##
# This class manages APIs communication.
class APIClient
include Google::APIClient::Logging
##
# 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>
# <li><code>:google_app_default</code></li>
# </ul>
# @option options [Boolean] :auto_refresh_token (true)
# The setting that controls whether or not the api client attempts to
# refresh authorization when a 401 is hit in #execute. If the token does
# not support it, this option is ignored.
# @option options [String] :application_name
# The name of the application using the client.
# @option options [String | Array | nil] :scope
# The scope(s) used when using google application default credentials
# @option options [String] :application_version
# The version number of the application using the client.
# @option options [String] :user_agent
# ("{app_name} google-api-ruby-client/{version} {os_name}/{os_version}")
# The user agent used by the client. Most developers will want to
# leave this value alone and use the `:application_name` option instead.
# @option options [String] :host ("www.googleapis.com")
# The API hostname used by the client. This rarely needs to be changed.
# @option options [String] :port (443)
# The port number used by the client. This rarely needs to be changed.
# @option options [String] :discovery_path ("/discovery/v1")
# The discovery base path. This rarely needs to be changed.
# @option options [String] :ca_file
# Optional set of root certificates to use when validating SSL connections.
# By default, a bundled set of trusted roots will be used.
# @options options[Hash] :force_encoding
# Experimental option. True if response body should be force encoded into the charset
# specified in the Content-Type header. Mostly intended for compressed content.
# @options options[Hash] :faraday_options
# Pass through of options to set on the Faraday connection
def initialize(options={})
logger.debug { "#{self.class} - Initializing client with options #{options}" }
# Normalize key to String to allow indifferent access.
options = options.inject({}) do |accu, (key, value)|
accu[key.to_sym] = value
accu
end
# Almost all API usage will have a host of 'www.googleapis.com'.
self.host = options[:host] || 'www.googleapis.com'
self.port = options[:port] || 443
self.discovery_path = options[:discovery_path] || '/discovery/v1'
# Most developers will want to leave this value alone and use the
# application_name option.
if options[:application_name]
app_name = options[:application_name]
app_version = options[:application_version]
application_string = "#{app_name}/#{app_version || '0.0.0'}"
else
logger.warn { "#{self.class} - Please provide :application_name and :application_version when initializing the client" }
end
proxy = options[:proxy] || Object::ENV["http_proxy"]
self.user_agent = options[:user_agent] || (
"#{application_string} " +
"google-api-ruby-client/#{Google::APIClient::VERSION::STRING} #{ENV::OS_VERSION} (gzip)"
).strip
# The writer method understands a few Symbols and will generate useful
# default authentication mechanisms.
self.authorization =
options.key?(:authorization) ? options[:authorization] : :oauth_2
if !options['scope'].nil? and self.authorization.respond_to?(:scope=)
self.authorization.scope = options['scope']
end
self.auto_refresh_token = options.fetch(:auto_refresh_token) { true }
self.key = options[:key]
self.user_ip = options[:user_ip]
self.retries = options.fetch(:retries) { 0 }
self.expired_auth_retry = options.fetch(:expired_auth_retry) { true }
@discovery_uris = {}
@discovery_documents = {}
@discovered_apis = {}
ca_file = options[:ca_file] || File.expand_path('../../cacerts.pem', __FILE__)
self.connection = Faraday.new do |faraday|
faraday.response :charset if options[:force_encoding]
faraday.response :gzip
faraday.options.params_encoder = Faraday::FlatParamsEncoder
faraday.ssl.ca_file = ca_file
faraday.ssl.verify = true
faraday.proxy proxy
faraday.adapter Faraday.default_adapter
if options[:faraday_option].is_a?(Hash)
options[:faraday_option].each_pair do |option, value|
faraday.options.send("#{option}=", value)
end
end
end
return self
end
##
# Returns the authorization mechanism used by the client.
#
# @return [#generate_authenticated_request] The authorization mechanism.
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
new_authorization = Signet::OAuth1::Client.new(
:temporary_credential_uri =>
'https://www.google.com/accounts/OAuthGetRequestToken',
:authorization_uri =>
'https://www.google.com/accounts/OAuthAuthorizeToken',
:token_credential_uri =>
'https://www.google.com/accounts/OAuthGetAccessToken',
:client_credential_key => 'anonymous',
:client_credential_secret => 'anonymous'
)
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
new_authorization = Signet::OAuth1::Client.new(
:client_credential_key => nil,
:client_credential_secret => nil,
:two_legged => true
)
when :google_app_default
require 'googleauth'
new_authorization = Google::Auth.get_application_default
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 !new_authorization.respond_to?(:generate_authenticated_request)
raise TypeError,
'Expected authorization mechanism to respond to ' +
'#generate_authenticated_request.'
end
end
@authorization = new_authorization
return @authorization
end
##
# Default Faraday/HTTP connection.
#
# @return [Faraday::Connection]
attr_accessor :connection
##
# The setting that controls whether or not the api client attempts to
# refresh authorization when a 401 is hit in #execute.
#
# @return [Boolean]
attr_accessor :auto_refresh_token
##
# The application's API key issued by the API console.
#
# @return [String] The API key.
attr_accessor :key
##
# The IP address of the user this request is being performed on behalf of.
#
# @return [String] The user's IP address.
attr_accessor :user_ip
##
# The user agent used by the client.
#
# @return [String]
# The user agent string used in the User-Agent header.
attr_accessor :user_agent
##
# The API hostname used by the client.
#
# @return [String]
# The API hostname. Should almost always be 'www.googleapis.com'.
attr_accessor :host
##
# The port number used by the client.
#
# @return [String]
# The port number. Should almost always be 443.
attr_accessor :port
##
# The base path used by the client for discovery.
#
# @return [String]
# The base path. Should almost always be '/discovery/v1'.
attr_accessor :discovery_path
##
# Number of times to retry on recoverable errors
#
# @return [FixNum]
# Number of retries
attr_accessor :retries
##
# Whether or not an expired auth token should be re-acquired
# (and the operation retried) regardless of retries setting
# @return [Boolean]
# Auto retry on auth expiry
attr_accessor :expired_auth_retry
##
# Returns the URI for the directory document.
#
# @return [Addressable::URI] The URI of the directory document.
def directory_uri
return resolve_uri(self.discovery_path + '/apis')
end
##
# Manually registers a URI as a discovery document for a specific version
# of an API.
#
# @param [String, Symbol] api The API name.
# @param [String] version The desired version of the API.
# @param [Addressable::URI] uri The URI of the discovery document.
# @return [Google::APIClient::API] The service object.
def register_discovery_uri(api, version, uri)
api = api.to_s
version = version || 'v1'
@discovery_uris["#{api}:#{version}"] = uri
discovered_api(api, version)
end
##
# Returns the URI for the discovery document.
#
# @param [String, Symbol] api The API name.
# @param [String] version The desired version of the API.
# @return [Addressable::URI] The URI of the discovery document.
def discovery_uri(api, version=nil)
api = api.to_s
version = version || 'v1'
return @discovery_uris["#{api}:#{version}"] ||= (
resolve_uri(
self.discovery_path + '/apis/{api}/{version}/rest',
'api' => api,
'version' => version
)
)
end
##
# Manually registers a pre-loaded discovery document for a specific version
# of an API.
#
# @param [String, Symbol] api The API name.
# @param [String] version The desired version of the API.
# @param [String, StringIO] discovery_document
# The contents of the discovery document.
# @return [Google::APIClient::API] The service object.
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}"] =
MultiJson.load(discovery_document)
discovered_api(api, version)
end
##
# Returns the parsed directory document.
#
# @return [Hash] The parsed JSON from the directory document.
def directory_document
return @directory_document ||= (begin
response = self.execute!(
:http_method => :get,
:uri => self.directory_uri,
:authenticated => false
)
response.data
end)
end
##
# Returns the parsed discovery document.
#
# @param [String, Symbol] api The API name.
# @param [String] version The desired version of the API.
# @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
response = self.execute!(
:http_method => :get,
:uri => self.discovery_uri(api, version),
:authenticated => false
)
response.data
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
end)
end
##
# Returns the service object for a given service name and service version.
#
# @param [String, Symbol] api The API name.
# @param [String] version The desired version of the API.
#
# @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 #{api.class}."
end
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
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, Symbol] api The API the method is within.
# @param [String] version The desired version of the API.
#
# @return [Google::APIClient::Method] The method object.
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
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
end
##
# Returns the service object with the highest version number.
#
# @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] api The name of the service.
#
# @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 #{api.class}."
end
api = api.to_s
return self.discovered_apis.detect do |a|
a.name == api && a.preferred == true
end
end
##
# Verifies an ID token against a server certificate. Used to ensure that
# an ID token supplied by an untrusted client-side mechanism is valid.
# Raises an error if the token is invalid or missing.
#
# @deprecated Use the google-id-token gem for verifying JWTs
def verify_id_token!
require 'jwt'
require 'openssl'
@certificates ||= {}
if !self.authorization.respond_to?(:id_token)
raise ArgumentError, (
"Current authorization mechanism does not support ID tokens: " +
"#{self.authorization.class.to_s}"
)
elsif !self.authorization.id_token
raise ArgumentError, (
"Could not verify ID token, ID token missing. " +
"Scopes were: #{self.authorization.scope.inspect}"
)
else
check_cached_certs = lambda do
valid = false
for _key, cert in @certificates
begin
self.authorization.decoded_id_token(cert.public_key)
valid = true
rescue JWT::DecodeError, Signet::UnsafeOperationError
# Expected exception. Ignore, ID token has not been validated.
end
end
valid
end
if check_cached_certs.call()
return true
end
response = self.execute!(
:http_method => :get,
:uri => 'https://www.googleapis.com/oauth2/v1/certs',
:authenticated => false
)
@certificates.merge!(
Hash[MultiJson.load(response.body).map do |key, cert|
[key, OpenSSL::X509::Certificate.new(cert)]
end]
)
if check_cached_certs.call()
return true
else
raise InvalidIDTokenError,
"Could not verify ID token against any available certificate."
end
end
return nil
end
##
# Generates a request.
#
# @option options [Google::APIClient::Method] :api_method
# The method object or the RPC name of the method being executed.
# @option options [Hash, Array] :parameters
# The parameters to send to the method.
# @option options [Hash, Array] :headers The HTTP headers for the request.
# @option options [String] :body The body of the request.
# @option options [String] :version ("v1")
# The service version. Only used if `api_method` is a `String`.
# @option options [#generate_authenticated_request] :authorization
# The authorization mechanism for the response. Used only if
# `:authenticated` is `true`.
# @option options [TrueClass, FalseClass] :authenticated (true)
# `true` if the request must be signed or somehow
# authenticated, `false` otherwise.
#
# @return [Google::APIClient::Reference] The generated request.
#
# @example
# request = client.generate_request(
# :api_method => 'plus.activities.list',
# :parameters =>
# {'collection' => 'public', 'userId' => 'me'}
# )
def generate_request(options={})
options = {
:api_client => self
}.merge(options)
return Google::APIClient::Request.new(options)
end
##
# Executes a request, wrapping it in a Result object.
#
# @param [Google::APIClient::Request, Hash, Array] params
# Either a Google::APIClient::Request, a Hash, or an Array.
#
# If a Google::APIClient::Request, no other parameters are expected.
#
# If a Hash, the below parameters are handled. If an Array, the
# parameters are assumed to be in the below order:
#
# - (Google::APIClient::Method) api_method:
# The method object or the RPC name of the method being executed.
# - (Hash, Array) parameters:
# The parameters to send to the method.
# - (String) body: The body of the request.
# - (Hash, Array) headers: The HTTP headers for the request.
# - (Hash) options: A set of options for the request, of which:
# - (#generate_authenticated_request) :authorization (default: true) -
# The authorization mechanism for the response. Used only if
# `:authenticated` is `true`.
# - (TrueClass, FalseClass) :authenticated (default: true) -
# `true` if the request must be signed or somehow
# authenticated, `false` otherwise.
# - (TrueClass, FalseClass) :gzip (default: true) -
# `true` if gzip enabled, `false` otherwise.
# - (FixNum) :retries -
# # of times to retry on recoverable errors
#
# @return [Google::APIClient::Result] The result from the API, nil if batch.
#
# @example
# result = client.execute(batch_request)
#
# @example
# plus = client.discovered_api('plus')
# result = client.execute(
# :api_method => plus.activities.list,
# :parameters => {'collection' => 'public', 'userId' => 'me'}
# )
#
# @see Google::APIClient#generate_request
def execute!(*params)
if params.first.kind_of?(Google::APIClient::Request)
request = params.shift
options = params.shift || {}
else
# This block of code allows us to accept multiple parameter passing
# styles, and maintaining some backwards compatibility.
#
# Note: I'm extremely tempted to deprecate this style of execute call.
if params.last.respond_to?(:to_hash) && params.size == 1
options = params.pop
else
options = {}
end
options[:api_method] = params.shift if params.size > 0
options[:parameters] = params.shift if params.size > 0
options[:body] = params.shift if params.size > 0
options[:headers] = params.shift if params.size > 0
options.update(params.shift) if params.size > 0
request = self.generate_request(options)
end
request.headers['User-Agent'] ||= '' + self.user_agent unless self.user_agent.nil?
request.headers['Accept-Encoding'] ||= 'gzip' unless options[:gzip] == false
request.headers['Content-Type'] ||= ''
request.parameters['key'] ||= self.key unless self.key.nil?
request.parameters['userIp'] ||= self.user_ip unless self.user_ip.nil?
connection = options[:connection] || self.connection
request.authorization = options[:authorization] || self.authorization unless options[:authenticated] == false
tries = 1 + (options[:retries] || self.retries)
attempt = 0
Retriable.retriable :tries => tries,
:on => [TransmissionError],
:on_retry => client_error_handler,
:interval => lambda {|attempts| (2 ** attempts) + rand} do
attempt += 1
# This 2nd level retriable only catches auth errors, and supports 1 retry, which allows
# auth to be re-attempted without having to retry all sorts of other failures like
# NotFound, etc
Retriable.retriable :tries => ((expired_auth_retry || tries > 1) && attempt == 1) ? 2 : 1,
:on => [AuthorizationError],
:on_retry => authorization_error_handler(request.authorization) do
result = request.send(connection, true)
case result.status
when 200...300
result
when 301, 302, 303, 307
request = generate_request(request.to_hash.merge({
:uri => result.headers['location'],
:api_method => nil
}))
raise RedirectError.new(result.headers['location'], result)
when 401
raise AuthorizationError.new(result.error_message || 'Invalid/Expired Authentication', result)
when 400, 402...500
raise ClientError.new(result.error_message || "A client error has occurred", result)
when 500...600
raise ServerError.new(result.error_message || "A server error has occurred", result)
else
raise TransmissionError.new(result.error_message || "A transmission error has occurred", result)
end
end
end
end
##
# Same as Google::APIClient#execute!, but does not raise an exception for
# normal API errros.
#
# @see Google::APIClient#execute
def execute(*params)
begin
return self.execute!(*params)
rescue TransmissionError => e
return e.result
end
end
protected
##
# Resolves a URI template against the client's configured base.
#
# @api private
# @param [String, Addressable::URI, Addressable::Template] template
# The template to resolve.
# @param [Hash] mapping The mapping that corresponds to the template.
# @return [Addressable::URI] The expanded URI.
def resolve_uri(template, mapping={})
@base_uri ||= Addressable::URI.new(
:scheme => 'https',
:host => self.host,
:port => self.port
).normalize
template = if template.kind_of?(Addressable::Template)
template.pattern
elsif template.respond_to?(:to_str)
template.to_str
else
raise TypeError,
"Expected String, Addressable::URI, or Addressable::Template, " +
"got #{template.class}."
end
return Addressable::Template.new(@base_uri + template).expand(mapping)
end
##
# Returns on proc for special processing of retries for authorization errors
# Only 401s should be retried and only if the credentials are refreshable
#
# @param [#fetch_access_token!] authorization
# OAuth 2 credentials
# @return [Proc]
def authorization_error_handler(authorization)
can_refresh = authorization.respond_to?(:refresh_token) && auto_refresh_token
Proc.new do |exception, tries|
next unless exception.kind_of?(AuthorizationError)
if can_refresh
begin
logger.debug("Attempting refresh of access token & retry of request")
authorization.fetch_access_token!
next
rescue Signet::AuthorizationError
end
end
raise exception
end
end
##
# Returns on proc for special processing of retries as not all client errors
# are recoverable. Only 401s should be retried (via authorization_error_handler)
#
# @return [Proc]
def client_error_handler
Proc.new do |exception, tries|
raise exception if exception.kind_of?(ClientError)
end
end
end
end

View File

@ -1,28 +0,0 @@
# Copyright 2013 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.
require 'faraday'
require 'signet/oauth_2/client'
module Google
class APIClient
class ComputeServiceAccount < Signet::OAuth2::Client
def fetch_access_token(options={})
connection = options[:connection] || Faraday.default_connection
response = connection.get 'http://metadata/computeMetadata/v1beta1/instance/service-accounts/default/token'
Signet::OAuth2.parse_credentials(response.body, response.headers['content-type'])
end
end
end
end

View File

@ -1,59 +0,0 @@
# Copyright 2013 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.
require 'signet/oauth_2/client'
require_relative 'storage'
require_relative 'storages/file_store'
module Google
class APIClient
##
# Represents cached OAuth 2 tokens stored on local disk in a
# JSON serialized file. Meant to resemble the serialized format
# http://google-api-python-client.googlecode.com/hg/docs/epy/oauth2client.file.Storage-class.html
#
# @deprecated
# Use {Google::APIClient::Storage} and {Google::APIClient::FileStore} instead
#
class FileStorage
attr_accessor :storage
def initialize(path)
store = Google::APIClient::FileStore.new(path)
@storage = Google::APIClient::Storage.new(store)
@storage.authorize
end
def load_credentials
storage.authorize
end
def authorization
storage.authorization
end
##
# Write the credentials to the specified file.
#
# @param [Signet::OAuth2::Client] authorization
# Optional authorization instance. If not provided, the authorization
# already associated with this instance will be written.
def write_credentials(auth=nil)
storage.write_credentials(auth)
end
end
end
end

View File

@ -1,126 +0,0 @@
# 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.
require 'webrick'
require 'launchy'
module Google
class APIClient
# Small helper for the sample apps for performing OAuth 2.0 flows from the command
# line or in any other installed app environment.
#
# @example
#
# client = Google::APIClient.new
# flow = Google::APIClient::InstalledAppFlow.new(
# :client_id => '691380668085.apps.googleusercontent.com',
# :client_secret => '...',
# :scope => 'https://www.googleapis.com/auth/drive'
# )
# client.authorization = flow.authorize
#
class InstalledAppFlow
RESPONSE_BODY = <<-HTML
<html>
<head>
<script>
function closeWindow() {
window.open('', '_self', '');
window.close();
}
setTimeout(closeWindow, 10);
</script>
</head>
<body>You may close this window.</body>
</html>
HTML
##
# Configure the flow
#
# @param [Hash] options The configuration parameters for the client.
# @option options [Fixnum] :port
# Port to run the embedded server on. Defaults to 9292
# @option options [String] :client_id
# A unique identifier issued to the client to identify itself to the
# authorization server.
# @option options [String] :client_secret
# A shared symmetric secret issued by the authorization server,
# which is used to authenticate the client.
# @option options [String] :scope
# The scope of the access request, expressed either as an Array
# or as a space-delimited String.
#
# @see Signet::OAuth2::Client
def initialize(options)
@port = options[:port] || 9292
@authorization = Signet::OAuth2::Client.new({
:authorization_uri => 'https://accounts.google.com/o/oauth2/auth',
:token_credential_uri => 'https://accounts.google.com/o/oauth2/token',
:redirect_uri => "http://localhost:#{@port}/"}.update(options)
)
end
##
# Request authorization. Opens a browser and waits for response.
#
# @param [Google::APIClient::Storage] storage
# Optional object that responds to :write_credentials, used to serialize
# the OAuth 2 credentials after completing the flow.
#
# @return [Signet::OAuth2::Client]
# Authorization instance, nil if user cancelled.
def authorize(storage=nil)
auth = @authorization
server = WEBrick::HTTPServer.new(
:Port => @port,
:BindAddress =>"localhost",
:Logger => WEBrick::Log.new(STDOUT, 0),
:AccessLog => []
)
begin
trap("INT") { server.shutdown }
server.mount_proc '/' do |req, res|
auth.code = req.query['code']
if auth.code
auth.fetch_access_token!
end
res.status = WEBrick::HTTPStatus::RC_ACCEPTED
res.body = RESPONSE_BODY
server.stop
end
Launchy.open(auth.authorization_uri.to_s)
server.start
ensure
server.shutdown
end
if @authorization.access_token
if storage.respond_to?(:write_credentials)
storage.write_credentials(@authorization)
end
return @authorization
else
return nil
end
end
end
end
end

View File

@ -1,126 +0,0 @@
# 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.
require 'jwt'
require 'signet/oauth_2/client'
require 'delegate'
module Google
class APIClient
##
# Generates access tokens using the JWT assertion profile. Requires a
# service account & access to the private key.
#
# @example Using Signet
#
# key = Google::APIClient::KeyUtils.load_from_pkcs12('client.p12', 'notasecret')
# client.authorization = Signet::OAuth2::Client.new(
# :token_credential_uri => 'https://accounts.google.com/o/oauth2/token',
# :audience => 'https://accounts.google.com/o/oauth2/token',
# :scope => 'https://www.googleapis.com/auth/prediction',
# :issuer => '123456-abcdef@developer.gserviceaccount.com',
# :signing_key => key)
# client.authorization.fetch_access_token!
# client.execute(...)
#
# @deprecated
# Service accounts are now supported directly in Signet
# @see https://developers.google.com/accounts/docs/OAuth2ServiceAccount
class JWTAsserter
# @return [String] ID/email of the issuing party
attr_accessor :issuer
# @return [Fixnum] How long, in seconds, the assertion is valid for
attr_accessor :expiry
# @return [Fixnum] Seconds to expand the issued at/expiry window to account for clock skew
attr_accessor :skew
# @return [String] Scopes to authorize
attr_reader :scope
# @return [String,OpenSSL::PKey] key for signing assertions
attr_writer :key
# @return [String] Algorithm used for signing
attr_accessor :algorithm
##
# Initializes the asserter for a service account.
#
# @param [String] issuer
# Name/ID of the client issuing the assertion
# @param [String, Array] scope
# Scopes to authorize. May be a space delimited string or array of strings
# @param [String,OpenSSL::PKey] key
# Key for signing assertions
# @param [String] algorithm
# Algorithm to use, either 'RS256' for RSA with SHA-256
# or 'HS256' for HMAC with SHA-256
def initialize(issuer, scope, key, algorithm = "RS256")
self.issuer = issuer
self.scope = scope
self.expiry = 60 # 1 min default
self.skew = 60
self.key = key
self.algorithm = algorithm
end
##
# Set the scopes to authorize
#
# @param [String, Array] new_scope
# Scopes to authorize. May be a space delimited string or array of strings
def scope=(new_scope)
case new_scope
when Array
@scope = new_scope.join(' ')
when String
@scope = new_scope
when nil
@scope = ''
else
raise TypeError, "Expected Array or String, got #{new_scope.class}"
end
end
##
# Request a new access token.
#
# @param [String] person
# Email address of a user, if requesting a token to act on their behalf
# @param [Hash] options
# Pass through to Signet::OAuth2::Client.fetch_access_token
# @return [Signet::OAuth2::Client] Access token
#
# @see Signet::OAuth2::Client.fetch_access_token!
def authorize(person = nil, options={})
authorization = self.to_authorization(person)
authorization.fetch_access_token!(options)
return authorization
end
##
# Builds a Signet OAuth2 client
#
# @return [Signet::OAuth2::Client] Access token
def to_authorization(person = nil)
return Signet::OAuth2::Client.new(
:token_credential_uri => 'https://accounts.google.com/o/oauth2/token',
:audience => 'https://accounts.google.com/o/oauth2/token',
:scope => self.scope,
:issuer => @issuer,
:signing_key => @key,
:signing_algorithm => @algorithm,
:person => person
)
end
end
end
end

View File

@ -1,93 +0,0 @@
# 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
##
# Helper for loading keys from the PKCS12 files downloaded when
# setting up service accounts at the APIs Console.
#
module KeyUtils
##
# Loads a key from PKCS12 file, assuming a single private key
# is present.
#
# @param [String] keyfile
# Path of the PKCS12 file to load. If not a path to an actual file,
# assumes the string is the content of the file itself.
# @param [String] passphrase
# Passphrase for unlocking the private key
#
# @return [OpenSSL::PKey] The private key for signing assertions.
def self.load_from_pkcs12(keyfile, passphrase)
load_key(keyfile, passphrase) do |content, pass_phrase|
OpenSSL::PKCS12.new(content, pass_phrase).key
end
end
##
# Loads a key from a PEM file.
#
# @param [String] keyfile
# Path of the PEM file to load. If not a path to an actual file,
# assumes the string is the content of the file itself.
# @param [String] passphrase
# Passphrase for unlocking the private key
#
# @return [OpenSSL::PKey] The private key for signing assertions.
#
def self.load_from_pem(keyfile, passphrase)
load_key(keyfile, passphrase) do | content, pass_phrase|
OpenSSL::PKey::RSA.new(content, pass_phrase)
end
end
private
##
# Helper for loading keys from file or memory. Accepts a block
# to handle the specific file format.
#
# @param [String] keyfile
# Path of thefile to load. If not a path to an actual file,
# assumes the string is the content of the file itself.
# @param [String] passphrase
# Passphrase for unlocking the private key
#
# @yield [String, String]
# Key file & passphrase to extract key from
# @yieldparam [String] keyfile
# Contents of the file
# @yieldparam [String] passphrase
# Passphrase to unlock key
# @yieldreturn [OpenSSL::PKey]
# Private key
#
# @return [OpenSSL::PKey] The private key for signing assertions.
def self.load_key(keyfile, passphrase, &block)
begin
begin
content = File.open(keyfile, 'rb') { |io| io.read }
rescue
content = keyfile
end
block.call(content, passphrase)
rescue OpenSSL::OpenSSLError
raise ArgumentError.new("Invalid keyfile or passphrase")
end
end
end
end
end

View File

@ -1,41 +0,0 @@
# 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.
require 'google/api_client/auth/key_utils'
module Google
class APIClient
##
# Helper for loading keys from the PKCS12 files downloaded when
# setting up service accounts at the APIs Console.
#
module PKCS12
##
# Loads a key from PKCS12 file, assuming a single private key
# is present.
#
# @param [String] keyfile
# Path of the PKCS12 file to load. If not a path to an actual file,
# assumes the string is the content of the file itself.
# @param [String] passphrase
# Passphrase for unlocking the private key
#
# @return [OpenSSL::PKey] The private key for signing assertions.
# @deprecated
# Use {Google::APIClient::KeyUtils} instead
def self.load_key(keyfile, passphrase)
KeyUtils.load_from_pkcs12(keyfile, passphrase)
end
end
end
end

View File

@ -1,102 +0,0 @@
# Copyright 2013 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.
require 'signet/oauth_2/client'
module Google
class APIClient
##
# Represents cached OAuth 2 tokens stored on local disk in a
# JSON serialized file. Meant to resemble the serialized format
# http://google-api-python-client.googlecode.com/hg/docs/epy/oauth2client.file.Storage-class.html
#
class Storage
AUTHORIZATION_URI = 'https://accounts.google.com/o/oauth2/auth'
TOKEN_CREDENTIAL_URI = 'https://accounts.google.com/o/oauth2/token'
# @return [Object] Storage object.
attr_accessor :store
# @return [Signet::OAuth2::Client]
attr_reader :authorization
##
# Initializes the Storage object.
#
# @params [Object] Storage object
def initialize(store)
@store= store
@authorization = nil
end
##
# Write the credentials to the specified store.
#
# @params [Signet::OAuth2::Client] authorization
# Optional authorization instance. If not provided, the authorization
# already associated with this instance will be written.
def write_credentials(authorization=nil)
@authorization = authorization if authorization
if @authorization.respond_to?(:refresh_token) && @authorization.refresh_token
store.write_credentials(credentials_hash)
end
end
##
# Loads credentials and authorizes an client.
# @return [Object] Signet::OAuth2::Client or NIL
def authorize
@authorization = nil
cached_credentials = load_credentials
if cached_credentials && cached_credentials.size > 0
@authorization = Signet::OAuth2::Client.new(cached_credentials)
@authorization.issued_at = Time.at(cached_credentials['issued_at'].to_i)
self.refresh_authorization if @authorization.expired?
end
return @authorization
end
##
# refresh credentials and save them to store
def refresh_authorization
authorization.refresh!
self.write_credentials
end
private
##
# Attempt to read in credentials from the specified store.
def load_credentials
store.load_credentials
end
##
# @return [Hash] with credentials
def credentials_hash
{
:access_token => authorization.access_token,
:authorization_uri => AUTHORIZATION_URI,
:client_id => authorization.client_id,
:client_secret => authorization.client_secret,
:expires_in => authorization.expires_in,
:refresh_token => authorization.refresh_token,
:token_credential_uri => TOKEN_CREDENTIAL_URI,
:issued_at => authorization.issued_at.to_i
}
end
end
end
end

View File

@ -1,58 +0,0 @@
# Copyright 2013 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.
require 'json'
module Google
class APIClient
##
# Represents cached OAuth 2 tokens stored on local disk in a
# JSON serialized file. Meant to resemble the serialized format
# http://google-api-python-client.googlecode.com/hg/docs/epy/oauth2client.file.Storage-class.html
#
class FileStore
attr_accessor :path
##
# Initializes the FileStorage object.
#
# @param [String] path
# Path to the credentials file.
def initialize(path)
@path= path
end
##
# Attempt to read in credentials from the specified file.
def load_credentials
open(path, 'r') { |f| JSON.parse(f.read) }
rescue
nil
end
##
# Write the credentials to the specified file.
#
# @param [Signet::OAuth2::Client] authorization
# Optional authorization instance. If not provided, the authorization
# already associated with this instance will be written.
def write_credentials(credentials_hash)
open(self.path, 'w+') do |f|
f.write(credentials_hash.to_json)
end
end
end
end
end

View File

@ -1,54 +0,0 @@
# Copyright 2013 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.
require 'json'
module Google
class APIClient
class RedisStore
DEFAULT_REDIS_CREDENTIALS_KEY = "google_api_credentials"
attr_accessor :redis
##
# Initializes the RedisStore object.
#
# @params [Object] Redis instance
def initialize(redis, key = nil)
@redis= redis
@redis_credentials_key = key
end
##
# Attempt to read in credentials from redis.
def load_credentials
credentials = redis.get redis_credentials_key
JSON.parse(credentials) if credentials
end
def redis_credentials_key
@redis_credentials_key || DEFAULT_REDIS_CREDENTIALS_KEY
end
##
# Write the credentials to redis.
#
# @params [Hash] credentials
def write_credentials(credentials_hash)
redis.set(redis_credentials_key, credentials_hash.to_json)
end
end
end
end

View File

@ -1,326 +0,0 @@
# Copyright 2012 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.
require 'addressable/uri'
require 'google/api_client/reference'
require 'securerandom'
module Google
class APIClient
##
# Helper class to contain a response to an individual batched call.
#
# @api private
class BatchedCallResponse
# @return [String] UUID of the call
attr_reader :call_id
# @return [Fixnum] HTTP status code
attr_accessor :status
# @return [Hash] HTTP response headers
attr_accessor :headers
# @return [String] HTTP response body
attr_accessor :body
##
# Initialize the call response
#
# @param [String] call_id
# UUID of the original call
# @param [Fixnum] status
# HTTP status
# @param [Hash] headers
# HTTP response headers
# @param [#read, #to_str] body
# Response body
def initialize(call_id, status = nil, headers = nil, body = nil)
@call_id, @status, @headers, @body = call_id, status, headers, body
end
end
# Wraps multiple API calls into a single over-the-wire HTTP request.
#
# @example
#
# client = Google::APIClient.new
# urlshortener = client.discovered_api('urlshortener')
# batch = Google::APIClient::BatchRequest.new do |result|
# puts result.data
# end
#
# batch.add(:api_method => urlshortener.url.insert, :body_object => { 'longUrl' => 'http://example.com/foo' })
# batch.add(:api_method => urlshortener.url.insert, :body_object => { 'longUrl' => 'http://example.com/bar' })
#
# client.execute(batch)
#
class BatchRequest < Request
BATCH_BOUNDARY = "-----------RubyApiBatchRequest".freeze
# @api private
# @return [Array<(String,Google::APIClient::Request,Proc)] List of API calls in the batch
attr_reader :calls
##
# Creates a new batch request.
#
# @param [Hash] options
# Set of options for this request.
# @param [Proc] block
# Callback for every call's response. Won't be called if a call defined
# a callback of its own.
#
# @return [Google::APIClient::BatchRequest]
# The constructed object.
#
# @yield [Google::APIClient::Result]
# block to be called when result ready
def initialize(options = {}, &block)
@calls = []
@global_callback = nil
@global_callback = block if block_given?
@last_auto_id = 0
@base_id = SecureRandom.uuid
options[:uri] ||= 'https://www.googleapis.com/batch'
options[:http_method] ||= 'POST'
super options
end
##
# Add a new call to the batch request.
# Each call must have its own call ID; if not provided, one will
# automatically be generated, avoiding collisions. If duplicate call IDs
# are provided, an error will be thrown.
#
# @param [Hash, Google::APIClient::Request] call
# the call to be added.
# @param [String] call_id
# the ID to be used for this call. Must be unique
# @param [Proc] block
# callback for this call's response.
#
# @return [Google::APIClient::BatchRequest]
# the BatchRequest, for chaining
#
# @yield [Google::APIClient::Result]
# block to be called when result ready
def add(call, call_id = nil, &block)
unless call.kind_of?(Google::APIClient::Reference)
call = Google::APIClient::Reference.new(call)
end
call_id ||= new_id
if @calls.assoc(call_id)
raise BatchError,
'A call with this ID already exists: %s' % call_id
end
callback = block_given? ? block : @global_callback
@calls << [call_id, call, callback]
return self
end
##
# Processes the HTTP response to the batch request, issuing callbacks.
#
# @api private
#
# @param [Faraday::Response] response
# the HTTP response.
def process_http_response(response)
content_type = find_header('Content-Type', response.headers)
m = /.*boundary=(.+)/.match(content_type)
if m
boundary = m[1]
parts = response.body.split(/--#{Regexp.escape(boundary)}/)
parts = parts[1...-1]
parts.each do |part|
call_response = deserialize_call_response(part)
_, call, callback = @calls.assoc(call_response.call_id)
result = Google::APIClient::Result.new(call, call_response)
callback.call(result) if callback
end
end
Google::APIClient::Result.new(self, response)
end
##
# Return the request body for the BatchRequest's HTTP request.
#
# @api private
#
# @return [String]
# the request body.
def to_http_request
if @calls.nil? || @calls.empty?
raise BatchError, 'Cannot make an empty batch request'
end
parts = @calls.map {|(call_id, call, _callback)| serialize_call(call_id, call)}
build_multipart(parts, 'multipart/mixed', BATCH_BOUNDARY)
super
end
protected
##
# Helper method to find a header from its name, regardless of case.
#
# @api private
#
# @param [String] name
# the name of the header to find.
# @param [Hash] headers
# the hash of headers and their values.
#
# @return [String]
# the value of the desired header.
def find_header(name, headers)
_, header = headers.detect do |h, v|
h.downcase == name.downcase
end
return header
end
##
# Create a new call ID. Uses an auto-incrementing, conflict-avoiding ID.
#
# @api private
#
# @return [String]
# the new, unique ID.
def new_id
@last_auto_id += 1
while @calls.assoc(@last_auto_id)
@last_auto_id += 1
end
return @last_auto_id.to_s
end
##
# Convert a Content-ID header value to an id. Presumes the Content-ID
# header conforms to the format that id_to_header() returns.
#
# @api private
#
# @param [String] header
# Content-ID header value.
#
# @return [String]
# The extracted ID value.
def header_to_id(header)
if !header.start_with?('<') || !header.end_with?('>') ||
!header.include?('+')
raise BatchError, 'Invalid value for Content-ID: "%s"' % header
end
_base, call_id = header[1...-1].split('+')
return Addressable::URI.unencode(call_id)
end
##
# Auxiliary method to split the headers from the body in an HTTP response.
#
# @api private
#
# @param [String] response
# the response to parse.
#
# @return [Array<Hash>, String]
# the headers and the body, separately.
def split_headers_and_body(response)
headers = {}
payload = response.lstrip
while payload
line, payload = payload.split("\n", 2)
line.sub!(/\s+\z/, '')
break if line.empty?
match = /\A([^:]+):\s*/.match(line)
if match
headers[match[1]] = match.post_match
else
raise BatchError, 'Invalid header line in response: %s' % line
end
end
return headers, payload
end
##
# Convert a single batched response into a BatchedCallResponse object.
#
# @api private
#
# @param [String] call_response
# the request to deserialize.
#
# @return [Google::APIClient::BatchedCallResponse]
# the parsed and converted response.
def deserialize_call_response(call_response)
outer_headers, outer_body = split_headers_and_body(call_response)
status_line, payload = outer_body.split("\n", 2)
_protocol, status, _reason = status_line.split(' ', 3)
headers, body = split_headers_and_body(payload)
content_id = find_header('Content-ID', outer_headers)
call_id = header_to_id(content_id)
return BatchedCallResponse.new(call_id, status.to_i, headers, body)
end
##
# Serialize a single batched call for assembling the multipart message
#
# @api private
#
# @param [Google::APIClient::Request] call
# the call to serialize.
#
# @return [Faraday::UploadIO]
# the serialized request
def serialize_call(call_id, call)
method, uri, headers, body = call.to_http_request
request = "#{method.to_s.upcase} #{Addressable::URI.parse(uri).request_uri} HTTP/1.1"
headers.each do |header, value|
request << "\r\n%s: %s" % [header, value]
end
if body
# TODO - CompositeIO if body is a stream
request << "\r\n\r\n"
if body.respond_to?(:read)
request << body.read
else
request << body.to_s
end
end
Faraday::UploadIO.new(StringIO.new(request), 'application/http', 'ruby-api-request', 'Content-ID' => id_to_header(call_id))
end
##
# Convert an id to a Content-ID header value.
#
# @api private
#
# @param [String] call_id
# identifier of individual call.
#
# @return [String]
# A Content-ID header with the call_id encoded into it. A UUID is
# prepended to the value because Content-ID headers are supposed to be
# universally unique.
def id_to_header(call_id)
return '<%s+%s>' % [@base_id, Addressable::URI.encode(call_id)]
end
end
end
end

View File

@ -1,33 +0,0 @@
require 'faraday'
require 'zlib'
module Google
class APIClient
class Charset < Faraday::Response::Middleware
include Google::APIClient::Logging
def charset_for_content_type(type)
if type
m = type.match(/(?:charset|encoding)="?([a-z0-9-]+)"?/i)
if m
return Encoding.find(m[1])
end
end
nil
end
def adjust_encoding(env)
charset = charset_for_content_type(env[:response_headers]['content-type'])
if charset && env[:body].encoding != charset
env[:body].force_encoding(charset)
end
end
def on_complete(env)
adjust_encoding(env)
end
end
end
end
Faraday::Response.register_middleware :charset => Google::APIClient::Charset

View File

@ -1,179 +0,0 @@
# 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.
require 'compat/multi_json'
module Google
class APIClient
##
# Manages the persistence of client configuration data and secrets. Format
# inspired by the Google API Python client.
#
# @see https://developers.google.com/api-client-library/python/guide/aaa_client_secrets
#
# @example
# {
# "web": {
# "client_id": "asdfjasdljfasdkjf",
# "client_secret": "1912308409123890",
# "redirect_uris": ["https://www.example.com/oauth2callback"],
# "auth_uri": "https://accounts.google.com/o/oauth2/auth",
# "token_uri": "https://accounts.google.com/o/oauth2/token"
# }
# }
#
# @example
# {
# "installed": {
# "client_id": "837647042410-75ifg...usercontent.com",
# "client_secret":"asdlkfjaskd",
# "redirect_uris": ["http://localhost", "urn:ietf:oauth:2.0:oob"],
# "auth_uri": "https://accounts.google.com/o/oauth2/auth",
# "token_uri": "https://accounts.google.com/o/oauth2/token"
# }
# }
class ClientSecrets
##
# Reads client configuration from a file
#
# @param [String] filename
# Path to file to load
#
# @return [Google::APIClient::ClientSecrets]
# OAuth client settings
def self.load(filename=nil)
if filename && File.directory?(filename)
search_path = File.expand_path(filename)
filename = nil
end
while filename == nil
search_path ||= File.expand_path('.')
if File.exists?(File.join(search_path, 'client_secrets.json'))
filename = File.join(search_path, 'client_secrets.json')
elsif search_path == '/' || search_path =~ /[a-zA-Z]:[\/\\]/
raise ArgumentError,
'No client_secrets.json filename supplied ' +
'and/or could not be found in search path.'
else
search_path = File.expand_path(File.join(search_path, '..'))
end
end
data = File.open(filename, 'r') { |file| MultiJson.load(file.read) }
return self.new(data)
end
##
# Intialize OAuth client settings.
#
# @param [Hash] options
# Parsed client secrets files
def initialize(options={})
# Client auth configuration
@flow = options[:flow] || options.keys.first.to_s || 'web'
fdata = options[@flow]
@client_id = fdata[:client_id] || fdata["client_id"]
@client_secret = fdata[:client_secret] || fdata["client_secret"]
@redirect_uris = fdata[:redirect_uris] || fdata["redirect_uris"]
@redirect_uris ||= [fdata[:redirect_uri] || fdata["redirect_uri"]].compact
@javascript_origins = (
fdata[:javascript_origins] ||
fdata["javascript_origins"]
)
@javascript_origins ||= [fdata[:javascript_origin] || fdata["javascript_origin"]].compact
@authorization_uri = fdata[:auth_uri] || fdata["auth_uri"]
@authorization_uri ||= fdata[:authorization_uri]
@token_credential_uri = fdata[:token_uri] || fdata["token_uri"]
@token_credential_uri ||= fdata[:token_credential_uri]
# Associated token info
@access_token = fdata[:access_token] || fdata["access_token"]
@refresh_token = fdata[:refresh_token] || fdata["refresh_token"]
@id_token = fdata[:id_token] || fdata["id_token"]
@expires_in = fdata[:expires_in] || fdata["expires_in"]
@expires_at = fdata[:expires_at] || fdata["expires_at"]
@issued_at = fdata[:issued_at] || fdata["issued_at"]
end
attr_reader(
:flow, :client_id, :client_secret, :redirect_uris, :javascript_origins,
:authorization_uri, :token_credential_uri, :access_token,
:refresh_token, :id_token, :expires_in, :expires_at, :issued_at
)
##
# Serialize back to the original JSON form
#
# @return [String]
# JSON
def to_json
return MultiJson.dump(to_hash)
end
def to_hash
{
self.flow => ({
'client_id' => self.client_id,
'client_secret' => self.client_secret,
'redirect_uris' => self.redirect_uris,
'javascript_origins' => self.javascript_origins,
'auth_uri' => self.authorization_uri,
'token_uri' => self.token_credential_uri,
'access_token' => self.access_token,
'refresh_token' => self.refresh_token,
'id_token' => self.id_token,
'expires_in' => self.expires_in,
'expires_at' => self.expires_at,
'issued_at' => self.issued_at
}).inject({}) do |accu, (k, v)|
# Prunes empty values from JSON output.
unless v == nil || (v.respond_to?(:empty?) && v.empty?)
accu[k] = v
end
accu
end
}
end
def to_authorization
gem 'signet', '>= 0.4.0'
require 'signet/oauth_2/client'
# NOTE: Do not rely on this default value, as it may change
new_authorization = Signet::OAuth2::Client.new
new_authorization.client_id = self.client_id
new_authorization.client_secret = self.client_secret
new_authorization.authorization_uri = (
self.authorization_uri ||
'https://accounts.google.com/o/oauth2/auth'
)
new_authorization.token_credential_uri = (
self.token_credential_uri ||
'https://accounts.google.com/o/oauth2/token'
)
new_authorization.redirect_uri = self.redirect_uris.first
# These are supported, but unlikely.
new_authorization.access_token = self.access_token
new_authorization.refresh_token = self.refresh_token
new_authorization.id_token = self.id_token
new_authorization.expires_in = self.expires_in
new_authorization.issued_at = self.issued_at if self.issued_at
new_authorization.expires_at = self.expires_at if self.expires_at
return new_authorization
end
end
end
end

View File

@ -1,19 +0,0 @@
# 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.
require 'google/api_client/discovery/api'
require 'google/api_client/discovery/resource'
require 'google/api_client/discovery/method'
require 'google/api_client/discovery/schema'

View File

@ -1,310 +0,0 @@
# 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.
require 'addressable/uri'
require 'multi_json'
require 'active_support/inflector'
require 'google/api_client/discovery/resource'
require 'google/api_client/discovery/method'
require 'google/api_client/discovery/media'
module Google
class APIClient
##
# A service that has been described by a discovery document.
class API
##
# Creates a description of a particular version of a service.
#
# @param [String] document_base
# Base URI for the discovery document.
# @param [Hash] discovery_document
# The section of the discovery document that applies to this service
# version.
#
# @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.discovered_resources.each do |resource|
method_name = ActiveSupport::Inflector.underscore(resource.name).to_sym
if !self.respond_to?(method_name)
metaclass.send(:define_method, method_name) { resource }
end
end
self.discovered_methods.each do |method|
method_name = ActiveSupport::Inflector.underscore(method.name).to_sym
if !self.respond_to?(method_name)
metaclass.send(:define_method, method_name) { method }
end
end
end
# @return [String] unparsed discovery document for the API
attr_reader :discovery_document
##
# Returns the id of the service.
#
# @return [String] The service id.
def id
return (
@discovery_document['id'] ||
"#{self.name}:#{self.version}"
)
end
##
# Returns the identifier for the service.
#
# @return [String] The service identifier.
def name
return @discovery_document['name']
end
##
# Returns the version of the service.
#
# @return [String] The service version.
def version
return @discovery_document['version']
end
##
# Returns a human-readable title for the API.
#
# @return [Hash] The API title.
def title
return @discovery_document['title']
end
##
# Returns a human-readable description of the API.
#
# @return [Hash] The API description.
def description
return @discovery_document['description']
end
##
# Returns a URI for the API documentation.
#
# @return [Hash] The API documentation.
def documentation
return Addressable::URI.parse(@discovery_document['documentationLink'])
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 list of API features.
#
# @return [Array]
# The features supported by this API.
def features
return @discovery_document['features'] || []
end
##
# Returns the root URI for this service.
#
# @return [Addressable::URI] The root URI.
def root_uri
return @root_uri ||= (
Addressable::URI.parse(self.discovery_document['rootUrl'])
)
end
##
# Returns true if this API uses a data wrapper.
#
# @return [TrueClass, FalseClass]
# Whether or not this API uses a data wrapper.
def data_wrapper?
return self.features.include?('dataWrapper')
end
##
# Returns the base URI for the discovery document.
#
# @return [Addressable::URI] The base URI.
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 method_base
if @discovery_document['basePath']
return @method_base ||= (
self.root_uri.join(Addressable::URI.parse(@discovery_document['basePath']))
).normalize
else
return nil
end
end
##
# Updates the hierarchy of resources and methods with the new base.
#
# @param [Addressable::URI, #to_str, String] new_method_base
# The new base URI to use for the service.
def method_base=(new_method_base)
@method_base = Addressable::URI.parse(new_method_base)
self.discovered_resources.each do |resource|
resource.method_base = @method_base
end
self.discovered_methods.each do |method|
method.method_base = @method_base
end
end
##
# Returns the base URI for batch calls to this service.
#
# @return [Addressable::URI] The base URI that methods are joined to.
def batch_path
if @discovery_document['batchPath']
return @batch_path ||= (
self.document_base.join(Addressable::URI.parse('/' +
@discovery_document['batchPath']))
).normalize
else
return nil
end
end
##
# A list of schemas available for this version of the API.
#
# @return [Hash] A list of {Google::APIClient::Schema} objects.
def schemas
return @schemas ||= (
(@discovery_document['schemas'] || []).inject({}) do |accu, (k, v)|
accu[k] = Google::APIClient::Schema.parse(self, v)
accu
end
)
end
##
# Returns a schema for a kind value.
#
# @return [Google::APIClient::Schema] The associated Schema object.
def schema_for_kind(kind)
api_name, schema_name = kind.split('#', 2)
if api_name != self.name
raise ArgumentError,
"The kind does not match this API. " +
"Expected '#{self.name}', got '#{api_name}'."
end
for k, v in self.schemas
return v if k.downcase == schema_name.downcase
end
return nil
end
##
# A list of resources available at the root level of this version of the
# API.
#
# @return [Array] A list of {Google::APIClient::Resource} objects.
def discovered_resources
return @discovered_resources ||= (
(@discovery_document['resources'] || []).inject([]) do |accu, (k, v)|
accu << Google::APIClient::Resource.new(
self, self.method_base, k, v
)
accu
end
)
end
##
# A list of methods available at the root level of this version of the
# API.
#
# @return [Array] A list of {Google::APIClient::Method} objects.
def discovered_methods
return @discovered_methods ||= (
(@discovery_document['methods'] || []).inject([]) do |accu, (k, v)|
accu << Google::APIClient::Method.new(self, self.method_base, k, v)
accu
end
)
end
##
# Allows deep inspection of the discovery document.
def [](key)
return @discovery_document[key]
end
##
# Converts the service to a flat mapping of RPC names and method objects.
#
# @return [Hash] All methods available on the service.
#
# @example
# # Discover available methods
# method_names = client.discovered_api('buzz').to_h.keys
def to_h
return @hash ||= (begin
methods_hash = {}
self.discovered_methods.each do |method|
methods_hash[method.id] = method
end
self.discovered_resources.each do |resource|
methods_hash.merge!(resource.to_h)
end
methods_hash
end)
end
##
# Returns a <code>String</code> representation of the service's state.
#
# @return [String] The service's state, as a <code>String</code>.
def inspect
sprintf(
"#<%s:%#0x ID:%s>", self.class.to_s, self.object_id, self.id
)
end
##
# Marshalling support - serialize the API to a string (doc base + original
# discovery document).
def _dump(level)
MultiJson.dump([@document_base.to_s, @discovery_document])
end
##
# Marshalling support - Restore an API instance from serialized form
def self._load(obj)
new(*MultiJson.load(obj))
end
end
end
end

View File

@ -1,77 +0,0 @@
# 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.
require 'addressable/uri'
require 'addressable/template'
require 'google/api_client/errors'
module Google
class APIClient
##
# Media upload elements for discovered methods
class MediaUpload
##
# Creates a description of a particular method.
#
# @param [Google::APIClient::API] api
# Base discovery document for the API
# @param [Addressable::URI] method_base
# The base URI for the service.
# @param [Hash] discovery_document
# The media upload section of the discovery document.
#
# @return [Google::APIClient::Method] The constructed method object.
def initialize(api, method_base, discovery_document)
@api = api
@method_base = method_base
@discovery_document = discovery_document
end
##
# List of acceptable mime types
#
# @return [Array]
# List of acceptable mime types for uploaded content
def accepted_types
@discovery_document['accept']
end
##
# Maximum size of an uplad
# TODO: Parse & convert to numeric value
#
# @return [String]
def max_size
@discovery_document['maxSize']
end
##
# Returns the URI template for the method. A parameter list can be
# used to expand this into a URI.
#
# @return [Addressable::Template] The URI template.
def uri_template
return @uri_template ||= Addressable::Template.new(
@api.method_base.join(Addressable::URI.parse(@discovery_document['protocols']['simple']['path']))
)
end
end
end
end

View File

@ -1,363 +0,0 @@
# 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.
require 'addressable/uri'
require 'addressable/template'
require 'google/api_client/errors'
module Google
class APIClient
##
# A method that has been described by a discovery document.
class Method
##
# Creates a description of a particular method.
#
# @param [Google::APIClient::API] api
# The API this method belongs to.
# @param [Addressable::URI] method_base
# The base URI for the service.
# @param [String] method_name
# The identifier for the method.
# @param [Hash] discovery_document
# The section of the discovery document that applies to this method.
#
# @return [Google::APIClient::Method] The constructed method object.
def initialize(api, method_base, method_name, discovery_document)
@api = api
@method_base = method_base
@name = method_name
@discovery_document = discovery_document
end
# @return [String] unparsed discovery document for the method
attr_reader :discovery_document
##
# Returns the API this method belongs to.
#
# @return [Google::APIClient::API] The API this method belongs to.
attr_reader :api
##
# Returns the identifier for the method.
#
# @return [String] The method identifier.
attr_reader :name
##
# Returns the base URI for the method.
#
# @return [Addressable::URI]
# The base URI that this method will be joined to.
attr_reader :method_base
##
# Updates the method with the new base.
#
# @param [Addressable::URI, #to_str, String] new_method_base
# The new base URI to use for the method.
def method_base=(new_method_base)
@method_base = Addressable::URI.parse(new_method_base)
@uri_template = nil
end
##
# Returns a human-readable description of the method.
#
# @return [Hash] The API description.
def description
return @discovery_document['description']
end
##
# Returns the method ID.
#
# @return [String] The method identifier.
def id
return @discovery_document['id']
end
##
# Returns the HTTP method or 'GET' if none is specified.
#
# @return [String] The HTTP method that will be used in the request.
def http_method
return @discovery_document['httpMethod'] || 'GET'
end
##
# Returns the URI template for the method. A parameter list can be
# used to expand this into a URI.
#
# @return [Addressable::Template] The URI template.
def uri_template
return @uri_template ||= Addressable::Template.new(
self.method_base.join(Addressable::URI.parse("./" + @discovery_document['path']))
)
end
##
# Returns media upload information for this method, if supported
#
# @return [Google::APIClient::MediaUpload] Description of upload endpoints
def media_upload
if @discovery_document['mediaUpload']
return @media_upload ||= Google::APIClient::MediaUpload.new(self, self.method_base, @discovery_document['mediaUpload'])
else
return nil
end
end
##
# Returns the Schema object for the method's request, if any.
#
# @return [Google::APIClient::Schema] The request schema.
def request_schema
if @discovery_document['request']
schema_name = @discovery_document['request']['$ref']
return @api.schemas[schema_name]
else
return nil
end
end
##
# Returns the Schema object for the method's response, if any.
#
# @return [Google::APIClient::Schema] The response schema.
def response_schema
if @discovery_document['response']
schema_name = @discovery_document['response']['$ref']
return @api.schemas[schema_name]
else
return nil
end
end
##
# Normalizes parameters, converting to the appropriate types.
#
# @param [Hash, Array] parameters
# The parameters to normalize.
#
# @return [Hash] The normalized parameters.
def normalize_parameters(parameters={})
# Convert keys to Strings when appropriate
if parameters.kind_of?(Hash) || parameters.kind_of?(Array)
# Returning an array since parameters can be repeated (ie, Adsense Management API)
parameters = parameters.inject([]) do |accu, (k, v)|
k = k.to_s if k.kind_of?(Symbol)
k = k.to_str if k.respond_to?(:to_str)
unless k.kind_of?(String)
raise TypeError, "Expected String, got #{k.class}."
end
accu << [k, v]
accu
end
else
raise TypeError,
"Expected Hash or Array, got #{parameters.class}."
end
return parameters
end
##
# Expands the method's URI template using a parameter list.
#
# @api private
# @param [Hash, Array] parameters
# The parameter list to use.
#
# @return [Addressable::URI] The URI after expansion.
def generate_uri(parameters={})
parameters = self.normalize_parameters(parameters)
self.validate_parameters(parameters)
template_variables = self.uri_template.variables
upload_type = parameters.assoc('uploadType') || parameters.assoc('upload_type')
if upload_type
unless self.media_upload
raise ArgumentException, "Media upload not supported for this method"
end
case upload_type.last
when 'media', 'multipart', 'resumable'
uri = self.media_upload.uri_template.expand(parameters)
else
raise ArgumentException, "Invalid uploadType '#{upload_type}'"
end
else
uri = self.uri_template.expand(parameters)
end
query_parameters = parameters.reject do |k, v|
template_variables.include?(k)
end
# encode all non-template parameters
params = ""
unless query_parameters.empty?
params = "?" + Addressable::URI.form_encode(query_parameters.sort)
end
# Normalization is necessary because of undesirable percent-escaping
# during URI template expansion
return uri.normalize + params
end
##
# Generates an HTTP request for this method.
#
# @api private
# @param [Hash, Array] parameters
# The parameters to send.
# @param [String, StringIO] body The body for the HTTP request.
# @param [Hash, Array] headers The HTTP headers for the request.
# @option options [Faraday::Connection] :connection
# The HTTP connection to use.
#
# @return [Array] The generated HTTP request.
def generate_request(parameters={}, body='', headers={}, options={})
if !headers.kind_of?(Array) && !headers.kind_of?(Hash)
raise TypeError, "Expected Hash or Array, got #{headers.class}."
end
method = self.http_method.to_s.downcase.to_sym
uri = self.generate_uri(parameters)
headers = Faraday::Utils::Headers.new(headers)
return [method, uri, headers, body]
end
##
# Returns a <code>Hash</code> of the parameter descriptions for
# this method.
#
# @return [Hash] The parameter descriptions.
def parameter_descriptions
@parameter_descriptions ||= (
@discovery_document['parameters'] || {}
).inject({}) { |h,(k,v)| h[k]=v; h }
end
##
# Returns an <code>Array</code> of the parameters for this method.
#
# @return [Array] The parameters.
def parameters
@parameters ||= ((
@discovery_document['parameters'] || {}
).inject({}) { |h,(k,v)| h[k]=v; h }).keys
end
##
# Returns an <code>Array</code> of the required parameters for this
# method.
#
# @return [Array] The required parameters.
#
# @example
# # A list of all required parameters.
# method.required_parameters
def required_parameters
@required_parameters ||= ((self.parameter_descriptions.select do |k, v|
v['required']
end).inject({}) { |h,(k,v)| h[k]=v; h }).keys
end
##
# Returns an <code>Array</code> of the optional parameters for this
# method.
#
# @return [Array] The optional parameters.
#
# @example
# # A list of all optional parameters.
# method.optional_parameters
def optional_parameters
@optional_parameters ||= ((self.parameter_descriptions.reject do |k, v|
v['required']
end).inject({}) { |h,(k,v)| h[k]=v; h }).keys
end
##
# Verifies that the parameters are valid for this method. Raises an
# exception if validation fails.
#
# @api private
# @param [Hash, Array] parameters
# The parameters to verify.
#
# @return [NilClass] <code>nil</code> if validation passes.
def validate_parameters(parameters={})
parameters = self.normalize_parameters(parameters)
required_variables = ((self.parameter_descriptions.select do |k, v|
v['required']
end).inject({}) { |h,(k,v)| h[k]=v; h }).keys
missing_variables = required_variables - parameters.map { |(k, _)| k }
if missing_variables.size > 0
raise ArgumentError,
"Missing required parameters: #{missing_variables.join(', ')}."
end
parameters.each do |k, v|
# Handle repeated parameters.
if self.parameter_descriptions[k] &&
self.parameter_descriptions[k]['repeated'] &&
v.kind_of?(Array)
# If this is a repeated parameter and we've got an array as a
# value, just provide the whole array to the loop below.
items = v
else
# If this is not a repeated parameter, or if it is but we're
# being given a single value, wrap the value in an array, so that
# the loop below still works for the single element.
items = [v]
end
items.each do |item|
if self.parameter_descriptions[k]
enum = self.parameter_descriptions[k]['enum']
if enum && !enum.include?(item)
raise ArgumentError,
"Parameter '#{k}' has an invalid value: #{item}. " +
"Must be one of #{enum.inspect}."
end
pattern = self.parameter_descriptions[k]['pattern']
if pattern
regexp = Regexp.new("^#{pattern}$")
if item !~ regexp
raise ArgumentError,
"Parameter '#{k}' has an invalid value: #{item}. " +
"Must match: /^#{pattern}$/."
end
end
end
end
end
return nil
end
##
# Returns a <code>String</code> representation of the method's state.
#
# @return [String] The method's state, as a <code>String</code>.
def inspect
sprintf(
"#<%s:%#0x ID:%s>",
self.class.to_s, self.object_id, self.id
)
end
end
end
end

View File

@ -1,156 +0,0 @@
# 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.
require 'addressable/uri'
require 'active_support/inflector'
require 'google/api_client/discovery/method'
module Google
class APIClient
##
# A resource that has been described by a discovery document.
class Resource
##
# Creates a description of a particular version of a resource.
#
# @param [Google::APIClient::API] api
# The API this resource belongs to.
# @param [Addressable::URI] method_base
# The base URI for the service.
# @param [String] resource_name
# The identifier for the resource.
# @param [Hash] discovery_document
# The section of the discovery document that applies to this resource.
#
# @return [Google::APIClient::Resource] The constructed resource object.
def initialize(api, method_base, resource_name, discovery_document)
@api = api
@method_base = method_base
@name = resource_name
@discovery_document = discovery_document
metaclass = (class <<self; self; end)
self.discovered_resources.each do |resource|
method_name = ActiveSupport::Inflector.underscore(resource.name).to_sym
if !self.respond_to?(method_name)
metaclass.send(:define_method, method_name) { resource }
end
end
self.discovered_methods.each do |method|
method_name = ActiveSupport::Inflector.underscore(method.name).to_sym
if !self.respond_to?(method_name)
metaclass.send(:define_method, method_name) { method }
end
end
end
# @return [String] unparsed discovery document for the resource
attr_reader :discovery_document
##
# Returns the identifier for the resource.
#
# @return [String] The resource identifier.
attr_reader :name
##
# Returns the base URI for this resource.
#
# @return [Addressable::URI] The base URI that methods are joined to.
attr_reader :method_base
##
# Returns a human-readable description of the resource.
#
# @return [Hash] The API description.
def description
return @discovery_document['description']
end
##
# Updates the hierarchy of resources and methods with the new base.
#
# @param [Addressable::URI, #to_str, String] new_method_base
# The new base URI to use for the resource.
def method_base=(new_method_base)
@method_base = Addressable::URI.parse(new_method_base)
self.discovered_resources.each do |resource|
resource.method_base = @method_base
end
self.discovered_methods.each do |method|
method.method_base = @method_base
end
end
##
# A list of sub-resources available on this resource.
#
# @return [Array] A list of {Google::APIClient::Resource} objects.
def discovered_resources
return @discovered_resources ||= (
(@discovery_document['resources'] || []).inject([]) do |accu, (k, v)|
accu << Google::APIClient::Resource.new(
@api, self.method_base, k, v
)
accu
end
)
end
##
# A list of methods available on this resource.
#
# @return [Array] A list of {Google::APIClient::Method} objects.
def discovered_methods
return @discovered_methods ||= (
(@discovery_document['methods'] || []).inject([]) do |accu, (k, v)|
accu << Google::APIClient::Method.new(@api, self.method_base, k, v)
accu
end
)
end
##
# Converts the resource to a flat mapping of RPC names and method
# objects.
#
# @return [Hash] All methods available on the resource.
def to_h
return @hash ||= (begin
methods_hash = {}
self.discovered_methods.each do |method|
methods_hash[method.id] = method
end
self.discovered_resources.each do |resource|
methods_hash.merge!(resource.to_h)
end
methods_hash
end)
end
##
# Returns a <code>String</code> representation of the resource's state.
#
# @return [String] The resource's state, as a <code>String</code>.
def inspect
sprintf(
"#<%s:%#0x NAME:%s>", self.class.to_s, self.object_id, self.name
)
end
end
end
end

View File

@ -1,117 +0,0 @@
# 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.
require 'time'
require 'multi_json'
require 'compat/multi_json'
require 'base64'
require 'autoparse'
require 'addressable/uri'
require 'addressable/template'
require 'active_support/inflector'
require 'google/api_client/errors'
module Google
class APIClient
##
# @api private
module Schema
def self.parse(api, schema_data)
# This method is super-long, but hard to break up due to the
# unavoidable dependence on closures and execution context.
schema_name = schema_data['id']
# Due to an oversight, schema IDs may not be URI references.
# TODO(bobaman): Remove this code once this has been resolved.
schema_uri = (
api.document_base +
(schema_name[0..0] != '#' ? '#' + schema_name : schema_name)
)
# Due to an oversight, schema IDs may not be URI references.
# TODO(bobaman): Remove this whole lambda once this has been resolved.
reformat_references = lambda do |data|
# This code is not particularly efficient due to recursive traversal
# and excess object creation, but this hopefully shouldn't be an
# issue since it should only be called only once per schema per
# process.
if data.kind_of?(Hash) &&
data['$ref'] && !data['$ref'].kind_of?(Hash)
if data['$ref'].respond_to?(:to_str)
reference = data['$ref'].to_str
else
raise TypeError, "Expected String, got #{data['$ref'].class}"
end
reference = '#' + reference if reference[0..0] != '#'
data.merge({
'$ref' => reference
})
elsif data.kind_of?(Hash)
data.inject({}) do |accu, (key, value)|
if value.kind_of?(Hash)
accu[key] = reformat_references.call(value)
else
accu[key] = value
end
accu
end
else
data
end
end
schema_data = reformat_references.call(schema_data)
if schema_name
api_name_string = ActiveSupport::Inflector.camelize(api.name)
api_version_string = ActiveSupport::Inflector.camelize(api.version).gsub('.', '_')
# This is for compatibility with Ruby 1.8.7.
# TODO(bobaman) Remove this when we eventually stop supporting 1.8.7.
args = []
args << false if Class.method(:const_defined?).arity != 1
if Google::APIClient::Schema.const_defined?(api_name_string, *args)
api_name = Google::APIClient::Schema.const_get(
api_name_string, *args
)
else
api_name = Google::APIClient::Schema.const_set(
api_name_string, Module.new
)
end
if api_name.const_defined?(api_version_string, *args)
api_version = api_name.const_get(api_version_string, *args)
else
api_version = api_name.const_set(api_version_string, Module.new)
end
if api_version.const_defined?(schema_name, *args)
schema_class = api_version.const_get(schema_name, *args)
end
end
# It's possible the schema has already been defined. If so, don't
# redefine it. This means that reloading a schema which has already
# been loaded into memory is not possible.
unless schema_class
schema_class = AutoParse.generate(schema_data, :uri => schema_uri)
if schema_name
api_version.const_set(schema_name, schema_class)
end
end
return schema_class
end
end
end
end

View File

@ -1,42 +0,0 @@
# 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
module ENV
OS_VERSION = begin
if RUBY_PLATFORM =~ /mswin|win32|mingw|bccwin|cygwin/
# TODO(bobaman)
# Confirm that all of these Windows environments actually have access
# to the `ver` command.
`ver`.sub(/\s*\[Version\s*/, '/').sub(']', '').strip
elsif RUBY_PLATFORM =~ /darwin/i
"Mac OS X/#{`sw_vers -productVersion`}"
elsif RUBY_PLATFORM == 'java'
# Get the information from java system properties to avoid spawning a
# sub-process, which is not friendly in some contexts (web servers).
require 'java'
name = java.lang.System.getProperty('os.name')
version = java.lang.System.getProperty('os.version')
"#{name}/#{version}"
else
`uname -sr`.sub(' ', '/')
end
rescue Exception
RUBY_PLATFORM
end
end
end
end

View File

@ -1,65 +0,0 @@
# 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
attr_reader :result
def initialize(message = nil, result = nil)
super(message)
@result = result
end
end
##
# An exception that is raised if a redirect is required
#
class RedirectError < TransmissionError
end
##
# An exception that is raised if a method is called with missing or
# invalid parameter values.
class ValidationError < StandardError
end
##
# A 4xx class HTTP error occurred.
class ClientError < TransmissionError
end
##
# A 401 HTTP error occurred.
class AuthorizationError < ClientError
end
##
# A 5xx class HTTP error occurred.
class ServerError < TransmissionError
end
##
# An exception that is raised if an ID token could not be validated.
class InvalidIDTokenError < StandardError
end
# Error class for problems in batch requests.
class BatchError < StandardError
end
end
end

View File

@ -1,28 +0,0 @@
require 'faraday'
require 'zlib'
module Google
class APIClient
class Gzip < Faraday::Response::Middleware
include Google::APIClient::Logging
def on_complete(env)
encoding = env[:response_headers]['content-encoding'].to_s.downcase
case encoding
when 'gzip'
logger.debug { "Decompressing gzip encoded response (#{env[:body].length} bytes)" }
env[:body] = Zlib::GzipReader.new(StringIO.new(env[:body])).read
env[:response_headers].delete('content-encoding')
logger.debug { "Decompressed (#{env[:body].length} bytes)" }
when 'deflate'
logger.debug{ "Decompressing deflate encoded response (#{env[:body].length} bytes)" }
env[:body] = Zlib::Inflate.inflate(env[:body])
env[:response_headers].delete('content-encoding')
logger.debug { "Decompressed (#{env[:body].length} bytes)" }
end
end
end
end
end
Faraday::Response.register_middleware :gzip => Google::APIClient::Gzip

View File

@ -1,32 +0,0 @@
require 'logger'
module Google
class APIClient
class << self
##
# Logger for the API client
#
# @return [Logger] logger instance.
attr_accessor :logger
end
self.logger = Logger.new(STDOUT)
self.logger.level = Logger::WARN
##
# Module to make accessing the logger simpler
module Logging
##
# Logger for the API client
#
# @return [Logger] logger instance.
def logger
Google::APIClient.logger
end
end
end
end

View File

@ -1,259 +0,0 @@
# 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.
require 'google/api_client/reference'
module Google
class APIClient
##
# Uploadable media support. Holds an IO stream & content type.
#
# @see Faraday::UploadIO
# @example
# media = Google::APIClient::UploadIO.new('mymovie.m4v', 'video/mp4')
class UploadIO < Faraday::UploadIO
# @return [Fixnum] Size of chunks to upload. Default is nil, meaning upload the entire file in a single request
attr_accessor :chunk_size
##
# Get the length of the stream
#
# @return [Fixnum]
# Length of stream, in bytes
def length
io.respond_to?(:length) ? io.length : File.size(local_path)
end
end
##
# Wraps an input stream and limits data to a given range
#
# @example
# chunk = Google::APIClient::RangedIO.new(io, 0, 1000)
class RangedIO
##
# Bind an input stream to a specific range.
#
# @param [IO] io
# Source input stream
# @param [Fixnum] offset
# Starting offset of the range
# @param [Fixnum] length
# Length of range
def initialize(io, offset, length)
@io = io
@offset = offset
@length = length
self.rewind
end
##
# @see IO#read
def read(amount = nil, buf = nil)
buffer = buf || ''
if amount.nil?
size = @length - @pos
done = ''
elsif amount == 0
size = 0
done = ''
else
size = [@length - @pos, amount].min
done = nil
end
if size > 0
result = @io.read(size)
result.force_encoding("BINARY") if result.respond_to?(:force_encoding)
buffer << result if result
@pos = @pos + size
end
if buffer.length > 0
buffer
else
done
end
end
##
# @see IO#rewind
def rewind
self.pos = 0
end
##
# @see IO#pos
def pos
@pos
end
##
# @see IO#pos=
def pos=(pos)
@pos = pos
@io.pos = @offset + pos
end
end
##
# Resumable uploader.
#
class ResumableUpload < Request
# @return [Fixnum] Max bytes to send in a single request
attr_accessor :chunk_size
##
# Creates a new uploader.
#
# @param [Hash] options
# Request options
def initialize(options={})
super options
self.uri = options[:uri]
self.http_method = :put
@offset = options[:offset] || 0
@complete = false
@expired = false
end
##
# Sends all remaining chunks to the server
#
# @deprecated Pass the instance to {Google::APIClient#execute} instead
#
# @param [Google::APIClient] api_client
# API Client instance to use for sending
def send_all(api_client)
result = nil
until complete?
result = send_chunk(api_client)
break unless result.status == 308
end
return result
end
##
# Sends the next chunk to the server
#
# @deprecated Pass the instance to {Google::APIClient#execute} instead
#
# @param [Google::APIClient] api_client
# API Client instance to use for sending
def send_chunk(api_client)
return api_client.execute(self)
end
##
# Check if upload is complete
#
# @return [TrueClass, FalseClass]
# Whether or not the upload complete successfully
def complete?
return @complete
end
##
# Check if the upload URL expired (upload not completed in alotted time.)
# Expired uploads must be restarted from the beginning
#
# @return [TrueClass, FalseClass]
# Whether or not the upload has expired and can not be resumed
def expired?
return @expired
end
##
# Check if upload is resumable. That is, neither complete nor expired
#
# @return [TrueClass, FalseClass] True if upload can be resumed
def resumable?
return !(self.complete? or self.expired?)
end
##
# Convert to an HTTP request. Returns components in order of method, URI,
# request headers, and body
#
# @api private
#
# @return [Array<(Symbol, Addressable::URI, Hash, [#read,#to_str])>]
def to_http_request
if @complete
raise Google::APIClient::ClientError, "Upload already complete"
elsif @offset.nil?
self.headers.update({
'Content-Length' => "0",
'Content-Range' => "bytes */#{media.length}" })
else
start_offset = @offset
remaining = self.media.length - start_offset
chunk_size = self.media.chunk_size || self.chunk_size || self.media.length
content_length = [remaining, chunk_size].min
chunk = RangedIO.new(self.media.io, start_offset, content_length)
end_offset = start_offset + content_length - 1
self.headers.update({
'Content-Length' => "#{content_length}",
'Content-Type' => self.media.content_type,
'Content-Range' => "bytes #{start_offset}-#{end_offset}/#{media.length}" })
self.body = chunk
end
super
end
##
# Check the result from the server, updating the offset and/or location
# if available.
#
# @api private
#
# @param [Faraday::Response] response
# HTTP response
#
# @return [Google::APIClient::Result]
# Processed API response
def process_http_response(response)
case response.status
when 200...299
@complete = true
when 308
range = response.headers['range']
if range
@offset = range.scan(/\d+/).collect{|x| Integer(x)}.last + 1
end
if response.headers['location']
self.uri = response.headers['location']
end
when 400...499
@expired = true
when 500...599
# Invalidate the offset to mark it needs to be queried on the
# next request
@offset = nil
end
return Google::APIClient::Result.new(self, response)
end
##
# Hashified verison of the API request
#
# @return [Hash]
def to_hash
super.merge(:offset => @offset)
end
end
end
end

View File

@ -1,18 +0,0 @@
require 'rails/railtie'
require 'google/api_client/logging'
module Google
class APIClient
##
# Optional support class for Rails. Currently replaces the built-in logger
# with Rails' application log.
#
class Railtie < Rails::Railtie
initializer 'google-api-client' do |app|
logger = app.config.logger || Rails.logger
Google::APIClient.logger = logger unless logger.nil?
end
end
end
end

View File

@ -1,27 +0,0 @@
# 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.
require 'google/api_client/request'
module Google
class APIClient
##
# Subclass of Request for backwards compatibility with pre-0.5.0 versions of the library
#
# @deprecated
# use {Google::APIClient::Request} instead
class Reference < Request
end
end
end

View File

@ -1,350 +0,0 @@
# 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.
require 'faraday'
require 'faraday/request/multipart'
require 'compat/multi_json'
require 'addressable/uri'
require 'stringio'
require 'google/api_client/discovery'
require 'google/api_client/logging'
module Google
class APIClient
##
# Represents an API request.
class Request
include Google::APIClient::Logging
MULTIPART_BOUNDARY = "-----------RubyApiMultipartPost".freeze
# @return [Hash] Request parameters
attr_reader :parameters
# @return [Hash] Additional HTTP headers
attr_reader :headers
# @return [Google::APIClient::Method] API method to invoke
attr_reader :api_method
# @return [Google::APIClient::UploadIO] File to upload
attr_accessor :media
# @return [#generated_authenticated_request] User credentials
attr_accessor :authorization
# @return [TrueClass,FalseClass] True if request should include credentials
attr_accessor :authenticated
# @return [#read, #to_str] Request body
attr_accessor :body
##
# Build a request
#
# @param [Hash] options
# @option options [Hash, Array] :parameters
# Request parameters for the API method.
# @option options [Google::APIClient::Method] :api_method
# API method to invoke. Either :api_method or :uri must be specified
# @option options [TrueClass, FalseClass] :authenticated
# True if request should include credentials. Implicitly true if
# unspecified and :authorization present
# @option options [#generate_signed_request] :authorization
# OAuth credentials
# @option options [Google::APIClient::UploadIO] :media
# File to upload, if media upload request
# @option options [#to_json, #to_hash] :body_object
# Main body of the API request. Typically hash or object that can
# be serialized to JSON
# @option options [#read, #to_str] :body
# Raw body to send in POST/PUT requests
# @option options [String, Addressable::URI] :uri
# URI to request. Either :api_method or :uri must be specified
# @option options [String, Symbol] :http_method
# HTTP method when requesting a URI
def initialize(options={})
@parameters = Faraday::Utils::ParamsHash.new
@headers = Faraday::Utils::Headers.new
self.parameters.merge!(options[:parameters]) unless options[:parameters].nil?
self.headers.merge!(options[:headers]) unless options[:headers].nil?
self.api_method = options[:api_method]
self.authenticated = options[:authenticated]
self.authorization = options[:authorization]
# These parameters are handled differently because they're not
# parameters to the API method, but rather to the API system.
self.parameters['key'] ||= options[:key] if options[:key]
self.parameters['userIp'] ||= options[:user_ip] if options[:user_ip]
if options[:media]
self.initialize_media_upload(options)
elsif options[:body]
self.body = options[:body]
elsif options[:body_object]
self.headers['Content-Type'] ||= 'application/json'
self.body = serialize_body(options[:body_object])
else
self.body = ''
end
unless self.api_method
self.http_method = options[:http_method] || 'GET'
self.uri = options[:uri]
end
end
# @!attribute [r] upload_type
# @return [String] protocol used for upload
def upload_type
return self.parameters['uploadType'] || self.parameters['upload_type']
end
# @!attribute http_method
# @return [Symbol] HTTP method if invoking a URI
def http_method
return @http_method ||= self.api_method.http_method.to_s.downcase.to_sym
end
def http_method=(new_http_method)
if new_http_method.kind_of?(Symbol)
@http_method = new_http_method.to_s.downcase.to_sym
elsif new_http_method.respond_to?(:to_str)
@http_method = new_http_method.to_s.downcase.to_sym
else
raise TypeError,
"Expected String or Symbol, got #{new_http_method.class}."
end
end
def api_method=(new_api_method)
if new_api_method.nil? || new_api_method.kind_of?(Google::APIClient::Method)
@api_method = new_api_method
else
raise TypeError,
"Expected Google::APIClient::Method, got #{new_api_method.class}."
end
end
# @!attribute uri
# @return [Addressable::URI] URI to send request
def uri
return @uri ||= self.api_method.generate_uri(self.parameters)
end
def uri=(new_uri)
@uri = Addressable::URI.parse(new_uri)
@parameters.update(@uri.query_values) unless @uri.query_values.nil?
end
# Transmits the request with the given connection
#
# @api private
#
# @param [Faraday::Connection] connection
# the connection to transmit with
# @param [TrueValue,FalseValue] is_retry
# True if request has been previous sent
#
# @return [Google::APIClient::Result]
# result of API request
def send(connection, is_retry = false)
self.body.rewind if is_retry && self.body.respond_to?(:rewind)
env = self.to_env(connection)
logger.debug { "#{self.class} Sending API request #{env[:method]} #{env[:url].to_s} #{env[:request_headers]}" }
http_response = connection.app.call(env)
result = self.process_http_response(http_response)
logger.debug { "#{self.class} Result: #{result.status} #{result.headers}" }
# Resumamble slightly different than other upload protocols in that it requires at least
# 2 requests.
if result.status == 200 && self.upload_type == 'resumable' && self.media
upload = result.resumable_upload
unless upload.complete?
logger.debug { "#{self.class} Sending upload body" }
result = upload.send(connection)
end
end
return result
end
# Convert to an HTTP request. Returns components in order of method, URI,
# request headers, and body
#
# @api private
#
# @return [Array<(Symbol, Addressable::URI, Hash, [#read,#to_str])>]
def to_http_request
request = (
if self.api_method
self.api_method.generate_request(self.parameters, self.body, self.headers)
elsif self.uri
unless self.parameters.empty?
self.uri.query = Addressable::URI.form_encode(self.parameters)
end
[self.http_method, self.uri.to_s, self.headers, self.body]
end)
return request
end
##
# Hashified verison of the API request
#
# @return [Hash]
def to_hash
options = {}
if self.api_method
options[:api_method] = self.api_method
options[:parameters] = self.parameters
else
options[:http_method] = self.http_method
options[:uri] = self.uri
end
options[:headers] = self.headers
options[:body] = self.body
options[:media] = self.media
unless self.authorization.nil?
options[:authorization] = self.authorization
end
return options
end
##
# Prepares the request for execution, building a hash of parts
# suitable for sending to Faraday::Connection.
#
# @api private
#
# @param [Faraday::Connection] connection
# Connection for building the request
#
# @return [Hash]
# Encoded request
def to_env(connection)
method, uri, headers, body = self.to_http_request
http_request = connection.build_request(method) do |req|
req.url(uri.to_s)
req.headers.update(headers)
req.body = body
end
if self.authorization.respond_to?(:generate_authenticated_request)
http_request = self.authorization.generate_authenticated_request(
:request => http_request,
:connection => connection
)
end
http_request.to_env(connection)
end
##
# Convert HTTP response to an API Result
#
# @api private
#
# @param [Faraday::Response] response
# HTTP response
#
# @return [Google::APIClient::Result]
# Processed API response
def process_http_response(response)
Result.new(self, response)
end
protected
##
# Adjust headers & body for media uploads
#
# @api private
#
# @param [Hash] options
# @option options [Hash, Array] :parameters
# Request parameters for the API method.
# @option options [Google::APIClient::UploadIO] :media
# File to upload, if media upload request
# @option options [#to_json, #to_hash] :body_object
# Main body of the API request. Typically hash or object that can
# be serialized to JSON
# @option options [#read, #to_str] :body
# Raw body to send in POST/PUT requests
def initialize_media_upload(options)
self.media = options[:media]
case self.upload_type
when "media"
if options[:body] || options[:body_object]
raise ArgumentError, "Can not specify body & body object for simple uploads"
end
self.headers['Content-Type'] ||= self.media.content_type
self.headers['Content-Length'] ||= self.media.length.to_s
self.body = self.media
when "multipart"
unless options[:body_object]
raise ArgumentError, "Multipart requested but no body object"
end
metadata = StringIO.new(serialize_body(options[:body_object]))
build_multipart([Faraday::UploadIO.new(metadata, 'application/json', 'file.json'), self.media])
when "resumable"
file_length = self.media.length
self.headers['X-Upload-Content-Type'] = self.media.content_type
self.headers['X-Upload-Content-Length'] = file_length.to_s
if options[:body_object]
self.headers['Content-Type'] ||= 'application/json'
self.body = serialize_body(options[:body_object])
else
self.body = ''
end
end
end
##
# Assemble a multipart message from a set of parts
#
# @api private
#
# @param [Array<[#read,#to_str]>] parts
# Array of parts to encode.
# @param [String] mime_type
# MIME type of the message
# @param [String] boundary
# Boundary for separating each part of the message
def build_multipart(parts, mime_type = 'multipart/related', boundary = MULTIPART_BOUNDARY)
env = Faraday::Env.new
env.request = Faraday::RequestOptions.new
env.request.boundary = boundary
env.request_headers = {'Content-Type' => "#{mime_type};boundary=#{boundary}"}
multipart = Faraday::Request::Multipart.new
self.body = multipart.create_multipart(env, parts.map {|part| [nil, part]})
self.headers.update(env[:request_headers])
end
##
# Serialize body object to JSON
#
# @api private
#
# @param [#to_json,#to_hash] body
# object to serialize
#
# @return [String]
# JSON
def serialize_body(body)
return body.to_json if body.respond_to?(:to_json)
return MultiJson.dump(body.to_hash) if body.respond_to?(:to_hash)
raise TypeError, 'Could not convert body object to JSON.' +
'Must respond to :to_json or :to_hash.'
end
end
end
end

View File

@ -1,259 +0,0 @@
# 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
##
# This class wraps a result returned by an API call.
class Result
extend Forwardable
##
# Init the result
#
# @param [Google::APIClient::Request] request
# The original request
# @param [Faraday::Response] response
# Raw HTTP Response
def initialize(request, response)
@request = request
@response = response
@media_upload = reference if reference.kind_of?(ResumableUpload)
end
# @return [Google::APIClient::Request] Original request object
attr_reader :request
# @return [Faraday::Response] HTTP response
attr_reader :response
# @!attribute [r] reference
# @return [Google::APIClient::Request] Original request object
# @deprecated See {#request}
alias_method :reference, :request # For compatibility with pre-beta clients
# @!attribute [r] status
# @return [Fixnum] HTTP status code
# @!attribute [r] headers
# @return [Hash] HTTP response headers
# @!attribute [r] body
# @return [String] HTTP response body
def_delegators :@response, :status, :headers, :body
# @!attribute [r] resumable_upload
# @return [Google::APIClient::ResumableUpload] For resuming media uploads
def resumable_upload
@media_upload ||= (
options = self.reference.to_hash.merge(
:uri => self.headers['location'],
:media => self.reference.media
)
Google::APIClient::ResumableUpload.new(options)
)
end
##
# Get the content type of the response
# @!attribute [r] media_type
# @return [String]
# Value of content-type header
def media_type
_, content_type = self.headers.detect do |h, v|
h.downcase == 'Content-Type'.downcase
end
if content_type
return content_type[/^([^;]*);?.*$/, 1].strip.downcase
else
return nil
end
end
##
# Check if request failed - which is anything other than 200/201 OK
#
# @!attribute [r] error?
# @return [TrueClass, FalseClass]
# true if result of operation is an error
def error?
return !self.success?
end
##
# Check if request was successful
#
# @!attribute [r] success?
# @return [TrueClass, FalseClass]
# true if result of operation was successful
def success?
if self.response.status == 200 || self.response.status == 201
return true
else
return false
end
end
##
# Extracts error messages from the response body
#
# @!attribute [r] error_message
# @return [String]
# error message, if available
def error_message
if self.data?
if self.data.respond_to?(:error) &&
self.data.error.respond_to?(:message)
# You're going to get a terrible error message if the response isn't
# parsed successfully as an error.
return self.data.error.message
elsif self.data['error'] && self.data['error']['message']
return self.data['error']['message']
end
end
return self.body
end
##
# Check for parsable data in response
#
# @!attribute [r] data?
# @return [TrueClass, FalseClass]
# true if body can be parsed
def data?
!(self.body.nil? || self.body.empty? || self.media_type != 'application/json')
end
##
# Return parsed version of the response body.
#
# @!attribute [r] data
# @return [Object, Hash, String]
# Object if body parsable from API schema, Hash if JSON, raw body if unable to parse
def data
return @data ||= (begin
if self.data?
media_type = self.media_type
data = self.body
case media_type
when 'application/json'
data = MultiJson.load(data)
# Strip data wrapper, if present
data = data['data'] if data.has_key?('data')
else
raise ArgumentError,
"Content-Type not supported for parsing: #{media_type}"
end
if @request.api_method && @request.api_method.response_schema
# Automatically parse using the schema designated for the
# response of this API method.
data = @request.api_method.response_schema.new(data)
data
else
# Otherwise, return the raw unparsed value.
# This value must be indexable like a Hash.
data
end
end
end)
end
##
# Get the token used for requesting the next page of data
#
# @!attribute [r] next_page_token
# @return [String]
# next page token
def next_page_token
if self.data.respond_to?(:next_page_token)
return self.data.next_page_token
elsif self.data.respond_to?(:[])
return self.data["nextPageToken"]
else
raise TypeError, "Data object did not respond to #next_page_token."
end
end
##
# Build a request for fetching the next page of data
#
# @return [Google::APIClient::Request]
# API request for retrieving next page, nil if no page token available
def next_page
return nil unless self.next_page_token
merged_parameters = Hash[self.reference.parameters].merge({
self.page_token_param => self.next_page_token
})
# Because Requests can be coerced to Hashes, we can merge them,
# preserving all context except the API method parameters that we're
# using for pagination.
return Google::APIClient::Request.new(
Hash[self.reference].merge(:parameters => merged_parameters)
)
end
##
# Get the token used for requesting the previous page of data
#
# @!attribute [r] prev_page_token
# @return [String]
# previous page token
def prev_page_token
if self.data.respond_to?(:prev_page_token)
return self.data.prev_page_token
elsif self.data.respond_to?(:[])
return self.data["prevPageToken"]
else
raise TypeError, "Data object did not respond to #next_page_token."
end
end
##
# Build a request for fetching the previous page of data
#
# @return [Google::APIClient::Request]
# API request for retrieving previous page, nil if no page token available
def prev_page
return nil unless self.prev_page_token
merged_parameters = Hash[self.reference.parameters].merge({
self.page_token_param => self.prev_page_token
})
# Because Requests can be coerced to Hashes, we can merge them,
# preserving all context except the API method parameters that we're
# using for pagination.
return Google::APIClient::Request.new(
Hash[self.reference].merge(:parameters => merged_parameters)
)
end
##
# Pagination scheme used by this request/response
#
# @!attribute [r] pagination_type
# @return [Symbol]
# currently always :token
def pagination_type
return :token
end
##
# Name of the field that contains the pagination token
#
# @!attribute [r] page_token_param
# @return [String]
# currently always 'pageToken'
def page_token_param
return "pageToken"
end
end
end
end

View File

@ -1,233 +0,0 @@
# Copyright 2013 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.
require 'google/api_client'
require 'google/api_client/service/stub_generator'
require 'google/api_client/service/resource'
require 'google/api_client/service/request'
require 'google/api_client/service/result'
require 'google/api_client/service/batch'
require 'google/api_client/service/simple_file_store'
module Google
class APIClient
##
# Experimental new programming interface at the API level.
# Hides Google::APIClient. Designed to be easier to use, with less code.
#
# @example
# calendar = Google::APIClient::Service.new('calendar', 'v3')
# result = calendar.events.list('calendarId' => 'primary').execute()
class Service
include Google::APIClient::Service::StubGenerator
extend Forwardable
DEFAULT_CACHE_FILE = 'discovery.cache'
# Cache for discovered APIs.
@@discovered = {}
##
# Creates a new Service.
#
# @param [String, Symbol] api_name
# The name of the API this service will access.
# @param [String, Symbol] api_version
# The version of the API this service will access.
# @param [Hash] options
# The configuration parameters for the service.
# @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 [Boolean] :auto_refresh_token (true)
# The setting that controls whether or not the api client attempts to
# refresh authorization when a 401 is hit in #execute. If the token does
# not support it, this option is ignored.
# @option options [String] :application_name
# The name of the application using the client.
# @option options [String] :application_version
# The version number of the application using the client.
# @option options [String] :host ("www.googleapis.com")
# The API hostname used by the client. This rarely needs to be changed.
# @option options [String] :port (443)
# The port number used by the client. This rarely needs to be changed.
# @option options [String] :discovery_path ("/discovery/v1")
# The discovery base path. This rarely needs to be changed.
# @option options [String] :ca_file
# Optional set of root certificates to use when validating SSL connections.
# By default, a bundled set of trusted roots will be used.
# @option options [#generate_authenticated_request] :authorization
# The authorization mechanism for requests. Used only if
# `:authenticated` is `true`.
# @option options [TrueClass, FalseClass] :authenticated (default: true)
# `true` if requests must be signed or somehow
# authenticated, `false` otherwise.
# @option options [TrueClass, FalseClass] :gzip (default: true)
# `true` if gzip enabled, `false` otherwise.
# @option options [Faraday::Connection] :connection
# A custom connection to be used for all requests.
# @option options [ActiveSupport::Cache::Store, :default] :discovery_cache
# A cache store to place the discovery documents for loaded APIs.
# Avoids unnecessary roundtrips to the discovery service.
# :default loads the default local file cache store.
def initialize(api_name, api_version, options = {})
@api_name = api_name.to_s
if api_version.nil?
raise ArgumentError,
"API version must be set"
end
@api_version = api_version.to_s
if options && !options.respond_to?(:to_hash)
raise ArgumentError,
"expected options Hash, got #{options.class}"
end
params = {}
[:application_name, :application_version, :authorization, :host, :port,
:discovery_path, :auto_refresh_token, :key, :user_ip,
:ca_file].each do |option|
if options.include? option
params[option] = options[option]
end
end
@client = Google::APIClient.new(params)
@connection = options[:connection] || @client.connection
@options = options
# Initialize cache store. Default to SimpleFileStore if :cache_store
# is not provided and we have write permissions.
if options.include? :cache_store
@cache_store = options[:cache_store]
else
cache_exists = File.exists?(DEFAULT_CACHE_FILE)
if (cache_exists && File.writable?(DEFAULT_CACHE_FILE)) ||
(!cache_exists && File.writable?(Dir.pwd))
@cache_store = Google::APIClient::Service::SimpleFileStore.new(
DEFAULT_CACHE_FILE)
end
end
# Attempt to read API definition from memory cache.
# Not thread-safe, but the worst that can happen is a cache miss.
unless @api = @@discovered[[api_name, api_version]]
# Attempt to read API definition from cache store, if there is one.
# If there's a miss or no cache store, call discovery service.
if !@cache_store.nil?
@api = @cache_store.fetch("%s/%s" % [api_name, api_version]) do
@client.discovered_api(api_name, api_version)
end
else
@api = @client.discovered_api(api_name, api_version)
end
@@discovered[[api_name, api_version]] = @api
end
generate_call_stubs(self, @api)
end
##
# Returns the authorization mechanism used by the service.
#
# @return [#generate_authenticated_request] The authorization mechanism.
def_delegators :@client, :authorization, :authorization=
##
# The setting that controls whether or not the service attempts to
# refresh authorization when a 401 is hit during an API call.
#
# @return [Boolean]
def_delegators :@client, :auto_refresh_token, :auto_refresh_token=
##
# The application's API key issued by the API console.
#
# @return [String] The API key.
def_delegators :@client, :key, :key=
##
# The Faraday/HTTP connection used by this service.
#
# @return [Faraday::Connection]
attr_accessor :connection
##
# The cache store used for storing discovery documents.
#
# @return [ActiveSupport::Cache::Store,
# Google::APIClient::Service::SimpleFileStore,
# nil]
attr_reader :cache_store
##
# Prepares a Google::APIClient::BatchRequest object to make batched calls.
# @param [Array] calls
# Optional array of Google::APIClient::Service::Request to initialize
# the batch request with.
# @param [Proc] block
# Callback for every call's response. Won't be called if a call defined
# a callback of its own.
#
# @yield [Google::APIClient::Service::Result]
# block to be called when result ready
def batch(calls = nil, &block)
Google::APIClient::Service::BatchRequest.new(self, calls, &block)
end
##
# Executes an API request.
# Do not call directly; this method is only used by Request objects when
# executing.
#
# @param [Google::APIClient::Service::Request,
# Google::APIClient::Service::BatchCall] request
# The request to be executed.
def execute(request)
if request.instance_of? Google::APIClient::Service::Request
params = {:api_method => request.method,
:parameters => request.parameters,
:connection => @connection}
if request.respond_to? :body
if request.body.respond_to? :to_hash
params[:body_object] = request.body
else
params[:body] = request.body
end
end
if request.respond_to? :media
params[:media] = request.media
end
[:authenticated, :gzip].each do |option|
if @options.include? option
params[option] = @options[option]
end
end
result = @client.execute(params)
return Google::APIClient::Service::Result.new(request, result)
elsif request.instance_of? Google::APIClient::Service::BatchRequest
@client.execute(request.base_batch, {:connection => @connection})
end
end
end
end
end

View File

@ -1,110 +0,0 @@
# Copyright 2013 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.
require 'google/api_client/service/result'
require 'google/api_client/batch'
module Google
class APIClient
class Service
##
# Helper class to contain the result of an individual batched call.
#
class BatchedCallResult < Result
# @return [Fixnum] Index of the call
def call_index
return @base_result.response.call_id.to_i - 1
end
end
##
#
#
class BatchRequest
##
# Creates a new batch request.
# This class shouldn't be instantiated directly, but rather through
# Service.batch.
#
# @param [Array] calls
# List of Google::APIClient::Service::Request to be made.
# @param [Proc] block
# Callback for every call's response. Won't be called if a call
# defined a callback of its own.
#
# @yield [Google::APIClient::Service::Result]
# block to be called when result ready
def initialize(service, calls, &block)
@service = service
@base_batch = Google::APIClient::BatchRequest.new
@global_callback = block if block_given?
if calls && calls.length > 0
calls.each do |call|
add(call)
end
end
end
##
# Add a new call to the batch request.
#
# @param [Google::APIClient::Service::Request] call
# the call to be added.
# @param [Proc] block
# callback for this call's response.
#
# @return [Google::APIClient::Service::BatchRequest]
# the BatchRequest, for chaining
#
# @yield [Google::APIClient::Service::Result]
# block to be called when result ready
def add(call, &block)
if !block_given? && @global_callback.nil?
raise BatchError, 'Request needs a block'
end
callback = block || @global_callback
base_call = {
:api_method => call.method,
:parameters => call.parameters
}
if call.respond_to? :body
if call.body.respond_to? :to_hash
base_call[:body_object] = call.body
else
base_call[:body] = call.body
end
end
@base_batch.add(base_call) do |base_result|
result = Google::APIClient::Service::BatchedCallResult.new(
call, base_result)
callback.call(result)
end
return self
end
##
# Executes the batch request.
def execute
@service.execute(self)
end
attr_reader :base_batch
end
end
end
end

View File

@ -1,144 +0,0 @@
# Copyright 2013 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
class Service
##
# Handles an API request.
# This contains a full definition of the request to be made (including
# method name, parameters, body and media). The remote API call can be
# invoked with execute().
class Request
##
# Build a request.
# This class should not be directly instantiated in user code;
# instantiation is handled by the stub methods created on Service and
# Resource objects.
#
# @param [Google::APIClient::Service] service
# The parent Service instance that will execute the request.
# @param [Google::APIClient::Method] method
# The Method instance that describes the API method invoked by the
# request.
# @param [Hash] parameters
# A Hash of parameter names and values to be sent in the API call.
def initialize(service, method, parameters)
@service = service
@method = method
@parameters = parameters
@body = nil
@media = nil
metaclass = (class << self; self; end)
# If applicable, add "body", "body=" and resource-named methods for
# retrieving and setting the HTTP body for this request.
# Examples of setting the body for files.insert in the Drive API:
# request.body = object
# request.execute
# OR
# request.file = object
# request.execute
# OR
# request.body(object).execute
# OR
# request.file(object).execute
# Examples of retrieving the body for files.insert in the Drive API:
# object = request.body
# OR
# object = request.file
if method.request_schema
body_name = method.request_schema.data['id'].dup
body_name[0] = body_name[0].chr.downcase
body_name_equals = (body_name + '=').to_sym
body_name = body_name.to_sym
metaclass.send(:define_method, :body) do |*args|
if args.length == 1
@body = args.first
return self
elsif args.length == 0
return @body
else
raise ArgumentError,
"wrong number of arguments (#{args.length}; expecting 0 or 1)"
end
end
metaclass.send(:define_method, :body=) do |body|
@body = body
end
metaclass.send(:alias_method, body_name, :body)
metaclass.send(:alias_method, body_name_equals, :body=)
end
# If applicable, add "media" and "media=" for retrieving and setting
# the media object for this request.
# Examples of setting the media object:
# request.media = object
# request.execute
# OR
# request.media(object).execute
# Example of retrieving the media object:
# object = request.media
if method.media_upload
metaclass.send(:define_method, :media) do |*args|
if args.length == 1
@media = args.first
return self
elsif args.length == 0
return @media
else
raise ArgumentError,
"wrong number of arguments (#{args.length}; expecting 0 or 1)"
end
end
metaclass.send(:define_method, :media=) do |media|
@media = media
end
end
end
##
# Returns the parent service capable of executing this request.
#
# @return [Google::APIClient::Service] The parent service.
attr_reader :service
##
# Returns the Method instance that describes the API method invoked by
# the request.
#
# @return [Google::APIClient::Method] The API method description.
attr_reader :method
##
# Contains the Hash of parameter names and values to be sent as the
# parameters for the API call.
#
# @return [Hash] The request parameters.
attr_accessor :parameters
##
# Executes the request.
def execute
@service.execute(self)
end
end
end
end
end

View File

@ -1,40 +0,0 @@
# Copyright 2013 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
class Service
##
# Handles an API resource.
# Simple class that contains API methods and/or child resources.
class Resource
include Google::APIClient::Service::StubGenerator
##
# Build a resource.
# This class should not be directly instantiated in user code; resources
# are instantiated by the stub generation mechanism on Service creation.
#
# @param [Google::APIClient::Service] service
# The Service instance this resource belongs to.
# @param [Google::APIClient::API, Google::APIClient::Resource] root
# The node corresponding to this resource.
def initialize(service, root)
@service = service
generate_call_stubs(service, root)
end
end
end
end
end

View File

@ -1,164 +0,0 @@
# Copyright 2013 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
class Service
##
# Handles an API result.
# Wraps around the Google::APIClient::Result class, making it easier to
# handle the result (e.g. pagination) and keeping it in line with the rest
# of the Service programming interface.
class Result
extend Forwardable
##
# Init the result.
#
# @param [Google::APIClient::Service::Request] request
# The original request
# @param [Google::APIClient::Result] base_result
# The base result to be wrapped
def initialize(request, base_result)
@request = request
@base_result = base_result
end
# @!attribute [r] status
# @return [Fixnum] HTTP status code
# @!attribute [r] headers
# @return [Hash] HTTP response headers
# @!attribute [r] body
# @return [String] HTTP response body
def_delegators :@base_result, :status, :headers, :body
# @return [Google::APIClient::Service::Request] Original request object
attr_reader :request
##
# Get the content type of the response
# @!attribute [r] media_type
# @return [String]
# Value of content-type header
def_delegators :@base_result, :media_type
##
# Check if request failed
#
# @!attribute [r] error?
# @return [TrueClass, FalseClass]
# true if result of operation is an error
def_delegators :@base_result, :error?
##
# Check if request was successful
#
# @!attribute [r] success?
# @return [TrueClass, FalseClass]
# true if result of operation was successful
def_delegators :@base_result, :success?
##
# Extracts error messages from the response body
#
# @!attribute [r] error_message
# @return [String]
# error message, if available
def_delegators :@base_result, :error_message
##
# Check for parsable data in response
#
# @!attribute [r] data?
# @return [TrueClass, FalseClass]
# true if body can be parsed
def_delegators :@base_result, :data?
##
# Return parsed version of the response body.
#
# @!attribute [r] data
# @return [Object, Hash, String]
# Object if body parsable from API schema, Hash if JSON, raw body if unable to parse
def_delegators :@base_result, :data
##
# Pagination scheme used by this request/response
#
# @!attribute [r] pagination_type
# @return [Symbol]
# currently always :token
def_delegators :@base_result, :pagination_type
##
# Name of the field that contains the pagination token
#
# @!attribute [r] page_token_param
# @return [String]
# currently always 'pageToken'
def_delegators :@base_result, :page_token_param
##
# Get the token used for requesting the next page of data
#
# @!attribute [r] next_page_token
# @return [String]
# next page tokenx =
def_delegators :@base_result, :next_page_token
##
# Get the token used for requesting the previous page of data
#
# @!attribute [r] prev_page_token
# @return [String]
# previous page token
def_delegators :@base_result, :prev_page_token
# @!attribute [r] resumable_upload
def resumable_upload
# TODO(sgomes): implement resumable_upload for Service::Result
raise NotImplementedError
end
##
# Build a request for fetching the next page of data
#
# @return [Google::APIClient::Service::Request]
# API request for retrieving next page
def next_page
return nil unless self.next_page_token
request = @request.clone
# Make a deep copy of the parameters.
request.parameters = Marshal.load(Marshal.dump(request.parameters))
request.parameters[page_token_param] = self.next_page_token
return request
end
##
# Build a request for fetching the previous page of data
#
# @return [Google::APIClient::Service::Request]
# API request for retrieving previous page
def prev_page
return nil unless self.prev_page_token
request = @request.clone
# Make a deep copy of the parameters.
request.parameters = Marshal.load(Marshal.dump(request.parameters))
request.parameters[page_token_param] = self.prev_page_token
return request
end
end
end
end
end

View File

@ -1,151 +0,0 @@
# Copyright 2013 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
class Service
# Simple file store to be used in the event no ActiveSupport cache store
# is provided. This is not thread-safe, and does not support a number of
# features (such as expiration), but it's useful for the simple purpose of
# caching discovery documents to disk.
# Implements the basic cache methods of ActiveSupport::Cache::Store in a
# limited fashion.
class SimpleFileStore
# Creates a new SimpleFileStore.
#
# @param [String] file_path
# The path to the cache file on disk.
# @param [Object] options
# The options to be used with this SimpleFileStore. Not implemented.
def initialize(file_path, options = nil)
@file_path = file_path.to_s
end
# Returns true if a key exists in the cache.
#
# @param [String] name
# The name of the key. Will always be converted to a string.
# @param [Object] options
# The options to be used with this query. Not implemented.
def exist?(name, options = nil)
read_file
@cache.nil? ? nil : @cache.include?(name.to_s)
end
# Fetches data from the cache and returns it, using the given key.
# If the key is missing and no block is passed, returns nil.
# If the key is missing and a block is passed, executes the block, sets
# the key to its value, and returns it.
#
# @param [String] name
# The name of the key. Will always be converted to a string.
# @param [Object] options
# The options to be used with this query. Not implemented.
# @yield [String]
# optional block with the default value if the key is missing
def fetch(name, options = nil)
read_file
if block_given?
entry = read(name.to_s, options)
if entry.nil?
value = yield name.to_s
write(name.to_s, value)
return value
else
return entry
end
else
return read(name.to_s, options)
end
end
# Fetches data from the cache, using the given key.
# Returns nil if the key is missing.
#
# @param [String] name
# The name of the key. Will always be converted to a string.
# @param [Object] options
# The options to be used with this query. Not implemented.
def read(name, options = nil)
read_file
@cache.nil? ? nil : @cache[name.to_s]
end
# Writes the value to the cache, with the key.
#
# @param [String] name
# The name of the key. Will always be converted to a string.
# @param [Object] value
# The value to be written.
# @param [Object] options
# The options to be used with this query. Not implemented.
def write(name, value, options = nil)
read_file
@cache = {} if @cache.nil?
@cache[name.to_s] = value
write_file
return nil
end
# Deletes an entry in the cache.
# Returns true if an entry is deleted.
#
# @param [String] name
# The name of the key. Will always be converted to a string.
# @param [Object] options
# The options to be used with this query. Not implemented.
def delete(name, options = nil)
read_file
return nil if @cache.nil?
if @cache.include? name.to_s
@cache.delete name.to_s
write_file
return true
else
return nil
end
end
protected
# Read the entire cache file from disk.
# Will avoid reading if there have been no changes.
def read_file
if !File.exist? @file_path
@cache = nil
else
# Check for changes after our last read or write.
if @last_change.nil? || File.mtime(@file_path) > @last_change
File.open(@file_path) do |file|
@cache = Marshal.load(file)
@last_change = file.mtime
end
end
end
return @cache
end
# Write the entire cache contents to disk.
def write_file
File.open(@file_path, 'w') do |file|
Marshal.dump(@cache, file)
end
@last_change = File.mtime(@file_path)
end
end
end
end
end

View File

@ -1,61 +0,0 @@
# Copyright 2013 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.
require 'active_support/inflector'
module Google
class APIClient
class Service
##
# Auxiliary mixin to generate resource and method stubs.
# Used by the Service and Service::Resource classes to generate both
# top-level and nested resources and methods.
module StubGenerator
def generate_call_stubs(service, root)
metaclass = (class << self; self; end)
# Handle resources.
root.discovered_resources.each do |resource|
method_name = ActiveSupport::Inflector.underscore(resource.name).to_sym
if !self.respond_to?(method_name)
metaclass.send(:define_method, method_name) do
Google::APIClient::Service::Resource.new(service, resource)
end
end
end
# Handle methods.
root.discovered_methods.each do |method|
method_name = ActiveSupport::Inflector.underscore(method.name).to_sym
if !self.respond_to?(method_name)
metaclass.send(:define_method, method_name) do |*args|
if args.length > 1
raise ArgumentError,
"wrong number of arguments (#{args.length} for 1)"
elsif !args.first.respond_to?(:to_hash) && !args.first.nil?
raise ArgumentError,
"expected parameter Hash, got #{args.first.class}"
else
return Google::APIClient::Service::Request.new(
service, method, args.first
)
end
end
end
end
end
end
end
end
end

View File

@ -1,21 +0,0 @@
# 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.
require 'google/api_client/auth/pkcs12'
require 'google/api_client/auth/jwt_asserter'
require 'google/api_client/auth/key_utils'
require 'google/api_client/auth/compute_service_account'
require 'google/api_client/auth/storage'
require 'google/api_client/auth/storages/redis_store'
require 'google/api_client/auth/storages/file_store'

View File

@ -1,26 +0,0 @@
# 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
module VERSION
MAJOR = 0
MINOR = 8
TINY = 6
PATCH = nil
STRING = [MAJOR, MINOR, TINY, PATCH].compact.join('.')
end
end
end

View File

@ -1,8 +0,0 @@
{ "access_token":"access_token_123456789",
"authorization_uri":"https://accounts.google.com/o/oauth2/auth",
"client_id":"123456789p.apps.googleusercontent.com",
"client_secret":"very_secret",
"expires_in":3600,
"refresh_token":"refresh_token_12345679",
"token_credential_uri":"https://accounts.google.com/o/oauth2/token",
"issued_at":1386053761}

View File

@ -1 +0,0 @@
{"installed":{"auth_uri":"https://accounts.google.com/o/oauth2/auth","client_secret":"i8YaXdGgiQ4_KrTVNGsB7QP1","token_uri":"https://accounts.google.com/o/oauth2/token","client_email":"","client_x509_cert_url":"","client_id":"898243283568.apps.googleusercontent.com","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs"}}

Binary file not shown.

View File

@ -1,33 +0,0 @@
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus posuere urna bibendum diam vulputate fringilla. Fusce elementum fermentum justo id aliquam. Integer vel felis ut arcu elementum lacinia. Duis congue urna eget nisl dapibus tristique molestie turpis sollicitudin. Vivamus in justo quam. Proin condimentum mollis tortor at molestie. Cras luctus, nunc a convallis iaculis, est risus consequat nisi, sit amet sollicitudin metus mi a urna. Aliquam accumsan, massa quis condimentum varius, sapien massa faucibus nibh, a dignissim magna nibh a lacus. Nunc aliquet, nunc ac pulvinar consectetur, sapien lacus hendrerit enim, nec dapibus lorem mi eget risus. Praesent vitae justo eget dolor blandit ullamcorper. Duis id nibh vitae sem aliquam vehicula et ac massa. In neque elit, molestie pulvinar viverra at, vestibulum quis velit.
Mauris sit amet placerat enim. Duis vel tellus ac dui auctor tincidunt id nec augue. Donec ut blandit turpis. Mauris dictum urna id urna vestibulum accumsan. Maecenas sagittis urna vitae erat facilisis gravida. Phasellus tellus augue, commodo ut iaculis vitae, interdum ut dolor. Proin at dictum lorem. Quisque pellentesque neque ante, vitae rutrum elit. Pellentesque sit amet erat orci. Praesent justo diam, tristique eu tempus ut, vestibulum eget dui. Maecenas et elementum justo. Cras a augue a elit porttitor placerat eget ut magna.
Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam adipiscing tellus in arcu bibendum volutpat. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed laoreet faucibus tristique. Duis metus eros, molestie eget dignissim in, imperdiet fermentum nulla. Vestibulum laoreet lorem eu justo vestibulum lobortis. Praesent pharetra leo vel mauris rhoncus commodo sollicitudin ante auctor. Ut sagittis, tortor nec placerat rutrum, neque ipsum cursus nisl, ut lacinia magna risus ac risus. Sed volutpat commodo orci, sodales fermentum dui accumsan eu. Donec egestas ullamcorper elit at condimentum. In euismod sodales posuere. Nullam lacinia tempus molestie. Etiam vitae ullamcorper dui. Fusce congue suscipit arcu, at consectetur diam gravida id. Quisque augue urna, commodo eleifend volutpat vitae, tincidunt ac ligula. Curabitur eget orci nisl, vel placerat ipsum.
Curabitur rutrum euismod nisi, consectetur varius tortor condimentum non. Pellentesque rhoncus nisi eu purus ultricies suscipit. Morbi ante nisi, varius nec molestie bibendum, pharetra quis enim. Proin eget nunc ante. Cras aliquam enim vel nunc laoreet ut facilisis nunc interdum. Fusce libero ipsum, posuere eget blandit quis, bibendum vitae quam. Integer dictum faucibus lacus eget facilisis. Duis adipiscing tortor magna, vel tincidunt risus. In non augue eu nisl sodales cursus vel eget nisi. Maecenas dignissim lectus elementum eros fermentum gravida et eget leo. Aenean quis cursus arcu. Mauris posuere purus non diam mattis vehicula. Integer nec orci velit.
Integer ac justo ac magna adipiscing condimentum vitae tincidunt dui. Morbi augue arcu, blandit nec interdum sit amet, condimentum vel nisl. Nulla vehicula tincidunt laoreet. Aliquam ornare elementum urna, sed vehicula magna porta id. Vestibulum dictum ultrices tortor sit amet tincidunt. Praesent bibendum, metus vel volutpat interdum, nisl nunc cursus libero, vel congue ligula mi et felis. Nulla mollis elementum nulla, in accumsan risus consequat at. Suspendisse potenti. Vestibulum enim lorem, dignissim ut porta vestibulum, porta eget mi. Fusce a elit ac dui sodales gravida. Pellentesque sed elit at dui dapibus mattis a non arcu.
Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In nec posuere augue. Praesent non suscipit arcu. Sed nibh risus, lacinia ut molestie vitae, tristique eget turpis. Sed pretium volutpat arcu, non rutrum leo volutpat sed. Maecenas quis neque nisl, sit amet ornare dolor. Nulla pharetra pulvinar tellus sed eleifend. Aliquam eget mattis nulla. Nulla dictum vehicula velit, non facilisis lorem volutpat id. Fusce scelerisque sem vitae purus dapibus lobortis. Mauris ac turpis nec nibh consequat porttitor. Ut sit amet iaculis lorem. Vivamus blandit erat ac odio venenatis fringilla a sit amet ante. Quisque ut urna sed augue laoreet sagittis.
Integer nisl urna, bibendum id lobortis in, tempor non velit. Fusce sed volutpat quam. Suspendisse eu placerat purus. Maecenas quis feugiat lectus. Sed accumsan malesuada dui, a pretium purus facilisis quis. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc ac purus id lacus malesuada placerat et in nunc. Ut imperdiet tincidunt est, at consectetur augue egestas hendrerit. Pellentesque eu erat a dui dignissim adipiscing. Integer quis leo non felis placerat eleifend. Fusce luctus mi a lorem mattis eget accumsan libero posuere. Sed pellentesque, odio id pharetra tempus, enim quam placerat metus, auctor aliquam elit mi facilisis quam. Nam at velit et eros rhoncus accumsan.
Donec tellus diam, fringilla ac viverra fringilla, rhoncus sit amet purus. Cras et ligula sed nibh tempor gravida. Aliquam id tempus mauris. Ut convallis quam sed arcu varius eget mattis magna tincidunt. Aliquam et suscipit est. Sed metus augue, tristique sed accumsan eget, euismod et augue. Nam augue sapien, placerat vel facilisis eu, tempor id risus. Aliquam mollis egestas mi. Fusce scelerisque convallis mauris quis blandit. Mauris nec ante id lacus sagittis tincidunt ornare vehicula dui. Curabitur tristique mattis nunc, vel cursus libero viverra feugiat. Suspendisse at sapien velit, a lacinia dolor. Vivamus in est non odio feugiat lacinia sodales ut magna.
Donec interdum ligula id ipsum dapibus consectetur. Pellentesque vitae posuere ligula. Morbi rhoncus bibendum eleifend. Suspendisse fringilla nunc at elit malesuada vitae ullamcorper lorem laoreet. Suspendisse a ante at ipsum iaculis cursus. Duis accumsan ligula quis nibh luctus pretium. Duis ultrices scelerisque dolor, et vulputate lectus commodo ut.
Vestibulum ac tincidunt lorem. Vestibulum lorem massa, dictum a scelerisque ut, convallis vitae eros. Morbi ipsum nisl, lacinia non tempor nec, lobortis id diam. Fusce quis magna nunc. Proin ultricies congue justo sed mattis. Vestibulum sit amet arcu tellus. Quisque ultricies porta massa iaculis vehicula. Vestibulum sollicitudin tempor urna vel sodales. Pellentesque ultricies tellus vel metus porta nec iaculis sapien mollis. Maecenas ullamcorper, metus eget imperdiet sagittis, odio orci dapibus neque, in vulputate nunc nibh non libero. Donec velit quam, lobortis quis tempus a, hendrerit id arcu.
Donec nec ante at tortor dignissim mattis. Curabitur vehicula tincidunt magna id sagittis. Proin euismod dignissim porta. Curabitur non turpis purus, in rutrum nulla. Nam turpis nulla, tincidunt et hendrerit non, posuere nec enim. Curabitur leo enim, lobortis ut placerat id, condimentum nec massa. In bibendum, lectus sit amet molestie commodo, felis massa rutrum nisl, ac fermentum ligula lacus in ipsum.
Pellentesque mi nulla, scelerisque vitae tempus id, consequat a augue. Quisque vel nisi sit amet ipsum faucibus laoreet sed vitae lorem. Praesent nunc tortor, volutpat ac commodo non, pharetra sed neque. Curabitur nec felis at mi blandit aliquet eu ornare justo. Mauris dignissim purus quis nisl porttitor interdum. Aenean id ipsum enim, blandit commodo justo. Quisque facilisis elit quis velit commodo scelerisque lobortis sapien condimentum. Cras sit amet porttitor velit. Praesent nec tempor arcu.
Donec varius mi adipiscing elit semper vel feugiat ipsum dictum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec non quam nisl, ac mattis justo. Vestibulum sed massa eget velit tristique auctor ut ac sapien. Curabitur aliquet ligula eget dui ornare at scelerisque mauris faucibus. Vestibulum id mauris metus, sed vestibulum nibh. Nulla egestas dictum blandit. Mauris vitae nibh at dui mollis lobortis. Phasellus sem leo, euismod at fringilla quis, mollis in nibh. Aenean vel lacus et elit pharetra elementum. Aliquam at ligula id sem bibendum volutpat. Pellentesque quis elit a massa dapibus viverra ut et lorem. Donec nulla eros, iaculis nec commodo vel, suscipit sit amet tortor. Integer tempor, elit at viverra imperdiet, velit sapien laoreet nunc, id laoreet ligula risus vel risus. Nullam sed tortor metus.
In nunc orci, tempor vulputate pretium vel, suscipit quis risus. Suspendisse accumsan facilisis felis eget posuere. Donec a faucibus felis. Proin nibh erat, sollicitudin quis vestibulum id, tincidunt quis justo. In sed purus eu nisi dignissim condimentum. Sed mattis dapibus lorem id vulputate. Suspendisse nec elit a augue interdum consequat quis id magna. In eleifend aliquam tempor. In in lacus augue.
Ut euismod sollicitudin lorem, id aliquam magna dictum sed. Nunc fringilla lobortis nisi sed consectetur. Nulla facilisi. Aenean nec lobortis augue. Curabitur ullamcorper dapibus libero, vel pellentesque arcu sollicitudin non. Praesent varius, turpis nec sollicitudin bibendum, elit tortor rhoncus lacus, gravida luctus leo nisi in felis. Ut metus eros, molestie non faucibus vel, condimentum ac elit.
Suspendisse nisl justo, lacinia sit amet interdum nec, tincidunt placerat urna. Suspendisse potenti. In et odio sed purus malesuada cursus sed nec lectus. Cras commodo, orci sit amet hendrerit iaculis, nunc urna facilisis tellus, vel laoreet odio nulla quis nibh. Maecenas ut justo ut lacus posuere sodales. Vestibulum facilisis fringilla diam at volutpat. Proin a hendrerit urna. Aenean placerat pulvinar arcu, sit amet lobortis neque eleifend in. Aenean risus nulla, facilisis ut tincidunt vitae, fringilla at ligula. Praesent eleifend est at sem lacinia auctor. Nulla ornare nunc in erat laoreet blandit.
Suspendisse pharetra leo ac est porta consequat. Nunc sem nibh, gravida vel aliquam a, ornare in tortor. Nulla vel sapien et felis placerat pellentesque id scelerisque nisl. Praesent et posuere.

View File

@ -1,19 +0,0 @@
Bag Attributes
friendlyName: privatekey
localKeyID: 54 69 6D 65 20 31 33 35 31 38 38 38 31 37 38 36 39 36
Key Attributes: <No Attributes>
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDYDyPb3GhyFx5i/wxS/jFsO6wSLys1ehAk6QZoBXGlg7ETVrIJ
HYh9gXQUno4tJiQoaO8wOvleIRrqI0LkiftCXKWVSrzOiV+O9GkKx1byw1yAIZus
QdwMT7X0O9hrZLZwhICWC9s6cGhnlCVxLIP/+JkVK7hxEq/LxoSszNV77wIDAQAB
AoGAa2G69L7quil7VMBmI6lqbtyJfNAsrXtpIq8eG/z4qsZ076ObAKTI/XeldcoH
57CZL+xXVKU64umZMt0rleJuGXdlauEUbsSx+biGewRfGTgC4rUSjmE539rBvmRW
gaKliorepPMp/+B9CcG/2YfDPRvG/2cgTXJHVvneo+xHL4ECQQD2Jx5Mvs8z7s2E
jY1mkpRKqh4Z7rlitkAwe1NXcVC8hz5ASu7ORyTl8EPpKAfRMYl1ofK/ozT1URXf
kL5nChPfAkEA4LPUJ6cqrY4xrrtdGaM4iGIxzen5aZlKz/YNlq5LuQKbnLLHMuXU
ohp/ynpqNWbcAFbmtGSMayxGKW5+fJgZ8QJAUBOZv82zCmn9YcnK3juBEmkVMcp/
dKVlbGAyVJgAc9RrY+78kQ6D6mmnLgpfwKYk2ae9mKo3aDbgrsIfrtWQcQJAfFGi
CEpJp3orbLQG319ZsMM7MOTJdC42oPZOMFbAWFzkAX88DKHx0bn9h+XQizkccSej
Ppz+v3DgZJ3YZ1Cz0QJBALiqIokZ+oa3AY6oT0aiec6txrGvNPPbwOsrBpFqGNbu
AByzWWBoBi40eKMSIR30LqN9H8YnJ91Aoy1njGYyQaw=
-----END RSA PRIVATE KEY-----

View File

@ -1,584 +0,0 @@
{
"kind": "discovery#describeItem",
"name": "zoo",
"version": "v1",
"description": "Zoo API used for testing",
"basePath": "/zoo/",
"rootUrl": "https://www.googleapis.com/",
"servicePath": "zoo/v1/",
"rpcPath": "/rpc",
"parameters": {
"alt": {
"type": "string",
"description": "Data format for the response.",
"default": "json",
"enum": [
"json"
],
"enumDescriptions": [
"Responses with Content-Type of application/json"
],
"location": "query"
},
"fields": {
"type": "string",
"description": "Selector specifying which fields to include in a partial response.",
"location": "query"
},
"key": {
"type": "string",
"description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.",
"location": "query"
},
"oauth_token": {
"type": "string",
"description": "OAuth 2.0 token for the current user.",
"location": "query"
},
"prettyPrint": {
"type": "boolean",
"description": "Returns response with indentations and line breaks.",
"default": "true",
"location": "query"
},
"quotaUser": {
"type": "string",
"description": "Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. Overrides userIp if both are provided.",
"location": "query"
},
"userIp": {
"type": "string",
"description": "IP address of the site where the request originates. Use this if you want to enforce per-user limits.",
"location": "query"
}
},
"features": [
"dataWrapper"
],
"schemas": {
"Animal": {
"id": "Animal",
"type": "object",
"properties": {
"etag": {
"type": "string"
},
"kind": {
"type": "string",
"default": "zoo#animal"
},
"name": {
"type": "string"
},
"photo": {
"type": "object",
"properties": {
"filename": {
"type": "string"
},
"hash": {
"type": "string"
},
"hashAlgorithm": {
"type": "string"
},
"size": {
"type": "integer"
},
"type": {
"type": "string"
}
}
}
}
},
"Animal2": {
"id": "Animal2",
"type": "object",
"properties": {
"kind": {
"type": "string",
"default": "zoo#animal"
},
"name": {
"type": "string"
}
}
},
"AnimalFeed": {
"id": "AnimalFeed",
"type": "object",
"properties": {
"etag": {
"type": "string"
},
"items": {
"type": "array",
"items": {
"$ref": "Animal"
}
},
"kind": {
"type": "string",
"default": "zoo#animalFeed"
}
}
},
"AnimalMap": {
"id": "AnimalMap",
"type": "object",
"properties": {
"etag": {
"type": "string"
},
"animals": {
"type": "object",
"description": "Map of animal id to animal data",
"additionalProperties": {
"$ref": "Animal"
}
},
"kind": {
"type": "string",
"default": "zoo#animalMap"
}
}
},
"LoadFeed": {
"id": "LoadFeed",
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"doubleVal": {
"type": "number"
},
"nullVal": {
"type": "null"
},
"booleanVal": {
"type": "boolean",
"description": "True or False."
},
"anyVal": {
"type": "any",
"description": "Anything will do."
},
"enumVal": {
"type": "string"
},
"kind": {
"type": "string",
"default": "zoo#loadValue"
},
"longVal": {
"type": "integer"
},
"stringVal": {
"type": "string"
}
}
}
},
"kind": {
"type": "string",
"default": "zoo#loadFeed"
}
}
}
},
"methods": {
"query": {
"path": "query",
"id": "bigquery.query",
"httpMethod": "GET",
"parameters": {
"q": {
"type": "string",
"location": "query",
"required": false,
"repeated": false
},
"i": {
"type": "integer",
"location": "query",
"required": false,
"repeated": false,
"minimum": "0",
"maximum": "4294967295",
"default": "20"
},
"n": {
"type": "number",
"location": "query",
"required": false,
"repeated": false
},
"b": {
"type": "boolean",
"location": "query",
"required": false,
"repeated": false
},
"a": {
"type": "any",
"location": "query",
"required": false,
"repeated": false
},
"o": {
"type": "object",
"location": "query",
"required": false,
"repeated": false
},
"e": {
"type": "string",
"location": "query",
"required": false,
"repeated": false,
"enum": [
"foo",
"bar"
]
},
"er": {
"type": "string",
"location": "query",
"required": false,
"repeated": true,
"enum": [
"one",
"two",
"three"
]
},
"rr": {
"type": "string",
"location": "query",
"required": false,
"repeated": true,
"pattern": "[a-z]+"
}
}
}
},
"resources": {
"my": {
"resources": {
"favorites": {
"methods": {
"list": {
"path": "favorites/@me/mine",
"id": "zoo.animals.mine",
"httpMethod": "GET",
"parameters": {
"max-results": {
"location": "query",
"required": false
}
}
}
}
}
}
},
"global": {
"resources": {
"print": {
"methods": {
"assert": {
"path": "global/print/assert",
"id": "zoo.animals.mine",
"httpMethod": "GET",
"parameters": {
"max-results": {
"location": "query",
"required": false
}
}
}
}
}
}
},
"animals": {
"methods": {
"crossbreed": {
"path": "animals/crossbreed",
"id": "zoo.animals.crossbreed",
"httpMethod": "POST",
"description": "Cross-breed animals",
"response": {
"$ref": "Animal2"
},
"mediaUpload": {
"accept": [
"image/png"
],
"protocols": {
"simple": {
"multipart": true,
"path": "upload/activities/{userId}/@self"
},
"resumable": {
"multipart": true,
"path": "upload/activities/{userId}/@self"
}
}
}
},
"delete": {
"path": "animals/{name}",
"id": "zoo.animals.delete",
"httpMethod": "DELETE",
"description": "Delete animals",
"parameters": {
"name": {
"location": "path",
"required": true,
"description": "Name of the animal to delete",
"type": "string"
}
},
"parameterOrder": [
"name"
]
},
"get": {
"path": "animals/{name}",
"id": "zoo.animals.get",
"httpMethod": "GET",
"description": "Get animals",
"supportsMediaDownload": true,
"parameters": {
"name": {
"location": "path",
"required": true,
"description": "Name of the animal to load",
"type": "string"
},
"projection": {
"location": "query",
"type": "string",
"enum": [
"full"
],
"enumDescriptions": [
"Include everything"
]
}
},
"parameterOrder": [
"name"
],
"response": {
"$ref": "Animal"
}
},
"getmedia": {
"path": "animals/{name}",
"id": "zoo.animals.get",
"httpMethod": "GET",
"description": "Get animals",
"parameters": {
"name": {
"location": "path",
"required": true,
"description": "Name of the animal to load",
"type": "string"
},
"projection": {
"location": "query",
"type": "string",
"enum": [
"full"
],
"enumDescriptions": [
"Include everything"
]
}
},
"parameterOrder": [
"name"
]
},
"insert": {
"path": "animals",
"id": "zoo.animals.insert",
"httpMethod": "POST",
"description": "Insert animals",
"request": {
"$ref": "Animal"
},
"response": {
"$ref": "Animal"
},
"mediaUpload": {
"accept": [
"image/png"
],
"maxSize": "1KB",
"protocols": {
"simple": {
"multipart": true,
"path": "upload/activities/{userId}/@self"
},
"resumable": {
"multipart": true,
"path": "upload/activities/{userId}/@self"
}
}
}
},
"list": {
"path": "animals",
"id": "zoo.animals.list",
"httpMethod": "GET",
"description": "List animals",
"parameters": {
"max-results": {
"location": "query",
"description": "Maximum number of results to return",
"type": "integer",
"minimum": "0"
},
"name": {
"location": "query",
"description": "Restrict result to animals with this name",
"type": "string"
},
"projection": {
"location": "query",
"type": "string",
"enum": [
"full"
],
"enumDescriptions": [
"Include absolutely everything"
]
},
"start-token": {
"location": "query",
"description": "Pagination token",
"type": "string"
}
},
"response": {
"$ref": "AnimalFeed"
}
},
"patch": {
"path": "animals/{name}",
"id": "zoo.animals.patch",
"httpMethod": "PATCH",
"description": "Update animals",
"parameters": {
"name": {
"location": "path",
"required": true,
"description": "Name of the animal to update",
"type": "string"
}
},
"parameterOrder": [
"name"
],
"request": {
"$ref": "Animal"
},
"response": {
"$ref": "Animal"
}
},
"update": {
"path": "animals/{name}",
"id": "zoo.animals.update",
"httpMethod": "PUT",
"description": "Update animals",
"parameters": {
"name": {
"location": "path",
"description": "Name of the animal to update",
"type": "string"
}
},
"parameterOrder": [
"name"
],
"request": {
"$ref": "Animal"
},
"response": {
"$ref": "Animal"
}
}
}
},
"load": {
"methods": {
"list": {
"path": "load",
"id": "zoo.load.list",
"httpMethod": "GET",
"response": {
"$ref": "LoadFeed"
}
}
}
},
"loadNoTemplate": {
"methods": {
"list": {
"path": "loadNoTemplate",
"id": "zoo.loadNoTemplate.list",
"httpMethod": "GET"
}
}
},
"scopedAnimals": {
"methods": {
"list": {
"path": "scopedanimals",
"id": "zoo.scopedAnimals.list",
"httpMethod": "GET",
"description": "List animals (scoped)",
"parameters": {
"max-results": {
"location": "query",
"description": "Maximum number of results to return",
"type": "integer",
"minimum": "0"
},
"name": {
"location": "query",
"description": "Restrict result to animals with this name",
"type": "string"
},
"projection": {
"location": "query",
"type": "string",
"enum": [
"full"
],
"enumDescriptions": [
"Include absolutely everything"
]
},
"start-token": {
"location": "query",
"description": "Pagination token",
"type": "string"
}
},
"response": {
"$ref": "AnimalFeed"
}
}
}
}
}
}

View File

@ -1,122 +0,0 @@
require 'spec_helper'
require 'google/api_client'
require 'google/api_client/version'
describe Google::APIClient::Storage do
let(:client) { Google::APIClient.new(:application_name => 'API Client Tests') }
let(:root_path) { File.expand_path(File.join(__FILE__, '..', '..', '..')) }
let(:json_file) { File.expand_path(File.join(root_path, 'fixtures', 'files', 'auth_stored_credentials.json')) }
let(:store) { double }
let(:client_stub) { double }
subject { Google::APIClient::Storage.new(store) }
describe 'authorize' do
it 'should authorize' do
expect(subject).to respond_to(:authorization)
expect(subject.store).to be == store
end
end
describe 'authorize' do
describe 'with credentials' do
it 'should initialize a new OAuth Client' do
expect(subject).to receive(:load_credentials).and_return({:first => 'a dummy'})
expect(client_stub).to receive(:issued_at=)
expect(client_stub).to receive(:expired?).and_return(false)
expect(Signet::OAuth2::Client).to receive(:new).and_return(client_stub)
expect(subject).not_to receive(:refresh_authorization)
subject.authorize
end
it 'should refresh authorization' do
expect(subject).to receive(:load_credentials).and_return({:first => 'a dummy'})
expect(client_stub).to receive(:issued_at=)
expect(client_stub).to receive(:expired?).and_return(true)
expect(Signet::OAuth2::Client).to receive(:new).and_return(client_stub)
expect(subject).to receive(:refresh_authorization)
auth = subject.authorize
expect(auth).to be == subject.authorization
expect(auth).not_to be_nil
end
end
describe 'without credentials' do
it 'should return nil' do
expect(subject.authorization).to be_nil
expect(subject).to receive(:load_credentials).and_return({})
expect(subject.authorize).to be_nil
expect(subject.authorization).to be_nil
end
end
end
describe 'write_credentials' do
it 'should call store to write credentials' do
authorization_stub = double
expect(authorization_stub).to receive(:refresh_token).and_return(true)
expect(subject).to receive(:credentials_hash)
expect(subject.store).to receive(:write_credentials)
subject.write_credentials(authorization_stub)
expect(subject.authorization).to be == authorization_stub
end
it 'should not call store to write credentials' do
expect(subject).not_to receive(:credentials_hash)
expect(subject.store).not_to receive(:write_credentials)
expect {
subject.write_credentials()
}.not_to raise_error
end
it 'should not call store to write credentials' do
expect(subject).not_to receive(:credentials_hash)
expect(subject.store).not_to receive(:write_credentials)
expect {
subject.write_credentials('something')
}.not_to raise_error
end
end
describe 'refresh_authorization' do
it 'should call refresh and write credentials' do
expect(subject).to receive(:write_credentials)
authorization_stub = double
expect(subject).to receive(:authorization).and_return(authorization_stub)
expect(authorization_stub).to receive(:refresh!).and_return(true)
subject.refresh_authorization
end
end
describe 'load_credentials' do
it 'should call store to load credentials' do
expect(subject.store).to receive(:load_credentials)
subject.send(:load_credentials)
end
end
describe 'credentials_hash' do
it 'should return an hash' do
authorization_stub = double
expect(authorization_stub).to receive(:access_token)
expect(authorization_stub).to receive(:client_id)
expect(authorization_stub).to receive(:client_secret)
expect(authorization_stub).to receive(:expires_in)
expect(authorization_stub).to receive(:refresh_token)
expect(authorization_stub).to receive(:issued_at).and_return('100')
allow(subject).to receive(:authorization).and_return(authorization_stub)
credentials = subject.send(:credentials_hash)
expect(credentials).to include(:access_token)
expect(credentials).to include(:authorization_uri)
expect(credentials).to include(:client_id)
expect(credentials).to include(:client_secret)
expect(credentials).to include(:expires_in)
expect(credentials).to include(:refresh_token)
expect(credentials).to include(:token_credential_uri)
expect(credentials).to include(:issued_at)
end
end
end

View File

@ -1,40 +0,0 @@
require 'spec_helper'
require 'google/api_client'
require 'google/api_client/version'
describe Google::APIClient::FileStore do
let(:root_path) { File.expand_path(File.join(__FILE__, '..','..','..', '..','..')) }
let(:json_file) { File.expand_path(File.join(root_path, 'fixtures', 'files', 'auth_stored_credentials.json')) }
let(:credentials_hash) {{
"access_token"=>"my_access_token",
"authorization_uri"=>"https://accounts.google.com/o/oauth2/auth",
"client_id"=>"123456_test_client_id@.apps.googleusercontent.com",
"client_secret"=>"123456_client_secret",
"expires_in"=>3600,
"refresh_token"=>"my_refresh_token",
"token_credential_uri"=>"https://accounts.google.com/o/oauth2/token",
"issued_at"=>1384440275
}}
subject{Google::APIClient::FileStore.new('a file path')}
it 'should have a path' do
expect(subject.path).to be == 'a file path'
subject.path = 'an other file path'
expect(subject.path).to be == 'an other file path'
end
it 'should load credentials' do
subject.path = json_file
credentials = subject.load_credentials
expect(credentials).to include('access_token', 'authorization_uri', 'refresh_token')
end
it 'should write credentials' do
io_stub = StringIO.new
expect(subject).to receive(:open).and_return(io_stub)
subject.write_credentials(credentials_hash)
end
end

View File

@ -1,70 +0,0 @@
require 'spec_helper'
require 'google/api_client'
require 'google/api_client/version'
describe Google::APIClient::RedisStore do
let(:root_path) { File.expand_path(File.join(__FILE__, '..', '..', '..', '..', '..')) }
let(:json_file) { File.expand_path(File.join(root_path, 'fixtures', 'files', 'auth_stored_credentials.json')) }
let(:redis) {double}
let(:credentials_hash) { {
"access_token" => "my_access_token",
"authorization_uri" => "https://accounts.google.com/o/oauth2/auth",
"client_id" => "123456_test_client_id@.apps.googleusercontent.com",
"client_secret" => "123456_client_secret",
"expires_in" => 3600,
"refresh_token" => "my_refresh_token",
"token_credential_uri" => "https://accounts.google.com/o/oauth2/token",
"issued_at" => 1384440275
} }
subject { Google::APIClient::RedisStore.new('a redis instance') }
it 'should have a redis instance' do
expect(subject.redis).to be == 'a redis instance'
subject.redis = 'an other redis instance'
expect(subject.redis).to be == 'an other redis instance'
end
describe 'load_credentials' do
it 'should load credentials' do
subject.redis= redis
expect(redis).to receive(:get).and_return(credentials_hash.to_json)
expect(subject.load_credentials).to be == credentials_hash
end
it 'should return nil' do
subject.redis= redis
expect(redis).to receive(:get).and_return(nil)
expect(subject.load_credentials).to be_nil
end
end
describe 'redis_credentials_key' do
context 'without given key' do
it 'should return default key' do
expect(subject.redis_credentials_key).to be == "google_api_credentials"
end
end
context 'with given key' do
let(:redis_store) { Google::APIClient::RedisStore.new('a redis instance', 'another_google_api_credentials') }
it 'should use given key' do
expect(redis_store.redis_credentials_key).to be == "another_google_api_credentials"
end
end
end
describe 'write credentials' do
it 'should write credentials' do
subject.redis= redis
expect(redis).to receive(:set).and_return('ok')
expect(subject.write_credentials(credentials_hash)).to be_truthy
end
end
end

View File

@ -1,248 +0,0 @@
# Copyright 2012 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.
require 'spec_helper'
require 'google/api_client'
RSpec.describe Google::APIClient::BatchRequest do
CLIENT = Google::APIClient.new(:application_name => 'API Client Tests') unless defined?(CLIENT)
after do
# Reset client to not-quite-pristine state
CLIENT.key = nil
CLIENT.user_ip = nil
end
it 'should raise an error if making an empty batch request' do
batch = Google::APIClient::BatchRequest.new
expect(lambda do
CLIENT.execute(batch)
end).to raise_error(Google::APIClient::BatchError)
end
it 'should allow query parameters in batch requests' do
batch = Google::APIClient::BatchRequest.new
batch.add(:uri => 'https://example.com', :parameters => {
'a' => '12345'
})
method, uri, headers, body = batch.to_http_request
expect(body.read).to include("/?a=12345")
end
describe 'with the discovery API' do
before do
CLIENT.authorization = nil
@discovery = CLIENT.discovered_api('discovery', 'v1')
end
describe 'with two valid requests' do
before do
@call1 = {
:api_method => @discovery.apis.get_rest,
:parameters => {
'api' => 'plus',
'version' => 'v1'
}
}
@call2 = {
:api_method => @discovery.apis.get_rest,
:parameters => {
'api' => 'discovery',
'version' => 'v1'
}
}
end
it 'should execute both when using a global callback' do
block_called = 0
ids = ['first_call', 'second_call']
expected_ids = ids.clone
batch = Google::APIClient::BatchRequest.new do |result|
block_called += 1
expect(result.status).to eq(200)
expect(expected_ids).to include(result.response.call_id)
expected_ids.delete(result.response.call_id)
end
batch.add(@call1, ids[0])
batch.add(@call2, ids[1])
CLIENT.execute(batch)
expect(block_called).to eq(2)
end
it 'should execute both when using individual callbacks' do
batch = Google::APIClient::BatchRequest.new
call1_returned, call2_returned = false, false
batch.add(@call1) do |result|
call1_returned = true
expect(result.status).to eq(200)
end
batch.add(@call2) do |result|
call2_returned = true
expect(result.status).to eq(200)
end
CLIENT.execute(batch)
expect(call1_returned).to be_truthy
expect(call2_returned).to be_truthy
end
it 'should raise an error if using the same call ID more than once' do
batch = Google::APIClient::BatchRequest.new
expect(lambda do
batch.add(@call1, 'my_id')
batch.add(@call2, 'my_id')
end).to raise_error(Google::APIClient::BatchError)
end
end
describe 'with a valid request and an invalid one' do
before do
@call1 = {
:api_method => @discovery.apis.get_rest,
:parameters => {
'api' => 'plus',
'version' => 'v1'
}
}
@call2 = {
:api_method => @discovery.apis.get_rest,
:parameters => {
'api' => 0,
'version' => 1
}
}
end
it 'should execute both when using a global callback' do
block_called = 0
ids = ['first_call', 'second_call']
expected_ids = ids.clone
batch = Google::APIClient::BatchRequest.new do |result|
block_called += 1
expect(expected_ids).to include(result.response.call_id)
expected_ids.delete(result.response.call_id)
if result.response.call_id == ids[0]
expect(result.status).to eq(200)
else
expect(result.status).to be >= 400
expect(result.status).to be < 500
end
end
batch.add(@call1, ids[0])
batch.add(@call2, ids[1])
CLIENT.execute(batch)
expect(block_called).to eq(2)
end
it 'should execute both when using individual callbacks' do
batch = Google::APIClient::BatchRequest.new
call1_returned, call2_returned = false, false
batch.add(@call1) do |result|
call1_returned = true
expect(result.status).to eq(200)
end
batch.add(@call2) do |result|
call2_returned = true
expect(result.status).to be >= 400
expect(result.status).to be < 500
end
CLIENT.execute(batch)
expect(call1_returned).to be_truthy
expect(call2_returned).to be_truthy
end
end
end
describe 'with the calendar API' do
before do
CLIENT.authorization = nil
@calendar = CLIENT.discovered_api('calendar', 'v3')
end
describe 'with two valid requests' do
before do
event1 = {
'summary' => 'Appointment 1',
'location' => 'Somewhere',
'start' => {
'dateTime' => '2011-01-01T10:00:00.000-07:00'
},
'end' => {
'dateTime' => '2011-01-01T10:25:00.000-07:00'
},
'attendees' => [
{
'email' => 'myemail@mydomain.tld'
}
]
}
event2 = {
'summary' => 'Appointment 2',
'location' => 'Somewhere as well',
'start' => {
'dateTime' => '2011-01-02T10:00:00.000-07:00'
},
'end' => {
'dateTime' => '2011-01-02T10:25:00.000-07:00'
},
'attendees' => [
{
'email' => 'myemail@mydomain.tld'
}
]
}
@call1 = {
:api_method => @calendar.events.insert,
:parameters => {'calendarId' => 'myemail@mydomain.tld'},
:body => MultiJson.dump(event1),
:headers => {'Content-Type' => 'application/json'}
}
@call2 = {
:api_method => @calendar.events.insert,
:parameters => {'calendarId' => 'myemail@mydomain.tld'},
:body => MultiJson.dump(event2),
:headers => {'Content-Type' => 'application/json'}
}
end
it 'should convert to a correct HTTP request' do
batch = Google::APIClient::BatchRequest.new { |result| }
batch.add(@call1, '1').add(@call2, '2')
request = batch.to_env(CLIENT.connection)
boundary = Google::APIClient::BatchRequest::BATCH_BOUNDARY
expect(request[:method].to_s.downcase).to eq('post')
expect(request[:url].to_s).to eq('https://www.googleapis.com/batch')
expect(request[:request_headers]['Content-Type']).to eq("multipart/mixed;boundary=#{boundary}")
body = request[:body].read
expect(body).to include(@call1[:body])
expect(body).to include(@call2[:body])
end
end
end
end

View File

@ -1,53 +0,0 @@
# encoding:utf-8
# Copyright 2013 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.
require 'spec_helper'
require 'google/api_client/client_secrets'
FIXTURES_PATH = File.expand_path('../../../fixtures', __FILE__)
RSpec.describe Google::APIClient::ClientSecrets do
context 'with JSON file' do
let(:file) { File.join(FIXTURES_PATH, 'files', 'client_secrets.json') }
subject(:secrets) { Google::APIClient::ClientSecrets.load(file)}
it 'should load the correct client ID' do
expect(secrets.client_id).to be == '898243283568.apps.googleusercontent.com'
end
it 'should load the correct client secret' do
expect(secrets.client_secret).to be == 'i8YaXdGgiQ4_KrTVNGsB7QP1'
end
context 'serialzed to hash' do
subject(:hash) { secrets.to_hash }
it 'should contain the flow as the first key' do
expect(hash).to have_key "installed"
end
it 'should contain the client ID' do
expect(hash["installed"]["client_id"]).to be == '898243283568.apps.googleusercontent.com'
end
it 'should contain the client secret' do
expect(hash["installed"]["client_secret"]).to be == 'i8YaXdGgiQ4_KrTVNGsB7QP1'
end
end
end
end

View File

@ -1,708 +0,0 @@
# encoding:utf-8
# 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.
require 'spec_helper'
require 'faraday'
require 'multi_json'
require 'compat/multi_json'
require 'signet/oauth_1/client'
require 'google/api_client'
fixtures_path = File.expand_path('../../../fixtures', __FILE__)
RSpec.describe Google::APIClient do
include ConnectionHelpers
CLIENT = Google::APIClient.new(:application_name => 'API Client Tests') unless defined?(CLIENT)
after do
# Reset client to not-quite-pristine state
CLIENT.key = nil
CLIENT.user_ip = nil
end
it 'should raise a type error for bogus authorization' do
expect(lambda do
Google::APIClient.new(:application_name => 'API Client Tests', :authorization => 42)
end).to raise_error(TypeError)
end
it 'should not be able to retrieve the discovery document for a bogus API' do
expect(lambda do
CLIENT.discovery_document('bogus')
end).to raise_error(Google::APIClient::TransmissionError)
expect(lambda do
CLIENT.discovered_api('bogus')
end).to raise_error(Google::APIClient::TransmissionError)
end
it 'should raise an error for bogus services' do
expect(lambda do
CLIENT.discovered_api(42)
end).to raise_error(TypeError)
end
it 'should raise an error for bogus services' do
expect(lambda do
CLIENT.preferred_version(42)
end).to raise_error(TypeError)
end
it 'should raise an error for bogus methods' do
expect(lambda do
CLIENT.execute(42)
end).to raise_error(TypeError)
end
it 'should not return a preferred version for bogus service names' do
expect(CLIENT.preferred_version('bogus')).to eq(nil)
end
describe 'with zoo API' do
it 'should return API instance registered from file' do
zoo_json = File.join(fixtures_path, 'files', 'zoo.json')
contents = File.open(zoo_json, 'rb') { |io| io.read }
api = CLIENT.register_discovery_document('zoo', 'v1', contents)
expect(api).to be_kind_of(Google::APIClient::API)
end
end
describe 'with the prediction API' do
before do
CLIENT.authorization = nil
# The prediction API no longer exposes a v1, so we have to be
# careful about looking up the wrong API version.
@prediction = CLIENT.discovered_api('prediction', 'v1.2')
end
it 'should correctly determine the discovery URI' do
expect(CLIENT.discovery_uri('prediction')).to be ===
'https://www.googleapis.com/discovery/v1/apis/prediction/v1/rest'
end
it 'should correctly determine the discovery URI if :user_ip is set' do
CLIENT.user_ip = '127.0.0.1'
conn = stub_connection do |stub|
stub.get('/discovery/v1/apis/prediction/v1.2/rest?userIp=127.0.0.1') do |env|
[200, {}, '{}']
end
end
CLIENT.execute(
:http_method => 'GET',
:uri => CLIENT.discovery_uri('prediction', 'v1.2'),
:authenticated => false,
:connection => conn
)
conn.verify
end
it 'should correctly determine the discovery URI if :key is set' do
CLIENT.key = 'qwerty'
conn = stub_connection do |stub|
stub.get('/discovery/v1/apis/prediction/v1.2/rest?key=qwerty') do |env|
[200, {}, '{}']
end
end
request = CLIENT.execute(
:http_method => 'GET',
:uri => CLIENT.discovery_uri('prediction', 'v1.2'),
:authenticated => false,
:connection => conn
)
conn.verify
end
it 'should correctly determine the discovery URI if both are set' do
CLIENT.key = 'qwerty'
CLIENT.user_ip = '127.0.0.1'
conn = stub_connection do |stub|
stub.get('/discovery/v1/apis/prediction/v1.2/rest?key=qwerty&userIp=127.0.0.1') do |env|
[200, {}, '{}']
end
end
request = CLIENT.execute(
:http_method => 'GET',
:uri => CLIENT.discovery_uri('prediction', 'v1.2'),
:authenticated => false,
:connection => conn
)
conn.verify
end
it 'should correctly generate API objects' do
expect(CLIENT.discovered_api('prediction', 'v1.2').name).to eq('prediction')
expect(CLIENT.discovered_api('prediction', 'v1.2').version).to eq('v1.2')
expect(CLIENT.discovered_api(:prediction, 'v1.2').name).to eq('prediction')
expect(CLIENT.discovered_api(:prediction, 'v1.2').version).to eq('v1.2')
end
it 'should discover methods' do
expect(CLIENT.discovered_method(
'prediction.training.insert', 'prediction', 'v1.2'
).name).to eq('insert')
expect(CLIENT.discovered_method(
:'prediction.training.insert', :prediction, 'v1.2'
).name).to eq('insert')
expect(CLIENT.discovered_method(
'prediction.training.delete', 'prediction', 'v1.2'
).name).to eq('delete')
end
it 'should define the origin API in discovered methods' do
expect(CLIENT.discovered_method(
'prediction.training.insert', 'prediction', 'v1.2'
).api.name).to eq('prediction')
end
it 'should not find methods that are not in the discovery document' do
expect(CLIENT.discovered_method(
'prediction.bogus', 'prediction', 'v1.2'
)).to eq(nil)
end
it 'should raise an error for bogus methods' do
expect(lambda do
CLIENT.discovered_method(42, 'prediction', 'v1.2')
end).to raise_error(TypeError)
end
it 'should raise an error for bogus methods' do
expect(lambda do
CLIENT.execute(:api_method => CLIENT.discovered_api('prediction', 'v1.2'))
end).to raise_error(TypeError)
end
it 'should correctly determine the preferred version' do
expect(CLIENT.preferred_version('prediction').version).not_to eq('v1')
expect(CLIENT.preferred_version(:prediction).version).not_to eq('v1')
end
it 'should return a batch path' do
expect(CLIENT.discovered_api('prediction', 'v1.2').batch_path).not_to be_nil
end
it 'should generate valid requests' do
conn = stub_connection do |stub|
stub.post('/prediction/v1.2/training?data=12345') do |env|
expect(env[:body]).to eq('')
[200, {}, '{}']
end
end
request = CLIENT.execute(
:api_method => @prediction.training.insert,
:parameters => {'data' => '12345'},
:connection => conn
)
conn.verify
end
it 'should generate valid requests when parameter value includes semicolon' do
conn = stub_connection do |stub|
# semicolon (;) in parameter value was being converted to
# bare ampersand (&) in 0.4.7. ensure that it gets converted
# to a CGI-escaped semicolon (%3B) instead.
stub.post('/prediction/v1.2/training?data=12345%3B67890') do |env|
expect(env[:body]).to eq('')
[200, {}, '{}']
end
end
request = CLIENT.execute(
:api_method => @prediction.training.insert,
:parameters => {'data' => '12345;67890'},
:connection => conn
)
conn.verify
end
it 'should generate valid requests when multivalued parameters are passed' do
conn = stub_connection do |stub|
stub.post('/prediction/v1.2/training?data=1&data=2') do |env|
expect(env.params['data']).to include('1', '2')
[200, {}, '{}']
end
end
request = CLIENT.execute(
:api_method => @prediction.training.insert,
:parameters => {'data' => ['1', '2']},
:connection => conn
)
conn.verify
end
it 'should generate requests against the correct URIs' do
conn = stub_connection do |stub|
stub.post('/prediction/v1.2/training?data=12345') do |env|
[200, {}, '{}']
end
end
request = CLIENT.execute(
:api_method => @prediction.training.insert,
:parameters => {'data' => '12345'},
:connection => conn
)
conn.verify
end
it 'should generate requests against the correct URIs' do
conn = stub_connection do |stub|
stub.post('/prediction/v1.2/training?data=12345') do |env|
[200, {}, '{}']
end
end
request = CLIENT.execute(
:api_method => @prediction.training.insert,
:parameters => {'data' => '12345'},
:connection => conn
)
conn.verify
end
it 'should allow modification to the base URIs for testing purposes' do
# Using a new client instance here to avoid caching rebased discovery doc
prediction_rebase =
Google::APIClient.new(:application_name => 'API Client Tests').discovered_api('prediction', 'v1.2')
prediction_rebase.method_base =
'https://testing-domain.example.com/prediction/v1.2/'
conn = stub_connection do |stub|
stub.post('/prediction/v1.2/training') do |env|
expect(env[:url].host).to eq('testing-domain.example.com')
[200, {}, '{}']
end
end
request = CLIENT.execute(
:api_method => prediction_rebase.training.insert,
:parameters => {'data' => '123'},
:connection => conn
)
conn.verify
end
it 'should generate OAuth 1 requests' do
CLIENT.authorization = :oauth_1
CLIENT.authorization.token_credential_key = '12345'
CLIENT.authorization.token_credential_secret = '12345'
conn = stub_connection do |stub|
stub.post('/prediction/v1.2/training?data=12345') do |env|
expect(env[:request_headers]).to have_key('Authorization')
expect(env[:request_headers]['Authorization']).to match(/^OAuth/)
[200, {}, '{}']
end
end
request = CLIENT.execute(
:api_method => @prediction.training.insert,
:parameters => {'data' => '12345'},
:connection => conn
)
conn.verify
end
it 'should generate OAuth 2 requests' do
CLIENT.authorization = :oauth_2
CLIENT.authorization.access_token = '12345'
conn = stub_connection do |stub|
stub.post('/prediction/v1.2/training?data=12345') do |env|
expect(env[:request_headers]).to have_key('Authorization')
expect(env[:request_headers]['Authorization']).to match(/^Bearer/)
[200, {}, '{}']
end
end
request = CLIENT.execute(
:api_method => @prediction.training.insert,
:parameters => {'data' => '12345'},
:connection => conn
)
conn.verify
end
it 'should not be able to execute improperly authorized requests' do
CLIENT.authorization = :oauth_1
CLIENT.authorization.token_credential_key = '12345'
CLIENT.authorization.token_credential_secret = '12345'
result = CLIENT.execute(
@prediction.training.insert,
{'data' => '12345'}
)
expect(result.response.status).to eq(401)
end
it 'should not be able to execute improperly authorized requests' do
CLIENT.authorization = :oauth_2
CLIENT.authorization.access_token = '12345'
result = CLIENT.execute(
@prediction.training.insert,
{'data' => '12345'}
)
expect(result.response.status).to eq(401)
end
it 'should not be able to execute improperly authorized requests' do
expect(lambda do
CLIENT.authorization = :oauth_1
CLIENT.authorization.token_credential_key = '12345'
CLIENT.authorization.token_credential_secret = '12345'
result = CLIENT.execute!(
@prediction.training.insert,
{'data' => '12345'}
)
end).to raise_error(Google::APIClient::ClientError)
end
it 'should not be able to execute improperly authorized requests' do
expect(lambda do
CLIENT.authorization = :oauth_2
CLIENT.authorization.access_token = '12345'
result = CLIENT.execute!(
@prediction.training.insert,
{'data' => '12345'}
)
end).to raise_error(Google::APIClient::ClientError)
end
it 'should correctly handle unnamed parameters' do
conn = stub_connection do |stub|
stub.post('/prediction/v1.2/training') do |env|
expect(env[:request_headers]).to have_key('Content-Type')
expect(env[:request_headers]['Content-Type']).to eq('application/json')
[200, {}, '{}']
end
end
CLIENT.authorization = :oauth_2
CLIENT.authorization.access_token = '12345'
CLIENT.execute(
:api_method => @prediction.training.insert,
:body => MultiJson.dump({"id" => "bucket/object"}),
:headers => {'Content-Type' => 'application/json'},
:connection => conn
)
conn.verify
end
end
describe 'with the plus API' do
before do
CLIENT.authorization = nil
@plus = CLIENT.discovered_api('plus')
end
it 'should correctly determine the discovery URI' do
expect(CLIENT.discovery_uri('plus')).to be ===
'https://www.googleapis.com/discovery/v1/apis/plus/v1/rest'
end
it 'should find APIs that are in the discovery document' do
expect(CLIENT.discovered_api('plus').name).to eq('plus')
expect(CLIENT.discovered_api('plus').version).to eq('v1')
expect(CLIENT.discovered_api(:plus).name).to eq('plus')
expect(CLIENT.discovered_api(:plus).version).to eq('v1')
end
it 'should find methods that are in the discovery document' do
# TODO(bobaman) Fix this when the RPC names are correct
expect(CLIENT.discovered_method(
'plus.activities.list', 'plus'
).name).to eq('list')
end
it 'should define the origin API in discovered methods' do
expect(CLIENT.discovered_method(
'plus.activities.list', 'plus'
).api.name).to eq('plus')
end
it 'should not find methods that are not in the discovery document' do
expect(CLIENT.discovered_method('plus.bogus', 'plus')).to eq(nil)
end
it 'should generate requests against the correct URIs' do
conn = stub_connection do |stub|
stub.get('/plus/v1/people/107807692475771887386/activities/public') do |env|
[200, {}, '{}']
end
end
request = CLIENT.execute(
:api_method => @plus.activities.list,
:parameters => {
'userId' => '107807692475771887386', 'collection' => 'public'
},
:authenticated => false,
:connection => conn
)
conn.verify
end
it 'should correctly validate parameters' do
expect(lambda do
CLIENT.execute(
:api_method => @plus.activities.list,
:parameters => {'alt' => 'json'},
:authenticated => false
)
end).to raise_error(ArgumentError)
end
it 'should correctly validate parameters' do
expect(lambda do
CLIENT.execute(
:api_method => @plus.activities.list,
:parameters => {
'userId' => '107807692475771887386', 'collection' => 'bogus'
},
:authenticated => false
).to_env(CLIENT.connection)
end).to raise_error(ArgumentError)
end
it 'should correctly determine the service root_uri' do
expect(@plus.root_uri.to_s).to eq('https://www.googleapis.com/')
end
end
describe 'with the adsense API' do
before do
CLIENT.authorization = nil
@adsense = CLIENT.discovered_api('adsense', 'v1.3')
end
it 'should correctly determine the discovery URI' do
expect(CLIENT.discovery_uri('adsense', 'v1.3').to_s).to be ===
'https://www.googleapis.com/discovery/v1/apis/adsense/v1.3/rest'
end
it 'should find APIs that are in the discovery document' do
expect(CLIENT.discovered_api('adsense', 'v1.3').name).to eq('adsense')
expect(CLIENT.discovered_api('adsense', 'v1.3').version).to eq('v1.3')
end
it 'should return a batch path' do
expect(CLIENT.discovered_api('adsense', 'v1.3').batch_path).not_to be_nil
end
it 'should find methods that are in the discovery document' do
expect(CLIENT.discovered_method(
'adsense.reports.generate', 'adsense', 'v1.3'
).name).to eq('generate')
end
it 'should not find methods that are not in the discovery document' do
expect(CLIENT.discovered_method('adsense.bogus', 'adsense', 'v1.3')).to eq(nil)
end
it 'should generate requests against the correct URIs' do
conn = stub_connection do |stub|
stub.get('/adsense/v1.3/adclients') do |env|
[200, {}, '{}']
end
end
request = CLIENT.execute(
:api_method => @adsense.adclients.list,
:authenticated => false,
:connection => conn
)
conn.verify
end
it 'should not be able to execute requests without authorization' do
result = CLIENT.execute(
:api_method => @adsense.adclients.list,
:authenticated => false
)
expect(result.response.status).to eq(401)
end
it 'should fail when validating missing required parameters' do
expect(lambda do
CLIENT.execute(
:api_method => @adsense.reports.generate,
:authenticated => false
)
end).to raise_error(ArgumentError)
end
it 'should succeed when validating parameters in a correct call' do
conn = stub_connection do |stub|
stub.get('/adsense/v1.3/reports?dimension=DATE&endDate=2010-01-01&metric=PAGE_VIEWS&startDate=2000-01-01') do |env|
[200, {}, '{}']
end
end
expect(lambda do
CLIENT.execute(
:api_method => @adsense.reports.generate,
:parameters => {
'startDate' => '2000-01-01',
'endDate' => '2010-01-01',
'dimension' => 'DATE',
'metric' => 'PAGE_VIEWS'
},
:authenticated => false,
:connection => conn
)
end).not_to raise_error
conn.verify
end
it 'should fail when validating parameters with invalid values' do
expect(lambda do
CLIENT.execute(
:api_method => @adsense.reports.generate,
:parameters => {
'startDate' => '2000-01-01',
'endDate' => '2010-01-01',
'dimension' => 'BAD_CHARACTERS=-&*(£&',
'metric' => 'PAGE_VIEWS'
},
:authenticated => false
)
end).to raise_error(ArgumentError)
end
it 'should succeed when validating repeated parameters in a correct call' do
conn = stub_connection do |stub|
stub.get('/adsense/v1.3/reports?dimension=DATE&dimension=PRODUCT_CODE'+
'&endDate=2010-01-01&metric=CLICKS&metric=PAGE_VIEWS&'+
'startDate=2000-01-01') do |env|
[200, {}, '{}']
end
end
expect(lambda do
CLIENT.execute(
:api_method => @adsense.reports.generate,
:parameters => {
'startDate' => '2000-01-01',
'endDate' => '2010-01-01',
'dimension' => ['DATE', 'PRODUCT_CODE'],
'metric' => ['PAGE_VIEWS', 'CLICKS']
},
:authenticated => false,
:connection => conn
)
end).not_to raise_error
conn.verify
end
it 'should fail when validating incorrect repeated parameters' do
expect(lambda do
CLIENT.execute(
:api_method => @adsense.reports.generate,
:parameters => {
'startDate' => '2000-01-01',
'endDate' => '2010-01-01',
'dimension' => ['DATE', 'BAD_CHARACTERS=-&*(£&'],
'metric' => ['PAGE_VIEWS', 'CLICKS']
},
:authenticated => false
)
end).to raise_error(ArgumentError)
end
it 'should generate valid requests when multivalued parameters are passed' do
conn = stub_connection do |stub|
stub.get('/adsense/v1.3/reports?dimension=DATE&dimension=PRODUCT_CODE'+
'&endDate=2010-01-01&metric=CLICKS&metric=PAGE_VIEWS&'+
'startDate=2000-01-01') do |env|
expect(env.params['dimension']).to include('DATE', 'PRODUCT_CODE')
expect(env.params['metric']).to include('CLICKS', 'PAGE_VIEWS')
[200, {}, '{}']
end
end
request = CLIENT.execute(
:api_method => @adsense.reports.generate,
:parameters => {
'startDate' => '2000-01-01',
'endDate' => '2010-01-01',
'dimension' => ['DATE', 'PRODUCT_CODE'],
'metric' => ['PAGE_VIEWS', 'CLICKS']
},
:authenticated => false,
:connection => conn
)
conn.verify
end
end
describe 'with the Drive API' do
before do
CLIENT.authorization = nil
@drive = CLIENT.discovered_api('drive', 'v1')
end
it 'should include media upload info methods' do
expect(@drive.files.insert.media_upload).not_to eq(nil)
end
it 'should include accepted media types' do
expect(@drive.files.insert.media_upload.accepted_types).not_to be_empty
end
it 'should have an upload path' do
expect(@drive.files.insert.media_upload.uri_template).not_to eq(nil)
end
it 'should have a max file size' do
expect(@drive.files.insert.media_upload.max_size).not_to eq(nil)
end
end
describe 'with the Pub/Sub API' do
before do
CLIENT.authorization = nil
@pubsub = CLIENT.discovered_api('pubsub', 'v1beta2')
end
it 'should generate requests against the correct URIs' do
conn = stub_connection do |stub|
stub.get('/v1beta2/projects/12345/topics') do |env|
expect(env[:url].host).to eq('pubsub.googleapis.com')
[200, {}, '{}']
end
end
request = CLIENT.execute(
:api_method => @pubsub.projects.topics.list,
:parameters => {'project' => 'projects/12345'},
:connection => conn
)
conn.verify
end
it 'should correctly determine the service root_uri' do
expect(@pubsub.root_uri.to_s).to eq('https://pubsub.googleapis.com/')
end
it 'should discover correct method URIs' do
list = CLIENT.discovered_method(
"pubsub.projects.topics.list", "pubsub", "v1beta2"
)
expect(list.uri_template.pattern).to eq(
"https://pubsub.googleapis.com/v1beta2/{+project}/topics"
)
publish = CLIENT.discovered_method(
"pubsub.projects.topics.publish", "pubsub", "v1beta2"
)
expect(publish.uri_template.pattern).to eq(
"https://pubsub.googleapis.com/v1beta2/{+topic}:publish"
)
end
end
end

View File

@ -1,98 +0,0 @@
# Encoding: utf-8
# Copyright 2012 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.
require 'spec_helper'
require 'google/api_client'
RSpec.describe Google::APIClient::Gzip do
def create_connection(&block)
Faraday.new do |b|
b.response :charset
b.response :gzip
b.adapter :test do |stub|
stub.get '/', &block
end
end
end
it 'should ignore non-zipped content' do
conn = create_connection do |env|
[200, {}, 'Hello world']
end
result = conn.get('/')
expect(result.body).to eq("Hello world")
end
it 'should decompress gziped content' do
conn = create_connection do |env|
[200, { 'Content-Encoding' => 'gzip'}, Base64.decode64('H4sICLVGwlEAA3RtcADzSM3JyVcozy/KSeECANXgObcMAAAA')]
end
result = conn.get('/')
expect(result.body).to eq("Hello world\n")
end
it 'should inflate with the correct charset encoding' do
conn = create_connection do |env|
[200,
{ 'Content-Encoding' => 'deflate', 'Content-Type' => 'application/json;charset=BIG5'},
Base64.decode64('eJxb8nLp7t2VAA8fBCI=')]
end
result = conn.get('/')
expect(result.body.encoding).to eq(Encoding::BIG5)
expect(result.body).to eq('日本語'.encode("BIG5"))
end
describe 'with API Client' do
before do
@client = Google::APIClient.new(:application_name => 'test')
@client.authorization = nil
end
it 'should send gzip in user agent' do
conn = create_connection do |env|
agent = env[:request_headers]['User-Agent']
expect(agent).not_to be_nil
expect(agent).to include 'gzip'
[200, {}, 'Hello world']
end
@client.execute(:uri => 'http://www.example.com/', :connection => conn)
end
it 'should send gzip in accept-encoding' do
conn = create_connection do |env|
encoding = env[:request_headers]['Accept-Encoding']
expect(encoding).not_to be_nil
expect(encoding).to include 'gzip'
[200, {}, 'Hello world']
end
@client.execute(:uri => 'http://www.example.com/', :connection => conn)
end
it 'should not send gzip in accept-encoding if disabled for request' do
conn = create_connection do |env|
encoding = env[:request_headers]['Accept-Encoding']
expect(encoding).not_to include('gzip') unless encoding.nil?
[200, {}, 'Hello world']
end
response = @client.execute(:uri => 'http://www.example.com/', :gzip => false, :connection => conn)
puts response.status
end
end
end

View File

@ -1,178 +0,0 @@
# Copyright 2012 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.
require 'spec_helper'
require 'google/api_client'
fixtures_path = File.expand_path('../../../fixtures', __FILE__)
RSpec.describe Google::APIClient::UploadIO do
it 'should reject invalid file paths' do
expect(lambda do
media = Google::APIClient::UploadIO.new('doesnotexist', 'text/plain')
end).to raise_error
end
describe 'with a file' do
before do
@file = File.expand_path('files/sample.txt', fixtures_path)
@media = Google::APIClient::UploadIO.new(@file, 'text/plain')
end
it 'should report the correct file length' do
expect(@media.length).to eq(File.size(@file))
end
it 'should have a mime type' do
expect(@media.content_type).to eq('text/plain')
end
end
describe 'with StringIO' do
before do
@content = "hello world"
@media = Google::APIClient::UploadIO.new(StringIO.new(@content), 'text/plain', 'test.txt')
end
it 'should report the correct file length' do
expect(@media.length).to eq(@content.length)
end
it 'should have a mime type' do
expect(@media.content_type).to eq('text/plain')
end
end
end
RSpec.describe Google::APIClient::RangedIO do
before do
@source = StringIO.new("1234567890abcdef")
@io = Google::APIClient::RangedIO.new(@source, 1, 5)
end
it 'should return the correct range when read entirely' do
expect(@io.read).to eq("23456")
end
it 'should maintain position' do
expect(@io.read(1)).to eq('2')
expect(@io.read(2)).to eq('34')
expect(@io.read(2)).to eq('56')
end
it 'should allow rewinds' do
expect(@io.read(2)).to eq('23')
@io.rewind()
expect(@io.read(2)).to eq('23')
end
it 'should allow setting position' do
@io.pos = 3
expect(@io.read).to eq('56')
end
it 'should not allow position to be set beyond range' do
@io.pos = 10
expect(@io.read).to eq('')
end
it 'should return empty string when read amount is zero' do
expect(@io.read(0)).to eq('')
end
it 'should return empty string at EOF if amount is nil' do
@io.read
expect(@io.read).to eq('')
end
it 'should return nil at EOF if amount is positive int' do
@io.read
expect(@io.read(1)).to eq(nil)
end
end
RSpec.describe Google::APIClient::ResumableUpload do
CLIENT = Google::APIClient.new(:application_name => 'API Client Tests') unless defined?(CLIENT)
after do
# Reset client to not-quite-pristine state
CLIENT.key = nil
CLIENT.user_ip = nil
end
before do
@drive = CLIENT.discovered_api('drive', 'v1')
@file = File.expand_path('files/sample.txt', fixtures_path)
@media = Google::APIClient::UploadIO.new(@file, 'text/plain')
@uploader = Google::APIClient::ResumableUpload.new(
:media => @media,
:api_method => @drive.files.insert,
:uri => 'https://www.googleapis.com/upload/drive/v1/files/12345')
end
it 'should consider 20x status as complete' do
request = @uploader.to_http_request
@uploader.process_http_response(mock_result(200))
expect(@uploader.complete?).to eq(true)
end
it 'should consider 30x status as incomplete' do
request = @uploader.to_http_request
@uploader.process_http_response(mock_result(308))
expect(@uploader.complete?).to eq(false)
expect(@uploader.expired?).to eq(false)
end
it 'should consider 40x status as fatal' do
request = @uploader.to_http_request
@uploader.process_http_response(mock_result(404))
expect(@uploader.expired?).to eq(true)
end
it 'should detect changes to location' do
request = @uploader.to_http_request
@uploader.process_http_response(mock_result(308, 'location' => 'https://www.googleapis.com/upload/drive/v1/files/abcdef'))
expect(@uploader.uri.to_s).to eq('https://www.googleapis.com/upload/drive/v1/files/abcdef')
end
it 'should resume from the saved range reported by the server' do
@uploader.chunk_size = 200
@uploader.to_http_request # Send bytes 0-199, only 0-99 saved
@uploader.process_http_response(mock_result(308, 'range' => '0-99'))
method, url, headers, body = @uploader.to_http_request # Send bytes 100-299
expect(headers['Content-Range']).to eq("bytes 100-299/#{@media.length}")
expect(headers['Content-length']).to eq("200")
end
it 'should resync the offset after 5xx errors' do
@uploader.chunk_size = 200
@uploader.to_http_request
@uploader.process_http_response(mock_result(500)) # Invalidates range
method, url, headers, body = @uploader.to_http_request # Resync
expect(headers['Content-Range']).to eq("bytes */#{@media.length}")
expect(headers['Content-length']).to eq("0")
@uploader.process_http_response(mock_result(308, 'range' => '0-99'))
method, url, headers, body = @uploader.to_http_request # Send next chunk at correct range
expect(headers['Content-Range']).to eq("bytes 100-299/#{@media.length}")
expect(headers['Content-length']).to eq("200")
end
def mock_result(status, headers = {})
reference = Google::APIClient::Reference.new(:api_method => @drive.files.insert)
double('result', :status => status, :headers => headers, :reference => reference)
end
end

View File

@ -1,29 +0,0 @@
# Copyright 2012 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.
require 'spec_helper'
require 'google/api_client'
RSpec.describe Google::APIClient::Request do
CLIENT = Google::APIClient.new(:application_name => 'API Client Tests') unless defined?(CLIENT)
it 'should normalize parameter names to strings' do
request = Google::APIClient::Request.new(:uri => 'https://www.google.com', :parameters => {
:a => '1', 'b' => '2'
})
expect(request.parameters['a']).to eq('1')
expect(request.parameters['b']).to eq('2')
end
end

View File

@ -1,207 +0,0 @@
# Copyright 2012 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.
require 'spec_helper'
require 'google/api_client'
RSpec.describe Google::APIClient::Result do
CLIENT = Google::APIClient.new(:application_name => 'API Client Tests') unless defined?(CLIENT)
describe 'with the plus API' do
before do
CLIENT.authorization = nil
@plus = CLIENT.discovered_api('plus', 'v1')
@reference = Google::APIClient::Reference.new({
:api_method => @plus.activities.list,
:parameters => {
'userId' => 'me',
'collection' => 'public',
'maxResults' => 20
}
})
@request = @reference.to_http_request
# Response double
@response = double("response")
allow(@response).to receive(:status).and_return(200)
allow(@response).to receive(:headers).and_return({
'etag' => '12345',
'x-google-apiary-auth-scopes' =>
'https://www.googleapis.com/auth/plus.me',
'content-type' => 'application/json; charset=UTF-8',
'date' => 'Mon, 23 Apr 2012 00:00:00 GMT',
'cache-control' => 'private, max-age=0, must-revalidate, no-transform',
'server' => 'GSE',
'connection' => 'close'
})
end
describe 'with a next page token' do
before do
allow(@response).to receive(:body).and_return(
<<-END_OF_STRING
{
"kind": "plus#activityFeed",
"etag": "FOO",
"nextPageToken": "NEXT+PAGE+TOKEN",
"selfLink": "https://www.googleapis.com/plus/v1/people/foo/activities/public?",
"nextLink": "https://www.googleapis.com/plus/v1/people/foo/activities/public?maxResults=20&pageToken=NEXT%2BPAGE%2BTOKEN",
"title": "Plus Public Activity Feed for ",
"updated": "2012-04-23T00:00:00.000Z",
"id": "123456790",
"items": []
}
END_OF_STRING
)
@result = Google::APIClient::Result.new(@reference, @response)
end
it 'should indicate a successful response' do
expect(@result.error?).to be_falsey
end
it 'should return the correct next page token' do
expect(@result.next_page_token).to eq('NEXT+PAGE+TOKEN')
end
it 'should escape the next page token when calling next_page' do
reference = @result.next_page
expect(Hash[reference.parameters]).to include('pageToken')
expect(Hash[reference.parameters]['pageToken']).to eq('NEXT+PAGE+TOKEN')
url = reference.to_env(CLIENT.connection)[:url]
expect(url.to_s).to include('pageToken=NEXT%2BPAGE%2BTOKEN')
end
it 'should return content type correctly' do
expect(@result.media_type).to eq('application/json')
end
it 'should return the result data correctly' do
expect(@result.data?).to be_truthy
expect(@result.data.class.to_s).to eq(
'Google::APIClient::Schema::Plus::V1::ActivityFeed'
)
expect(@result.data.kind).to eq('plus#activityFeed')
expect(@result.data.etag).to eq('FOO')
expect(@result.data.nextPageToken).to eq('NEXT+PAGE+TOKEN')
expect(@result.data.selfLink).to eq(
'https://www.googleapis.com/plus/v1/people/foo/activities/public?'
)
expect(@result.data.nextLink).to eq(
'https://www.googleapis.com/plus/v1/people/foo/activities/public?' +
'maxResults=20&pageToken=NEXT%2BPAGE%2BTOKEN'
)
expect(@result.data.title).to eq('Plus Public Activity Feed for ')
expect(@result.data.id).to eq("123456790")
expect(@result.data.items).to be_empty
end
end
describe 'without a next page token' do
before do
allow(@response).to receive(:body).and_return(
<<-END_OF_STRING
{
"kind": "plus#activityFeed",
"etag": "FOO",
"selfLink": "https://www.googleapis.com/plus/v1/people/foo/activities/public?",
"title": "Plus Public Activity Feed for ",
"updated": "2012-04-23T00:00:00.000Z",
"id": "123456790",
"items": []
}
END_OF_STRING
)
@result = Google::APIClient::Result.new(@reference, @response)
end
it 'should not return a next page token' do
expect(@result.next_page_token).to eq(nil)
end
it 'should return content type correctly' do
expect(@result.media_type).to eq('application/json')
end
it 'should return the result data correctly' do
expect(@result.data?).to be_truthy
expect(@result.data.class.to_s).to eq(
'Google::APIClient::Schema::Plus::V1::ActivityFeed'
)
expect(@result.data.kind).to eq('plus#activityFeed')
expect(@result.data.etag).to eq('FOO')
expect(@result.data.selfLink).to eq(
'https://www.googleapis.com/plus/v1/people/foo/activities/public?'
)
expect(@result.data.title).to eq('Plus Public Activity Feed for ')
expect(@result.data.id).to eq("123456790")
expect(@result.data.items).to be_empty
end
end
describe 'with JSON error response' do
before do
allow(@response).to receive(:body).and_return(
<<-END_OF_STRING
{
"error": {
"errors": [
{
"domain": "global",
"reason": "parseError",
"message": "Parse Error"
}
],
"code": 400,
"message": "Parse Error"
}
}
END_OF_STRING
)
allow(@response).to receive(:status).and_return(400)
@result = Google::APIClient::Result.new(@reference, @response)
end
it 'should return error status correctly' do
expect(@result.error?).to be_truthy
end
it 'should return the correct error message' do
expect(@result.error_message).to eq('Parse Error')
end
end
describe 'with 204 No Content response' do
before do
allow(@response).to receive(:body).and_return('')
allow(@response).to receive(:status).and_return(204)
allow(@response).to receive(:headers).and_return({})
@result = Google::APIClient::Result.new(@reference, @response)
end
it 'should indicate no data is available' do
expect(@result.data?).to be_falsey
end
it 'should return nil for data' do
expect(@result.data).to eq(nil)
end
it 'should return nil for media_type' do
expect(@result.media_type).to eq(nil)
end
end
end
end

View File

@ -1,169 +0,0 @@
# Copyright 2012 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.
require 'spec_helper'
require 'google/api_client'
fixtures_path = File.expand_path('../../../fixtures', __FILE__)
RSpec.describe Google::APIClient::KeyUtils do
it 'should read PKCS12 files from the filesystem' do
if RUBY_PLATFORM == 'java' && RUBY_VERSION.start_with?('1.8')
pending "Reading from PKCS12 not supported on jruby 1.8.x"
end
path = File.expand_path('files/privatekey.p12', fixtures_path)
key = Google::APIClient::KeyUtils.load_from_pkcs12(path, 'notasecret')
expect(key).not_to eq(nil)
end
it 'should read PKCS12 files from loaded files' do
if RUBY_PLATFORM == 'java' && RUBY_VERSION.start_with?('1.8')
pending "Reading from PKCS12 not supported on jruby 1.8.x"
end
path = File.expand_path('files/privatekey.p12', fixtures_path)
content = File.read(path)
key = Google::APIClient::KeyUtils.load_from_pkcs12(content, 'notasecret')
expect(key).not_to eq(nil)
end
it 'should read PEM files from the filesystem' do
path = File.expand_path('files/secret.pem', fixtures_path)
key = Google::APIClient::KeyUtils.load_from_pem(path, 'notasecret')
expect(key).not_to eq(nil)
end
it 'should read PEM files from loaded files' do
path = File.expand_path('files/secret.pem', fixtures_path)
content = File.read(path)
key = Google::APIClient::KeyUtils.load_from_pem(content, 'notasecret')
expect(key).not_to eq(nil)
end
end
RSpec.describe Google::APIClient::JWTAsserter do
include ConnectionHelpers
before do
@key = OpenSSL::PKey::RSA.new 2048
end
it 'should generate valid JWTs' do
asserter = Google::APIClient::JWTAsserter.new('client1', 'scope1 scope2', @key)
jwt = asserter.to_authorization.to_jwt
expect(jwt).not_to eq(nil)
claim = JWT.decode(jwt, @key.public_key, true)
claim = claim[0] if claim[0]
expect(claim["iss"]).to eq('client1')
expect(claim["scope"]).to eq('scope1 scope2')
end
it 'should allow impersonation' do
conn = stub_connection do |stub|
stub.post('/o/oauth2/token') do |env|
params = Addressable::URI.form_unencode(env[:body])
JWT.decode(params.assoc("assertion").last, @key.public_key)
expect(params.assoc("grant_type")).to eq(['grant_type','urn:ietf:params:oauth:grant-type:jwt-bearer'])
[200, {'content-type' => 'application/json'}, '{
"access_token" : "1/abcdef1234567890",
"token_type" : "Bearer",
"expires_in" : 3600
}']
end
end
asserter = Google::APIClient::JWTAsserter.new('client1', 'scope1 scope2', @key)
auth = asserter.authorize('user1@email.com', { :connection => conn })
expect(auth).not_to eq(nil?)
expect(auth.person).to eq('user1@email.com')
conn.verify
end
it 'should send valid access token request' do
conn = stub_connection do |stub|
stub.post('/o/oauth2/token') do |env|
params = Addressable::URI.form_unencode(env[:body])
JWT.decode(params.assoc("assertion").last, @key.public_key)
expect(params.assoc("grant_type")).to eq(['grant_type','urn:ietf:params:oauth:grant-type:jwt-bearer'])
[200, {'content-type' => 'application/json'}, '{
"access_token" : "1/abcdef1234567890",
"token_type" : "Bearer",
"expires_in" : 3600
}']
end
end
asserter = Google::APIClient::JWTAsserter.new('client1', 'scope1 scope2', @key)
auth = asserter.authorize(nil, { :connection => conn })
expect(auth).not_to eq(nil?)
expect(auth.access_token).to eq("1/abcdef1234567890")
conn.verify
end
it 'should be refreshable' do
conn = stub_connection do |stub|
stub.post('/o/oauth2/token') do |env|
params = Addressable::URI.form_unencode(env[:body])
JWT.decode(params.assoc("assertion").last, @key.public_key)
expect(params.assoc("grant_type")).to eq(['grant_type','urn:ietf:params:oauth:grant-type:jwt-bearer'])
[200, {'content-type' => 'application/json'}, '{
"access_token" : "1/abcdef1234567890",
"token_type" : "Bearer",
"expires_in" : 3600
}']
end
stub.post('/o/oauth2/token') do |env|
params = Addressable::URI.form_unencode(env[:body])
JWT.decode(params.assoc("assertion").last, @key.public_key)
expect(params.assoc("grant_type")).to eq(['grant_type','urn:ietf:params:oauth:grant-type:jwt-bearer'])
[200, {'content-type' => 'application/json'}, '{
"access_token" : "1/0987654321fedcba",
"token_type" : "Bearer",
"expires_in" : 3600
}']
end
end
asserter = Google::APIClient::JWTAsserter.new('client1', 'scope1 scope2', @key)
auth = asserter.authorize(nil, { :connection => conn })
expect(auth).not_to eq(nil?)
expect(auth.access_token).to eq("1/abcdef1234567890")
auth.fetch_access_token!(:connection => conn)
expect(auth.access_token).to eq("1/0987654321fedcba")
conn.verify
end
end
RSpec.describe Google::APIClient::ComputeServiceAccount do
include ConnectionHelpers
it 'should query metadata server' do
conn = stub_connection do |stub|
stub.get('/computeMetadata/v1beta1/instance/service-accounts/default/token') do |env|
expect(env.url.host).to eq('metadata')
[200, {'content-type' => 'application/json'}, '{
"access_token" : "1/abcdef1234567890",
"token_type" : "Bearer",
"expires_in" : 3600
}']
end
end
service_account = Google::APIClient::ComputeServiceAccount.new
auth = service_account.fetch_access_token!({ :connection => conn })
expect(auth).not_to eq(nil?)
expect(auth["access_token"]).to eq("1/abcdef1234567890")
conn.verify
end
end

View File

@ -1,618 +0,0 @@
# encoding:utf-8
# Copyright 2013 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.
require 'spec_helper'
require 'google/api_client'
require 'google/api_client/service'
fixtures_path = File.expand_path('../../../fixtures', __FILE__)
RSpec.describe Google::APIClient::Service do
include ConnectionHelpers
APPLICATION_NAME = 'API Client Tests'
it 'should error out when called without an API name or version' do
expect(lambda do
Google::APIClient::Service.new
end).to raise_error(ArgumentError)
end
it 'should error out when called without an API version' do
expect(lambda do
Google::APIClient::Service.new('foo')
end).to raise_error(ArgumentError)
end
it 'should error out when the options hash is not a hash' do
expect(lambda do
Google::APIClient::Service.new('foo', 'v1', 42)
end).to raise_error(ArgumentError)
end
describe 'with the AdSense Management API' do
it 'should make a valid call for a method with no parameters' do
conn = stub_connection do |stub|
stub.get('/adsense/v1.3/adclients') do |env|
[200, {}, '{}']
end
end
adsense = Google::APIClient::Service.new(
'adsense',
'v1.3',
{
:application_name => APPLICATION_NAME,
:authenticated => false,
:connection => conn,
:cache_store => nil
}
)
req = adsense.adclients.list.execute()
conn.verify
end
it 'should make a valid call for a method with parameters' do
conn = stub_connection do |stub|
stub.get('/adsense/v1.3/adclients/1/adunits') do |env|
[200, {}, '{}']
end
end
adsense = Google::APIClient::Service.new(
'adsense',
'v1.3',
{
:application_name => APPLICATION_NAME,
:authenticated => false,
:connection => conn,
:cache_store => nil
}
)
req = adsense.adunits.list(:adClientId => '1').execute()
end
it 'should make a valid call for a deep method' do
conn = stub_connection do |stub|
stub.get('/adsense/v1.3/accounts/1/adclients') do |env|
[200, {}, '{}']
end
end
adsense = Google::APIClient::Service.new(
'adsense',
'v1.3',
{
:application_name => APPLICATION_NAME,
:authenticated => false,
:connection => conn,
:cache_store => nil
}
)
req = adsense.accounts.adclients.list(:accountId => '1').execute()
end
describe 'with no connection' do
before do
@adsense = Google::APIClient::Service.new('adsense', 'v1.3',
{:application_name => APPLICATION_NAME, :cache_store => nil})
end
it 'should return a resource when using a valid resource name' do
expect(@adsense.accounts).to be_a(Google::APIClient::Service::Resource)
end
it 'should throw an error when using an invalid resource name' do
expect(lambda do
@adsense.invalid_resource
end).to raise_error
end
it 'should return a request when using a valid method name' do
req = @adsense.adclients.list
expect(req).to be_a(Google::APIClient::Service::Request)
expect(req.method.id).to eq('adsense.adclients.list')
expect(req.parameters).to be_nil
end
it 'should throw an error when using an invalid method name' do
expect(lambda do
@adsense.adclients.invalid_method
end).to raise_error
end
it 'should return a valid request with parameters' do
req = @adsense.adunits.list(:adClientId => '1')
expect(req).to be_a(Google::APIClient::Service::Request)
expect(req.method.id).to eq('adsense.adunits.list')
expect(req.parameters).not_to be_nil
expect(req.parameters[:adClientId]).to eq('1')
end
end
end
describe 'with the Prediction API' do
it 'should make a valid call with an object body' do
conn = stub_connection do |stub|
stub.post('/prediction/v1.5/trainedmodels?project=1') do |env|
expect(env.body).to eq('{"id":"1"}')
[200, {}, '{}']
end
end
prediction = Google::APIClient::Service.new(
'prediction',
'v1.5',
{
:application_name => APPLICATION_NAME,
:authenticated => false,
:connection => conn,
:cache_store => nil
}
)
req = prediction.trainedmodels.insert(:project => '1').body({'id' => '1'}).execute()
conn.verify
end
it 'should make a valid call with a text body' do
conn = stub_connection do |stub|
stub.post('/prediction/v1.5/trainedmodels?project=1') do |env|
expect(env.body).to eq('{"id":"1"}')
[200, {}, '{}']
end
end
prediction = Google::APIClient::Service.new(
'prediction',
'v1.5',
{
:application_name => APPLICATION_NAME,
:authenticated => false,
:connection => conn,
:cache_store => nil
}
)
req = prediction.trainedmodels.insert(:project => '1').body('{"id":"1"}').execute()
conn.verify
end
describe 'with no connection' do
before do
@prediction = Google::APIClient::Service.new('prediction', 'v1.5',
{:application_name => APPLICATION_NAME, :cache_store => nil})
end
it 'should return a valid request with a body' do
req = @prediction.trainedmodels.insert(:project => '1').body({'id' => '1'})
expect(req).to be_a(Google::APIClient::Service::Request)
expect(req.method.id).to eq('prediction.trainedmodels.insert')
expect(req.body).to eq({'id' => '1'})
expect(req.parameters).not_to be_nil
expect(req.parameters[:project]).to eq('1')
end
it 'should return a valid request with a body when using resource name' do
req = @prediction.trainedmodels.insert(:project => '1').training({'id' => '1'})
expect(req).to be_a(Google::APIClient::Service::Request)
expect(req.method.id).to eq('prediction.trainedmodels.insert')
expect(req.training).to eq({'id' => '1'})
expect(req.parameters).not_to be_nil
expect(req.parameters[:project]).to eq('1')
end
end
end
describe 'with the Drive API' do
before do
@metadata = {
'title' => 'My movie',
'description' => 'The best home movie ever made'
}
@file = File.expand_path('files/sample.txt', fixtures_path)
@media = Google::APIClient::UploadIO.new(@file, 'text/plain')
end
it 'should make a valid call with an object body and media upload' do
conn = stub_connection do |stub|
stub.post('/upload/drive/v1/files?uploadType=multipart') do |env|
expect(env.body).to be_a Faraday::CompositeReadIO
[200, {}, '{}']
end
end
drive = Google::APIClient::Service.new(
'drive',
'v1',
{
:application_name => APPLICATION_NAME,
:authenticated => false,
:connection => conn,
:cache_store => nil
}
)
req = drive.files.insert(:uploadType => 'multipart').body(@metadata).media(@media).execute()
conn.verify
end
describe 'with no connection' do
before do
@drive = Google::APIClient::Service.new('drive', 'v1',
{:application_name => APPLICATION_NAME, :cache_store => nil})
end
it 'should return a valid request with a body and media upload' do
req = @drive.files.insert(:uploadType => 'multipart').body(@metadata).media(@media)
expect(req).to be_a(Google::APIClient::Service::Request)
expect(req.method.id).to eq('drive.files.insert')
expect(req.body).to eq(@metadata)
expect(req.media).to eq(@media)
expect(req.parameters).not_to be_nil
expect(req.parameters[:uploadType]).to eq('multipart')
end
it 'should return a valid request with a body and media upload when using resource name' do
req = @drive.files.insert(:uploadType => 'multipart').file(@metadata).media(@media)
expect(req).to be_a(Google::APIClient::Service::Request)
expect(req.method.id).to eq('drive.files.insert')
expect(req.file).to eq(@metadata)
expect(req.media).to eq(@media)
expect(req.parameters).not_to be_nil
expect(req.parameters[:uploadType]).to eq('multipart')
end
end
end
describe 'with the Discovery API' do
it 'should make a valid end-to-end request' do
discovery = Google::APIClient::Service.new('discovery', 'v1',
{:application_name => APPLICATION_NAME, :authenticated => false,
:cache_store => nil})
result = discovery.apis.get_rest(:api => 'discovery', :version => 'v1').execute
expect(result).not_to be_nil
expect(result.data.name).to eq('discovery')
expect(result.data.version).to eq('v1')
end
end
end
RSpec.describe Google::APIClient::Service::Result do
describe 'with the plus API' do
before do
@plus = Google::APIClient::Service.new('plus', 'v1',
{:application_name => APPLICATION_NAME, :cache_store => nil})
@reference = Google::APIClient::Reference.new({
:api_method => @plus.activities.list.method,
:parameters => {
'userId' => 'me',
'collection' => 'public',
'maxResults' => 20
}
})
@request = @plus.activities.list(:userId => 'me', :collection => 'public',
:maxResults => 20)
# Response double
@response = double("response")
allow(@response).to receive(:status).and_return(200)
allow(@response).to receive(:headers).and_return({
'etag' => '12345',
'x-google-apiary-auth-scopes' =>
'https://www.googleapis.com/auth/plus.me',
'content-type' => 'application/json; charset=UTF-8',
'date' => 'Mon, 23 Apr 2012 00:00:00 GMT',
'cache-control' => 'private, max-age=0, must-revalidate, no-transform',
'server' => 'GSE',
'connection' => 'close'
})
end
describe 'with a next page token' do
before do
@body = <<-END_OF_STRING
{
"kind": "plus#activityFeed",
"etag": "FOO",
"nextPageToken": "NEXT+PAGE+TOKEN",
"selfLink": "https://www.googleapis.com/plus/v1/people/foo/activities/public?",
"nextLink": "https://www.googleapis.com/plus/v1/people/foo/activities/public?maxResults=20&pageToken=NEXT%2BPAGE%2BTOKEN",
"title": "Plus Public Activity Feed for ",
"updated": "2012-04-23T00:00:00.000Z",
"id": "123456790",
"items": []
}
END_OF_STRING
allow(@response).to receive(:body).and_return(@body)
base_result = Google::APIClient::Result.new(@reference, @response)
@result = Google::APIClient::Service::Result.new(@request, base_result)
end
it 'should indicate a successful response' do
expect(@result.error?).to be_falsey
end
it 'should return the correct next page token' do
expect(@result.next_page_token).to eq('NEXT+PAGE+TOKEN')
end
it 'generate a correct request when calling next_page' do
next_page_request = @result.next_page
expect(next_page_request.parameters).to include('pageToken')
expect(next_page_request.parameters['pageToken']).to eq('NEXT+PAGE+TOKEN')
@request.parameters.each_pair do |param, value|
expect(next_page_request.parameters[param]).to eq(value)
end
end
it 'should return content type correctly' do
expect(@result.media_type).to eq('application/json')
end
it 'should return the body correctly' do
expect(@result.body).to eq(@body)
end
it 'should return the result data correctly' do
expect(@result.data?).to be_truthy
expect(@result.data.class.to_s).to eq(
'Google::APIClient::Schema::Plus::V1::ActivityFeed'
)
expect(@result.data.kind).to eq('plus#activityFeed')
expect(@result.data.etag).to eq('FOO')
expect(@result.data.nextPageToken).to eq('NEXT+PAGE+TOKEN')
expect(@result.data.selfLink).to eq(
'https://www.googleapis.com/plus/v1/people/foo/activities/public?'
)
expect(@result.data.nextLink).to eq(
'https://www.googleapis.com/plus/v1/people/foo/activities/public?' +
'maxResults=20&pageToken=NEXT%2BPAGE%2BTOKEN'
)
expect(@result.data.title).to eq('Plus Public Activity Feed for ')
expect(@result.data.id).to eq("123456790")
expect(@result.data.items).to be_empty
end
end
describe 'without a next page token' do
before do
@body = <<-END_OF_STRING
{
"kind": "plus#activityFeed",
"etag": "FOO",
"selfLink": "https://www.googleapis.com/plus/v1/people/foo/activities/public?",
"title": "Plus Public Activity Feed for ",
"updated": "2012-04-23T00:00:00.000Z",
"id": "123456790",
"items": []
}
END_OF_STRING
allow(@response).to receive(:body).and_return(@body)
base_result = Google::APIClient::Result.new(@reference, @response)
@result = Google::APIClient::Service::Result.new(@request, base_result)
end
it 'should not return a next page token' do
expect(@result.next_page_token).to eq(nil)
end
it 'should return content type correctly' do
expect(@result.media_type).to eq('application/json')
end
it 'should return the body correctly' do
expect(@result.body).to eq(@body)
end
it 'should return the result data correctly' do
expect(@result.data?).to be_truthy
expect(@result.data.class.to_s).to eq(
'Google::APIClient::Schema::Plus::V1::ActivityFeed'
)
expect(@result.data.kind).to eq('plus#activityFeed')
expect(@result.data.etag).to eq('FOO')
expect(@result.data.selfLink).to eq(
'https://www.googleapis.com/plus/v1/people/foo/activities/public?'
)
expect(@result.data.title).to eq('Plus Public Activity Feed for ')
expect(@result.data.id).to eq("123456790")
expect(@result.data.items).to be_empty
end
end
describe 'with JSON error response' do
before do
@body = <<-END_OF_STRING
{
"error": {
"errors": [
{
"domain": "global",
"reason": "parseError",
"message": "Parse Error"
}
],
"code": 400,
"message": "Parse Error"
}
}
END_OF_STRING
allow(@response).to receive(:body).and_return(@body)
allow(@response).to receive(:status).and_return(400)
base_result = Google::APIClient::Result.new(@reference, @response)
@result = Google::APIClient::Service::Result.new(@request, base_result)
end
it 'should return error status correctly' do
expect(@result.error?).to be_truthy
end
it 'should return the correct error message' do
expect(@result.error_message).to eq('Parse Error')
end
it 'should return the body correctly' do
expect(@result.body).to eq(@body)
end
end
describe 'with 204 No Content response' do
before do
allow(@response).to receive(:body).and_return('')
allow(@response).to receive(:status).and_return(204)
allow(@response).to receive(:headers).and_return({})
base_result = Google::APIClient::Result.new(@reference, @response)
@result = Google::APIClient::Service::Result.new(@request, base_result)
end
it 'should indicate no data is available' do
expect(@result.data?).to be_falsey
end
it 'should return nil for data' do
expect(@result.data).to eq(nil)
end
it 'should return nil for media_type' do
expect(@result.media_type).to eq(nil)
end
end
end
end
RSpec.describe Google::APIClient::Service::BatchRequest do
include ConnectionHelpers
context 'with a service connection' do
before do
@conn = stub_connection do |stub|
stub.post('/batch') do |env|
[500, {'Content-Type' => 'application/json'}, '{}']
end
end
@discovery = Google::APIClient::Service.new('discovery', 'v1',
{:application_name => APPLICATION_NAME, :authorization => nil,
:cache_store => nil, :connection => @conn})
@calls = [
@discovery.apis.get_rest(:api => 'plus', :version => 'v1'),
@discovery.apis.get_rest(:api => 'discovery', :version => 'v1')
]
end
it 'should use the service connection' do
batch = @discovery.batch(@calls) do
end
batch.execute
@conn.verify
end
end
describe 'with the discovery API' do
before do
@discovery = Google::APIClient::Service.new('discovery', 'v1',
{:application_name => APPLICATION_NAME, :authorization => nil,
:cache_store => nil})
end
describe 'with two valid requests' do
before do
@calls = [
@discovery.apis.get_rest(:api => 'plus', :version => 'v1'),
@discovery.apis.get_rest(:api => 'discovery', :version => 'v1')
]
end
it 'should execute both when using a global callback' do
block_called = 0
batch = @discovery.batch(@calls) do |result|
block_called += 1
expect(result.status).to eq(200)
end
batch.execute
expect(block_called).to eq(2)
end
it 'should execute both when using individual callbacks' do
call1_returned, call2_returned = false, false
batch = @discovery.batch
batch.add(@calls[0]) do |result|
call1_returned = true
expect(result.status).to eq(200)
expect(result.call_index).to eq(0)
end
batch.add(@calls[1]) do |result|
call2_returned = true
expect(result.status).to eq(200)
expect(result.call_index).to eq(1)
end
batch.execute
expect(call1_returned).to eq(true)
expect(call2_returned).to eq(true)
end
end
describe 'with a valid request and an invalid one' do
before do
@calls = [
@discovery.apis.get_rest(:api => 'plus', :version => 'v1'),
@discovery.apis.get_rest(:api => 'invalid', :version => 'invalid')
]
end
it 'should execute both when using a global callback' do
block_called = 0
batch = @discovery.batch(@calls) do |result|
block_called += 1
if result.call_index == 0
expect(result.status).to eq(200)
else
expect(result.status).to be >= 400
expect(result.status).to be < 500
end
end
batch.execute
expect(block_called).to eq(2)
end
it 'should execute both when using individual callbacks' do
call1_returned, call2_returned = false, false
batch = @discovery.batch
batch.add(@calls[0]) do |result|
call1_returned = true
expect(result.status).to eq(200)
expect(result.call_index).to eq(0)
end
batch.add(@calls[1]) do |result|
call2_returned = true
expect(result.status).to be >= 400
expect(result.status).to be < 500
expect(result.call_index).to eq(1)
end
batch.execute
expect(call1_returned).to eq(true)
expect(call2_returned).to eq(true)
end
end
end
end

View File

@ -1,133 +0,0 @@
# encoding:utf-8
# Copyright 2013 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.
require 'spec_helper'
require 'google/api_client/service/simple_file_store'
RSpec.describe Google::APIClient::Service::SimpleFileStore do
FILE_NAME = 'test.cache'
describe 'with no cache file' do
before(:each) do
File.delete(FILE_NAME) if File.exists?(FILE_NAME)
@cache = Google::APIClient::Service::SimpleFileStore.new(FILE_NAME)
end
it 'should return nil when asked if a key exists' do
expect(@cache.exist?('invalid')).to be_nil
expect(File.exists?(FILE_NAME)).to be_falsey
end
it 'should return nil when asked to read a key' do
expect(@cache.read('invalid')).to be_nil
expect(File.exists?(FILE_NAME)).to be_falsey
end
it 'should return nil when asked to fetch a key' do
expect(@cache.fetch('invalid')).to be_nil
expect(File.exists?(FILE_NAME)).to be_falsey
end
it 'should create a cache file when asked to fetch a key with a default' do
expect(@cache.fetch('new_key') do
'value'
end).to eq('value')
expect(File.exists?(FILE_NAME)).to be_truthy
end
it 'should create a cache file when asked to write a key' do
@cache.write('new_key', 'value')
expect(File.exists?(FILE_NAME)).to be_truthy
end
it 'should return nil when asked to delete a key' do
expect(@cache.delete('invalid')).to be_nil
expect(File.exists?(FILE_NAME)).to be_falsey
end
end
describe 'with an existing cache' do
before(:each) do
File.delete(FILE_NAME) if File.exists?(FILE_NAME)
@cache = Google::APIClient::Service::SimpleFileStore.new(FILE_NAME)
@cache.write('existing_key', 'existing_value')
end
it 'should return true when asked if an existing key exists' do
expect(@cache.exist?('existing_key')).to be_truthy
end
it 'should return false when asked if a nonexistent key exists' do
expect(@cache.exist?('invalid')).to be_falsey
end
it 'should return the value for an existing key when asked to read it' do
expect(@cache.read('existing_key')).to eq('existing_value')
end
it 'should return nil for a nonexistent key when asked to read it' do
expect(@cache.read('invalid')).to be_nil
end
it 'should return the value for an existing key when asked to read it' do
expect(@cache.read('existing_key')).to eq('existing_value')
end
it 'should return nil for a nonexistent key when asked to fetch it' do
expect(@cache.fetch('invalid')).to be_nil
end
it 'should return and save the default value for a nonexistent key when asked to fetch it with a default' do
expect(@cache.fetch('new_key') do
'value'
end).to eq('value')
expect(@cache.read('new_key')).to eq('value')
end
it 'should remove an existing value and return true when asked to delete it' do
expect(@cache.delete('existing_key')).to be_truthy
expect(@cache.read('existing_key')).to be_nil
end
it 'should return false when asked to delete a nonexistent key' do
expect(@cache.delete('invalid')).to be_falsey
end
it 'should convert keys to strings when storing them' do
@cache.write(:symbol_key, 'value')
expect(@cache.read('symbol_key')).to eq('value')
end
it 'should convert keys to strings when reading them' do
expect(@cache.read(:existing_key)).to eq('existing_value')
end
it 'should convert keys to strings when fetching them' do
expect(@cache.fetch(:existing_key)).to eq('existing_value')
end
it 'should convert keys to strings when deleting them' do
expect(@cache.delete(:existing_key)).to be_truthy
expect(@cache.read('existing_key')).to be_nil
end
end
after(:all) do
File.delete(FILE_NAME) if File.exists?(FILE_NAME)
end
end

View File

@ -1,352 +0,0 @@
# 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.
require 'spec_helper'
require 'faraday'
require 'signet/oauth_1/client'
require 'google/api_client'
shared_examples_for 'configurable user agent' do
include ConnectionHelpers
it 'should allow the user agent to be modified' do
client.user_agent = 'Custom User Agent/1.2.3'
expect(client.user_agent).to eq('Custom User Agent/1.2.3')
end
it 'should allow the user agent to be set to nil' do
client.user_agent = nil
expect(client.user_agent).to eq(nil)
end
it 'should not allow the user agent to be used with bogus values' do
expect(lambda do
client.user_agent = 42
client.execute(:uri=>'https://www.google.com/')
end).to raise_error(TypeError)
end
it 'should transmit a User-Agent header when sending requests' do
client.user_agent = 'Custom User Agent/1.2.3'
conn = stub_connection do |stub|
stub.get('/') do |env|
headers = env[:request_headers]
expect(headers).to have_key('User-Agent')
expect(headers['User-Agent']).to eq(client.user_agent)
[200, {}, ['']]
end
end
client.execute(:uri=>'https://www.google.com/', :connection => conn)
conn.verify
end
end
RSpec.describe Google::APIClient do
include ConnectionHelpers
let(:client) { Google::APIClient.new(:application_name => 'API Client Tests') }
it "should pass the faraday options provided on initialization to FaraDay configuration block" do
client = Google::APIClient.new(faraday_option: {timeout: 999})
expect(client.connection.options.timeout).to be == 999
end
it 'should make its version number available' do
expect(Google::APIClient::VERSION::STRING).to be_instance_of(String)
end
it 'should default to OAuth 2' do
expect(Signet::OAuth2::Client).to be === client.authorization
end
describe 'configure for no authentication' do
before do
client.authorization = nil
end
it_should_behave_like 'configurable user agent'
end
describe 'configured for OAuth 1' do
before do
client.authorization = :oauth_1
client.authorization.token_credential_key = 'abc'
client.authorization.token_credential_secret = '123'
end
it 'should use the default OAuth1 client configuration' do
expect(client.authorization.temporary_credential_uri.to_s).to eq(
'https://www.google.com/accounts/OAuthGetRequestToken'
)
expect(client.authorization.authorization_uri.to_s).to include(
'https://www.google.com/accounts/OAuthAuthorizeToken'
)
expect(client.authorization.token_credential_uri.to_s).to eq(
'https://www.google.com/accounts/OAuthGetAccessToken'
)
expect(client.authorization.client_credential_key).to eq('anonymous')
expect(client.authorization.client_credential_secret).to eq('anonymous')
end
it_should_behave_like 'configurable user agent'
end
describe 'configured for OAuth 2' do
before do
client.authorization = :oauth_2
client.authorization.access_token = '12345'
end
# TODO
it_should_behave_like 'configurable user agent'
end
describe 'when executing requests' do
before do
@prediction = client.discovered_api('prediction', 'v1.2')
client.authorization = :oauth_2
@connection = stub_connection do |stub|
stub.post('/prediction/v1.2/training?data=12345') do |env|
expect(env[:request_headers]['Authorization']).to eq('Bearer 12345')
[200, {}, '{}']
end
end
end
after do
@connection.verify
end
it 'should use default authorization' do
client.authorization.access_token = "12345"
client.execute(
:api_method => @prediction.training.insert,
:parameters => {'data' => '12345'},
:connection => @connection
)
end
it 'should use request scoped authorization when provided' do
client.authorization.access_token = "abcdef"
new_auth = Signet::OAuth2::Client.new(:access_token => '12345')
client.execute(
:api_method => @prediction.training.insert,
:parameters => {'data' => '12345'},
:authorization => new_auth,
:connection => @connection
)
end
it 'should accept options with batch/request style execute' do
client.authorization.access_token = "abcdef"
new_auth = Signet::OAuth2::Client.new(:access_token => '12345')
request = client.generate_request(
:api_method => @prediction.training.insert,
:parameters => {'data' => '12345'}
)
client.execute(
request,
:authorization => new_auth,
:connection => @connection
)
end
it 'should accept options in array style execute' do
client.authorization.access_token = "abcdef"
new_auth = Signet::OAuth2::Client.new(:access_token => '12345')
client.execute(
@prediction.training.insert, {'data' => '12345'}, '', {},
{ :authorization => new_auth, :connection => @connection }
)
end
end
describe 'when retries enabled' do
before do
client.retries = 2
end
after do
@connection.verify
end
it 'should follow redirects' do
client.authorization = nil
@connection = stub_connection do |stub|
stub.get('/foo') do |env|
[302, {'location' => 'https://www.google.com/bar'}, '{}']
end
stub.get('/bar') do |env|
[200, {}, '{}']
end
end
client.execute(
:uri => 'https://www.google.com/foo',
:connection => @connection
)
end
it 'should refresh tokens on 401 errors' do
client.authorization.access_token = '12345'
expect(client.authorization).to receive(:fetch_access_token!)
@connection = stub_connection do |stub|
stub.get('/foo') do |env|
[401, {}, '{}']
end
stub.get('/foo') do |env|
[200, {}, '{}']
end
end
client.execute(
:uri => 'https://www.google.com/foo',
:connection => @connection
)
end
it 'should not attempt multiple token refreshes' do
client.authorization.access_token = '12345'
expect(client.authorization).to receive(:fetch_access_token!).once
@connection = stub_connection do |stub|
stub.get('/foo') do |env|
[401, {}, '{}']
end
end
client.execute(
:uri => 'https://www.google.com/foo',
:connection => @connection
)
end
it 'should not retry on client errors' do
count = 0
@connection = stub_connection do |stub|
stub.get('/foo') do |env|
expect(count).to eq(0)
count += 1
[403, {}, '{}']
end
end
client.execute(
:uri => 'https://www.google.com/foo',
:connection => @connection,
:authenticated => false
)
end
it 'should retry on 500 errors' do
client.authorization = nil
@connection = stub_connection do |stub|
stub.get('/foo') do |env|
[500, {}, '{}']
end
stub.get('/foo') do |env|
[200, {}, '{}']
end
end
expect(client.execute(
:uri => 'https://www.google.com/foo',
:connection => @connection
).status).to eq(200)
end
it 'should fail after max retries' do
client.authorization = nil
count = 0
@connection = stub_connection do |stub|
stub.get('/foo') do |env|
count += 1
[500, {}, '{}']
end
end
expect(client.execute(
:uri => 'https://www.google.com/foo',
:connection => @connection
).status).to eq(500)
expect(count).to eq(3)
end
end
describe 'when retries disabled and expired_auth_retry on (default)' do
before do
client.retries = 0
end
after do
@connection.verify
end
it 'should refresh tokens on 401 errors' do
client.authorization.access_token = '12345'
expect(client.authorization).to receive(:fetch_access_token!)
@connection = stub_connection do |stub|
stub.get('/foo') do |env|
[401, {}, '{}']
end
stub.get('/foo') do |env|
[200, {}, '{}']
end
end
client.execute(
:uri => 'https://www.gogole.com/foo',
:connection => @connection
)
end
end
describe 'when retries disabled and expired_auth_retry off' do
before do
client.retries = 0
client.expired_auth_retry = false
end
it 'should not refresh tokens on 401 errors' do
client.authorization.access_token = '12345'
expect(client.authorization).not_to receive(:fetch_access_token!)
@connection = stub_connection do |stub|
stub.get('/foo') do |env|
[401, {}, '{}']
end
stub.get('/foo') do |env|
[200, {}, '{}']
end
end
resp = client.execute(
:uri => 'https://www.gogole.com/foo',
:connection => @connection
)
expect(resp.response.status).to be == 401
end
end
end

View File

@ -1,66 +0,0 @@
$LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
$LOAD_PATH.uniq!
require 'rspec'
require 'faraday'
begin
require 'simplecov'
require 'coveralls'
SimpleCov.formatter = Coveralls::SimpleCov::Formatter
SimpleCov.start
rescue LoadError
# SimpleCov missing, so just run specs with no coverage.
end
Faraday::Adapter.load_middleware(:test)
module Faraday
class Connection
def verify
if app.kind_of?(Faraday::Adapter::Test)
app.stubs.verify_stubbed_calls
else
raise TypeError, "Expected test adapter"
end
end
end
end
module ConnectionHelpers
def stub_connection(&block)
stubs = Faraday::Adapter::Test::Stubs.new do |stub|
block.call(stub)
end
connection = Faraday.new do |builder|
builder.options.params_encoder = Faraday::FlatParamsEncoder
builder.adapter(:test, stubs)
end
end
end
module JSONMatchers
class EqualsJson
def initialize(expected)
@expected = JSON.parse(expected)
end
def matches?(target)
@target = JSON.parse(target)
@target.eql?(@expected)
end
def failure_message
"expected #{@target.inspect} to be #{@expected}"
end
def negative_failure_message
"expected #{@target.inspect} not to be #{@expected}"
end
end
def be_json(expected)
EqualsJson.new(expected)
end
end
RSpec.configure do |config|
end

View File

@ -1,9 +0,0 @@
#!/usr/bin/env ruby
$LOAD_PATH.unshift(
File.expand_path(File.join(File.dirname(__FILE__), '../lib'))
)
$LOAD_PATH.uniq!
require 'yard/cli/wiki'
YARD::CLI::Wiki.run(*ARGV)

View File

@ -1,12 +0,0 @@
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__)))
$LOAD_PATH.uniq!
YARD::Templates::Engine.register_template_path File.dirname(__FILE__) + '/../templates'
require 'yard/templates/template'
require 'yard/templates/helpers/wiki_helper'
::YARD::Templates::Template.extra_includes |= [
YARD::Templates::Helpers::WikiHelper
]
require 'yard/serializers/wiki_serializer'

View File

@ -1,44 +0,0 @@
require 'yard'
require 'yard/serializers/wiki_serializer'
require 'yard/cli/yardoc'
module YARD
module CLI
class Wiki < Yardoc
# Creates a new instance of the commandline utility
def initialize
super
@options = SymbolHash.new(false)
@options.update(
:format => :html,
:template => :default,
:markup => :rdoc, # default is :rdoc but falls back on :none
:serializer => YARD::Serializers::WikiSerializer.new, # Sigh. :-(
:default_return => "Object",
:hide_void_return => false,
:no_highlight => false,
:files => [],
:verifier => Verifier.new
)
@visibilities = [:public]
@assets = {}
@excluded = []
@files = []
@hidden_tags = []
@use_cache = false
@use_yardopts_file = true
@use_document_file = true
@generate = true
@options_file = DEFAULT_YARDOPTS_FILE
@statistics = true
@list = false
@save_yardoc = true
@has_markup = false
if defined?(::Encoding) && ::Encoding.respond_to?(:default_external=)
::Encoding.default_external, ::Encoding.default_internal = 'utf-8', 'utf-8'
end
end
end
end
end

View File

@ -1,27 +0,0 @@
require 'rake'
require 'rake/tasklib'
require 'yard/rake/yardoc_task'
require 'yard/cli/wiki'
module YARD
module Rake
# The rake task to run {CLI::Yardoc} and generate documentation.
class WikidocTask < YardocTask
protected
# Defines the rake task
# @return [void]
def define
desc "Generate Wiki Documentation with YARD"
task(name) do
before.call if before.is_a?(Proc)
yardoc = YARD::CLI::Wiki.new
yardoc.parse_arguments *(options + files)
yardoc.options[:verifier] = verifier if verifier
yardoc.run
after.call if after.is_a?(Proc)
end
end
end
end
end

View File

@ -1,68 +0,0 @@
# encoding: utf-8
require 'yard/serializers/file_system_serializer'
module YARD
module Serializers
##
# Subclass required to get correct filename for the top level namespace.
# :-(
class WikiSerializer < FileSystemSerializer
# Post-process the data before serializing.
# Strip unnecessary whitespace.
# Convert stuff into more wiki-friendly stuff.
# FULL OF HACKS!
def serialize(object, data)
data = data.encode("UTF-8")
if object == "Sidebar.wiki"
data = data.gsub(/^#sidebar Sidebar\n/, "")
end
data = data.gsub(/\n\s*\n/, "\n")
# ASCII/UTF-8 erb error work-around.
data = data.gsub(/--/, "")
data = data.gsub(/——/, "----")
data = data.gsub(/----\n----/, "----")
# HACK! Google Code Wiki treats <code> blocks like <pre> blocks.
data = data.gsub(/\<code\>(.+)\<\/code\>/, "`\\1`")
super(object, data)
end
def serialized_path(object)
return object if object.is_a?(String)
if object.is_a?(CodeObjects::ExtraFileObject)
fspath = ['file.' + object.name + (extension.empty? ? '' : ".#{extension}")]
else
# This line is the only change of significance.
# Changed from 'top-level-namespace' to 'TopLevelNamespace' to
# conform to wiki word page naming convention.
objname = object != YARD::Registry.root ? object.name.to_s : "TopLevelNamespace"
objname += '_' + object.scope.to_s[0,1] if object.is_a?(CodeObjects::MethodObject)
fspath = [objname + (extension.empty? ? '' : ".#{extension}")]
if object.namespace && object.namespace.path != ""
fspath.unshift(*object.namespace.path.split(CodeObjects::NSEP))
end
end
# Don't change the filenames, it just makes it more complicated
# to figure out the original name.
#fspath.map! do |p|
# p.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
#end
# Remove special chars from filenames.
# Windows disallows \ / : * ? " < > | but we will just remove any
# non alphanumeric (plus period, underscore and dash).
fspath.map! do |p|
p.gsub(/[^\w\.-]/) do |x|
encoded = '_'
x.each_byte { |b| encoded << ("%X" % b) }
encoded
end
end
fspath.join("")
end
end
end
end

View File

@ -1,502 +0,0 @@
require 'cgi'
require 'rdiscount'
module YARD
module Templates::Helpers
# The helper module for HTML templates.
module WikiHelper
include MarkupHelper
# @return [String] escapes text
def h(text)
out = ""
text = text.split(/\n/)
text.each_with_index do |line, i|
out <<
case line
when /^\s*$/; "\n\n"
when /^\s+\S/, /^=/; line + "\n"
else; line + (text[i + 1] =~ /^\s+\S/ ? "\n" : " ")
end
end
out.strip
end
# @return [String] wraps text at +col+ columns.
def wrap(text, col = 72)
text.strip.gsub(/(.{1,#{col}})( +|$\n?)|(.{1,#{col}})/, "\\1\\3\n")
end
# Escapes a URL
#
# @param [String] text the URL
# @return [String] the escaped URL
def urlencode(text)
CGI.escape(text.to_s)
end
def indent(text, len = 2)
text.gsub(/^/, ' ' * len)
end
def unindent(text)
lines = text.split("\n", -1)
min_indent_size = text.size
for line in lines
indent_size = (line.gsub("\t", " ") =~ /[^\s]/) || text.size
min_indent_size = indent_size if indent_size < min_indent_size
end
text.gsub("\t", " ").gsub(Regexp.new("^" + " " * min_indent_size), '')
end
# @group Converting Markup to HTML
# Turns text into HTML using +markup+ style formatting.
#
# @param [String] text the text to format
# @param [Symbol] markup examples are +:markdown+, +:textile+, +:rdoc+.
# To add a custom markup type, see {MarkupHelper}
# @return [String] the HTML
def htmlify(text, markup = options[:markup])
markup_meth = "html_markup_#{markup}"
return text unless respond_to?(markup_meth)
return "" unless text
return text unless markup
load_markup_provider(markup)
html = send(markup_meth, text)
if html.respond_to?(:encode)
html = html.force_encoding(text.encoding) # for libs that mess with encoding
html = html.encode(:invalid => :replace, :replace => '?')
end
html = resolve_links(html)
html = html.gsub(/<pre>(?:\s*<code>)?(.+?)(?:<\/code>\s*)?<\/pre>/m) do
str = unindent($1).strip
str = html_syntax_highlight(CGI.unescapeHTML(str)) unless options[:no_highlight]
str
end unless markup == :text
html
end
# Converts Markdown to HTML
# @param [String] text input Markdown text
# @return [String] output HTML
# @since 0.6.0
def html_markup_markdown(text)
Markdown.new(text).to_html
end
# Converts Textile to HTML
# @param [String] text the input Textile text
# @return [String] output HTML
# @since 0.6.0
def html_markup_textile(text)
doc = markup_class(:textile).new(text)
doc.hard_breaks = false if doc.respond_to?(:hard_breaks=)
doc.to_html
end
# Converts plaintext to HTML
# @param [String] text the input text
# @return [String] the output HTML
# @since 0.6.0
def html_markup_text(text)
"<pre>" + text + "</pre>"
end
# Converts HTML to HTML
# @param [String] text input html
# @return [String] output HTML
# @since 0.6.0
def html_markup_html(text)
text
end
# @return [String] HTMLified text as a single line (paragraphs removed)
def htmlify_line(*args)
htmlify(*args)
end
# Fixes RDoc behaviour with ++ only supporting alphanumeric text.
#
# @todo Refactor into own SimpleMarkup subclass
def fix_typewriter(text)
text.gsub(/\+(?! )([^\n\+]{1,900})(?! )\+/) do
type_text, pre_text, no_match = $1, $`, $&
pre_match = pre_text.scan(%r(</?(?:pre|tt|code).*?>))
if pre_match.last.nil? || pre_match.last.include?('/')
'`' + h(type_text) + '`'
else
no_match
end
end
end
# Don't allow -- to turn into &#8212; element. The chances of this being
# some --option is far more likely than the typographical meaning.
#
# @todo Refactor into own SimpleMarkup subclass
def fix_dash_dash(text)
text.gsub(/&#8212;(?=\S)/, '--')
end
# @group Syntax Highlighting Source Code
# Syntax highlights +source+ in language +type+.
#
# @note To support a specific language +type+, implement the method
# +html_syntax_highlight_TYPE+ in this class.
#
# @param [String] source the source code to highlight
# @param [Symbol] type the language type (:ruby, :plain, etc). Use
# :plain for no syntax highlighting.
# @return [String] the highlighted source
def html_syntax_highlight(source, type = nil)
return "" unless source
return "{{{\n#{source}\n}}}"
end
# @return [String] unhighlighted source
def html_syntax_highlight_plain(source)
return "" unless source
return "{{{\n#{source}\n}}}"
end
# @group Linking Objects and URLs
# Resolves any text in the form of +{Name}+ to the object specified by
# Name. Also supports link titles in the form +{Name title}+.
#
# @example Linking to an instance method
# resolve_links("{MyClass#method}") # => "<a href='...'>MyClass#method</a>"
# @example Linking to a class with a title
# resolve_links("{A::B::C the C class}") # => "<a href='...'>the c class</a>"
# @param [String] text the text to resolve links in
# @return [String] HTML with linkified references
def resolve_links(text)
code_tags = 0
text.gsub(/<(\/)?(pre|code|tt)|\{(\S+?)(?:\s(.*?\S))?\}(?=[\W<]|.+<\/|$)/) do |str|
closed, tag, name, title, match = $1, $2, $3, $4, $&
if tag
code_tags += (closed ? -1 : 1)
next str
end
next str unless code_tags == 0
next(match) if name[0,1] == '|'
if object.is_a?(String)
object
else
link = linkify(name, title)
if link == name || link == title
match = /(.+)?(\{#{Regexp.quote name}(?:\s.*?)?\})(.+)?/.match(text)
file = (@file ? @file : object.file) || '(unknown)'
line = (@file ? 1 : (object.docstring.line_range ? object.docstring.line_range.first : 1)) + (match ? $`.count("\n") : 0)
log.warn "In file `#{file}':#{line}: Cannot resolve link to #{name} from text" + (match ? ":" : ".")
log.warn((match[1] ? '...' : '') + match[2].gsub("\n","") + (match[3] ? '...' : '')) if match
end
link
end
end
end
def unlink(value)
value.gsub(/\b(([A-Z][a-z]+){2,99})\b/, "!\\1")
end
# (see BaseHelper#link_file)
def link_file(filename, title = nil, anchor = nil)
link_url(url_for_file(filename, anchor), title)
end
# (see BaseHelper#link_include_object)
def link_include_object(obj)
htmlify(obj.docstring)
end
# (see BaseHelper#link_object)
def link_object(obj, otitle = nil, anchor = nil, relative = true)
return otitle if obj.nil?
obj = Registry.resolve(object, obj, true, true) if obj.is_a?(String)
if !otitle && obj.root?
title = "Top Level Namespace"
elsif otitle
# title = "`" + otitle.to_s + "`"
title = otitle.to_s
elsif object.is_a?(CodeObjects::Base)
# title = "`" + h(object.relative_path(obj)) + "`"
title = h(object.relative_path(obj))
else
# title = "`" + h(obj.to_s) + "`"
title = h(obj.to_s)
end
unless serializer
return unlink(title)
end
return unlink(title) if obj.is_a?(CodeObjects::Proxy)
link = url_for(obj, anchor, relative)
if link
link_url(link, title, :formatted => false)
else
unlink(title)
end
end
# (see BaseHelper#link_url)
def link_url(url, title = nil, params = {})
title ||= url
if url.to_s == ""
title
else
if params[:formatted]
"<a href=\"#{url}\">#{title}</a>"
else
"[#{url} #{title}]"
end
end
end
# @group URL Helpers
# @param [CodeObjects::Base] object the object to get an anchor for
# @return [String] the anchor for a specific object
def anchor_for(object)
# Method:_Google::APIClient#execute!
case object
when CodeObjects::MethodObject
if object.scope == :instance
"Method:_#{object.path}"
elsif object.scope == :class
"Method:_#{object.path}"
end
when CodeObjects::ClassVariableObject
"#{object.name.to_s.gsub('@@', '')}-#{object.type}"
when CodeObjects::Base
"#{object.name}-#{object.type}"
when CodeObjects::Proxy
object.path
else
object.to_s
end
end
# Returns the URL for an object.
#
# @param [String, CodeObjects::Base] obj the object (or object path) to link to
# @param [String] anchor the anchor to link to
# @param [Boolean] relative use a relative or absolute link
# @return [String] the URL location of the object
def url_for(obj, anchor = nil, relative = true)
link = nil
return link unless serializer
if obj.kind_of?(CodeObjects::Base) && obj.root?
return 'TopLevelNamespace'
end
if obj.is_a?(CodeObjects::Base) && !obj.is_a?(CodeObjects::NamespaceObject)
# If the obj is not a namespace obj make it the anchor.
anchor, obj = obj, obj.namespace
end
objpath = serializer.serialized_path(obj)
return link unless objpath
if relative
fromobj = object
if object.is_a?(CodeObjects::Base) &&
!object.is_a?(CodeObjects::NamespaceObject)
fromobj = fromobj.namespace
end
from = serializer.serialized_path(fromobj)
link = File.relative_path(from, objpath)
else
link = objpath
end
return (
link.gsub(/\.html$/, '').gsub(/\.wiki$/, '') +
(anchor ? '#' + urlencode(anchor_for(anchor)) : '')
)
end
# Returns the URL for a specific file
#
# @param [String] filename the filename to link to
# @param [String] anchor optional anchor
# @return [String] the URL pointing to the file
def url_for_file(filename, anchor = nil)
fromobj = object
if CodeObjects::Base === fromobj && !fromobj.is_a?(CodeObjects::NamespaceObject)
fromobj = fromobj.namespace
end
from = serializer.serialized_path(fromobj)
if filename == options[:readme]
filename = 'Documentation'
else
filename = File.basename(filename).gsub(/\.[^.]+$/, '').capitalize
end
link = File.relative_path(from, filename)
return (
link.gsub(/\.html$/, '').gsub(/\.wiki$/, '') +
(anchor ? '#' + urlencode(anchor) : '')
)
end
# @group Formatting Objects and Attributes
# Formats a list of objects and links them
# @return [String] a formatted list of objects
def format_object_name_list(objects)
objects.sort_by {|o| o.name.to_s.downcase }.map do |o|
"<span class='name'>" + linkify(o, o.name) + "</span>"
end.join(", ")
end
# Formats a list of types from a tag.
#
# @param [Array<String>, FalseClass] typelist
# the list of types to be formatted.
#
# @param [Boolean] brackets omits the surrounding
# brackets if +brackets+ is set to +false+.
#
# @return [String] the list of types formatted
# as [Type1, Type2, ...] with the types linked
# to their respective descriptions.
#
def format_types(typelist, brackets = true)
return unless typelist.is_a?(Array)
list = typelist.map do |type|
type = type.gsub(/([<>])/) { h($1) }
type = type.gsub(/([\w:]+)/) do
$1 == "lt" || $1 == "gt" ? "`#{$1}`" : linkify($1, $1)
end
end
list.empty? ? "" : (brackets ? "(#{list.join(", ")})" : list.join(", "))
end
# Get the return types for a method signature.
#
# @param [CodeObjects::MethodObject] meth the method object
# @param [Boolean] link whether to link the types
# @return [String] the signature types
# @since 0.5.3
def signature_types(meth, link = true)
meth = convert_method_to_overload(meth)
type = options[:default_return] || ""
if meth.tag(:return) && meth.tag(:return).types
types = meth.tags(:return).map {|t| t.types ? t.types : [] }.flatten.uniq
first = link ? h(types.first) : format_types([types.first], false)
if types.size == 2 && types.last == 'nil'
type = first + '<sup>?</sup>'
elsif types.size == 2 && types.last =~ /^(Array)?<#{Regexp.quote types.first}>$/
type = first + '<sup>+</sup>'
elsif types.size > 2
type = [first, '...'].join(', ')
elsif types == ['void'] && options[:hide_void_return]
type = ""
else
type = link ? h(types.join(", ")) : format_types(types, false)
end
elsif !type.empty?
type = link ? h(type) : format_types([type], false)
end
type = "(#{type.to_s.strip}) " unless type.empty?
type
end
# Formats the signature of method +meth+.
#
# @param [CodeObjects::MethodObject] meth the method object to list
# the signature of
# @param [Boolean] link whether to link the method signature to the details view
# @param [Boolean] show_extras whether to show extra meta-data (visibility, attribute info)
# @param [Boolean] full_attr_name whether to show the full attribute name
# ("name=" instead of "name")
# @return [String] the formatted method signature
def signature(meth, link = true, show_extras = true, full_attr_name = true)
meth = convert_method_to_overload(meth)
type = signature_types(meth, link)
name = full_attr_name ? meth.name : meth.name.to_s.gsub(/^(\w+)=$/, '\1')
blk = format_block(meth)
args = !full_attr_name && meth.writer? ? "" : format_args(meth)
extras = []
extras_text = ''
if show_extras
if rw = meth.attr_info
attname = [rw[:read] ? 'read' : nil, rw[:write] ? 'write' : nil].compact
attname = attname.size == 1 ? attname.join('') + 'only' : nil
extras << attname if attname
end
extras << meth.visibility if meth.visibility != :public
extras_text = ' <span class="extras">(' + extras.join(", ") + ')</span>' unless extras.empty?
end
title = "%s *`%s`* `%s` `%s`" % [type, h(name.to_s).strip, args, blk]
title.gsub!(/<tt>/, "")
title.gsub!(/<\/tt>/, "")
title.gsub!(/`\s*`/, "")
title.strip!
if link
if meth.is_a?(YARD::CodeObjects::MethodObject)
link_title =
"#{h meth.name(true)} (#{meth.scope} #{meth.type})"
else
link_title = "#{h name} (#{meth.type})"
end
# This has to be raw HTML, can't wiki-format a link title otherwise.
"<a href=\"#{url_for(meth)}\">#{title}</a>#{extras_text}"
else
title + extras_text
end
end
# @group Getting the Character Encoding
# Returns the current character set. The default value can be overridden
# by setting the +LANG+ environment variable or by overriding this
# method. In Ruby 1.9 you can also modify this value by setting
# +Encoding.default_external+.
#
# @return [String] the current character set
# @since 0.5.4
def charset
return 'utf-8' unless RUBY19 || lang = ENV['LANG']
if RUBY19
lang = Encoding.default_external.name.downcase
else
lang = lang.downcase.split('.').last
end
case lang
when "ascii-8bit", "us-ascii", "ascii-7bit"; 'iso-8859-1'
else; lang
end
end
# @endgroup
private
# Converts a set of hash options into HTML attributes for a tag
#
# @param [Hash{String => String}] opts the tag options
# @return [String] the tag attributes of an HTML tag
def tag_attrs(opts = {})
opts.sort_by {|k, v| k.to_s }.map {|k,v| "#{k}=#{v.to_s.inspect}" if v }.join(" ")
end
# Converts a {CodeObjects::MethodObject} into an overload object
# @since 0.5.3
def convert_method_to_overload(meth)
# use first overload tag if it has a return type and method itself does not
if !meth.tag(:return) && meth.tags(:overload).size == 1 && meth.tag(:overload).tag(:return)
return meth.tag(:overload)
end
meth
end
end
end
end

View File

@ -1,43 +0,0 @@
lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '../../../lib'))
$LOAD_PATH.unshift(lib_dir)
$LOAD_PATH.uniq!
require 'yard-google-code'
include T('default/module')
def init
super
sections.place(:subclasses).before(:children)
sections.place(:constructor_details, [T('method_details')]).before(:methodmissing)
# Weird bug w/ doubled sections
sections.uniq!
end
def constructor_details
ctors = object.meths(:inherited => true, :included => true)
return unless @ctor = ctors.find {|o| o.name == :initialize }
return if prune_method_listing([@ctor]).empty?
erb(:constructor_details)
end
def subclasses
return if object.path == "Object" # don't show subclasses for Object
unless globals.subclasses
globals.subclasses = {}
list = run_verifier Registry.all(:class)
list.each do |o|
(globals.subclasses[o.superclass.path] ||= []) << o if o.superclass
end
end
@subclasses = globals.subclasses[object.path]
return if @subclasses.nil? || @subclasses.empty?
@subclasses = @subclasses.sort_by {|o| o.path }.map do |child|
name = child.path
if object.namespace
name = object.relative_path(child)
end
[name, child]
end
erb(:subclasses)
end

View File

@ -1,54 +0,0 @@
lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '../../../lib'))
$LOAD_PATH.unshift(lib_dir)
$LOAD_PATH.uniq!
require 'yard-google-code'
def init
return if object.docstring.blank? && !object.has_tag?(:api)
sections :index, [:private, :deprecated, :abstract, :todo, :note, :returns_void, :text], T('tags')
end
def private
return unless object.has_tag?(:api) && object.tag(:api).text == 'private'
erb(:private)
end
def abstract
return unless object.has_tag?(:abstract)
erb(:abstract)
end
def deprecated
return unless object.has_tag?(:deprecated)
erb(:deprecated)
end
def todo
return unless object.has_tag?(:todo)
erb(:todo)
end
def note
return unless object.has_tag?(:note)
erb(:note)
end
def returns_void
return unless object.type == :method
return if object.name == :initialize && object.scope == :instance
return unless object.tags(:return).size == 1 && object.tag(:return).types == ['void']
erb(:returns_void)
end
def docstring_text
text = ""
unless object.tags(:overload).size == 1 && object.docstring.empty?
text = object.docstring
end
if text.strip.empty? && object.tags(:return).size == 1 && object.tag(:return).text
text = object.tag(:return).text.gsub(/\A([a-z])/) {|x| x.upcase }
end
text.strip
end

View File

@ -1,8 +0,0 @@
lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '../../../lib'))
$LOAD_PATH.unshift(lib_dir)
$LOAD_PATH.uniq!
require 'yard-google-code'
def init
sections :header, [T('method_details')]
end

View File

@ -1,8 +0,0 @@
lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '../../../lib'))
$LOAD_PATH.unshift(lib_dir)
$LOAD_PATH.uniq!
require 'yard-google-code'
def init
sections :header, [:method_signature, T('docstring')]
end

View File

@ -1,133 +0,0 @@
lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '../../../lib'))
$LOAD_PATH.unshift(lib_dir)
$LOAD_PATH.uniq!
require 'yard-google-code'
include Helpers::ModuleHelper
def init
sections :header, :box_info, :pre_docstring, T('docstring'), :children,
:constant_summary, [T('docstring')], :inherited_constants,
:inherited_methods,
:methodmissing, [T('method_details')],
:attribute_details, [T('method_details')],
:method_details_list, [T('method_details')]
end
def pre_docstring
return if object.docstring.blank?
erb(:pre_docstring)
end
def children
@inner = [[:modules, []], [:classes, []]]
object.children.each do |child|
@inner[0][1] << child if child.type == :module
@inner[1][1] << child if child.type == :class
end
@inner.map! {|v| [v[0], run_verifier(v[1].sort_by {|o| o.name.to_s })] }
return if (@inner[0][1].size + @inner[1][1].size) == 0
erb(:children)
end
def methodmissing
mms = object.meths(:inherited => true, :included => true)
return unless @mm = mms.find {|o| o.name == :method_missing && o.scope == :instance }
erb(:methodmissing)
end
def method_listing(include_specials = true)
return @smeths ||= method_listing.reject {|o| special_method?(o) } unless include_specials
return @meths if @meths
@meths = object.meths(:inherited => false, :included => false)
@meths = sort_listing(prune_method_listing(@meths))
@meths
end
def special_method?(meth)
return true if meth.name(true) == '#method_missing'
return true if meth.constructor?
false
end
def attr_listing
return @attrs if @attrs
@attrs = []
[:class, :instance].each do |scope|
object.attributes[scope].each do |name, rw|
@attrs << (rw[:read] || rw[:write])
end
end
@attrs = sort_listing(prune_method_listing(@attrs, false))
end
def constant_listing
return @constants if @constants
@constants = object.constants(:included => false, :inherited => false)
@constants += object.cvars
@constants = run_verifier(@constants)
@constants
end
def sort_listing(list)
list.sort_by {|o| [o.scope.to_s, o.name.to_s.downcase] }
end
def docstring_full(obj)
docstring = ""
if obj.tags(:overload).size == 1 && obj.docstring.empty?
docstring = obj.tag(:overload).docstring
else
docstring = obj.docstring
end
if docstring.summary.empty? && obj.tags(:return).size == 1 && obj.tag(:return).text
docstring = Docstring.new(obj.tag(:return).text.gsub(/\A([a-z])/) {|x| x.upcase }.strip)
end
docstring
end
def docstring_summary(obj)
docstring_full(obj).summary
end
def groups(list, type = "Method")
if groups_data = object.groups
others = list.select {|m| !m.group }
groups_data.each do |name|
items = list.select {|m| m.group == name }
yield(items, name) unless items.empty?
end
else
others = []
group_data = {}
list.each do |meth|
if meth.group
(group_data[meth.group] ||= []) << meth
else
others << meth
end
end
group_data.each {|group, items| yield(items, group) unless items.empty? }
end
scopes(others) {|items, scope| yield(items, "#{scope.to_s.capitalize} #{type} Summary") }
end
def scopes(list)
[:class, :instance].each do |scope|
items = list.select {|m| m.scope == scope }
yield(items, scope) unless items.empty?
end
end
def mixed_into(object)
unless globals.mixed_into
globals.mixed_into = {}
list = run_verifier Registry.all(:class, :module)
list.each {|o| o.mixins.each {|m| (globals.mixed_into[m.path] ||= []) << o } }
end
globals.mixed_into[object.path] || []
end

View File

@ -1,55 +0,0 @@
lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '../../../lib'))
$LOAD_PATH.unshift(lib_dir)
$LOAD_PATH.uniq!
require 'yard-google-code'
def init
tags = Tags::Library.visible_tags - [:abstract, :deprecated, :note, :todo]
create_tag_methods(tags - [:example, :option, :overload, :see])
sections :index, tags
sections.any(:overload).push(T('docstring'))
end
def return
if object.type == :method
return if object.name == :initialize && object.scope == :instance
return if object.tags(:return).size == 1 && object.tag(:return).types == ['void']
end
tag(:return)
end
private
def tag(name, opts = nil)
return unless object.has_tag?(name)
opts ||= options_for_tag(name)
@no_names = true if opts[:no_names]
@no_types = true if opts[:no_types]
@name = name
out = erb('tag')
@no_names, @no_types = nil, nil
out
end
def create_tag_methods(tags)
tags.each do |tag|
next if respond_to?(tag)
instance_eval(<<-eof, __FILE__, __LINE__ + 1)
def #{tag}; tag(#{tag.inspect}) end
eof
end
end
def options_for_tag(tag)
opts = {:no_types => true, :no_names => true}
case Tags::Library.factory_method_for(tag)
when :with_types
opts[:no_types] = false
when :with_types_and_name
opts[:no_types] = false
opts[:no_names] = false
when :with_name
opts[:no_names] = false
end
opts
end