Nuke it from orbit, it's the only way to be sure
This commit is contained in:
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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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'
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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'
|
||||
@@ -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
|
||||
Reference in new issue
Block a user