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

This commit is contained in:
Steven Bazyl committed 2015-04-16 17:21:38 -07:00
1 parent 82f69445ad
commit 9b7809174d
72 files changed
-11948

No files matched your search

@@ -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
@@ -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
-126
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
-126
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
-93
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
-41
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
-102
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
@@ -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
@@ -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
-326
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
-33
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
-179
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
-19
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'
-310
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
-77
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
-363
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
-156
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
-117
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
-42
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
-65
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
-28
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
-32
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
-259
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
-18
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
-27
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
-350
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
-259
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
-233
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
-110
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
-144
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
-40
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
-164
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
@@ -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
@@ -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
-21
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'
-26
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