diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d11caf68bc..b1be0e451fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,7 +40,7 @@ # 0.7.0 * Remove CLI * Support for automatic retires & backoff. Off by default, enable by setting `retries` on `APIClient` -* Experimental new interface (see `Google::APIClient::Service`) +* Experimental new interface (see `Legacy::Google::APIClient::Service`) * Fix warnings when using Faraday separately * Support Google Compute Engine service accounts * Enable gzip compression for responses diff --git a/README.md b/README.md index 510ae817e9b..9e4bf982826 100644 --- a/README.md +++ b/README.md @@ -32,12 +32,12 @@ $ gem install google-api-client ## Example Usage ```ruby -require 'google/api_client' -require 'google/api_client/client_secrets' -require 'google/api_client/auth/installed_app' +require 'legacy/google/api_client' +require 'legacy/google/api_client/client_secrets' +require 'legacy/google/api_client/auth/installed_app' # Initialize the client. -client = Google::APIClient.new( +client = Legacy::Google::APIClient.new( :application_name => 'Example Ruby application', :application_version => '1.0.0' ) @@ -48,11 +48,11 @@ client = Google::APIClient.new( plus = client.discovered_api('plus') # Load client secrets from your client_secrets.json. -client_secrets = Google::APIClient::ClientSecrets.load +client_secrets = Legacy::Google::APIClient::ClientSecrets.load # Run installed application flow. Check the samples for a more # complete example that saves the credentials between runs. -flow = Google::APIClient::InstalledAppFlow.new( +flow = Legacy::Google::APIClient::InstalledAppFlow.new( :client_id => client_secrets.client_id, :client_secret => client_secrets.client_secret, :scope => ['https://www.googleapis.com/auth/plus.me'] @@ -118,7 +118,7 @@ client.execute(...) This is simpler API to use than in previous versions, although that is still available: ```ruby -key = Google::APIClient::KeyUtils.load_from_pkcs12('client.p12', 'notasecret') +key = Legacy::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', @@ -143,16 +143,16 @@ The default value for retries is 0, but will be enabled by default in future rel ### Batching Requests -Some Google APIs support batching requests into a single HTTP request. Use `Google::APIClient::BatchRequest` +Some Google APIs support batching requests into a single HTTP request. Use `Legacy::Google::APIClient::BatchRequest` to bundle multiple requests together. Example: ```ruby -client = Google::APIClient.new +client = Legacy::Google::APIClient.new urlshortener = client.discovered_api('urlshortener') -batch = Google::APIClient::BatchRequest.new do |result| +batch = Legacy::Google::APIClient::BatchRequest.new do |result| puts result.data end @@ -173,13 +173,13 @@ end ### Media Upload -For APIs that support file uploads, use `Google::APIClient::UploadIO` to load the stream. Both multipart and resumable +For APIs that support file uploads, use `Legacy::Google::APIClient::UploadIO` to load the stream. Both multipart and resumable uploads can be used. For example, to upload a file to Google Drive using multipart ```ruby drive = client.discovered_api('drive', 'v2') -media = Google::APIClient::UploadIO.new('mymovie.m4v', 'video/mp4') +media = Legacy::Google::APIClient::UploadIO.new('mymovie.m4v', 'video/mp4') metadata = { 'title' => 'My movie', 'description' => 'The best home movie ever made' diff --git a/Rakefile b/Rakefile index dca3b090389..d95046a57b9 100644 --- a/Rakefile +++ b/Rakefile @@ -11,7 +11,7 @@ require File.join(File.dirname(__FILE__), 'lib/google/api_client', 'version') PKG_DISPLAY_NAME = 'Google API Client' PKG_NAME = PKG_DISPLAY_NAME.downcase.gsub(/\s/, '-') -PKG_VERSION = Google::APIClient::VERSION::STRING +PKG_VERSION = Legacy::Google::APIClient::VERSION::STRING PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}" PKG_HOMEPAGE = 'https://github.com/google/google-api-ruby-client' diff --git a/google-api-client.gemspec b/google-api-client.gemspec index e5e34980588..6b48a2b744d 100644 --- a/google-api-client.gemspec +++ b/google-api-client.gemspec @@ -1,9 +1,9 @@ # -*- encoding: utf-8 -*- -require File.join(File.dirname(__FILE__), 'lib/google/api_client', 'version') +require File.join(File.dirname(__FILE__), 'lib/legacy/google/api_client', 'version') Gem::Specification.new do |s| s.name = "google-api-client" - s.version = Google::APIClient::VERSION::STRING + s.version = Legacy::Google::APIClient::VERSION::STRING s.required_rubygems_version = ">= 1.3.5" s.require_paths = ["lib"] diff --git a/lib/google/api_client.rb b/lib/google/api_client.rb deleted file mode 100644 index ef3c2b1d34c..00000000000 --- a/lib/google/api_client.rb +++ /dev/null @@ -1,750 +0,0 @@ -# Copyright 2010 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -require 'faraday' -require 'multi_json' -require 'compat/multi_json' -require 'stringio' -require 'retriable' - -require 'google/api_client/version' -require 'google/api_client/logging' -require 'google/api_client/errors' -require 'google/api_client/environment' -require 'google/api_client/discovery' -require 'google/api_client/request' -require 'google/api_client/reference' -require 'google/api_client/result' -require 'google/api_client/media' -require 'google/api_client/service_account' -require 'google/api_client/batch' -require 'google/api_client/gzip' -require 'google/api_client/charset' -require 'google/api_client/client_secrets' -require 'google/api_client/railtie' if defined?(Rails) - -module Google - - ## - # This class manages APIs communication. - class APIClient - include Google::APIClient::Logging - - ## - # Creates a new Google API client. - # - # @param [Hash] options The configuration parameters for the client. - # @option options [Symbol, #generate_authenticated_request] :authorization - # (:oauth_1) - # The authorization mechanism used by the client. The following - # mechanisms are supported out-of-the-box: - # - # @option options [Boolean] :auto_refresh_token (true) - # The setting that controls whether or not the api client attempts to - # refresh authorization when a 401 is hit in #execute. If the token does - # not support it, this option is ignored. - # @option options [String] :application_name - # The name of the application using the client. - # @option options [String | Array | nil] :scope - # The scope(s) used when using google application default credentials - # @option options [String] :application_version - # The version number of the application using the client. - # @option options [String] :user_agent - # ("{app_name} google-api-ruby-client/{version} {os_name}/{os_version}") - # The user agent used by the client. Most developers will want to - # leave this value alone and use the `:application_name` option instead. - # @option options [String] :host ("www.googleapis.com") - # The API hostname used by the client. This rarely needs to be changed. - # @option options [String] :port (443) - # The port number used by the client. This rarely needs to be changed. - # @option options [String] :discovery_path ("/discovery/v1") - # The discovery base path. This rarely needs to be changed. - # @option options [String] :ca_file - # Optional set of root certificates to use when validating SSL connections. - # By default, a bundled set of trusted roots will be used. - # @options options[Hash] :force_encoding - # Experimental option. True if response body should be force encoded into the charset - # specified in the Content-Type header. Mostly intended for compressed content. - # @options options[Hash] :faraday_options - # Pass through of options to set on the Faraday connection - def initialize(options={}) - logger.debug { "#{self.class} - Initializing client with options #{options}" } - - # Normalize key to String to allow indifferent access. - options = options.inject({}) do |accu, (key, value)| - accu[key.to_sym] = value - accu - end - # Almost all API usage will have a host of 'www.googleapis.com'. - self.host = options[:host] || 'www.googleapis.com' - self.port = options[:port] || 443 - self.discovery_path = options[:discovery_path] || '/discovery/v1' - - # Most developers will want to leave this value alone and use the - # application_name option. - if options[:application_name] - app_name = options[:application_name] - app_version = options[:application_version] - application_string = "#{app_name}/#{app_version || '0.0.0'}" - else - logger.warn { "#{self.class} - Please provide :application_name and :application_version when initializing the client" } - end - - proxy = options[:proxy] || Object::ENV["http_proxy"] - - self.user_agent = options[:user_agent] || ( - "#{application_string} " + - "google-api-ruby-client/#{Google::APIClient::VERSION::STRING} #{ENV::OS_VERSION} (gzip)" - ).strip - # The writer method understands a few Symbols and will generate useful - # default authentication mechanisms. - self.authorization = - options.key?(:authorization) ? options[:authorization] : :oauth_2 - if !options['scope'].nil? and self.authorization.respond_to?(:scope=) - self.authorization.scope = options['scope'] - end - self.auto_refresh_token = options.fetch(:auto_refresh_token) { true } - self.key = options[:key] - self.user_ip = options[:user_ip] - self.retries = options.fetch(:retries) { 0 } - self.expired_auth_retry = options.fetch(:expired_auth_retry) { true } - @discovery_uris = {} - @discovery_documents = {} - @discovered_apis = {} - ca_file = options[:ca_file] || File.expand_path('../../cacerts.pem', __FILE__) - self.connection = Faraday.new do |faraday| - faraday.response :charset if options[:force_encoding] - faraday.response :gzip - faraday.options.params_encoder = Faraday::FlatParamsEncoder - faraday.ssl.ca_file = ca_file - faraday.ssl.verify = true - faraday.proxy proxy - faraday.adapter Faraday.default_adapter - if options[:faraday_option].is_a?(Hash) - options[:faraday_option].each_pair do |option, value| - faraday.options.send("#{option}=", value) - end - end - end - return self - end - - ## - # Returns the authorization mechanism used by the client. - # - # @return [#generate_authenticated_request] The authorization mechanism. - attr_reader :authorization - - ## - # Sets the authorization mechanism used by the client. - # - # @param [#generate_authenticated_request] new_authorization - # The new authorization mechanism. - def authorization=(new_authorization) - case new_authorization - when :oauth_1, :oauth - require 'signet/oauth_1/client' - # NOTE: Do not rely on this default value, as it may change - new_authorization = Signet::OAuth1::Client.new( - :temporary_credential_uri => - 'https://www.google.com/accounts/OAuthGetRequestToken', - :authorization_uri => - 'https://www.google.com/accounts/OAuthAuthorizeToken', - :token_credential_uri => - 'https://www.google.com/accounts/OAuthGetAccessToken', - :client_credential_key => 'anonymous', - :client_credential_secret => 'anonymous' - ) - when :two_legged_oauth_1, :two_legged_oauth - require 'signet/oauth_1/client' - # NOTE: Do not rely on this default value, as it may change - new_authorization = Signet::OAuth1::Client.new( - :client_credential_key => nil, - :client_credential_secret => nil, - :two_legged => true - ) - when :google_app_default - require 'googleauth' - new_authorization = Google::Auth.get_application_default - - when :oauth_2 - require 'signet/oauth_2/client' - # NOTE: Do not rely on this default value, as it may change - new_authorization = Signet::OAuth2::Client.new( - :authorization_uri => - 'https://accounts.google.com/o/oauth2/auth', - :token_credential_uri => - 'https://accounts.google.com/o/oauth2/token' - ) - when nil - # No authorization mechanism - else - if !new_authorization.respond_to?(:generate_authenticated_request) - raise TypeError, - 'Expected authorization mechanism to respond to ' + - '#generate_authenticated_request.' - end - end - @authorization = new_authorization - return @authorization - end - - ## - # Default Faraday/HTTP connection. - # - # @return [Faraday::Connection] - attr_accessor :connection - - ## - # The setting that controls whether or not the api client attempts to - # refresh authorization when a 401 is hit in #execute. - # - # @return [Boolean] - attr_accessor :auto_refresh_token - - ## - # The application's API key issued by the API console. - # - # @return [String] The API key. - attr_accessor :key - - ## - # The IP address of the user this request is being performed on behalf of. - # - # @return [String] The user's IP address. - attr_accessor :user_ip - - ## - # The user agent used by the client. - # - # @return [String] - # The user agent string used in the User-Agent header. - attr_accessor :user_agent - - ## - # The API hostname used by the client. - # - # @return [String] - # The API hostname. Should almost always be 'www.googleapis.com'. - attr_accessor :host - - ## - # The port number used by the client. - # - # @return [String] - # The port number. Should almost always be 443. - attr_accessor :port - - ## - # The base path used by the client for discovery. - # - # @return [String] - # The base path. Should almost always be '/discovery/v1'. - attr_accessor :discovery_path - - ## - # Number of times to retry on recoverable errors - # - # @return [FixNum] - # Number of retries - attr_accessor :retries - - ## - # Whether or not an expired auth token should be re-acquired - # (and the operation retried) regardless of retries setting - # @return [Boolean] - # Auto retry on auth expiry - attr_accessor :expired_auth_retry - - ## - # Returns the URI for the directory document. - # - # @return [Addressable::URI] The URI of the directory document. - def directory_uri - return resolve_uri(self.discovery_path + '/apis') - end - - ## - # Manually registers a URI as a discovery document for a specific version - # of an API. - # - # @param [String, Symbol] api The API name. - # @param [String] version The desired version of the API. - # @param [Addressable::URI] uri The URI of the discovery document. - # @return [Google::APIClient::API] The service object. - def register_discovery_uri(api, version, uri) - api = api.to_s - version = version || 'v1' - @discovery_uris["#{api}:#{version}"] = uri - discovered_api(api, version) - end - - ## - # Returns the URI for the discovery document. - # - # @param [String, Symbol] api The API name. - # @param [String] version The desired version of the API. - # @return [Addressable::URI] The URI of the discovery document. - def discovery_uri(api, version=nil) - api = api.to_s - version = version || 'v1' - return @discovery_uris["#{api}:#{version}"] ||= ( - resolve_uri( - self.discovery_path + '/apis/{api}/{version}/rest', - 'api' => api, - 'version' => version - ) - ) - end - - ## - # Manually registers a pre-loaded discovery document for a specific version - # of an API. - # - # @param [String, Symbol] api The API name. - # @param [String] version The desired version of the API. - # @param [String, StringIO] discovery_document - # The contents of the discovery document. - # @return [Google::APIClient::API] The service object. - def register_discovery_document(api, version, discovery_document) - api = api.to_s - version = version || 'v1' - if discovery_document.kind_of?(StringIO) - discovery_document.rewind - discovery_document = discovery_document.string - elsif discovery_document.respond_to?(:to_str) - discovery_document = discovery_document.to_str - else - raise TypeError, - "Expected String or StringIO, got #{discovery_document.class}." - end - @discovery_documents["#{api}:#{version}"] = - MultiJson.load(discovery_document) - discovered_api(api, version) - end - - ## - # Returns the parsed directory document. - # - # @return [Hash] The parsed JSON from the directory document. - def directory_document - return @directory_document ||= (begin - response = self.execute!( - :http_method => :get, - :uri => self.directory_uri, - :authenticated => false - ) - response.data - end) - end - - ## - # Returns the parsed discovery document. - # - # @param [String, Symbol] api The API name. - # @param [String] version The desired version of the API. - # @return [Hash] The parsed JSON from the discovery document. - def discovery_document(api, version=nil) - api = api.to_s - version = version || 'v1' - return @discovery_documents["#{api}:#{version}"] ||= (begin - response = self.execute!( - :http_method => :get, - :uri => self.discovery_uri(api, version), - :authenticated => false - ) - response.data - end) - end - - ## - # Returns all APIs published in the directory document. - # - # @return [Array] The list of available APIs. - def discovered_apis - @directory_apis ||= (begin - document_base = self.directory_uri - if self.directory_document && self.directory_document['items'] - self.directory_document['items'].map do |discovery_document| - Google::APIClient::API.new( - document_base, - discovery_document - ) - end - else - [] - end - end) - end - - ## - # Returns the service object for a given service name and service version. - # - # @param [String, Symbol] api The API name. - # @param [String] version The desired version of the API. - # - # @return [Google::APIClient::API] The service object. - def discovered_api(api, version=nil) - if !api.kind_of?(String) && !api.kind_of?(Symbol) - raise TypeError, - "Expected String or Symbol, got #{api.class}." - end - api = api.to_s - version = version || 'v1' - return @discovered_apis["#{api}:#{version}"] ||= begin - document_base = self.discovery_uri(api, version) - discovery_document = self.discovery_document(api, version) - if document_base && discovery_document - Google::APIClient::API.new( - document_base, - discovery_document - ) - else - nil - end - end - end - - ## - # Returns the method object for a given RPC name and service version. - # - # @param [String, Symbol] rpc_name The RPC name of the desired method. - # @param [String, Symbol] api The API the method is within. - # @param [String] version The desired version of the API. - # - # @return [Google::APIClient::Method] The method object. - def discovered_method(rpc_name, api, version=nil) - if !rpc_name.kind_of?(String) && !rpc_name.kind_of?(Symbol) - raise TypeError, - "Expected String or Symbol, got #{rpc_name.class}." - end - rpc_name = rpc_name.to_s - api = api.to_s - version = version || 'v1' - service = self.discovered_api(api, version) - if service.to_h[rpc_name] - return service.to_h[rpc_name] - else - return nil - end - end - - ## - # Returns the service object with the highest version number. - # - # @note Warning: This method should be used with great care. - # As APIs are updated, minor differences between versions may cause - # incompatibilities. Requesting a specific version will avoid this issue. - # - # @param [String, Symbol] api The name of the service. - # - # @return [Google::APIClient::API] The service object. - def preferred_version(api) - if !api.kind_of?(String) && !api.kind_of?(Symbol) - raise TypeError, - "Expected String or Symbol, got #{api.class}." - end - api = api.to_s - return self.discovered_apis.detect do |a| - a.name == api && a.preferred == true - end - end - - ## - # Verifies an ID token against a server certificate. Used to ensure that - # an ID token supplied by an untrusted client-side mechanism is valid. - # Raises an error if the token is invalid or missing. - # - # @deprecated Use the google-id-token gem for verifying JWTs - def verify_id_token! - require 'jwt' - require 'openssl' - @certificates ||= {} - if !self.authorization.respond_to?(:id_token) - raise ArgumentError, ( - "Current authorization mechanism does not support ID tokens: " + - "#{self.authorization.class.to_s}" - ) - elsif !self.authorization.id_token - raise ArgumentError, ( - "Could not verify ID token, ID token missing. " + - "Scopes were: #{self.authorization.scope.inspect}" - ) - else - check_cached_certs = lambda do - valid = false - for _key, cert in @certificates - begin - self.authorization.decoded_id_token(cert.public_key) - valid = true - rescue JWT::DecodeError, Signet::UnsafeOperationError - # Expected exception. Ignore, ID token has not been validated. - end - end - valid - end - if check_cached_certs.call() - return true - end - response = self.execute!( - :http_method => :get, - :uri => 'https://www.googleapis.com/oauth2/v1/certs', - :authenticated => false - ) - @certificates.merge!( - Hash[MultiJson.load(response.body).map do |key, cert| - [key, OpenSSL::X509::Certificate.new(cert)] - end] - ) - if check_cached_certs.call() - return true - else - raise InvalidIDTokenError, - "Could not verify ID token against any available certificate." - end - end - return nil - end - - ## - # Generates a request. - # - # @option options [Google::APIClient::Method] :api_method - # The method object or the RPC name of the method being executed. - # @option options [Hash, Array] :parameters - # The parameters to send to the method. - # @option options [Hash, Array] :headers The HTTP headers for the request. - # @option options [String] :body The body of the request. - # @option options [String] :version ("v1") - # The service version. Only used if `api_method` is a `String`. - # @option options [#generate_authenticated_request] :authorization - # The authorization mechanism for the response. Used only if - # `:authenticated` is `true`. - # @option options [TrueClass, FalseClass] :authenticated (true) - # `true` if the request must be signed or somehow - # authenticated, `false` otherwise. - # - # @return [Google::APIClient::Reference] The generated request. - # - # @example - # request = client.generate_request( - # :api_method => 'plus.activities.list', - # :parameters => - # {'collection' => 'public', 'userId' => 'me'} - # ) - def generate_request(options={}) - options = { - :api_client => self - }.merge(options) - return Google::APIClient::Request.new(options) - end - - ## - # Executes a request, wrapping it in a Result object. - # - # @param [Google::APIClient::Request, Hash, Array] params - # Either a Google::APIClient::Request, a Hash, or an Array. - # - # If a Google::APIClient::Request, no other parameters are expected. - # - # If a Hash, the below parameters are handled. If an Array, the - # parameters are assumed to be in the below order: - # - # - (Google::APIClient::Method) api_method: - # The method object or the RPC name of the method being executed. - # - (Hash, Array) parameters: - # The parameters to send to the method. - # - (String) body: The body of the request. - # - (Hash, Array) headers: The HTTP headers for the request. - # - (Hash) options: A set of options for the request, of which: - # - (#generate_authenticated_request) :authorization (default: true) - - # The authorization mechanism for the response. Used only if - # `:authenticated` is `true`. - # - (TrueClass, FalseClass) :authenticated (default: true) - - # `true` if the request must be signed or somehow - # authenticated, `false` otherwise. - # - (TrueClass, FalseClass) :gzip (default: true) - - # `true` if gzip enabled, `false` otherwise. - # - (FixNum) :retries - - # # of times to retry on recoverable errors - # - # @return [Google::APIClient::Result] The result from the API, nil if batch. - # - # @example - # result = client.execute(batch_request) - # - # @example - # plus = client.discovered_api('plus') - # result = client.execute( - # :api_method => plus.activities.list, - # :parameters => {'collection' => 'public', 'userId' => 'me'} - # ) - # - # @see Google::APIClient#generate_request - def execute!(*params) - if params.first.kind_of?(Google::APIClient::Request) - request = params.shift - options = params.shift || {} - else - # This block of code allows us to accept multiple parameter passing - # styles, and maintaining some backwards compatibility. - # - # Note: I'm extremely tempted to deprecate this style of execute call. - if params.last.respond_to?(:to_hash) && params.size == 1 - options = params.pop - else - options = {} - end - - options[:api_method] = params.shift if params.size > 0 - options[:parameters] = params.shift if params.size > 0 - options[:body] = params.shift if params.size > 0 - options[:headers] = params.shift if params.size > 0 - options.update(params.shift) if params.size > 0 - request = self.generate_request(options) - end - - request.headers['User-Agent'] ||= '' + self.user_agent unless self.user_agent.nil? - request.headers['Accept-Encoding'] ||= 'gzip' unless options[:gzip] == false - request.headers['Content-Type'] ||= '' - request.parameters['key'] ||= self.key unless self.key.nil? - request.parameters['userIp'] ||= self.user_ip unless self.user_ip.nil? - - connection = options[:connection] || self.connection - request.authorization = options[:authorization] || self.authorization unless options[:authenticated] == false - - tries = 1 + (options[:retries] || self.retries) - attempt = 0 - - Retriable.retriable :tries => tries, - :on => [TransmissionError], - :on_retry => client_error_handler, - :interval => lambda {|attempts| (2 ** attempts) + rand} do - attempt += 1 - - # This 2nd level retriable only catches auth errors, and supports 1 retry, which allows - # auth to be re-attempted without having to retry all sorts of other failures like - # NotFound, etc - Retriable.retriable :tries => ((expired_auth_retry || tries > 1) && attempt == 1) ? 2 : 1, - :on => [AuthorizationError], - :on_retry => authorization_error_handler(request.authorization) do - result = request.send(connection, true) - - case result.status - when 200...300 - result - when 301, 302, 303, 307 - request = generate_request(request.to_hash.merge({ - :uri => result.headers['location'], - :api_method => nil - })) - raise RedirectError.new(result.headers['location'], result) - when 401 - raise AuthorizationError.new(result.error_message || 'Invalid/Expired Authentication', result) - when 400, 402...500 - raise ClientError.new(result.error_message || "A client error has occurred", result) - when 500...600 - raise ServerError.new(result.error_message || "A server error has occurred", result) - else - raise TransmissionError.new(result.error_message || "A transmission error has occurred", result) - end - end - end - end - - ## - # Same as Google::APIClient#execute!, but does not raise an exception for - # normal API errros. - # - # @see Google::APIClient#execute - def execute(*params) - begin - return self.execute!(*params) - rescue TransmissionError => e - return e.result - end - end - - protected - - ## - # Resolves a URI template against the client's configured base. - # - # @api private - # @param [String, Addressable::URI, Addressable::Template] template - # The template to resolve. - # @param [Hash] mapping The mapping that corresponds to the template. - # @return [Addressable::URI] The expanded URI. - def resolve_uri(template, mapping={}) - @base_uri ||= Addressable::URI.new( - :scheme => 'https', - :host => self.host, - :port => self.port - ).normalize - template = if template.kind_of?(Addressable::Template) - template.pattern - elsif template.respond_to?(:to_str) - template.to_str - else - raise TypeError, - "Expected String, Addressable::URI, or Addressable::Template, " + - "got #{template.class}." - end - return Addressable::Template.new(@base_uri + template).expand(mapping) - end - - - ## - # Returns on proc for special processing of retries for authorization errors - # Only 401s should be retried and only if the credentials are refreshable - # - # @param [#fetch_access_token!] authorization - # OAuth 2 credentials - # @return [Proc] - def authorization_error_handler(authorization) - can_refresh = authorization.respond_to?(:refresh_token) && auto_refresh_token - Proc.new do |exception, tries| - next unless exception.kind_of?(AuthorizationError) - if can_refresh - begin - logger.debug("Attempting refresh of access token & retry of request") - authorization.fetch_access_token! - next - rescue Signet::AuthorizationError - end - end - raise exception - end - end - - ## - # Returns on proc for special processing of retries as not all client errors - # are recoverable. Only 401s should be retried (via authorization_error_handler) - # - # @return [Proc] - def client_error_handler - Proc.new do |exception, tries| - raise exception if exception.kind_of?(ClientError) - end - end - - end - -end diff --git a/lib/google/api_client/auth/file_storage.rb b/lib/google/api_client/auth/file_storage.rb deleted file mode 100644 index b3d01716600..00000000000 --- a/lib/google/api_client/auth/file_storage.rb +++ /dev/null @@ -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 diff --git a/lib/google/api_client/auth/installed_app.rb b/lib/google/api_client/auth/installed_app.rb deleted file mode 100644 index bdbb655d530..00000000000 --- a/lib/google/api_client/auth/installed_app.rb +++ /dev/null @@ -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 - - - - - You may close this window. - - 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 - diff --git a/lib/google/api_client/auth/jwt_asserter.rb b/lib/google/api_client/auth/jwt_asserter.rb deleted file mode 100644 index 35ad6ec8ea9..00000000000 --- a/lib/google/api_client/auth/jwt_asserter.rb +++ /dev/null @@ -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 diff --git a/lib/google/api_client/auth/key_utils.rb b/lib/google/api_client/auth/key_utils.rb deleted file mode 100644 index 6b6e0cfe5f8..00000000000 --- a/lib/google/api_client/auth/key_utils.rb +++ /dev/null @@ -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 diff --git a/lib/google/api_client/auth/pkcs12.rb b/lib/google/api_client/auth/pkcs12.rb deleted file mode 100644 index 94c43185dbd..00000000000 --- a/lib/google/api_client/auth/pkcs12.rb +++ /dev/null @@ -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 diff --git a/lib/google/api_client/auth/storage.rb b/lib/google/api_client/auth/storage.rb deleted file mode 100644 index c762316e754..00000000000 --- a/lib/google/api_client/auth/storage.rb +++ /dev/null @@ -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 diff --git a/lib/google/api_client/auth/storages/file_store.rb b/lib/google/api_client/auth/storages/file_store.rb deleted file mode 100644 index cd3eae710d9..00000000000 --- a/lib/google/api_client/auth/storages/file_store.rb +++ /dev/null @@ -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 diff --git a/lib/google/api_client/auth/storages/redis_store.rb b/lib/google/api_client/auth/storages/redis_store.rb deleted file mode 100644 index 3f76f7ca860..00000000000 --- a/lib/google/api_client/auth/storages/redis_store.rb +++ /dev/null @@ -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 diff --git a/lib/google/api_client/batch.rb b/lib/google/api_client/batch.rb deleted file mode 100644 index 45a2e31044e..00000000000 --- a/lib/google/api_client/batch.rb +++ /dev/null @@ -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, 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 \ No newline at end of file diff --git a/lib/google/api_client/charset.rb b/lib/google/api_client/charset.rb deleted file mode 100644 index 47b11ba841f..00000000000 --- a/lib/google/api_client/charset.rb +++ /dev/null @@ -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 \ No newline at end of file diff --git a/lib/google/api_client/client_secrets.rb b/lib/google/api_client/client_secrets.rb deleted file mode 100644 index a9cc2413896..00000000000 --- a/lib/google/api_client/client_secrets.rb +++ /dev/null @@ -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 diff --git a/lib/google/api_client/discovery/api.rb b/lib/google/api_client/discovery/api.rb deleted file mode 100644 index 3bbc90da3f3..00000000000 --- a/lib/google/api_client/discovery/api.rb +++ /dev/null @@ -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 String representation of the service's state. - # - # @return [String] The service's state, as a String. - 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 diff --git a/lib/google/api_client/discovery/media.rb b/lib/google/api_client/discovery/media.rb deleted file mode 100644 index ffa7e87c3db..00000000000 --- a/lib/google/api_client/discovery/media.rb +++ /dev/null @@ -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 diff --git a/lib/google/api_client/discovery/method.rb b/lib/google/api_client/discovery/method.rb deleted file mode 100644 index 3a06857c0e3..00000000000 --- a/lib/google/api_client/discovery/method.rb +++ /dev/null @@ -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 Hash 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 Array 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 Array 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 Array 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] nil 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 String representation of the method's state. - # - # @return [String] The method's state, as a String. - def inspect - sprintf( - "#<%s:%#0x ID:%s>", - self.class.to_s, self.object_id, self.id - ) - end - end - end -end diff --git a/lib/google/api_client/discovery/resource.rb b/lib/google/api_client/discovery/resource.rb deleted file mode 100644 index 9b757c684da..00000000000 --- a/lib/google/api_client/discovery/resource.rb +++ /dev/null @@ -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 <String representation of the resource's state. - # - # @return [String] The resource's state, as a String. - def inspect - sprintf( - "#<%s:%#0x NAME:%s>", self.class.to_s, self.object_id, self.name - ) - end - end - end -end diff --git a/lib/google/api_client/discovery/schema.rb b/lib/google/api_client/discovery/schema.rb deleted file mode 100644 index 57666e698db..00000000000 --- a/lib/google/api_client/discovery/schema.rb +++ /dev/null @@ -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 diff --git a/lib/google/api_client/environment.rb b/lib/google/api_client/environment.rb deleted file mode 100644 index 00cc864a1c7..00000000000 --- a/lib/google/api_client/environment.rb +++ /dev/null @@ -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(']', '') - 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.strip - end - end -end diff --git a/lib/google/api_client/errors.rb b/lib/google/api_client/errors.rb deleted file mode 100644 index 9644c692a21..00000000000 --- a/lib/google/api_client/errors.rb +++ /dev/null @@ -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 diff --git a/lib/google/api_client/gzip.rb b/lib/google/api_client/gzip.rb deleted file mode 100644 index 42fabbbdba4..00000000000 --- a/lib/google/api_client/gzip.rb +++ /dev/null @@ -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 \ No newline at end of file diff --git a/lib/google/api_client/logging.rb b/lib/google/api_client/logging.rb deleted file mode 100644 index 09a075b5c9a..00000000000 --- a/lib/google/api_client/logging.rb +++ /dev/null @@ -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 \ No newline at end of file diff --git a/lib/google/api_client/media.rb b/lib/google/api_client/media.rb deleted file mode 100644 index 5066bcebdfa..00000000000 --- a/lib/google/api_client/media.rb +++ /dev/null @@ -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 \ No newline at end of file diff --git a/lib/google/api_client/railtie.rb b/lib/google/api_client/railtie.rb deleted file mode 100644 index 86d9a6b204b..00000000000 --- a/lib/google/api_client/railtie.rb +++ /dev/null @@ -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 diff --git a/lib/google/api_client/request.rb b/lib/google/api_client/request.rb deleted file mode 100644 index d043e001628..00000000000 --- a/lib/google/api_client/request.rb +++ /dev/null @@ -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 diff --git a/lib/google/api_client/result.rb b/lib/google/api_client/result.rb deleted file mode 100644 index c48bec04a5d..00000000000 --- a/lib/google/api_client/result.rb +++ /dev/null @@ -1,255 +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 - # - # @!attribute [r] error? - # @return [TrueClass, FalseClass] - # true if result of operation is an error - def error? - return self.response.status >= 400 - end - - ## - # Check if request was successful - # - # @!attribute [r] success? - # @return [TrueClass, FalseClass] - # true if result of operation was successful - def success? - return !self.error? - 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 diff --git a/lib/google/api_client/service.rb b/lib/google/api_client/service.rb deleted file mode 100755 index 28f2605d92a..00000000000 --- a/lib/google/api_client/service.rb +++ /dev/null @@ -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: - # - # @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 diff --git a/lib/google/api_client/service/batch.rb b/lib/google/api_client/service/batch.rb deleted file mode 100644 index 7ba406e6121..00000000000 --- a/lib/google/api_client/service/batch.rb +++ /dev/null @@ -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 diff --git a/lib/google/api_client/service/request.rb b/lib/google/api_client/service/request.rb deleted file mode 100755 index dcbc7e32130..00000000000 --- a/lib/google/api_client/service/request.rb +++ /dev/null @@ -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 diff --git a/lib/google/api_client/service/resource.rb b/lib/google/api_client/service/resource.rb deleted file mode 100755 index b493769d4f4..00000000000 --- a/lib/google/api_client/service/resource.rb +++ /dev/null @@ -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 diff --git a/lib/google/api_client/service/result.rb b/lib/google/api_client/service/result.rb deleted file mode 100755 index 7957ea6a266..00000000000 --- a/lib/google/api_client/service/result.rb +++ /dev/null @@ -1,162 +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 - 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 - 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 diff --git a/lib/google/api_client/service/simple_file_store.rb b/lib/google/api_client/service/simple_file_store.rb deleted file mode 100644 index 216b3fac5ff..00000000000 --- a/lib/google/api_client/service/simple_file_store.rb +++ /dev/null @@ -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 \ No newline at end of file diff --git a/lib/google/api_client/service/stub_generator.rb b/lib/google/api_client/service/stub_generator.rb deleted file mode 100755 index 3c84dddbd2e..00000000000 --- a/lib/google/api_client/service/stub_generator.rb +++ /dev/null @@ -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 diff --git a/lib/legacy/google/api_client.rb b/lib/legacy/google/api_client.rb new file mode 100644 index 00000000000..3162597024c --- /dev/null +++ b/lib/legacy/google/api_client.rb @@ -0,0 +1,753 @@ +# Copyright 2010 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +require 'faraday' +require 'multi_json' +require 'compat/multi_json' +require 'stringio' +require 'retriable' + +require 'legacy/google/api_client/version' +require 'legacy/google/api_client/logging' +require 'legacy/google/api_client/errors' +require 'legacy/google/api_client/environment' +require 'legacy/google/api_client/discovery' +require 'legacy/google/api_client/request' +require 'legacy/google/api_client/reference' +require 'legacy/google/api_client/result' +require 'legacy/google/api_client/media' +require 'legacy/google/api_client/service_account' +require 'legacy/google/api_client/batch' +require 'legacy/google/api_client/gzip' +require 'legacy/google/api_client/charset' +require 'legacy/google/api_client/client_secrets' +require 'legacy/google/api_client/railtie' if defined?(Rails) + + +module Legacy + module Google + + ## + # This class manages APIs communication. + class APIClient + include Legacy::Google::APIClient::Logging + + ## + # Creates a new Google API client. + # + # @param [Hash] options The configuration parameters for the client. + # @option options [Symbol, #generate_authenticated_request] :authorization + # (:oauth_1) + # The authorization mechanism used by the client. The following + # mechanisms are supported out-of-the-box: + # + # @option options [Boolean] :auto_refresh_token (true) + # The setting that controls whether or not the api client attempts to + # refresh authorization when a 401 is hit in #execute. If the token does + # not support it, this option is ignored. + # @option options [String] :application_name + # The name of the application using the client. + # @option options [String | Array | nil] :scope + # The scope(s) used when using google application default credentials + # @option options [String] :application_version + # The version number of the application using the client. + # @option options [String] :user_agent + # ("{app_name} google-api-ruby-client/{version} {os_name}/{os_version}") + # The user agent used by the client. Most developers will want to + # leave this value alone and use the `:application_name` option instead. + # @option options [String] :host ("www.googleapis.com") + # The API hostname used by the client. This rarely needs to be changed. + # @option options [String] :port (443) + # The port number used by the client. This rarely needs to be changed. + # @option options [String] :discovery_path ("/discovery/v1") + # The discovery base path. This rarely needs to be changed. + # @option options [String] :ca_file + # Optional set of root certificates to use when validating SSL connections. + # By default, a bundled set of trusted roots will be used. + # @options options[Hash] :force_encoding + # Experimental option. True if response body should be force encoded into the charset + # specified in the Content-Type header. Mostly intended for compressed content. + # @options options[Hash] :faraday_options + # Pass through of options to set on the Faraday connection + def initialize(options={}) + logger.debug { "#{self.class} - Initializing client with options #{options}" } + + # Normalize key to String to allow indifferent access. + options = options.inject({}) do |accu, (key, value)| + accu[key.to_sym] = value + accu + end + # Almost all API usage will have a host of 'www.googleapis.com'. + self.host = options[:host] || 'www.googleapis.com' + self.port = options[:port] || 443 + self.discovery_path = options[:discovery_path] || '/discovery/v1' + + # Most developers will want to leave this value alone and use the + # application_name option. + if options[:application_name] + app_name = options[:application_name] + app_version = options[:application_version] + application_string = "#{app_name}/#{app_version || '0.0.0'}" + else + logger.warn { "#{self.class} - Please provide :application_name and :application_version when initializing the client" } + end + + proxy = options[:proxy] || Object::ENV["http_proxy"] + + self.user_agent = options[:user_agent] || ( + "#{application_string} " + + "google-api-ruby-client/#{Legacy::Google::APIClient::VERSION::STRING} #{ENV::OS_VERSION} (gzip)" + ).strip + # The writer method understands a few Symbols and will generate useful + # default authentication mechanisms. + self.authorization = + options.key?(:authorization) ? options[:authorization] : :oauth_2 + if !options['scope'].nil? and self.authorization.respond_to?(:scope=) + self.authorization.scope = options['scope'] + end + self.auto_refresh_token = options.fetch(:auto_refresh_token) { true } + self.key = options[:key] + self.user_ip = options[:user_ip] + self.retries = options.fetch(:retries) { 0 } + self.expired_auth_retry = options.fetch(:expired_auth_retry) { true } + @discovery_uris = {} + @discovery_documents = {} + @discovered_apis = {} + ca_file = options[:ca_file] || File.expand_path('../../../cacerts.pem', __FILE__) + self.connection = Faraday.new do |faraday| + faraday.response :charset if options[:force_encoding] + faraday.response :gzip + faraday.options.params_encoder = Faraday::FlatParamsEncoder + faraday.ssl.ca_file = ca_file + faraday.ssl.verify = true + faraday.proxy proxy + faraday.adapter Faraday.default_adapter + if options[:faraday_option].is_a?(Hash) + options[:faraday_option].each_pair do |option, value| + faraday.options.send("#{option}=", value) + end + end + end + return self + end + + ## + # Returns the authorization mechanism used by the client. + # + # @return [#generate_authenticated_request] The authorization mechanism. + attr_reader :authorization + + ## + # Sets the authorization mechanism used by the client. + # + # @param [#generate_authenticated_request] new_authorization + # The new authorization mechanism. + def authorization=(new_authorization) + case new_authorization + when :oauth_1, :oauth + require 'signet/oauth_1/client' + # NOTE: Do not rely on this default value, as it may change + new_authorization = Signet::OAuth1::Client.new( + :temporary_credential_uri => + 'https://www.google.com/accounts/OAuthGetRequestToken', + :authorization_uri => + 'https://www.google.com/accounts/OAuthAuthorizeToken', + :token_credential_uri => + 'https://www.google.com/accounts/OAuthGetAccessToken', + :client_credential_key => 'anonymous', + :client_credential_secret => 'anonymous' + ) + when :two_legged_oauth_1, :two_legged_oauth + require 'signet/oauth_1/client' + # NOTE: Do not rely on this default value, as it may change + new_authorization = Signet::OAuth1::Client.new( + :client_credential_key => nil, + :client_credential_secret => nil, + :two_legged => true + ) + when :google_app_default + require 'googleauth' + new_authorization = Google::Auth.get_application_default + + when :oauth_2 + require 'signet/oauth_2/client' + # NOTE: Do not rely on this default value, as it may change + new_authorization = Signet::OAuth2::Client.new( + :authorization_uri => + 'https://accounts.google.com/o/oauth2/auth', + :token_credential_uri => + 'https://accounts.google.com/o/oauth2/token' + ) + when nil + # No authorization mechanism + else + if !new_authorization.respond_to?(:generate_authenticated_request) + raise TypeError, + 'Expected authorization mechanism to respond to ' + + '#generate_authenticated_request.' + end + end + @authorization = new_authorization + return @authorization + end + + ## + # Default Faraday/HTTP connection. + # + # @return [Faraday::Connection] + attr_accessor :connection + + ## + # The setting that controls whether or not the api client attempts to + # refresh authorization when a 401 is hit in #execute. + # + # @return [Boolean] + attr_accessor :auto_refresh_token + + ## + # The application's API key issued by the API console. + # + # @return [String] The API key. + attr_accessor :key + + ## + # The IP address of the user this request is being performed on behalf of. + # + # @return [String] The user's IP address. + attr_accessor :user_ip + + ## + # The user agent used by the client. + # + # @return [String] + # The user agent string used in the User-Agent header. + attr_accessor :user_agent + + ## + # The API hostname used by the client. + # + # @return [String] + # The API hostname. Should almost always be 'www.googleapis.com'. + attr_accessor :host + + ## + # The port number used by the client. + # + # @return [String] + # The port number. Should almost always be 443. + attr_accessor :port + + ## + # The base path used by the client for discovery. + # + # @return [String] + # The base path. Should almost always be '/discovery/v1'. + attr_accessor :discovery_path + + ## + # Number of times to retry on recoverable errors + # + # @return [FixNum] + # Number of retries + attr_accessor :retries + + ## + # Whether or not an expired auth token should be re-acquired + # (and the operation retried) regardless of retries setting + # @return [Boolean] + # Auto retry on auth expiry + attr_accessor :expired_auth_retry + + ## + # Returns the URI for the directory document. + # + # @return [Addressable::URI] The URI of the directory document. + def directory_uri + return resolve_uri(self.discovery_path + '/apis') + end + + ## + # Manually registers a URI as a discovery document for a specific version + # of an API. + # + # @param [String, Symbol] api The API name. + # @param [String] version The desired version of the API. + # @param [Addressable::URI] uri The URI of the discovery document. + # @return [Legacy::Google::APIClient::API] The service object. + def register_discovery_uri(api, version, uri) + api = api.to_s + version = version || 'v1' + @discovery_uris["#{api}:#{version}"] = uri + discovered_api(api, version) + end + + ## + # Returns the URI for the discovery document. + # + # @param [String, Symbol] api The API name. + # @param [String] version The desired version of the API. + # @return [Addressable::URI] The URI of the discovery document. + def discovery_uri(api, version=nil) + api = api.to_s + version = version || 'v1' + return @discovery_uris["#{api}:#{version}"] ||= ( + resolve_uri( + self.discovery_path + '/apis/{api}/{version}/rest', + 'api' => api, + 'version' => version + ) + ) + end + + ## + # Manually registers a pre-loaded discovery document for a specific version + # of an API. + # + # @param [String, Symbol] api The API name. + # @param [String] version The desired version of the API. + # @param [String, StringIO] discovery_document + # The contents of the discovery document. + # @return [Legacy::Google::APIClient::API] The service object. + def register_discovery_document(api, version, discovery_document) + api = api.to_s + version = version || 'v1' + if discovery_document.kind_of?(StringIO) + discovery_document.rewind + discovery_document = discovery_document.string + elsif discovery_document.respond_to?(:to_str) + discovery_document = discovery_document.to_str + else + raise TypeError, + "Expected String or StringIO, got #{discovery_document.class}." + end + @discovery_documents["#{api}:#{version}"] = + MultiJson.load(discovery_document) + discovered_api(api, version) + end + + ## + # Returns the parsed directory document. + # + # @return [Hash] The parsed JSON from the directory document. + def directory_document + return @directory_document ||= (begin + response = self.execute!( + :http_method => :get, + :uri => self.directory_uri, + :authenticated => false + ) + response.data + end) + end + + ## + # Returns the parsed discovery document. + # + # @param [String, Symbol] api The API name. + # @param [String] version The desired version of the API. + # @return [Hash] The parsed JSON from the discovery document. + def discovery_document(api, version=nil) + api = api.to_s + version = version || 'v1' + return @discovery_documents["#{api}:#{version}"] ||= (begin + response = self.execute!( + :http_method => :get, + :uri => self.discovery_uri(api, version), + :authenticated => false + ) + response.data + end) + end + + ## + # Returns all APIs published in the directory document. + # + # @return [Array] The list of available APIs. + def discovered_apis + @directory_apis ||= (begin + document_base = self.directory_uri + if self.directory_document && self.directory_document['items'] + self.directory_document['items'].map do |discovery_document| + Legacy::Google::APIClient::API.new( + document_base, + discovery_document + ) + end + else + [] + end + end) + end + + ## + # Returns the service object for a given service name and service version. + # + # @param [String, Symbol] api The API name. + # @param [String] version The desired version of the API. + # + # @return [Legacy::Google::APIClient::API] The service object. + def discovered_api(api, version=nil) + if !api.kind_of?(String) && !api.kind_of?(Symbol) + raise TypeError, + "Expected String or Symbol, got #{api.class}." + end + api = api.to_s + version = version || 'v1' + return @discovered_apis["#{api}:#{version}"] ||= begin + document_base = self.discovery_uri(api, version) + discovery_document = self.discovery_document(api, version) + if document_base && discovery_document + Legacy::Google::APIClient::API.new( + document_base, + discovery_document + ) + else + nil + end + end + end + + ## + # Returns the method object for a given RPC name and service version. + # + # @param [String, Symbol] rpc_name The RPC name of the desired method. + # @param [String, Symbol] api The API the method is within. + # @param [String] version The desired version of the API. + # + # @return [Legacy::Google::APIClient::Method] The method object. + def discovered_method(rpc_name, api, version=nil) + if !rpc_name.kind_of?(String) && !rpc_name.kind_of?(Symbol) + raise TypeError, + "Expected String or Symbol, got #{rpc_name.class}." + end + rpc_name = rpc_name.to_s + api = api.to_s + version = version || 'v1' + service = self.discovered_api(api, version) + if service.to_h[rpc_name] + return service.to_h[rpc_name] + else + return nil + end + end + + ## + # Returns the service object with the highest version number. + # + # @note Warning: This method should be used with great care. + # As APIs are updated, minor differences between versions may cause + # incompatibilities. Requesting a specific version will avoid this issue. + # + # @param [String, Symbol] api The name of the service. + # + # @return [Legacy::Google::APIClient::API] The service object. + def preferred_version(api) + if !api.kind_of?(String) && !api.kind_of?(Symbol) + raise TypeError, + "Expected String or Symbol, got #{api.class}." + end + api = api.to_s + return self.discovered_apis.detect do |a| + a.name == api && a.preferred == true + end + end + + ## + # Verifies an ID token against a server certificate. Used to ensure that + # an ID token supplied by an untrusted client-side mechanism is valid. + # Raises an error if the token is invalid or missing. + # + # @deprecated Use the google-id-token gem for verifying JWTs + def verify_id_token! + require 'jwt' + require 'openssl' + @certificates ||= {} + if !self.authorization.respond_to?(:id_token) + raise ArgumentError, ( + "Current authorization mechanism does not support ID tokens: " + + "#{self.authorization.class.to_s}" + ) + elsif !self.authorization.id_token + raise ArgumentError, ( + "Could not verify ID token, ID token missing. " + + "Scopes were: #{self.authorization.scope.inspect}" + ) + else + check_cached_certs = lambda do + valid = false + for _key, cert in @certificates + begin + self.authorization.decoded_id_token(cert.public_key) + valid = true + rescue JWT::DecodeError, Signet::UnsafeOperationError + # Expected exception. Ignore, ID token has not been validated. + end + end + valid + end + if check_cached_certs.call() + return true + end + response = self.execute!( + :http_method => :get, + :uri => 'https://www.googleapis.com/oauth2/v1/certs', + :authenticated => false + ) + @certificates.merge!( + Hash[MultiJson.load(response.body).map do |key, cert| + [key, OpenSSL::X509::Certificate.new(cert)] + end] + ) + if check_cached_certs.call() + return true + else + raise InvalidIDTokenError, + "Could not verify ID token against any available certificate." + end + end + return nil + end + + ## + # Generates a request. + # + # @option options [Legacy::Google::APIClient::Method] :api_method + # The method object or the RPC name of the method being executed. + # @option options [Hash, Array] :parameters + # The parameters to send to the method. + # @option options [Hash, Array] :headers The HTTP headers for the request. + # @option options [String] :body The body of the request. + # @option options [String] :version ("v1") + # The service version. Only used if `api_method` is a `String`. + # @option options [#generate_authenticated_request] :authorization + # The authorization mechanism for the response. Used only if + # `:authenticated` is `true`. + # @option options [TrueClass, FalseClass] :authenticated (true) + # `true` if the request must be signed or somehow + # authenticated, `false` otherwise. + # + # @return [Legacy::Google::APIClient::Reference] The generated request. + # + # @example + # request = client.generate_request( + # :api_method => 'plus.activities.list', + # :parameters => + # {'collection' => 'public', 'userId' => 'me'} + # ) + def generate_request(options={}) + options = { + :api_client => self + }.merge(options) + return Legacy::Google::APIClient::Request.new(options) + end + + ## + # Executes a request, wrapping it in a Result object. + # + # @param [Legacy::Google::APIClient::Request, Hash, Array] params + # Either a Legacy::Google::APIClient::Request, a Hash, or an Array. + # + # If a Legacy::Google::APIClient::Request, no other parameters are expected. + # + # If a Hash, the below parameters are handled. If an Array, the + # parameters are assumed to be in the below order: + # + # - (Legacy::Google::APIClient::Method) api_method: + # The method object or the RPC name of the method being executed. + # - (Hash, Array) parameters: + # The parameters to send to the method. + # - (String) body: The body of the request. + # - (Hash, Array) headers: The HTTP headers for the request. + # - (Hash) options: A set of options for the request, of which: + # - (#generate_authenticated_request) :authorization (default: true) - + # The authorization mechanism for the response. Used only if + # `:authenticated` is `true`. + # - (TrueClass, FalseClass) :authenticated (default: true) - + # `true` if the request must be signed or somehow + # authenticated, `false` otherwise. + # - (TrueClass, FalseClass) :gzip (default: true) - + # `true` if gzip enabled, `false` otherwise. + # - (FixNum) :retries - + # # of times to retry on recoverable errors + # + # @return [Legacy::Google::APIClient::Result] The result from the API, nil if batch. + # + # @example + # result = client.execute(batch_request) + # + # @example + # plus = client.discovered_api('plus') + # result = client.execute( + # :api_method => plus.activities.list, + # :parameters => {'collection' => 'public', 'userId' => 'me'} + # ) + # + # @see Legacy::Google::APIClient#generate_request + def execute!(*params) + if params.first.kind_of?(Legacy::Google::APIClient::Request) + request = params.shift + options = params.shift || {} + else + # This block of code allows us to accept multiple parameter passing + # styles, and maintaining some backwards compatibility. + # + # Note: I'm extremely tempted to deprecate this style of execute call. + if params.last.respond_to?(:to_hash) && params.size == 1 + options = params.pop + else + options = {} + end + + options[:api_method] = params.shift if params.size > 0 + options[:parameters] = params.shift if params.size > 0 + options[:body] = params.shift if params.size > 0 + options[:headers] = params.shift if params.size > 0 + options.update(params.shift) if params.size > 0 + request = self.generate_request(options) + end + + request.headers['User-Agent'] ||= '' + self.user_agent unless self.user_agent.nil? + request.headers['Accept-Encoding'] ||= 'gzip' unless options[:gzip] == false + request.headers['Content-Type'] ||= '' + request.parameters['key'] ||= self.key unless self.key.nil? + request.parameters['userIp'] ||= self.user_ip unless self.user_ip.nil? + + connection = options[:connection] || self.connection + request.authorization = options[:authorization] || self.authorization unless options[:authenticated] == false + + tries = 1 + (options[:retries] || self.retries) + attempt = 0 + + Retriable.retriable :tries => tries, + :on => [TransmissionError], + :on_retry => client_error_handler, + :interval => lambda {|attempts| (2 ** attempts) + rand} do + attempt += 1 + + # This 2nd level retriable only catches auth errors, and supports 1 retry, which allows + # auth to be re-attempted without having to retry all sorts of other failures like + # NotFound, etc + Retriable.retriable :tries => ((expired_auth_retry || tries > 1) && attempt == 1) ? 2 : 1, + :on => [AuthorizationError], + :on_retry => authorization_error_handler(request.authorization) do + result = request.send(connection, true) + + case result.status + when 200...300 + result + when 301, 302, 303, 307 + request = generate_request(request.to_hash.merge({ + :uri => result.headers['location'], + :api_method => nil + })) + raise RedirectError.new(result.headers['location'], result) + when 401 + raise AuthorizationError.new(result.error_message || 'Invalid/Expired Authentication', result) + when 400, 402...500 + raise ClientError.new(result.error_message || "A client error has occurred", result) + when 500...600 + raise ServerError.new(result.error_message || "A server error has occurred", result) + else + raise TransmissionError.new(result.error_message || "A transmission error has occurred", result) + end + end + end + end + + ## + # Same as Legacy::Google::APIClient#execute!, but does not raise an exception for + # normal API errros. + # + # @see Legacy::Google::APIClient#execute + def execute(*params) + begin + return self.execute!(*params) + rescue TransmissionError => e + return e.result + end + end + + protected + + ## + # Resolves a URI template against the client's configured base. + # + # @api private + # @param [String, Addressable::URI, Addressable::Template] template + # The template to resolve. + # @param [Hash] mapping The mapping that corresponds to the template. + # @return [Addressable::URI] The expanded URI. + def resolve_uri(template, mapping={}) + @base_uri ||= Addressable::URI.new( + :scheme => 'https', + :host => self.host, + :port => self.port + ).normalize + template = if template.kind_of?(Addressable::Template) + template.pattern + elsif template.respond_to?(:to_str) + template.to_str + else + raise TypeError, + "Expected String, Addressable::URI, or Addressable::Template, " + + "got #{template.class}." + end + return Addressable::Template.new(@base_uri + template).expand(mapping) + end + + + ## + # Returns on proc for special processing of retries for authorization errors + # Only 401s should be retried and only if the credentials are refreshable + # + # @param [#fetch_access_token!] authorization + # OAuth 2 credentials + # @return [Proc] + def authorization_error_handler(authorization) + can_refresh = authorization.respond_to?(:refresh_token) && auto_refresh_token + Proc.new do |exception, tries| + next unless exception.kind_of?(AuthorizationError) + if can_refresh + begin + logger.debug("Attempting refresh of access token & retry of request") + authorization.fetch_access_token! + next + rescue Signet::AuthorizationError + end + end + raise exception + end + end + + ## + # Returns on proc for special processing of retries as not all client errors + # are recoverable. Only 401s should be retried (via authorization_error_handler) + # + # @return [Proc] + def client_error_handler + Proc.new do |exception, tries| + raise exception if exception.kind_of?(ClientError) + end + end + + end + + end +end diff --git a/lib/google/api_client/auth/compute_service_account.rb b/lib/legacy/google/api_client/auth/compute_service_account.rb similarity index 59% rename from lib/google/api_client/auth/compute_service_account.rb rename to lib/legacy/google/api_client/auth/compute_service_account.rb index 118f1e6eb1f..2943e064de7 100644 --- a/lib/google/api_client/auth/compute_service_account.rb +++ b/lib/legacy/google/api_client/auth/compute_service_account.rb @@ -15,13 +15,15 @@ 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']) +module Legacy + 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 diff --git a/lib/legacy/google/api_client/auth/file_storage.rb b/lib/legacy/google/api_client/auth/file_storage.rb new file mode 100644 index 00000000000..a980f2a6c7d --- /dev/null +++ b/lib/legacy/google/api_client/auth/file_storage.rb @@ -0,0 +1,61 @@ +# 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 Legacy + 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 {Legacy::Google::APIClient::Storage} and {Legacy::Google::APIClient::FileStore} instead + # + class FileStorage + + attr_accessor :storage + + def initialize(path) + store = Legacy::Google::APIClient::FileStore.new(path) + @storage = Legacy::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 +end diff --git a/lib/legacy/google/api_client/auth/installed_app.rb b/lib/legacy/google/api_client/auth/installed_app.rb new file mode 100644 index 00000000000..1deea5ac7c5 --- /dev/null +++ b/lib/legacy/google/api_client/auth/installed_app.rb @@ -0,0 +1,127 @@ +# 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 Legacy + 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 = Legacy::Google::APIClient.new + # flow = Legacy::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 + + + + + You may close this window. + + 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 [Legacy::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 +end diff --git a/lib/legacy/google/api_client/auth/jwt_asserter.rb b/lib/legacy/google/api_client/auth/jwt_asserter.rb new file mode 100644 index 00000000000..5ceba58ae33 --- /dev/null +++ b/lib/legacy/google/api_client/auth/jwt_asserter.rb @@ -0,0 +1,128 @@ +# 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 Legacy + 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 = Legacy::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 +end diff --git a/lib/legacy/google/api_client/auth/key_utils.rb b/lib/legacy/google/api_client/auth/key_utils.rb new file mode 100644 index 00000000000..0582fd3622d --- /dev/null +++ b/lib/legacy/google/api_client/auth/key_utils.rb @@ -0,0 +1,95 @@ +# 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 Legacy + 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 +end diff --git a/lib/legacy/google/api_client/auth/pkcs12.rb b/lib/legacy/google/api_client/auth/pkcs12.rb new file mode 100644 index 00000000000..f882279a8fd --- /dev/null +++ b/lib/legacy/google/api_client/auth/pkcs12.rb @@ -0,0 +1,44 @@ +# 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 'legacy/google/api_client/auth/key_utils' + +module Legacy + 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 {Legacy::Google::APIClient::KeyUtils} instead + def self.load_key(keyfile, passphrase) + KeyUtils.load_from_pkcs12(keyfile, passphrase) + end + end + end + end +end diff --git a/lib/legacy/google/api_client/auth/storage.rb b/lib/legacy/google/api_client/auth/storage.rb new file mode 100644 index 00000000000..0be27185505 --- /dev/null +++ b/lib/legacy/google/api_client/auth/storage.rb @@ -0,0 +1,104 @@ +# 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 Legacy + 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 +end diff --git a/lib/legacy/google/api_client/auth/storages/file_store.rb b/lib/legacy/google/api_client/auth/storages/file_store.rb new file mode 100644 index 00000000000..5ad42329e1b --- /dev/null +++ b/lib/legacy/google/api_client/auth/storages/file_store.rb @@ -0,0 +1,60 @@ +# 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 Legacy + 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 +end diff --git a/lib/legacy/google/api_client/auth/storages/redis_store.rb b/lib/legacy/google/api_client/auth/storages/redis_store.rb new file mode 100644 index 00000000000..94e7be4d7ff --- /dev/null +++ b/lib/legacy/google/api_client/auth/storages/redis_store.rb @@ -0,0 +1,56 @@ +# 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 Legacy + 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 +end diff --git a/lib/legacy/google/api_client/batch.rb b/lib/legacy/google/api_client/batch.rb new file mode 100644 index 00000000000..c1ece35bf93 --- /dev/null +++ b/lib/legacy/google/api_client/batch.rb @@ -0,0 +1,328 @@ +# 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 'legacy/google/api_client/reference' +require 'securerandom' + +module Legacy + 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 = Legacy::Google::APIClient.new + # urlshortener = client.discovered_api('urlshortener') + # batch = Legacy::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,Legacy::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 [Legacy::Google::APIClient::BatchRequest] + # The constructed object. + # + # @yield [Legacy::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, Legacy::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 [Legacy::Google::APIClient::BatchRequest] + # the BatchRequest, for chaining + # + # @yield [Legacy::Google::APIClient::Result] + # block to be called when result ready + def add(call, call_id = nil, &block) + unless call.kind_of?(Legacy::Google::APIClient::Reference) + call = Legacy::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 = Legacy::Google::APIClient::Result.new(call, call_response) + callback.call(result) if callback + end + end + Legacy::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, 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 [Legacy::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 [Legacy::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 +end diff --git a/lib/legacy/google/api_client/charset.rb b/lib/legacy/google/api_client/charset.rb new file mode 100644 index 00000000000..2ec2f2fae09 --- /dev/null +++ b/lib/legacy/google/api_client/charset.rb @@ -0,0 +1,35 @@ +require 'faraday' +require 'zlib' + +module Legacy + module Google + class APIClient + class Charset < Faraday::Response::Middleware + include Legacy::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 +end + +Faraday::Response.register_middleware :charset => Legacy::Google::APIClient::Charset diff --git a/lib/legacy/google/api_client/client_secrets.rb b/lib/legacy/google/api_client/client_secrets.rb new file mode 100644 index 00000000000..bc424d5cb2e --- /dev/null +++ b/lib/legacy/google/api_client/client_secrets.rb @@ -0,0 +1,180 @@ +# 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 Legacy + 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 [Legacy::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 +end diff --git a/lib/google/api_client/discovery.rb b/lib/legacy/google/api_client/discovery.rb similarity index 73% rename from lib/google/api_client/discovery.rb rename to lib/legacy/google/api_client/discovery.rb index bb01d67ce76..59a5d56dd69 100644 --- a/lib/google/api_client/discovery.rb +++ b/lib/legacy/google/api_client/discovery.rb @@ -13,7 +13,7 @@ # 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' +require 'legacy/google/api_client/discovery/api' +require 'legacy/google/api_client/discovery/resource' +require 'legacy/google/api_client/discovery/method' +require 'legacy/google/api_client/discovery/schema' diff --git a/lib/legacy/google/api_client/discovery/api.rb b/lib/legacy/google/api_client/discovery/api.rb new file mode 100644 index 00000000000..ab2a49653a5 --- /dev/null +++ b/lib/legacy/google/api_client/discovery/api.rb @@ -0,0 +1,312 @@ +# 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 'legacy/google/api_client/discovery/resource' +require 'legacy/google/api_client/discovery/method' +require 'legacy/google/api_client/discovery/media' + +module Legacy + 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 [Legacy::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 {Legacy::Google::APIClient::Schema} objects. + def schemas + return @schemas ||= ( + (@discovery_document['schemas'] || []).inject({}) do |accu, (k, v)| + accu[k] = Legacy::Google::APIClient::Schema.parse(self, v) + accu + end + ) + end + + ## + # Returns a schema for a kind value. + # + # @return [Legacy::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 {Legacy::Google::APIClient::Resource} objects. + def discovered_resources + return @discovered_resources ||= ( + (@discovery_document['resources'] || []).inject([]) do |accu, (k, v)| + accu << Legacy::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 {Legacy::Google::APIClient::Method} objects. + def discovered_methods + return @discovered_methods ||= ( + (@discovery_document['methods'] || []).inject([]) do |accu, (k, v)| + accu << Legacy::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 String representation of the service's state. + # + # @return [String] The service's state, as a String. + 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 +end diff --git a/lib/legacy/google/api_client/discovery/media.rb b/lib/legacy/google/api_client/discovery/media.rb new file mode 100644 index 00000000000..c5db92e6eae --- /dev/null +++ b/lib/legacy/google/api_client/discovery/media.rb @@ -0,0 +1,78 @@ +# 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 'legacy/google/api_client/errors' + +module Legacy + module Google + class APIClient + ## + # Media upload elements for discovered methods + class MediaUpload + + ## + # Creates a description of a particular method. + # + # @param [Legacy::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 [Legacy::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 +end diff --git a/lib/legacy/google/api_client/discovery/method.rb b/lib/legacy/google/api_client/discovery/method.rb new file mode 100644 index 00000000000..060a25dd3ef --- /dev/null +++ b/lib/legacy/google/api_client/discovery/method.rb @@ -0,0 +1,364 @@ +# 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 'legacy/google/api_client/errors' + +module Legacy + module Google + class APIClient + ## + # A method that has been described by a discovery document. + class Method + + ## + # Creates a description of a particular method. + # + # @param [Legacy::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 [Legacy::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 [Legacy::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 [Legacy::Google::APIClient::MediaUpload] Description of upload endpoints + def media_upload + if @discovery_document['mediaUpload'] + return @media_upload ||= Legacy::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 [Legacy::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 [Legacy::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 Hash 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 Array 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 Array 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 Array 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] nil 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 String representation of the method's state. + # + # @return [String] The method's state, as a String. + def inspect + sprintf( + "#<%s:%#0x ID:%s>", + self.class.to_s, self.object_id, self.id + ) + end + end + end + end +end diff --git a/lib/legacy/google/api_client/discovery/resource.rb b/lib/legacy/google/api_client/discovery/resource.rb new file mode 100644 index 00000000000..71ca9595cbe --- /dev/null +++ b/lib/legacy/google/api_client/discovery/resource.rb @@ -0,0 +1,157 @@ +# 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 'legacy/google/api_client/discovery/method' + +module Legacy + 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 [Legacy::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 [Legacy::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 <String representation of the resource's state. + # + # @return [String] The resource's state, as a String. + def inspect + sprintf( + "#<%s:%#0x NAME:%s>", self.class.to_s, self.object_id, self.name + ) + end + end + end + end +end diff --git a/lib/legacy/google/api_client/discovery/schema.rb b/lib/legacy/google/api_client/discovery/schema.rb new file mode 100644 index 00000000000..9f2c2ee864d --- /dev/null +++ b/lib/legacy/google/api_client/discovery/schema.rb @@ -0,0 +1,118 @@ +# 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 'legacy/google/api_client/errors' + +module Legacy + 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 Legacy::Google::APIClient::Schema.const_defined?(api_name_string, *args) + api_name = Legacy::Google::APIClient::Schema.const_get( + api_name_string, *args + ) + else + api_name = Legacy::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 +end diff --git a/lib/legacy/google/api_client/environment.rb b/lib/legacy/google/api_client/environment.rb new file mode 100644 index 00000000000..cc0931c4bba --- /dev/null +++ b/lib/legacy/google/api_client/environment.rb @@ -0,0 +1,43 @@ +# 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 Legacy + 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(']', '') + 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.strip + end + end + end +end diff --git a/lib/legacy/google/api_client/errors.rb b/lib/legacy/google/api_client/errors.rb new file mode 100644 index 00000000000..45f7f14ec2c --- /dev/null +++ b/lib/legacy/google/api_client/errors.rb @@ -0,0 +1,66 @@ +# 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 Legacy + 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 +end diff --git a/lib/legacy/google/api_client/gzip.rb b/lib/legacy/google/api_client/gzip.rb new file mode 100644 index 00000000000..9cab8e22a08 --- /dev/null +++ b/lib/legacy/google/api_client/gzip.rb @@ -0,0 +1,30 @@ +require 'faraday' +require 'zlib' + +module Legacy + module Google + class APIClient + class Gzip < Faraday::Response::Middleware + include Legacy::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 +end + +Faraday::Response.register_middleware :gzip => Legacy::Google::APIClient::Gzip diff --git a/lib/legacy/google/api_client/logging.rb b/lib/legacy/google/api_client/logging.rb new file mode 100644 index 00000000000..d449cd09973 --- /dev/null +++ b/lib/legacy/google/api_client/logging.rb @@ -0,0 +1,34 @@ +require 'logger' + +module Legacy + 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 + Legacy::Google::APIClient.logger + end + end + + end + + + end +end diff --git a/lib/legacy/google/api_client/media.rb b/lib/legacy/google/api_client/media.rb new file mode 100644 index 00000000000..9ae31d8b45f --- /dev/null +++ b/lib/legacy/google/api_client/media.rb @@ -0,0 +1,261 @@ +# 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 'legacy/google/api_client/reference' + +module Legacy + module Google + class APIClient + ## + # Uploadable media support. Holds an IO stream & content type. + # + # @see Faraday::UploadIO + # @example + # media = Legacy::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 = Legacy::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 {Legacy::Google::APIClient#execute} instead + # + # @param [Legacy::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 {Legacy::Google::APIClient#execute} instead + # + # @param [Legacy::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 Legacy::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 [Legacy::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 Legacy::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 +end diff --git a/lib/legacy/google/api_client/railtie.rb b/lib/legacy/google/api_client/railtie.rb new file mode 100644 index 00000000000..cc941b763c7 --- /dev/null +++ b/lib/legacy/google/api_client/railtie.rb @@ -0,0 +1,20 @@ +require 'rails/railtie' +require 'legacy/google/api_client/logging' + +module Legacy + 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 + Legacy::Google::APIClient.logger = logger unless logger.nil? + end + end + end + end +end diff --git a/lib/google/api_client/reference.rb b/lib/legacy/google/api_client/reference.rb similarity index 64% rename from lib/google/api_client/reference.rb rename to lib/legacy/google/api_client/reference.rb index 15b34250d74..55728a599fd 100644 --- a/lib/google/api_client/reference.rb +++ b/lib/legacy/google/api_client/reference.rb @@ -12,16 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'google/api_client/request' +require 'legacy/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 +module Legacy + module Google + class APIClient + ## + # Subclass of Request for backwards compatibility with pre-0.5.0 versions of the library + # + # @deprecated + # use {Legacy::Google::APIClient::Request} instead + class Reference < Request + end end end end diff --git a/lib/legacy/google/api_client/request.rb b/lib/legacy/google/api_client/request.rb new file mode 100644 index 00000000000..0e780dfe4db --- /dev/null +++ b/lib/legacy/google/api_client/request.rb @@ -0,0 +1,352 @@ +# 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 'legacy/google/api_client/discovery' +require 'legacy/google/api_client/logging' + +module Legacy + module Google + class APIClient + + ## + # Represents an API request. + class Request + include Legacy::Google::APIClient::Logging + + MULTIPART_BOUNDARY = "-----------RubyApiMultipartPost".freeze + + # @return [Hash] Request parameters + attr_reader :parameters + # @return [Hash] Additional HTTP headers + attr_reader :headers + # @return [Legacy::Google::APIClient::Method] API method to invoke + attr_reader :api_method + # @return [Legacy::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 [Legacy::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 [Legacy::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?(Legacy::Google::APIClient::Method) + @api_method = new_api_method + else + raise TypeError, + "Expected Legacy::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 [Legacy::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 [Legacy::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 [Legacy::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 +end diff --git a/lib/legacy/google/api_client/result.rb b/lib/legacy/google/api_client/result.rb new file mode 100644 index 00000000000..1ac94c6c3c8 --- /dev/null +++ b/lib/legacy/google/api_client/result.rb @@ -0,0 +1,256 @@ +# 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 Legacy + module Google + class APIClient + ## + # This class wraps a result returned by an API call. + class Result + extend Forwardable + + ## + # Init the result + # + # @param [Legacy::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 [Legacy::Google::APIClient::Request] Original request object + attr_reader :request + # @return [Faraday::Response] HTTP response + attr_reader :response + # @!attribute [r] reference + # @return [Legacy::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 [Legacy::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 + ) + Legacy::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 + # + # @!attribute [r] error? + # @return [TrueClass, FalseClass] + # true if result of operation is an error + def error? + return self.response.status >= 400 + end + + ## + # Check if request was successful + # + # @!attribute [r] success? + # @return [TrueClass, FalseClass] + # true if result of operation was successful + def success? + return !self.error? + 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 [Legacy::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 Legacy::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 [Legacy::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 Legacy::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 +end diff --git a/lib/legacy/google/api_client/service.rb b/lib/legacy/google/api_client/service.rb new file mode 100755 index 00000000000..f17be4452e2 --- /dev/null +++ b/lib/legacy/google/api_client/service.rb @@ -0,0 +1,235 @@ +# 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 'legacy/google/api_client' +require 'legacy/google/api_client/service/stub_generator' +require 'legacy/google/api_client/service/resource' +require 'legacy/google/api_client/service/request' +require 'legacy/google/api_client/service/result' +require 'legacy/google/api_client/service/batch' +require 'legacy/google/api_client/service/simple_file_store' + +module Legacy + module Google + class APIClient + + ## + # Experimental new programming interface at the API level. + # Hides Legacy::Google::APIClient. Designed to be easier to use, with less code. + # + # @example + # calendar = Legacy::Google::APIClient::Service.new('calendar', 'v3') + # result = calendar.events.list('calendarId' => 'primary').execute() + class Service + include Legacy::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: + #
    + #
  • :two_legged_oauth_1
  • + #
  • :oauth_1
  • + #
  • :oauth_2
  • + #
+ # @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 = Legacy::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 = Legacy::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, + # Legacy::Google::APIClient::Service::SimpleFileStore, + # nil] + attr_reader :cache_store + + ## + # Prepares a Legacy::Google::APIClient::BatchRequest object to make batched calls. + # @param [Array] calls + # Optional array of Legacy::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 [Legacy::Google::APIClient::Service::Result] + # block to be called when result ready + def batch(calls = nil, &block) + Legacy::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 [Legacy::Google::APIClient::Service::Request, + # Legacy::Google::APIClient::Service::BatchCall] request + # The request to be executed. + def execute(request) + if request.instance_of? Legacy::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 Legacy::Google::APIClient::Service::Result.new(request, result) + elsif request.instance_of? Legacy::Google::APIClient::Service::BatchRequest + @client.execute(request.base_batch, {:connection => @connection}) + end + end + end + end + end +end diff --git a/lib/legacy/google/api_client/service/batch.rb b/lib/legacy/google/api_client/service/batch.rb new file mode 100644 index 00000000000..44c3fdcd5a8 --- /dev/null +++ b/lib/legacy/google/api_client/service/batch.rb @@ -0,0 +1,112 @@ +# 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 'legacy/google/api_client/service/result' +require 'legacy/google/api_client/batch' + +module Legacy + 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 Legacy::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 [Legacy::Google::APIClient::Service::Result] + # block to be called when result ready + def initialize(service, calls, &block) + @service = service + @base_batch = Legacy::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 [Legacy::Google::APIClient::Service::Request] call + # the call to be added. + # @param [Proc] block + # callback for this call's response. + # + # @return [Legacy::Google::APIClient::Service::BatchRequest] + # the BatchRequest, for chaining + # + # @yield [Legacy::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 = Legacy::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 +end diff --git a/lib/legacy/google/api_client/service/request.rb b/lib/legacy/google/api_client/service/request.rb new file mode 100755 index 00000000000..99c9000a75a --- /dev/null +++ b/lib/legacy/google/api_client/service/request.rb @@ -0,0 +1,146 @@ +# 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 Legacy + 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 [Legacy::Google::APIClient::Service] service + # The parent Service instance that will execute the request. + # @param [Legacy::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 [Legacy::Google::APIClient::Service] The parent service. + attr_reader :service + + ## + # Returns the Method instance that describes the API method invoked by + # the request. + # + # @return [Legacy::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 +end diff --git a/lib/legacy/google/api_client/service/resource.rb b/lib/legacy/google/api_client/service/resource.rb new file mode 100755 index 00000000000..f4f0f7f11ca --- /dev/null +++ b/lib/legacy/google/api_client/service/resource.rb @@ -0,0 +1,42 @@ +# 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 Legacy + module Google + class APIClient + class Service + ## + # Handles an API resource. + # Simple class that contains API methods and/or child resources. + class Resource + include Legacy::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 [Legacy::Google::APIClient::Service] service + # The Service instance this resource belongs to. + # @param [Legacy::Google::APIClient::API, Legacy::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 +end diff --git a/lib/legacy/google/api_client/service/result.rb b/lib/legacy/google/api_client/service/result.rb new file mode 100755 index 00000000000..f591dab9f53 --- /dev/null +++ b/lib/legacy/google/api_client/service/result.rb @@ -0,0 +1,164 @@ +# 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 Legacy + module Google + class APIClient + class Service + ## + # Handles an API result. + # Wraps around the Legacy::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 [Legacy::Google::APIClient::Service::Request] request + # The original request + # @param [Legacy::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 [Legacy::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 [Legacy::Google::APIClient::Service::Request] + # API request for retrieving next page + def next_page + 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 [Legacy::Google::APIClient::Service::Request] + # API request for retrieving previous page + def prev_page + 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 +end diff --git a/lib/legacy/google/api_client/service/simple_file_store.rb b/lib/legacy/google/api_client/service/simple_file_store.rb new file mode 100644 index 00000000000..8b37b29f3e5 --- /dev/null +++ b/lib/legacy/google/api_client/service/simple_file_store.rb @@ -0,0 +1,153 @@ +# 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 Legacy + 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 +end diff --git a/lib/legacy/google/api_client/service/stub_generator.rb b/lib/legacy/google/api_client/service/stub_generator.rb new file mode 100755 index 00000000000..abf1116a54e --- /dev/null +++ b/lib/legacy/google/api_client/service/stub_generator.rb @@ -0,0 +1,63 @@ +# 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 Legacy + 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 + Legacy::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 Legacy::Google::APIClient::Service::Request.new( + service, method, args.first + ) + end + end + end + end + end + end + end + end + end +end diff --git a/lib/google/api_client/service_account.rb b/lib/legacy/google/api_client/service_account.rb similarity index 60% rename from lib/google/api_client/service_account.rb rename to lib/legacy/google/api_client/service_account.rb index 3d941ae07cb..fc728f463a6 100644 --- a/lib/google/api_client/service_account.rb +++ b/lib/legacy/google/api_client/service_account.rb @@ -12,10 +12,10 @@ # 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' +require 'legacy/google/api_client/auth/pkcs12' +require 'legacy/google/api_client/auth/jwt_asserter' +require 'legacy/google/api_client/auth/key_utils' +require 'legacy/google/api_client/auth/compute_service_account' +require 'legacy/google/api_client/auth/storage' +require 'legacy/google/api_client/auth/storages/redis_store' +require 'legacy/google/api_client/auth/storages/file_store' diff --git a/lib/google/api_client/version.rb b/lib/legacy/google/api_client/version.rb similarity index 73% rename from lib/google/api_client/version.rb rename to lib/legacy/google/api_client/version.rb index edddd808b21..7d86a27a1b8 100644 --- a/lib/google/api_client/version.rb +++ b/lib/legacy/google/api_client/version.rb @@ -12,15 +12,16 @@ # 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 = 7 - PATCH = nil - STRING = [MAJOR, MINOR, TINY, PATCH].compact.join('.') +module Legacy + module Google + class APIClient + module VERSION + MAJOR = 0 + MINOR = 8 + TINY = 7 + PATCH = nil + STRING = [MAJOR, MINOR, TINY, PATCH].compact.join('.') + end end end end diff --git a/rakelib/wiki.rake b/rakelib/wiki.rake index 3e0d97d2e39..c5e5ed39c5a 100644 --- a/rakelib/wiki.rake +++ b/rakelib/wiki.rake @@ -18,7 +18,7 @@ the following Google APIs. WIKI preferred_apis = {} require 'google/api_client' - client = Google::APIClient.new + client = Legacy::Google::APIClient.new for api in client.discovered_apis if !preferred_apis.has_key?(api.name) preferred_apis[api.name] = api diff --git a/spec/google/api_client/auth/storage_spec.rb b/spec/legacy/google/api_client/auth/storage_spec.rb similarity index 93% rename from spec/google/api_client/auth/storage_spec.rb rename to spec/legacy/google/api_client/auth/storage_spec.rb index d8e5b960c79..bdedeb7d591 100644 --- a/spec/google/api_client/auth/storage_spec.rb +++ b/spec/legacy/google/api_client/auth/storage_spec.rb @@ -1,16 +1,16 @@ require 'spec_helper' -require 'google/api_client' -require 'google/api_client/version' +require 'legacy/google/api_client' +require 'legacy/google/api_client/version' -describe Google::APIClient::Storage do - let(:client) { Google::APIClient.new(:application_name => 'API Client Tests') } - let(:root_path) { File.expand_path(File.join(__FILE__, '..', '..', '..')) } +describe Legacy::Google::APIClient::Storage do + let(:client) { Legacy::Google::APIClient.new(:application_name => 'API Client Tests') } + let(:root_path) { File.expand_path(File.join(__FILE__, '..', '..', '..', '..')) } let(:json_file) { File.expand_path(File.join(root_path, 'fixtures', 'files', 'auth_stored_credentials.json')) } let(:store) { double } let(:client_stub) { double } - subject { Google::APIClient::Storage.new(store) } + subject { Legacy::Google::APIClient::Storage.new(store) } describe 'authorize' do it 'should authorize' do diff --git a/spec/google/api_client/auth/storages/file_store_spec.rb b/spec/legacy/google/api_client/auth/storages/file_store_spec.rb similarity index 84% rename from spec/google/api_client/auth/storages/file_store_spec.rb rename to spec/legacy/google/api_client/auth/storages/file_store_spec.rb index 2963b1d45ba..c10e9aa7d44 100644 --- a/spec/google/api_client/auth/storages/file_store_spec.rb +++ b/spec/legacy/google/api_client/auth/storages/file_store_spec.rb @@ -1,10 +1,10 @@ require 'spec_helper' -require 'google/api_client' -require 'google/api_client/version' +require 'legacy/google/api_client' +require 'legacy/google/api_client/version' -describe Google::APIClient::FileStore do - let(:root_path) { File.expand_path(File.join(__FILE__, '..','..','..', '..','..')) } +describe Legacy::Google::APIClient::FileStore do + let(:root_path) { File.expand_path(File.join(__FILE__, '..','..','..', '..','..','..')) } let(:json_file) { File.expand_path(File.join(root_path, 'fixtures', 'files', 'auth_stored_credentials.json')) } let(:credentials_hash) {{ @@ -18,7 +18,7 @@ "issued_at"=>1384440275 }} - subject{Google::APIClient::FileStore.new('a file path')} + subject{Legacy::Google::APIClient::FileStore.new('a file path')} it 'should have a path' do expect(subject.path).to be == 'a file path' diff --git a/spec/google/api_client/auth/storages/redis_store_spec.rb b/spec/legacy/google/api_client/auth/storages/redis_store_spec.rb similarity index 84% rename from spec/google/api_client/auth/storages/redis_store_spec.rb rename to spec/legacy/google/api_client/auth/storages/redis_store_spec.rb index de5abc4a101..9eecb1d0136 100644 --- a/spec/google/api_client/auth/storages/redis_store_spec.rb +++ b/spec/legacy/google/api_client/auth/storages/redis_store_spec.rb @@ -1,11 +1,11 @@ require 'spec_helper' -require 'google/api_client' -require 'google/api_client/version' +require 'legacy/google/api_client' +require 'legacy/google/api_client/version' -describe Google::APIClient::RedisStore do - let(:root_path) { File.expand_path(File.join(__FILE__, '..', '..', '..', '..', '..')) } +describe Legacy::Google::APIClient::RedisStore do + let(:root_path) { File.expand_path(File.join(__FILE__, '..', '..', '..', '..', '..', '..')) } let(:json_file) { File.expand_path(File.join(root_path, 'fixtures', 'files', 'auth_stored_credentials.json')) } let(:redis) {double} @@ -20,7 +20,7 @@ "issued_at" => 1384440275 } } - subject { Google::APIClient::RedisStore.new('a redis instance') } + subject { Legacy::Google::APIClient::RedisStore.new('a redis instance') } it 'should have a redis instance' do expect(subject.redis).to be == 'a redis instance' @@ -50,7 +50,7 @@ end end context 'with given key' do - let(:redis_store) { Google::APIClient::RedisStore.new('a redis instance', 'another_google_api_credentials') } + let(:redis_store) { Legacy::Google::APIClient::RedisStore.new('a redis instance', 'another_google_api_credentials') } it 'should use given key' do expect(redis_store.redis_credentials_key).to be == "another_google_api_credentials" end diff --git a/spec/google/api_client/batch_spec.rb b/spec/legacy/google/api_client/batch_spec.rb similarity index 87% rename from spec/google/api_client/batch_spec.rb rename to spec/legacy/google/api_client/batch_spec.rb index 3aa95a88b61..ecd9ae9405a 100644 --- a/spec/google/api_client/batch_spec.rb +++ b/spec/legacy/google/api_client/batch_spec.rb @@ -13,10 +13,10 @@ # limitations under the License. require 'spec_helper' -require 'google/api_client' +require 'legacy/google/api_client' -RSpec.describe Google::APIClient::BatchRequest do - CLIENT = Google::APIClient.new(:application_name => 'API Client Tests') unless defined?(CLIENT) +RSpec.describe Legacy::Google::APIClient::BatchRequest do + CLIENT = Legacy::Google::APIClient.new(:application_name => 'API Client Tests') unless defined?(CLIENT) after do # Reset client to not-quite-pristine state @@ -25,15 +25,15 @@ end it 'should raise an error if making an empty batch request' do - batch = Google::APIClient::BatchRequest.new + batch = Legacy::Google::APIClient::BatchRequest.new expect(lambda do CLIENT.execute(batch) - end).to raise_error(Google::APIClient::BatchError) + end).to raise_error(Legacy::Google::APIClient::BatchError) end it 'should allow query parameters in batch requests' do - batch = Google::APIClient::BatchRequest.new + batch = Legacy::Google::APIClient::BatchRequest.new batch.add(:uri => 'https://example.com', :parameters => { 'a' => '12345' }) @@ -70,7 +70,7 @@ block_called = 0 ids = ['first_call', 'second_call'] expected_ids = ids.clone - batch = Google::APIClient::BatchRequest.new do |result| + batch = Legacy::Google::APIClient::BatchRequest.new do |result| block_called += 1 expect(result.status).to eq(200) expect(expected_ids).to include(result.response.call_id) @@ -85,7 +85,7 @@ end it 'should execute both when using individual callbacks' do - batch = Google::APIClient::BatchRequest.new + batch = Legacy::Google::APIClient::BatchRequest.new call1_returned, call2_returned = false, false batch.add(@call1) do |result| @@ -103,12 +103,12 @@ end it 'should raise an error if using the same call ID more than once' do - batch = Google::APIClient::BatchRequest.new + batch = Legacy::Google::APIClient::BatchRequest.new expect(lambda do batch.add(@call1, 'my_id') batch.add(@call2, 'my_id') - end).to raise_error(Google::APIClient::BatchError) + end).to raise_error(Legacy::Google::APIClient::BatchError) end end @@ -135,7 +135,7 @@ block_called = 0 ids = ['first_call', 'second_call'] expected_ids = ids.clone - batch = Google::APIClient::BatchRequest.new do |result| + batch = Legacy::Google::APIClient::BatchRequest.new do |result| block_called += 1 expect(expected_ids).to include(result.response.call_id) expected_ids.delete(result.response.call_id) @@ -155,7 +155,7 @@ end it 'should execute both when using individual callbacks' do - batch = Google::APIClient::BatchRequest.new + batch = Legacy::Google::APIClient::BatchRequest.new call1_returned, call2_returned = false, false batch.add(@call1) do |result| @@ -231,10 +231,10 @@ end it 'should convert to a correct HTTP request' do - batch = Google::APIClient::BatchRequest.new { |result| } + batch = Legacy::Google::APIClient::BatchRequest.new { |result| } batch.add(@call1, '1').add(@call2, '2') request = batch.to_env(CLIENT.connection) - boundary = Google::APIClient::BatchRequest::BATCH_BOUNDARY + boundary = Legacy::Google::APIClient::BatchRequest::BATCH_BOUNDARY expect(request[:method].to_s.downcase).to eq('post') expect(request[:url].to_s).to eq('https://www.googleapis.com/batch') expect(request[:request_headers]['Content-Type']).to eq("multipart/mixed;boundary=#{boundary}") diff --git a/spec/google/api_client/client_secrets_spec.rb b/spec/legacy/google/api_client/client_secrets_spec.rb similarity index 85% rename from spec/google/api_client/client_secrets_spec.rb rename to spec/legacy/google/api_client/client_secrets_spec.rb index ead9bf7e99a..ee31aa829c5 100644 --- a/spec/google/api_client/client_secrets_spec.rb +++ b/spec/legacy/google/api_client/client_secrets_spec.rb @@ -16,15 +16,15 @@ require 'spec_helper' -require 'google/api_client/client_secrets' +require 'legacy/google/api_client/client_secrets' -FIXTURES_PATH = File.expand_path('../../../fixtures', __FILE__) +FIXTURES_PATH = File.expand_path('../../../../fixtures', __FILE__) -RSpec.describe Google::APIClient::ClientSecrets do +RSpec.describe Legacy::Google::APIClient::ClientSecrets do context 'with JSON file' do let(:file) { File.join(FIXTURES_PATH, 'files', 'client_secrets.json') } - subject(:secrets) { Google::APIClient::ClientSecrets.load(file)} + subject(:secrets) { Legacy::Google::APIClient::ClientSecrets.load(file)} it 'should load the correct client ID' do expect(secrets.client_id).to be == '898243283568.apps.googleusercontent.com' @@ -50,4 +50,4 @@ end end -end \ No newline at end of file +end diff --git a/spec/google/api_client/discovery_spec.rb b/spec/legacy/google/api_client/discovery_spec.rb similarity index 96% rename from spec/google/api_client/discovery_spec.rb rename to spec/legacy/google/api_client/discovery_spec.rb index d596538cca5..e68890cfad4 100644 --- a/spec/google/api_client/discovery_spec.rb +++ b/spec/legacy/google/api_client/discovery_spec.rb @@ -21,13 +21,13 @@ require 'multi_json' require 'compat/multi_json' require 'signet/oauth_1/client' -require 'google/api_client' +require 'legacy/google/api_client' -fixtures_path = File.expand_path('../../../fixtures', __FILE__) +fixtures_path = File.expand_path('../../../../fixtures', __FILE__) -RSpec.describe Google::APIClient do +RSpec.describe Legacy::Google::APIClient do include ConnectionHelpers - CLIENT = Google::APIClient.new(:application_name => 'API Client Tests') unless defined?(CLIENT) + CLIENT = Legacy::Google::APIClient.new(:application_name => 'API Client Tests') unless defined?(CLIENT) after do # Reset client to not-quite-pristine state @@ -37,17 +37,17 @@ it 'should raise a type error for bogus authorization' do expect(lambda do - Google::APIClient.new(:application_name => 'API Client Tests', :authorization => 42) + Legacy::Google::APIClient.new(:application_name => 'API Client Tests', :authorization => 42) end).to raise_error(TypeError) end it 'should not be able to retrieve the discovery document for a bogus API' do expect(lambda do CLIENT.discovery_document('bogus') - end).to raise_error(Google::APIClient::TransmissionError) + end).to raise_error(Legacy::Google::APIClient::TransmissionError) expect(lambda do CLIENT.discovered_api('bogus') - end).to raise_error(Google::APIClient::TransmissionError) + end).to raise_error(Legacy::Google::APIClient::TransmissionError) end it 'should raise an error for bogus services' do @@ -77,7 +77,7 @@ zoo_json = File.join(fixtures_path, 'files', 'zoo.json') contents = File.open(zoo_json, 'rb') { |io| io.read } api = CLIENT.register_discovery_document('zoo', 'v1', contents) - expect(api).to be_kind_of(Google::APIClient::API) + expect(api).to be_kind_of(Legacy::Google::APIClient::API) end end @@ -275,7 +275,7 @@ it 'should allow modification to the base URIs for testing purposes' do # Using a new client instance here to avoid caching rebased discovery doc prediction_rebase = - Google::APIClient.new(:application_name => 'API Client Tests').discovered_api('prediction', 'v1.2') + Legacy::Google::APIClient.new(:application_name => 'API Client Tests').discovered_api('prediction', 'v1.2') prediction_rebase.method_base = 'https://testing-domain.example.com/prediction/v1.2/' @@ -365,7 +365,7 @@ @prediction.training.insert, {'data' => '12345'} ) - end).to raise_error(Google::APIClient::ClientError) + end).to raise_error(Legacy::Google::APIClient::ClientError) end it 'should not be able to execute improperly authorized requests' do @@ -376,7 +376,7 @@ @prediction.training.insert, {'data' => '12345'} ) - end).to raise_error(Google::APIClient::ClientError) + end).to raise_error(Legacy::Google::APIClient::ClientError) end it 'should correctly handle unnamed parameters' do diff --git a/spec/google/api_client/gzip_spec.rb b/spec/legacy/google/api_client/gzip_spec.rb similarity index 94% rename from spec/google/api_client/gzip_spec.rb rename to spec/legacy/google/api_client/gzip_spec.rb index 0539b97d939..43b9fb3c40a 100644 --- a/spec/google/api_client/gzip_spec.rb +++ b/spec/legacy/google/api_client/gzip_spec.rb @@ -15,9 +15,9 @@ require 'spec_helper' -require 'google/api_client' +require 'legacy/google/api_client' -RSpec.describe Google::APIClient::Gzip do +RSpec.describe Legacy::Google::APIClient::Gzip do def create_connection(&block) Faraday.new do |b| @@ -59,7 +59,7 @@ def create_connection(&block) describe 'with API Client' do before do - @client = Google::APIClient.new(:application_name => 'test') + @client = Legacy::Google::APIClient.new(:application_name => 'test') @client.authorization = nil end diff --git a/spec/google/api_client/media_spec.rb b/spec/legacy/google/api_client/media_spec.rb similarity index 84% rename from spec/google/api_client/media_spec.rb rename to spec/legacy/google/api_client/media_spec.rb index 944981b187d..5806cbda7e5 100644 --- a/spec/google/api_client/media_spec.rb +++ b/spec/legacy/google/api_client/media_spec.rb @@ -14,21 +14,21 @@ require 'spec_helper' -require 'google/api_client' +require 'legacy/google/api_client' -fixtures_path = File.expand_path('../../../fixtures', __FILE__) +fixtures_path = File.expand_path('../../../../fixtures', __FILE__) -RSpec.describe Google::APIClient::UploadIO do +RSpec.describe Legacy::Google::APIClient::UploadIO do it 'should reject invalid file paths' do expect(lambda do - media = Google::APIClient::UploadIO.new('doesnotexist', 'text/plain') + media = Legacy::Google::APIClient::UploadIO.new('doesnotexist', 'text/plain') end).to raise_error end describe 'with a file' do before do @file = File.expand_path('files/sample.txt', fixtures_path) - @media = Google::APIClient::UploadIO.new(@file, 'text/plain') + @media = Legacy::Google::APIClient::UploadIO.new(@file, 'text/plain') end it 'should report the correct file length' do @@ -43,7 +43,7 @@ describe 'with StringIO' do before do @content = "hello world" - @media = Google::APIClient::UploadIO.new(StringIO.new(@content), 'text/plain', 'test.txt') + @media = Legacy::Google::APIClient::UploadIO.new(StringIO.new(@content), 'text/plain', 'test.txt') end it 'should report the correct file length' do @@ -56,10 +56,10 @@ end end -RSpec.describe Google::APIClient::RangedIO do +RSpec.describe Legacy::Google::APIClient::RangedIO do before do @source = StringIO.new("1234567890abcdef") - @io = Google::APIClient::RangedIO.new(@source, 1, 5) + @io = Legacy::Google::APIClient::RangedIO.new(@source, 1, 5) end it 'should return the correct range when read entirely' do @@ -104,8 +104,8 @@ end -RSpec.describe Google::APIClient::ResumableUpload do - CLIENT = Google::APIClient.new(:application_name => 'API Client Tests') unless defined?(CLIENT) +RSpec.describe Legacy::Google::APIClient::ResumableUpload do + CLIENT = Legacy::Google::APIClient.new(:application_name => 'API Client Tests') unless defined?(CLIENT) after do # Reset client to not-quite-pristine state @@ -116,8 +116,8 @@ before do @drive = CLIENT.discovered_api('drive', 'v2') @file = File.expand_path('files/sample.txt', fixtures_path) - @media = Google::APIClient::UploadIO.new(@file, 'text/plain') - @uploader = Google::APIClient::ResumableUpload.new( + @media = Legacy::Google::APIClient::UploadIO.new(@file, 'text/plain') + @uploader = Legacy::Google::APIClient::ResumableUpload.new( :media => @media, :api_method => @drive.files.insert, :uri => 'https://www.googleapis.com/upload/drive/v1/files/12345') @@ -171,7 +171,7 @@ end def mock_result(status, headers = {}) - reference = Google::APIClient::Reference.new(:api_method => @drive.files.insert) + reference = Legacy::Google::APIClient::Reference.new(:api_method => @drive.files.insert) double('result', :status => status, :headers => headers, :reference => reference) end diff --git a/spec/google/api_client/request_spec.rb b/spec/legacy/google/api_client/request_spec.rb similarity index 72% rename from spec/google/api_client/request_spec.rb rename to spec/legacy/google/api_client/request_spec.rb index c63f750dc6d..a796379a46e 100644 --- a/spec/google/api_client/request_spec.rb +++ b/spec/legacy/google/api_client/request_spec.rb @@ -14,13 +14,13 @@ require 'spec_helper' -require 'google/api_client' +require 'legacy/google/api_client' -RSpec.describe Google::APIClient::Request do - CLIENT = Google::APIClient.new(:application_name => 'API Client Tests') unless defined?(CLIENT) +RSpec.describe Legacy::Google::APIClient::Request do + CLIENT = Legacy::Google::APIClient.new(:application_name => 'API Client Tests') unless defined?(CLIENT) it 'should normalize parameter names to strings' do - request = Google::APIClient::Request.new(:uri => 'https://www.google.com', :parameters => { + request = Legacy::Google::APIClient::Request.new(:uri => 'https://www.google.com', :parameters => { :a => '1', 'b' => '2' }) expect(request.parameters['a']).to eq('1') diff --git a/spec/google/api_client/result_spec.rb b/spec/legacy/google/api_client/result_spec.rb similarity index 90% rename from spec/google/api_client/result_spec.rb rename to spec/legacy/google/api_client/result_spec.rb index 67c63b77cfc..362a651edd3 100644 --- a/spec/google/api_client/result_spec.rb +++ b/spec/legacy/google/api_client/result_spec.rb @@ -14,16 +14,16 @@ require 'spec_helper' -require 'google/api_client' +require 'legacy/google/api_client' -RSpec.describe Google::APIClient::Result do - CLIENT = Google::APIClient.new(:application_name => 'API Client Tests') unless defined?(CLIENT) +RSpec.describe Legacy::Google::APIClient::Result do + CLIENT = Legacy::Google::APIClient.new(:application_name => 'API Client Tests') unless defined?(CLIENT) describe 'with the plus API' do before do CLIENT.authorization = nil @plus = CLIENT.discovered_api('plus', 'v1') - @reference = Google::APIClient::Reference.new({ + @reference = Legacy::Google::APIClient::Reference.new({ :api_method => @plus.activities.list, :parameters => { 'userId' => 'me', @@ -65,7 +65,7 @@ } END_OF_STRING ) - @result = Google::APIClient::Result.new(@reference, @response) + @result = Legacy::Google::APIClient::Result.new(@reference, @response) end it 'should indicate a successful response' do @@ -91,7 +91,7 @@ it 'should return the result data correctly' do expect(@result.data?).to be_truthy expect(@result.data.class.to_s).to eq( - 'Google::APIClient::Schema::Plus::V1::ActivityFeed' + 'Legacy::Google::APIClient::Schema::Plus::V1::ActivityFeed' ) expect(@result.data.kind).to eq('plus#activityFeed') expect(@result.data.etag).to eq('FOO') @@ -124,7 +124,7 @@ } END_OF_STRING ) - @result = Google::APIClient::Result.new(@reference, @response) + @result = Legacy::Google::APIClient::Result.new(@reference, @response) end it 'should not return a next page token' do @@ -138,7 +138,7 @@ it 'should return the result data correctly' do expect(@result.data?).to be_truthy expect(@result.data.class.to_s).to eq( - 'Google::APIClient::Schema::Plus::V1::ActivityFeed' + 'Legacy::Google::APIClient::Schema::Plus::V1::ActivityFeed' ) expect(@result.data.kind).to eq('plus#activityFeed') expect(@result.data.etag).to eq('FOO') @@ -171,7 +171,7 @@ END_OF_STRING ) allow(@response).to receive(:status).and_return(400) - @result = Google::APIClient::Result.new(@reference, @response) + @result = Legacy::Google::APIClient::Result.new(@reference, @response) end it 'should return error status correctly' do @@ -188,7 +188,7 @@ allow(@response).to receive(:body).and_return('') allow(@response).to receive(:status).and_return(204) allow(@response).to receive(:headers).and_return({}) - @result = Google::APIClient::Result.new(@reference, @response) + @result = Legacy::Google::APIClient::Result.new(@reference, @response) end it 'should indicate no data is available' do diff --git a/spec/google/api_client/service_account_spec.rb b/spec/legacy/google/api_client/service_account_spec.rb similarity index 83% rename from spec/google/api_client/service_account_spec.rb rename to spec/legacy/google/api_client/service_account_spec.rb index 6314cea6bc1..194b83cf33e 100644 --- a/spec/google/api_client/service_account_spec.rb +++ b/spec/legacy/google/api_client/service_account_spec.rb @@ -14,17 +14,17 @@ require 'spec_helper' -require 'google/api_client' +require 'legacy/google/api_client' -fixtures_path = File.expand_path('../../../fixtures', __FILE__) +fixtures_path = File.expand_path('../../../../fixtures', __FILE__) -RSpec.describe Google::APIClient::KeyUtils do +RSpec.describe Legacy::Google::APIClient::KeyUtils do it 'should read PKCS12 files from the filesystem' do if RUBY_PLATFORM == 'java' && RUBY_VERSION.start_with?('1.8') pending "Reading from PKCS12 not supported on jruby 1.8.x" end path = File.expand_path('files/privatekey.p12', fixtures_path) - key = Google::APIClient::KeyUtils.load_from_pkcs12(path, 'notasecret') + key = Legacy::Google::APIClient::KeyUtils.load_from_pkcs12(path, 'notasecret') expect(key).not_to eq(nil) end @@ -34,26 +34,26 @@ end path = File.expand_path('files/privatekey.p12', fixtures_path) content = File.read(path) - key = Google::APIClient::KeyUtils.load_from_pkcs12(content, 'notasecret') + key = Legacy::Google::APIClient::KeyUtils.load_from_pkcs12(content, 'notasecret') expect(key).not_to eq(nil) end it 'should read PEM files from the filesystem' do path = File.expand_path('files/secret.pem', fixtures_path) - key = Google::APIClient::KeyUtils.load_from_pem(path, 'notasecret') + key = Legacy::Google::APIClient::KeyUtils.load_from_pem(path, 'notasecret') expect(key).not_to eq(nil) end it 'should read PEM files from loaded files' do path = File.expand_path('files/secret.pem', fixtures_path) content = File.read(path) - key = Google::APIClient::KeyUtils.load_from_pem(content, 'notasecret') + key = Legacy::Google::APIClient::KeyUtils.load_from_pem(content, 'notasecret') expect(key).not_to eq(nil) end end -RSpec.describe Google::APIClient::JWTAsserter do +RSpec.describe Legacy::Google::APIClient::JWTAsserter do include ConnectionHelpers before do @@ -61,7 +61,7 @@ end it 'should generate valid JWTs' do - asserter = Google::APIClient::JWTAsserter.new('client1', 'scope1 scope2', @key) + asserter = Legacy::Google::APIClient::JWTAsserter.new('client1', 'scope1 scope2', @key) jwt = asserter.to_authorization.to_jwt expect(jwt).not_to eq(nil) @@ -84,7 +84,7 @@ }'] end end - asserter = Google::APIClient::JWTAsserter.new('client1', 'scope1 scope2', @key) + asserter = Legacy::Google::APIClient::JWTAsserter.new('client1', 'scope1 scope2', @key) auth = asserter.authorize('user1@email.com', { :connection => conn }) expect(auth).not_to eq(nil?) expect(auth.person).to eq('user1@email.com') @@ -104,7 +104,7 @@ }'] end end - asserter = Google::APIClient::JWTAsserter.new('client1', 'scope1 scope2', @key) + asserter = Legacy::Google::APIClient::JWTAsserter.new('client1', 'scope1 scope2', @key) auth = asserter.authorize(nil, { :connection => conn }) expect(auth).not_to eq(nil?) expect(auth.access_token).to eq("1/abcdef1234567890") @@ -134,7 +134,7 @@ }'] end end - asserter = Google::APIClient::JWTAsserter.new('client1', 'scope1 scope2', @key) + asserter = Legacy::Google::APIClient::JWTAsserter.new('client1', 'scope1 scope2', @key) auth = asserter.authorize(nil, { :connection => conn }) expect(auth).not_to eq(nil?) expect(auth.access_token).to eq("1/abcdef1234567890") @@ -146,7 +146,7 @@ end end -RSpec.describe Google::APIClient::ComputeServiceAccount do +RSpec.describe Legacy::Google::APIClient::ComputeServiceAccount do include ConnectionHelpers it 'should query metadata server' do @@ -160,7 +160,7 @@ }'] end end - service_account = Google::APIClient::ComputeServiceAccount.new + service_account = Legacy::Google::APIClient::ComputeServiceAccount.new auth = service_account.fetch_access_token!({ :connection => conn }) expect(auth).not_to eq(nil?) expect(auth["access_token"]).to eq("1/abcdef1234567890") diff --git a/spec/google/api_client/service_spec.rb b/spec/legacy/google/api_client/service_spec.rb similarity index 86% rename from spec/google/api_client/service_spec.rb rename to spec/legacy/google/api_client/service_spec.rb index fbbdd53ee98..524f8c5feb1 100644 --- a/spec/google/api_client/service_spec.rb +++ b/spec/legacy/google/api_client/service_spec.rb @@ -16,31 +16,31 @@ require 'spec_helper' -require 'google/api_client' -require 'google/api_client/service' +require 'legacy/google/api_client' +require 'legacy/google/api_client/service' -fixtures_path = File.expand_path('../../../fixtures', __FILE__) +fixtures_path = File.expand_path('../../../../fixtures', __FILE__) -RSpec.describe Google::APIClient::Service do +RSpec.describe Legacy::Google::APIClient::Service do include ConnectionHelpers APPLICATION_NAME = 'API Client Tests' it 'should error out when called without an API name or version' do expect(lambda do - Google::APIClient::Service.new + Legacy::Google::APIClient::Service.new end).to raise_error(ArgumentError) end it 'should error out when called without an API version' do expect(lambda do - Google::APIClient::Service.new('foo') + Legacy::Google::APIClient::Service.new('foo') end).to raise_error(ArgumentError) end it 'should error out when the options hash is not a hash' do expect(lambda do - Google::APIClient::Service.new('foo', 'v1', 42) + Legacy::Google::APIClient::Service.new('foo', 'v1', 42) end).to raise_error(ArgumentError) end @@ -52,7 +52,7 @@ [200, {}, '{}'] end end - adsense = Google::APIClient::Service.new( + adsense = Legacy::Google::APIClient::Service.new( 'adsense', 'v1.3', { @@ -73,7 +73,7 @@ [200, {}, '{}'] end end - adsense = Google::APIClient::Service.new( + adsense = Legacy::Google::APIClient::Service.new( 'adsense', 'v1.3', { @@ -92,7 +92,7 @@ [200, {}, '{}'] end end - adsense = Google::APIClient::Service.new( + adsense = Legacy::Google::APIClient::Service.new( 'adsense', 'v1.3', { @@ -107,12 +107,12 @@ describe 'with no connection' do before do - @adsense = Google::APIClient::Service.new('adsense', 'v1.3', + @adsense = Legacy::Google::APIClient::Service.new('adsense', 'v1.3', {:application_name => APPLICATION_NAME, :cache_store => nil}) end it 'should return a resource when using a valid resource name' do - expect(@adsense.accounts).to be_a(Google::APIClient::Service::Resource) + expect(@adsense.accounts).to be_a(Legacy::Google::APIClient::Service::Resource) end it 'should throw an error when using an invalid resource name' do @@ -123,7 +123,7 @@ it 'should return a request when using a valid method name' do req = @adsense.adclients.list - expect(req).to be_a(Google::APIClient::Service::Request) + expect(req).to be_a(Legacy::Google::APIClient::Service::Request) expect(req.method.id).to eq('adsense.adclients.list') expect(req.parameters).to be_nil end @@ -136,7 +136,7 @@ it 'should return a valid request with parameters' do req = @adsense.adunits.list(:adClientId => '1') - expect(req).to be_a(Google::APIClient::Service::Request) + expect(req).to be_a(Legacy::Google::APIClient::Service::Request) expect(req.method.id).to eq('adsense.adunits.list') expect(req.parameters).not_to be_nil expect(req.parameters[:adClientId]).to eq('1') @@ -153,7 +153,7 @@ [200, {}, '{}'] end end - prediction = Google::APIClient::Service.new( + prediction = Legacy::Google::APIClient::Service.new( 'prediction', 'v1.5', { @@ -174,7 +174,7 @@ [200, {}, '{}'] end end - prediction = Google::APIClient::Service.new( + prediction = Legacy::Google::APIClient::Service.new( 'prediction', 'v1.5', { @@ -190,13 +190,13 @@ describe 'with no connection' do before do - @prediction = Google::APIClient::Service.new('prediction', 'v1.5', + @prediction = Legacy::Google::APIClient::Service.new('prediction', 'v1.5', {:application_name => APPLICATION_NAME, :cache_store => nil}) end it 'should return a valid request with a body' do req = @prediction.trainedmodels.insert(:project => '1').body({'id' => '1'}) - expect(req).to be_a(Google::APIClient::Service::Request) + expect(req).to be_a(Legacy::Google::APIClient::Service::Request) expect(req.method.id).to eq('prediction.trainedmodels.insert') expect(req.body).to eq({'id' => '1'}) expect(req.parameters).not_to be_nil @@ -205,7 +205,7 @@ it 'should return a valid request with a body when using resource name' do req = @prediction.trainedmodels.insert(:project => '1').training({'id' => '1'}) - expect(req).to be_a(Google::APIClient::Service::Request) + expect(req).to be_a(Legacy::Google::APIClient::Service::Request) expect(req.method.id).to eq('prediction.trainedmodels.insert') expect(req.training).to eq({'id' => '1'}) expect(req.parameters).not_to be_nil @@ -222,7 +222,7 @@ 'description' => 'The best home movie ever made' } @file = File.expand_path('files/sample.txt', fixtures_path) - @media = Google::APIClient::UploadIO.new(@file, 'text/plain') + @media = Legacy::Google::APIClient::UploadIO.new(@file, 'text/plain') end it 'should make a valid call with an object body and media upload' do @@ -232,7 +232,7 @@ [200, {}, '{}'] end end - drive = Google::APIClient::Service.new( + drive = Legacy::Google::APIClient::Service.new( 'drive', 'v2', { @@ -248,13 +248,13 @@ describe 'with no connection' do before do - @drive = Google::APIClient::Service.new('drive', 'v2', + @drive = Legacy::Google::APIClient::Service.new('drive', 'v2', {:application_name => APPLICATION_NAME, :cache_store => nil}) end it 'should return a valid request with a body and media upload' do req = @drive.files.insert(:uploadType => 'multipart').body(@metadata).media(@media) - expect(req).to be_a(Google::APIClient::Service::Request) + expect(req).to be_a(Legacy::Google::APIClient::Service::Request) expect(req.method.id).to eq('drive.files.insert') expect(req.body).to eq(@metadata) expect(req.media).to eq(@media) @@ -264,7 +264,7 @@ it 'should return a valid request with a body and media upload when using resource name' do req = @drive.files.insert(:uploadType => 'multipart').file(@metadata).media(@media) - expect(req).to be_a(Google::APIClient::Service::Request) + expect(req).to be_a(Legacy::Google::APIClient::Service::Request) expect(req.method.id).to eq('drive.files.insert') expect(req.file).to eq(@metadata) expect(req.media).to eq(@media) @@ -276,7 +276,7 @@ describe 'with the Discovery API' do it 'should make a valid end-to-end request' do - discovery = Google::APIClient::Service.new('discovery', 'v1', + discovery = Legacy::Google::APIClient::Service.new('discovery', 'v1', {:application_name => APPLICATION_NAME, :authenticated => false, :cache_store => nil}) result = discovery.apis.get_rest(:api => 'discovery', :version => 'v1').execute @@ -288,13 +288,13 @@ end -RSpec.describe Google::APIClient::Service::Result do +RSpec.describe Legacy::Google::APIClient::Service::Result do describe 'with the plus API' do before do - @plus = Google::APIClient::Service.new('plus', 'v1', + @plus = Legacy::Google::APIClient::Service.new('plus', 'v1', {:application_name => APPLICATION_NAME, :cache_store => nil}) - @reference = Google::APIClient::Reference.new({ + @reference = Legacy::Google::APIClient::Reference.new({ :api_method => @plus.activities.list.method, :parameters => { 'userId' => 'me', @@ -336,8 +336,8 @@ } END_OF_STRING allow(@response).to receive(:body).and_return(@body) - base_result = Google::APIClient::Result.new(@reference, @response) - @result = Google::APIClient::Service::Result.new(@request, base_result) + base_result = Legacy::Google::APIClient::Result.new(@reference, @response) + @result = Legacy::Google::APIClient::Service::Result.new(@request, base_result) end it 'should indicate a successful response' do @@ -368,7 +368,7 @@ it 'should return the result data correctly' do expect(@result.data?).to be_truthy expect(@result.data.class.to_s).to eq( - 'Google::APIClient::Schema::Plus::V1::ActivityFeed' + 'Legacy::Google::APIClient::Schema::Plus::V1::ActivityFeed' ) expect(@result.data.kind).to eq('plus#activityFeed') expect(@result.data.etag).to eq('FOO') @@ -400,8 +400,8 @@ } END_OF_STRING allow(@response).to receive(:body).and_return(@body) - base_result = Google::APIClient::Result.new(@reference, @response) - @result = Google::APIClient::Service::Result.new(@request, base_result) + base_result = Legacy::Google::APIClient::Result.new(@reference, @response) + @result = Legacy::Google::APIClient::Service::Result.new(@request, base_result) end it 'should not return a next page token' do @@ -419,7 +419,7 @@ it 'should return the result data correctly' do expect(@result.data?).to be_truthy expect(@result.data.class.to_s).to eq( - 'Google::APIClient::Schema::Plus::V1::ActivityFeed' + 'Legacy::Google::APIClient::Schema::Plus::V1::ActivityFeed' ) expect(@result.data.kind).to eq('plus#activityFeed') expect(@result.data.etag).to eq('FOO') @@ -451,8 +451,8 @@ END_OF_STRING allow(@response).to receive(:body).and_return(@body) allow(@response).to receive(:status).and_return(400) - base_result = Google::APIClient::Result.new(@reference, @response) - @result = Google::APIClient::Service::Result.new(@request, base_result) + base_result = Legacy::Google::APIClient::Result.new(@reference, @response) + @result = Legacy::Google::APIClient::Service::Result.new(@request, base_result) end it 'should return error status correctly' do @@ -473,8 +473,8 @@ allow(@response).to receive(:body).and_return('') allow(@response).to receive(:status).and_return(204) allow(@response).to receive(:headers).and_return({}) - base_result = Google::APIClient::Result.new(@reference, @response) - @result = Google::APIClient::Service::Result.new(@request, base_result) + base_result = Legacy::Google::APIClient::Result.new(@reference, @response) + @result = Legacy::Google::APIClient::Service::Result.new(@request, base_result) end it 'should indicate no data is available' do @@ -492,7 +492,7 @@ end end -RSpec.describe Google::APIClient::Service::BatchRequest do +RSpec.describe Legacy::Google::APIClient::Service::BatchRequest do include ConnectionHelpers @@ -503,7 +503,7 @@ [500, {'Content-Type' => 'application/json'}, '{}'] end end - @discovery = Google::APIClient::Service.new('discovery', 'v1', + @discovery = Legacy::Google::APIClient::Service.new('discovery', 'v1', {:application_name => APPLICATION_NAME, :authorization => nil, :cache_store => nil, :connection => @conn}) @calls = [ @@ -522,7 +522,7 @@ describe 'with the discovery API' do before do - @discovery = Google::APIClient::Service.new('discovery', 'v1', + @discovery = Legacy::Google::APIClient::Service.new('discovery', 'v1', {:application_name => APPLICATION_NAME, :authorization => nil, :cache_store => nil}) end @@ -615,4 +615,4 @@ end end end -end \ No newline at end of file +end diff --git a/spec/google/api_client/simple_file_store_spec.rb b/spec/legacy/google/api_client/simple_file_store_spec.rb similarity index 93% rename from spec/google/api_client/simple_file_store_spec.rb rename to spec/legacy/google/api_client/simple_file_store_spec.rb index cb7d8984759..e83606a8375 100644 --- a/spec/google/api_client/simple_file_store_spec.rb +++ b/spec/legacy/google/api_client/simple_file_store_spec.rb @@ -16,16 +16,16 @@ require 'spec_helper' -require 'google/api_client/service/simple_file_store' +require 'legacy/google/api_client/service/simple_file_store' -RSpec.describe Google::APIClient::Service::SimpleFileStore do +RSpec.describe Legacy::Google::APIClient::Service::SimpleFileStore do FILE_NAME = 'test.cache' describe 'with no cache file' do before(:each) do File.delete(FILE_NAME) if File.exists?(FILE_NAME) - @cache = Google::APIClient::Service::SimpleFileStore.new(FILE_NAME) + @cache = Legacy::Google::APIClient::Service::SimpleFileStore.new(FILE_NAME) end it 'should return nil when asked if a key exists' do @@ -64,7 +64,7 @@ describe 'with an existing cache' do before(:each) do File.delete(FILE_NAME) if File.exists?(FILE_NAME) - @cache = Google::APIClient::Service::SimpleFileStore.new(FILE_NAME) + @cache = Legacy::Google::APIClient::Service::SimpleFileStore.new(FILE_NAME) @cache.write('existing_key', 'existing_value') end @@ -130,4 +130,4 @@ after(:all) do File.delete(FILE_NAME) if File.exists?(FILE_NAME) end -end \ No newline at end of file +end diff --git a/spec/google/api_client_spec.rb b/spec/legacy/google/api_client_spec.rb similarity index 96% rename from spec/google/api_client_spec.rb rename to spec/legacy/google/api_client_spec.rb index eb9a59af7b8..3caabddea3f 100644 --- a/spec/google/api_client_spec.rb +++ b/spec/legacy/google/api_client_spec.rb @@ -16,7 +16,7 @@ require 'faraday' require 'signet/oauth_1/client' -require 'google/api_client' +require 'legacy/google/api_client' shared_examples_for 'configurable user agent' do include ConnectionHelpers @@ -54,18 +54,18 @@ end end -RSpec.describe Google::APIClient do +RSpec.describe Legacy::Google::APIClient do include ConnectionHelpers - let(:client) { Google::APIClient.new(:application_name => 'API Client Tests') } + let(:client) { Legacy::Google::APIClient.new(:application_name => 'API Client Tests') } it "should pass the faraday options provided on initialization to FaraDay configuration block" do - client = Google::APIClient.new(faraday_option: {timeout: 999}) + client = Legacy::Google::APIClient.new(faraday_option: {timeout: 999}) expect(client.connection.options.timeout).to be == 999 end it 'should make its version number available' do - expect(Google::APIClient::VERSION::STRING).to be_instance_of(String) + expect(Legacy::Google::APIClient::VERSION::STRING).to be_instance_of(String) end it 'should default to OAuth 2' do diff --git a/yard/lib/yard/templates/helpers/wiki_helper.rb b/yard/lib/yard/templates/helpers/wiki_helper.rb index e03dfb66814..f5debde9a12 100644 --- a/yard/lib/yard/templates/helpers/wiki_helper.rb +++ b/yard/lib/yard/templates/helpers/wiki_helper.rb @@ -262,7 +262,7 @@ def link_url(url, title = nil, params = {}) # @param [CodeObjects::Base] object the object to get an anchor for # @return [String] the anchor for a specific object def anchor_for(object) - # Method:_Google::APIClient#execute! + # Method:_Legacy::Google::APIClient#execute! case object when CodeObjects::MethodObject if object.scope == :instance