This commit is contained in:
Steven Bazyl 2013-01-02 11:51:15 -08:00
commit 250e9e7b2d
6 changed files with 71 additions and 35 deletions

View File

@ -23,11 +23,15 @@ The Google API Ruby Client makes it trivial to discover and access supported
APIs. APIs.
TEXT TEXT
PKG_FILES = FileList[ list = FileList[
'lib/**/*', 'spec/**/*', 'vendor/**/*', 'lib/**/*', 'spec/**/*', 'vendor/**/*',
'tasks/**/*', 'website/**/*', 'tasks/**/*', 'website/**/*',
'[A-Z]*', 'Rakefile' '[A-Z]*', 'Rakefile'
].exclude(/database\.yml/).exclude(/[_\.]git$/) ].exclude(/[_\.]git$/)
(open(".gitignore") { |file| file.read }).split("\n").each do |pattern|
list.exclude(pattern)
end
PKG_FILES = list
RCOV_ENABLED = !!(RUBY_PLATFORM != 'java' && RUBY_VERSION =~ /^1\.8/) RCOV_ENABLED = !!(RUBY_PLATFORM != 'java' && RUBY_VERSION =~ /^1\.8/)
if RCOV_ENABLED if RCOV_ENABLED

View File

@ -6,16 +6,16 @@ Gem::Specification.new do |s|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Bob Aman", "Steve Bazyl"] s.authors = ["Bob Aman", "Steve Bazyl"]
s.date = "2012-10-25" s.date = "2012-11-19"
s.description = "The Google API Ruby Client makes it trivial to discover and access supported\nAPIs.\n" s.description = "The Google API Ruby Client makes it trivial to discover and access supported\nAPIs.\n"
s.email = "sbazyl@google.com" s.email = "sbazyl@google.com"
s.executables = ["google-api"] s.executables = ["google-api"]
s.extra_rdoc_files = ["README.md"] s.extra_rdoc_files = ["README.md"]
s.files = ["lib/compat", "lib/compat/multi_json.rb", "lib/google", "lib/google/api_client", "lib/google/api_client/auth", "lib/google/api_client/auth/jwt_asserter.rb", "lib/google/api_client/auth/pkcs12.rb", "lib/google/api_client/batch.rb", "lib/google/api_client/client_secrets.rb", "lib/google/api_client/discovery", "lib/google/api_client/discovery/api.rb", "lib/google/api_client/discovery/media.rb", "lib/google/api_client/discovery/method.rb", "lib/google/api_client/discovery/resource.rb", "lib/google/api_client/discovery/schema.rb", "lib/google/api_client/discovery.rb", "lib/google/api_client/environment.rb", "lib/google/api_client/errors.rb", "lib/google/api_client/media.rb", "lib/google/api_client/reference.rb", "lib/google/api_client/request.rb", "lib/google/api_client/result.rb", "lib/google/api_client/service_account.rb", "lib/google/api_client/version.rb", "lib/google/api_client.rb", "lib/google/inflection.rb", "spec/fixtures", "spec/fixtures/files", "spec/fixtures/files/sample.txt", "spec/google", "spec/google/api_client", "spec/google/api_client/batch_spec.rb", "spec/google/api_client/discovery_spec.rb", "spec/google/api_client/media_spec.rb", "spec/google/api_client/result_spec.rb", "spec/google/api_client/service_account_spec.rb", "spec/google/api_client_spec.rb", "spec/spec_helper.rb", "tasks/gem.rake", "tasks/git.rake", "tasks/metrics.rake", "tasks/spec.rake", "tasks/wiki.rake", "tasks/yard.rake", "CHANGELOG.md", "Gemfile", "Gemfile.lock", "LICENSE", "Rakefile", "README.md", "bin/google-api"] s.files = ["lib/compat", "lib/compat/multi_json.rb", "lib/google", "lib/google/api_client", "lib/google/api_client.rb", "lib/google/api_client/auth", "lib/google/api_client/auth/jwt_asserter.rb", "lib/google/api_client/auth/key_utils.rb", "lib/google/api_client/auth/pkcs12.rb", "lib/google/api_client/batch.rb", "lib/google/api_client/client_secrets.rb", "lib/google/api_client/discovery", "lib/google/api_client/discovery.rb", "lib/google/api_client/discovery/api.rb", "lib/google/api_client/discovery/media.rb", "lib/google/api_client/discovery/method.rb", "lib/google/api_client/discovery/resource.rb", "lib/google/api_client/discovery/schema.rb", "lib/google/api_client/environment.rb", "lib/google/api_client/errors.rb", "lib/google/api_client/media.rb", "lib/google/api_client/reference.rb", "lib/google/api_client/request.rb", "lib/google/api_client/result.rb", "lib/google/api_client/service_account.rb", "lib/google/api_client/version.rb", "lib/google/inflection.rb", "spec/fixtures", "spec/fixtures/files", "spec/fixtures/files/privatekey.p12", "spec/fixtures/files/sample.txt", "spec/fixtures/files/secret.pem", "spec/google", "spec/google/api_client", "spec/google/api_client/batch_spec.rb", "spec/google/api_client/discovery_spec.rb", "spec/google/api_client/media_spec.rb", "spec/google/api_client/result_spec.rb", "spec/google/api_client/service_account_spec.rb", "spec/google/api_client_spec.rb", "spec/spec_helper.rb", "tasks/gem.rake", "tasks/git.rake", "tasks/metrics.rake", "tasks/spec.rake", "tasks/wiki.rake", "tasks/yard.rake", "CHANGELOG.md", "Gemfile", "LICENSE", "README.md", "Rakefile", "bin/google-api"]
s.homepage = "http://code.google.com/p/google-api-ruby-client/" s.homepage = "http://code.google.com/p/google-api-ruby-client/"
s.rdoc_options = ["--main", "README.md"] s.rdoc_options = ["--main", "README.md"]
s.require_paths = ["lib"] s.require_paths = ["lib"]
s.rubygems_version = "1.8.10" s.rubygems_version = "1.8.24"
s.summary = "Package Summary" s.summary = "Package Summary"
if s.respond_to? :specification_version then if s.respond_to? :specification_version then

View File

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

View File

@ -52,6 +52,10 @@ module Google
# <li><code>:oauth_1</code></li> # <li><code>:oauth_1</code></li>
# <li><code>:oauth_2</code></li> # <li><code>:oauth_2</code></li>
# </ul> # </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 # @option options [String] :application_name
# The name of the application using the client. # The name of the application using the client.
# @option options [String] :application_version # @option options [String] :application_version
@ -95,6 +99,7 @@ module Google
# default authentication mechanisms. # default authentication mechanisms.
self.authorization = self.authorization =
options.key?(:authorization) ? options[:authorization] : :oauth_2 options.key?(:authorization) ? options[:authorization] : :oauth_2
self.auto_refresh_token = options.fetch(:auto_refresh_token){ true }
self.key = options[:key] self.key = options[:key]
self.user_ip = options[:user_ip] self.user_ip = options[:user_ip]
@discovery_uris = {} @discovery_uris = {}
@ -165,6 +170,13 @@ module Google
# @return [String] The API key. # @return [String] The API key.
attr_accessor :key attr_accessor :key
##
# 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 IP address of the user this request is being performed on behalf of. # The IP address of the user this request is being performed on behalf of.
# #
@ -546,7 +558,7 @@ module Google
request.authorization = options[:authorization] || self.authorization unless options[:authenticated] == false request.authorization = options[:authorization] || self.authorization unless options[:authenticated] == false
result = request.send(connection) result = request.send(connection)
if result.status == 401 && authorization.respond_to?(:refresh_token) if result.status == 401 && authorization.respond_to?(:refresh_token) && auto_refresh_token
begin begin
authorization.fetch_access_token! authorization.fetch_access_token!
result = request.send(connection) result = request.send(connection)

View File

@ -27,7 +27,7 @@ module Google
# Represents an API request. # Represents an API request.
class Request class Request
MULTIPART_BOUNDARY = "-----------RubyApiMultipartPost".freeze MULTIPART_BOUNDARY = "-----------RubyApiMultipartPost".freeze
# @return [Hash] Request parameters # @return [Hash] Request parameters
attr_reader :parameters attr_reader :parameters
# @return [Hash] Additional HTTP headers # @return [Hash] Additional HTTP headers
@ -42,7 +42,7 @@ module Google
attr_accessor :authenticated attr_accessor :authenticated
# @return [#read, #to_str] Request body # @return [#read, #to_str] Request body
attr_accessor :body attr_accessor :body
## ##
# Build a request # Build a request
# #
@ -52,7 +52,7 @@ module Google
# @option options [Google::APIClient::Method] :api_method # @option options [Google::APIClient::Method] :api_method
# API method to invoke. Either :api_method or :uri must be specified # API method to invoke. Either :api_method or :uri must be specified
# @option options [TrueClass, FalseClass] :authenticated # @option options [TrueClass, FalseClass] :authenticated
# True if request should include credentials. Implicitly true if # True if request should include credentials. Implicitly true if
# unspecified and :authorization present # unspecified and :authorization present
# @option options [#generate_signed_request] :authorization # @option options [#generate_signed_request] :authorization
# OAuth credentials # OAuth credentials
@ -74,12 +74,12 @@ module Google
self.api_method = options[:api_method] self.api_method = options[:api_method]
self.authenticated = options[:authenticated] self.authenticated = options[:authenticated]
self.authorization = options[:authorization] self.authorization = options[:authorization]
# These parameters are handled differently because they're not # These parameters are handled differently because they're not
# parameters to the API method, but rather to the API system. # parameters to the API method, but rather to the API system.
self.parameters['key'] ||= options[:key] if options[:key] self.parameters['key'] ||= options[:key] if options[:key]
self.parameters['userIp'] ||= options[:user_ip] if options[:user_ip] self.parameters['userIp'] ||= options[:user_ip] if options[:user_ip]
if options[:media] if options[:media]
self.initialize_media_upload(options) self.initialize_media_upload(options)
elsif options[:body] elsif options[:body]
@ -90,13 +90,13 @@ module Google
else else
self.body = '' self.body = ''
end end
unless self.api_method unless self.api_method
self.http_method = options[:http_method] || 'GET' self.http_method = options[:http_method] || 'GET'
self.uri = options[:uri] self.uri = options[:uri]
end end
end end
# @!attribute [r] upload_type # @!attribute [r] upload_type
# @return [String] protocol used for upload # @return [String] protocol used for upload
def upload_type def upload_type
@ -128,7 +128,7 @@ module Google
"Expected Google::APIClient::Method, got #{new_api_method.class}." "Expected Google::APIClient::Method, got #{new_api_method.class}."
end end
end end
# @!attribute uri # @!attribute uri
# @return [Addressable::URI] URI to send request # @return [Addressable::URI] URI to send request
def uri def uri
@ -145,15 +145,15 @@ module Google
# #
# @api private # @api private
# #
# @param [Faraday::Connection] connection # @param [Faraday::Connection] connection
# the connection to transmit with # the connection to transmit with
# #
# @return [Google::APIClient::Result] # @return [Google::APIClient::Result]
# result of API request # result of API request
def send(connection) def send(connection)
http_response = connection.app.call(self.to_env(connection)) http_response = connection.app.call(self.to_env(connection))
result = self.process_http_response(http_response) result = self.process_http_response(http_response)
# Resumamble slightly different than other upload protocols in that it requires at least # Resumamble slightly different than other upload protocols in that it requires at least
# 2 requests. # 2 requests.
if self.upload_type == 'resumable' if self.upload_type == 'resumable'
@ -164,7 +164,7 @@ module Google
end end
return result return result
end end
# Convert to an HTTP request. Returns components in order of method, URI, # Convert to an HTTP request. Returns components in order of method, URI,
# request headers, and body # request headers, and body
# #
@ -172,7 +172,7 @@ module Google
# #
# @return [Array<(Symbol, Addressable::URI, Hash, [#read,#to_str])>] # @return [Array<(Symbol, Addressable::URI, Hash, [#read,#to_str])>]
def to_http_request def to_http_request
request = ( request = (
if self.uri if self.uri
unless self.parameters.empty? unless self.parameters.empty?
self.uri.query = Addressable::URI.form_encode(self.parameters) self.uri.query = Addressable::URI.form_encode(self.parameters)
@ -204,7 +204,7 @@ module Google
end end
return options return options
end end
## ##
# Prepares the request for execution, building a hash of parts # Prepares the request for execution, building a hash of parts
# suitable for sending to Faraday::Connection. # suitable for sending to Faraday::Connection.
@ -233,7 +233,7 @@ module Google
request_env = http_request.to_env(connection) request_env = http_request.to_env(connection)
end end
## ##
# Convert HTTP response to an API Result # Convert HTTP response to an API Result
# #
@ -247,9 +247,9 @@ module Google
def process_http_response(response) def process_http_response(response)
Result.new(self, response) Result.new(self, response)
end end
protected protected
## ##
# Adjust headers & body for media uploads # Adjust headers & body for media uploads
# #
@ -269,14 +269,14 @@ module Google
self.media = options[:media] self.media = options[:media]
case self.upload_type case self.upload_type
when "media" when "media"
if options[:body] || options[:body_object] if options[:body] || options[:body_object]
raise ArgumentError, "Can not specify body & body object for simple uploads" raise ArgumentError, "Can not specify body & body object for simple uploads"
end end
self.headers['Content-Type'] ||= self.media.content_type self.headers['Content-Type'] ||= self.media.content_type
self.body = self.media self.body = self.media
when "multipart" when "multipart"
unless options[:body_object] unless options[:body_object]
raise ArgumentError, "Multipart requested but no body object" raise ArgumentError, "Multipart requested but no body object"
end end
metadata = StringIO.new(serialize_body(options[:body_object])) metadata = StringIO.new(serialize_body(options[:body_object]))
build_multipart([Faraday::UploadIO.new(metadata, 'application/json', 'file.json'), self.media]) build_multipart([Faraday::UploadIO.new(metadata, 'application/json', 'file.json'), self.media])
@ -286,13 +286,13 @@ module Google
self.headers['X-Upload-Content-Length'] = file_length.to_s self.headers['X-Upload-Content-Length'] = file_length.to_s
if options[:body_object] if options[:body_object]
self.headers['Content-Type'] ||= 'application/json' self.headers['Content-Type'] ||= 'application/json'
self.body = serialize_body(options[:body_object]) self.body = serialize_body(options[:body_object])
else else
self.body = '' self.body = ''
end end
end end
end end
## ##
# Assemble a multipart message from a set of parts # Assemble a multipart message from a set of parts
# #
@ -304,7 +304,7 @@ module Google
# MIME type of the message # MIME type of the message
# @param [String] boundary # @param [String] boundary
# Boundary for separating each part of the message # Boundary for separating each part of the message
def build_multipart(parts, mime_type = 'multipart/related', boundary = MULTIPART_BOUNDARY) def build_multipart(parts, mime_type = 'multipart/related', boundary = MULTIPART_BOUNDARY)
env = { env = {
:request_headers => {'Content-Type' => "#{mime_type};boundary=#{boundary}"}, :request_headers => {'Content-Type' => "#{mime_type};boundary=#{boundary}"},
:request => { :boundary => boundary } :request => { :boundary => boundary }
@ -313,10 +313,10 @@ module Google
self.body = multipart.create_multipart(env, parts.map {|part| [nil, part]}) self.body = multipart.create_multipart(env, parts.map {|part| [nil, part]})
self.headers.update(env[:request_headers]) self.headers.update(env[:request_headers])
end end
## ##
# Serialize body object to JSON # Serialize body object to JSON
# #
# @api private # @api private
# #
# @param [#to_json,#to_hash] body # @param [#to_json,#to_hash] body
@ -326,7 +326,7 @@ module Google
# JSON # JSON
def serialize_body(body) def serialize_body(body)
return body.to_json if body.respond_to?(:to_json) return body.to_json if body.respond_to?(:to_json)
return MultiJson.dump(options[:body_object].to_hash) if body.respond_to?(:to_hash) return MultiJson.dump(body.to_hash) if body.respond_to?(:to_hash)
raise TypeError, 'Could not convert body object to JSON.' + raise TypeError, 'Could not convert body object to JSON.' +
'Must respond to :to_json or :to_hash.' 'Must respond to :to_json or :to_hash.'
end end

View File

@ -214,6 +214,23 @@ describe Google::APIClient do
conn.verify conn.verify
end end
it 'should generate valid requests when parameter value includes semicolon' do
conn = stub_connection do |stub|
# semicolon (;) in parameter value was being converted to
# bare ampersand (&) in 0.4.7. ensure that it gets converted
# to a CGI-escaped semicolon (%3B) instead.
stub.post('/prediction/v1.2/training?data=12345%3B67890') do |env|
env[:body].should == ''
end
end
request = CLIENT.execute(
:api_method => @prediction.training.insert,
:parameters => {'data' => '12345;67890'},
:connection => conn
)
conn.verify
end
it 'should generate valid requests when repeated parameters are passed' do it 'should generate valid requests when repeated parameters are passed' do
pending("This is caused by Faraday's encoding of query parameters.") pending("This is caused by Faraday's encoding of query parameters.")
conn = stub_connection do |stub| conn = stub_connection do |stub|