Merge pull request #87 from sgomes/new_programming_interface
New programming interface for the client library
This commit is contained in:
commit
0d7b3d040f
|
@ -0,0 +1,205 @@
|
|||
# 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'
|
||||
|
||||
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
|
||||
|
||||
# Cache for discovered APIs.
|
||||
@@discovered = {}
|
||||
|
||||
##
|
||||
# Creates a new Service.
|
||||
#
|
||||
# @param [String, Symbol] api_name
|
||||
# The name of the API this service will access.
|
||||
# @param [String, Symbol] api_version
|
||||
# The version of the API this service will access.
|
||||
# @param [Hash] options
|
||||
# The configuration parameters for the service.
|
||||
# @option options [Symbol, #generate_authenticated_request] :authorization
|
||||
# (:oauth_1)
|
||||
# The authorization mechanism used by the client. The following
|
||||
# mechanisms are supported out-of-the-box:
|
||||
# <ul>
|
||||
# <li><code>:two_legged_oauth_1</code></li>
|
||||
# <li><code>:oauth_1</code></li>
|
||||
# <li><code>:oauth_2</code></li>
|
||||
# </ul>
|
||||
# @option options [Boolean] :auto_refresh_token (true)
|
||||
# The setting that controls whether or not the api client attempts to
|
||||
# refresh authorization when a 401 is hit in #execute. If the token does
|
||||
# not support it, this option is ignored.
|
||||
# @option options [String] :application_name
|
||||
# The name of the application using the client.
|
||||
# @option options [String] :application_version
|
||||
# The version number of the application using the client.
|
||||
# @option options [String] :host ("www.googleapis.com")
|
||||
# The API hostname used by the client. This rarely needs to be changed.
|
||||
# @option options [String] :port (443)
|
||||
# The port number used by the client. This rarely needs to be changed.
|
||||
# @option options [String] :discovery_path ("/discovery/v1")
|
||||
# The discovery base path. This rarely needs to be changed.
|
||||
# @option options [String] :ca_file
|
||||
# Optional set of root certificates to use when validating SSL connections.
|
||||
# By default, a bundled set of trusted roots will be used.
|
||||
# @option options [#generate_authenticated_request] :authorization
|
||||
# The authorization mechanism for requests. Used only if
|
||||
# `:authenticated` is `true`.
|
||||
# @option options [TrueClass, FalseClass] :authenticated (default: true)
|
||||
# `true` if requests must be signed or somehow
|
||||
# authenticated, `false` otherwise.
|
||||
# @option options [TrueClass, FalseClass] :gzip (default: true)
|
||||
# `true` if gzip enabled, `false` otherwise.
|
||||
# @option options [Faraday::Connection] :connection
|
||||
# A custom connection to be used for all requests.
|
||||
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)
|
||||
@client.logger = options[:logger] if options.include? :logger
|
||||
|
||||
@connection = options[:connection] || @client.connection
|
||||
|
||||
@options = options
|
||||
|
||||
# Cache discovered APIs in memory.
|
||||
# Not thread-safe, but the worst that can happen is a cache miss.
|
||||
unless @api = @@discovered[[api_name, api_version]]
|
||||
@@discovered[[api_name, api_version]] = @api = @client.discovered_api(
|
||||
api_name, api_version)
|
||||
end
|
||||
|
||||
generate_call_stubs(self, @api)
|
||||
end
|
||||
|
||||
##
|
||||
# Logger for the Service.
|
||||
#
|
||||
# @return [Logger]
|
||||
# The logger instance.
|
||||
def_delegators :@client, :logger, :logger=
|
||||
|
||||
##
|
||||
# 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
|
||||
|
||||
##
|
||||
# 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)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,103 @@
|
|||
# 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
|
||||
}
|
||||
@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
|
|
@ -0,0 +1,144 @@
|
|||
# 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
|
|
@ -0,0 +1,40 @@
|
|||
# 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
|
|
@ -0,0 +1,162 @@
|
|||
# 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
|
|
@ -0,0 +1,59 @@
|
|||
# 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
|
||||
##
|
||||
# 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 = Google::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 = Google::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
|
|
@ -0,0 +1,572 @@
|
|||
# encoding:utf-8
|
||||
|
||||
# Copyright 2013 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
require 'google/api_client'
|
||||
require 'google/api_client/service'
|
||||
|
||||
fixtures_path = File.expand_path('../../../fixtures', __FILE__)
|
||||
|
||||
describe Google::APIClient::Service do
|
||||
include ConnectionHelpers
|
||||
|
||||
APPLICATION_NAME = 'API Client Tests'
|
||||
|
||||
it 'should error out when called without an API name or version' do
|
||||
(lambda do
|
||||
Google::APIClient::Service.new
|
||||
end).should raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it 'should error out when called without an API version' do
|
||||
(lambda do
|
||||
Google::APIClient::Service.new('foo')
|
||||
end).should raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it 'should error out when the options hash is not a hash' do
|
||||
(lambda do
|
||||
Google::APIClient::Service.new('foo', 'v1', 42)
|
||||
end).should raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
describe 'with the AdSense Management API' do
|
||||
|
||||
it 'should make a valid call for a method with no parameters' do
|
||||
conn = stub_connection do |stub|
|
||||
stub.get('/adsense/v1.3/adclients') do |env|
|
||||
end
|
||||
end
|
||||
adsense = Google::APIClient::Service.new(
|
||||
'adsense',
|
||||
'v1.3',
|
||||
{
|
||||
:application_name => APPLICATION_NAME,
|
||||
:authenticated => false,
|
||||
:connection => conn
|
||||
}
|
||||
)
|
||||
|
||||
req = adsense.adclients.list.execute()
|
||||
conn.verify
|
||||
end
|
||||
|
||||
it 'should make a valid call for a method with parameters' do
|
||||
conn = stub_connection do |stub|
|
||||
stub.get('/adsense/v1.3/adclients/1/adunits') do |env|
|
||||
end
|
||||
end
|
||||
adsense = Google::APIClient::Service.new(
|
||||
'adsense',
|
||||
'v1.3',
|
||||
{
|
||||
:application_name => APPLICATION_NAME,
|
||||
:authenticated => false,
|
||||
:connection => conn
|
||||
}
|
||||
)
|
||||
req = adsense.adunits.list(:adClientId => '1').execute()
|
||||
end
|
||||
|
||||
it 'should make a valid call for a deep method' do
|
||||
conn = stub_connection do |stub|
|
||||
stub.get('/adsense/v1.3/accounts/1/adclients') do |env|
|
||||
end
|
||||
end
|
||||
adsense = Google::APIClient::Service.new(
|
||||
'adsense',
|
||||
'v1.3',
|
||||
{
|
||||
:application_name => APPLICATION_NAME,
|
||||
:authenticated => false,
|
||||
:connection => conn
|
||||
}
|
||||
)
|
||||
req = adsense.accounts.adclients.list(:accountId => '1').execute()
|
||||
end
|
||||
|
||||
describe 'with no connection' do
|
||||
before do
|
||||
@adsense = Google::APIClient::Service.new('adsense', 'v1.3',
|
||||
{:application_name => APPLICATION_NAME})
|
||||
end
|
||||
|
||||
it 'should return a resource when using a valid resource name' do
|
||||
@adsense.accounts.should be_a(Google::APIClient::Service::Resource)
|
||||
end
|
||||
|
||||
it 'should throw an error when using an invalid resource name' do
|
||||
(lambda do
|
||||
@adsense.invalid_resource
|
||||
end).should raise_error
|
||||
end
|
||||
|
||||
it 'should return a request when using a valid method name' do
|
||||
req = @adsense.adclients.list
|
||||
req.should be_a(Google::APIClient::Service::Request)
|
||||
req.method.id.should == 'adsense.adclients.list'
|
||||
req.parameters.should be_nil
|
||||
end
|
||||
|
||||
it 'should throw an error when using an invalid method name' do
|
||||
(lambda do
|
||||
@adsense.adclients.invalid_method
|
||||
end).should raise_error
|
||||
end
|
||||
|
||||
it 'should return a valid request with parameters' do
|
||||
req = @adsense.adunits.list(:adClientId => '1')
|
||||
req.should be_a(Google::APIClient::Service::Request)
|
||||
req.method.id.should == 'adsense.adunits.list'
|
||||
req.parameters.should_not be_nil
|
||||
req.parameters[:adClientId].should == '1'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with the Prediction API' do
|
||||
|
||||
it 'should make a valid call with an object body' do
|
||||
conn = stub_connection do |stub|
|
||||
stub.post('/prediction/v1.5/trainedmodels?project=1') do |env|
|
||||
env.body.should == '{"id":"1"}'
|
||||
end
|
||||
end
|
||||
prediction = Google::APIClient::Service.new(
|
||||
'prediction',
|
||||
'v1.5',
|
||||
{
|
||||
:application_name => APPLICATION_NAME,
|
||||
:authenticated => false,
|
||||
:connection => conn
|
||||
}
|
||||
)
|
||||
req = prediction.trainedmodels.insert(:project => '1').body({'id' => '1'}).execute()
|
||||
conn.verify
|
||||
end
|
||||
|
||||
it 'should make a valid call with a text body' do
|
||||
conn = stub_connection do |stub|
|
||||
stub.post('/prediction/v1.5/trainedmodels?project=1') do |env|
|
||||
env.body.should == '{"id":"1"}'
|
||||
end
|
||||
end
|
||||
prediction = Google::APIClient::Service.new(
|
||||
'prediction',
|
||||
'v1.5',
|
||||
{
|
||||
:application_name => APPLICATION_NAME,
|
||||
:authenticated => false,
|
||||
:connection => conn
|
||||
}
|
||||
)
|
||||
req = prediction.trainedmodels.insert(:project => '1').body('{"id":"1"}').execute()
|
||||
conn.verify
|
||||
end
|
||||
|
||||
describe 'with no connection' do
|
||||
before do
|
||||
@prediction = Google::APIClient::Service.new('prediction', 'v1.5',
|
||||
{:application_name => APPLICATION_NAME})
|
||||
end
|
||||
|
||||
it 'should return a valid request with a body' do
|
||||
req = @prediction.trainedmodels.insert(:project => '1').body({'id' => '1'})
|
||||
req.should be_a(Google::APIClient::Service::Request)
|
||||
req.method.id.should == 'prediction.trainedmodels.insert'
|
||||
req.body.should == {'id' => '1'}
|
||||
req.parameters.should_not be_nil
|
||||
req.parameters[:project].should == '1'
|
||||
end
|
||||
|
||||
it 'should return a valid request with a body when using resource name' do
|
||||
req = @prediction.trainedmodels.insert(:project => '1').training({'id' => '1'})
|
||||
req.should be_a(Google::APIClient::Service::Request)
|
||||
req.method.id.should == 'prediction.trainedmodels.insert'
|
||||
req.training.should == {'id' => '1'}
|
||||
req.parameters.should_not be_nil
|
||||
req.parameters[:project].should == '1'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with the Drive API' do
|
||||
|
||||
before do
|
||||
@metadata = {
|
||||
'title' => 'My movie',
|
||||
'description' => 'The best home movie ever made'
|
||||
}
|
||||
@file = File.expand_path('files/sample.txt', fixtures_path)
|
||||
@media = Google::APIClient::UploadIO.new(@file, 'text/plain')
|
||||
end
|
||||
|
||||
it 'should make a valid call with an object body and media upload' do
|
||||
conn = stub_connection do |stub|
|
||||
stub.post('/upload/drive/v1/files?uploadType=multipart') do |env|
|
||||
env.body.should be_a Faraday::CompositeReadIO
|
||||
end
|
||||
end
|
||||
drive = Google::APIClient::Service.new(
|
||||
'drive',
|
||||
'v1',
|
||||
{
|
||||
:application_name => APPLICATION_NAME,
|
||||
:authenticated => false,
|
||||
:connection => conn
|
||||
}
|
||||
)
|
||||
req = drive.files.insert(:uploadType => 'multipart').body(@metadata).media(@media).execute()
|
||||
conn.verify
|
||||
end
|
||||
|
||||
describe 'with no connection' do
|
||||
before do
|
||||
@drive = Google::APIClient::Service.new('drive', 'v1',
|
||||
{:application_name => APPLICATION_NAME})
|
||||
end
|
||||
|
||||
it 'should return a valid request with a body and media upload' do
|
||||
req = @drive.files.insert(:uploadType => 'multipart').body(@metadata).media(@media)
|
||||
req.should be_a(Google::APIClient::Service::Request)
|
||||
req.method.id.should == 'drive.files.insert'
|
||||
req.body.should == @metadata
|
||||
req.media.should == @media
|
||||
req.parameters.should_not be_nil
|
||||
req.parameters[:uploadType].should == 'multipart'
|
||||
end
|
||||
|
||||
it 'should return a valid request with a body and media upload when using resource name' do
|
||||
req = @drive.files.insert(:uploadType => 'multipart').file(@metadata).media(@media)
|
||||
req.should be_a(Google::APIClient::Service::Request)
|
||||
req.method.id.should == 'drive.files.insert'
|
||||
req.file.should == @metadata
|
||||
req.media.should == @media
|
||||
req.parameters.should_not be_nil
|
||||
req.parameters[:uploadType].should == 'multipart'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with the Discovery API' do
|
||||
it 'should make a valid end-to-end request' do
|
||||
discovery = Google::APIClient::Service.new('discovery', 'v1',
|
||||
{:application_name => APPLICATION_NAME, :authenticated => false})
|
||||
result = discovery.apis.get_rest(:api => 'discovery', :version => 'v1').execute
|
||||
result.should_not be_nil
|
||||
result.data.name.should == 'discovery'
|
||||
result.data.version.should == 'v1'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe Google::APIClient::Service::Result do
|
||||
|
||||
describe 'with the plus API' do
|
||||
before do
|
||||
@plus = Google::APIClient::Service.new('plus', 'v1',
|
||||
{:application_name => APPLICATION_NAME})
|
||||
@reference = Google::APIClient::Reference.new({
|
||||
:api_method => @plus.activities.list.method,
|
||||
:parameters => {
|
||||
'userId' => 'me',
|
||||
'collection' => 'public',
|
||||
'maxResults' => 20
|
||||
}
|
||||
})
|
||||
@request = @plus.activities.list(:userId => 'me', :collection => 'public',
|
||||
:maxResults => 20)
|
||||
|
||||
# Response double
|
||||
@response = double("response")
|
||||
@response.stub(:status).and_return(200)
|
||||
@response.stub(:headers).and_return({
|
||||
'etag' => '12345',
|
||||
'x-google-apiary-auth-scopes' =>
|
||||
'https://www.googleapis.com/auth/plus.me',
|
||||
'content-type' => 'application/json; charset=UTF-8',
|
||||
'date' => 'Mon, 23 Apr 2012 00:00:00 GMT',
|
||||
'cache-control' => 'private, max-age=0, must-revalidate, no-transform',
|
||||
'server' => 'GSE',
|
||||
'connection' => 'close'
|
||||
})
|
||||
end
|
||||
|
||||
describe 'with a next page token' do
|
||||
before do
|
||||
@body = <<-END_OF_STRING
|
||||
{
|
||||
"kind": "plus#activityFeed",
|
||||
"etag": "FOO",
|
||||
"nextPageToken": "NEXT+PAGE+TOKEN",
|
||||
"selfLink": "https://www.googleapis.com/plus/v1/people/foo/activities/public?",
|
||||
"nextLink": "https://www.googleapis.com/plus/v1/people/foo/activities/public?maxResults=20&pageToken=NEXT%2BPAGE%2BTOKEN",
|
||||
"title": "Plus Public Activity Feed for ",
|
||||
"updated": "2012-04-23T00:00:00.000Z",
|
||||
"id": "123456790",
|
||||
"items": []
|
||||
}
|
||||
END_OF_STRING
|
||||
@response.stub(:body).and_return(@body)
|
||||
base_result = Google::APIClient::Result.new(@reference, @response)
|
||||
@result = Google::APIClient::Service::Result.new(@request, base_result)
|
||||
end
|
||||
|
||||
it 'should indicate a successful response' do
|
||||
@result.error?.should be_false
|
||||
end
|
||||
|
||||
it 'should return the correct next page token' do
|
||||
@result.next_page_token.should == 'NEXT+PAGE+TOKEN'
|
||||
end
|
||||
|
||||
it 'generate a correct request when calling next_page' do
|
||||
next_page_request = @result.next_page
|
||||
next_page_request.parameters.should include('pageToken')
|
||||
next_page_request.parameters['pageToken'].should == 'NEXT+PAGE+TOKEN'
|
||||
@request.parameters.each_pair do |param, value|
|
||||
next_page_request.parameters[param].should == value
|
||||
end
|
||||
end
|
||||
|
||||
it 'should return content type correctly' do
|
||||
@result.media_type.should == 'application/json'
|
||||
end
|
||||
|
||||
it 'should return the body correctly' do
|
||||
@result.body.should == @body
|
||||
end
|
||||
|
||||
it 'should return the result data correctly' do
|
||||
@result.data?.should be_true
|
||||
@result.data.class.to_s.should ==
|
||||
'Google::APIClient::Schema::Plus::V1::ActivityFeed'
|
||||
@result.data.kind.should == 'plus#activityFeed'
|
||||
@result.data.etag.should == 'FOO'
|
||||
@result.data.nextPageToken.should == 'NEXT+PAGE+TOKEN'
|
||||
@result.data.selfLink.should ==
|
||||
'https://www.googleapis.com/plus/v1/people/foo/activities/public?'
|
||||
@result.data.nextLink.should ==
|
||||
'https://www.googleapis.com/plus/v1/people/foo/activities/public?' +
|
||||
'maxResults=20&pageToken=NEXT%2BPAGE%2BTOKEN'
|
||||
@result.data.title.should == 'Plus Public Activity Feed for '
|
||||
@result.data.id.should == "123456790"
|
||||
@result.data.items.should be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe 'without a next page token' do
|
||||
before do
|
||||
@body = <<-END_OF_STRING
|
||||
{
|
||||
"kind": "plus#activityFeed",
|
||||
"etag": "FOO",
|
||||
"selfLink": "https://www.googleapis.com/plus/v1/people/foo/activities/public?",
|
||||
"title": "Plus Public Activity Feed for ",
|
||||
"updated": "2012-04-23T00:00:00.000Z",
|
||||
"id": "123456790",
|
||||
"items": []
|
||||
}
|
||||
END_OF_STRING
|
||||
@response.stub(:body).and_return(@body)
|
||||
base_result = Google::APIClient::Result.new(@reference, @response)
|
||||
@result = Google::APIClient::Service::Result.new(@request, base_result)
|
||||
end
|
||||
|
||||
it 'should not return a next page token' do
|
||||
@result.next_page_token.should == nil
|
||||
end
|
||||
|
||||
it 'should return content type correctly' do
|
||||
@result.media_type.should == 'application/json'
|
||||
end
|
||||
|
||||
it 'should return the body correctly' do
|
||||
@result.body.should == @body
|
||||
end
|
||||
|
||||
it 'should return the result data correctly' do
|
||||
@result.data?.should be_true
|
||||
@result.data.class.to_s.should ==
|
||||
'Google::APIClient::Schema::Plus::V1::ActivityFeed'
|
||||
@result.data.kind.should == 'plus#activityFeed'
|
||||
@result.data.etag.should == 'FOO'
|
||||
@result.data.selfLink.should ==
|
||||
'https://www.googleapis.com/plus/v1/people/foo/activities/public?'
|
||||
@result.data.title.should == 'Plus Public Activity Feed for '
|
||||
@result.data.id.should == "123456790"
|
||||
@result.data.items.should be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with JSON error response' do
|
||||
before do
|
||||
@body = <<-END_OF_STRING
|
||||
{
|
||||
"error": {
|
||||
"errors": [
|
||||
{
|
||||
"domain": "global",
|
||||
"reason": "parseError",
|
||||
"message": "Parse Error"
|
||||
}
|
||||
],
|
||||
"code": 400,
|
||||
"message": "Parse Error"
|
||||
}
|
||||
}
|
||||
END_OF_STRING
|
||||
@response.stub(:body).and_return(@body)
|
||||
@response.stub(:status).and_return(400)
|
||||
base_result = Google::APIClient::Result.new(@reference, @response)
|
||||
@result = Google::APIClient::Service::Result.new(@request, base_result)
|
||||
end
|
||||
|
||||
it 'should return error status correctly' do
|
||||
@result.error?.should be_true
|
||||
end
|
||||
|
||||
it 'should return the correct error message' do
|
||||
@result.error_message.should == 'Parse Error'
|
||||
end
|
||||
|
||||
it 'should return the body correctly' do
|
||||
@result.body.should == @body
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with 204 No Content response' do
|
||||
before do
|
||||
@response.stub(:body).and_return('')
|
||||
@response.stub(:status).and_return(204)
|
||||
@response.stub(:headers).and_return({})
|
||||
base_result = Google::APIClient::Result.new(@reference, @response)
|
||||
@result = Google::APIClient::Service::Result.new(@request, base_result)
|
||||
end
|
||||
|
||||
it 'should indicate no data is available' do
|
||||
@result.data?.should be_false
|
||||
end
|
||||
|
||||
it 'should return nil for data' do
|
||||
@result.data.should == nil
|
||||
end
|
||||
|
||||
it 'should return nil for media_type' do
|
||||
@result.media_type.should == nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe Google::APIClient::Service::BatchRequest do
|
||||
describe 'with the discovery API' do
|
||||
before do
|
||||
@discovery = Google::APIClient::Service.new('discovery', 'v1',
|
||||
{:application_name => APPLICATION_NAME, :authorization => nil})
|
||||
end
|
||||
|
||||
describe 'with two valid requests' do
|
||||
before do
|
||||
@calls = [
|
||||
@discovery.apis.get_rest(:api => 'plus', :version => 'v1'),
|
||||
@discovery.apis.get_rest(:api => 'discovery', :version => 'v1')
|
||||
]
|
||||
end
|
||||
|
||||
it 'should execute both when using a global callback' do
|
||||
block_called = 0
|
||||
batch = @discovery.batch(@calls) do |result|
|
||||
block_called += 1
|
||||
result.status.should == 200
|
||||
end
|
||||
|
||||
batch.execute
|
||||
block_called.should == 2
|
||||
end
|
||||
|
||||
it 'should execute both when using individual callbacks' do
|
||||
call1_returned, call2_returned = false, false
|
||||
batch = @discovery.batch
|
||||
|
||||
batch.add(@calls[0]) do |result|
|
||||
call1_returned = true
|
||||
result.status.should == 200
|
||||
result.call_index.should == 0
|
||||
end
|
||||
|
||||
batch.add(@calls[1]) do |result|
|
||||
call2_returned = true
|
||||
result.status.should == 200
|
||||
result.call_index.should == 1
|
||||
end
|
||||
|
||||
batch.execute
|
||||
call1_returned.should == true
|
||||
call2_returned.should == true
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with a valid request and an invalid one' do
|
||||
before do
|
||||
@calls = [
|
||||
@discovery.apis.get_rest(:api => 'plus', :version => 'v1'),
|
||||
@discovery.apis.get_rest(:api => 'invalid', :version => 'invalid')
|
||||
]
|
||||
end
|
||||
|
||||
it 'should execute both when using a global callback' do
|
||||
block_called = 0
|
||||
batch = @discovery.batch(@calls) do |result|
|
||||
block_called += 1
|
||||
if result.call_index == 0
|
||||
result.status.should == 200
|
||||
else
|
||||
result.status.should >= 400
|
||||
result.status.should < 500
|
||||
end
|
||||
end
|
||||
|
||||
batch.execute
|
||||
block_called.should == 2
|
||||
end
|
||||
|
||||
it 'should execute both when using individual callbacks' do
|
||||
call1_returned, call2_returned = false, false
|
||||
batch = @discovery.batch
|
||||
|
||||
batch.add(@calls[0]) do |result|
|
||||
call1_returned = true
|
||||
result.status.should == 200
|
||||
result.call_index.should == 0
|
||||
end
|
||||
|
||||
batch.add(@calls[1]) do |result|
|
||||
call2_returned = true
|
||||
result.status.should >= 400
|
||||
result.status.should < 500
|
||||
result.call_index.should == 1
|
||||
end
|
||||
|
||||
batch.execute
|
||||
call1_returned.should == true
|
||||
call2_returned.should == true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue