# See the License for the specific language governing permissions and
# limitations under the License.
-
-require 'stringio'
-require 'json'
+require 'faraday'
+require 'faraday/utils'
+require 'multi_json'
+require 'compat/multi_json'
require 'addressable/uri'
+require 'stringio'
require 'google/api_client/discovery'
+# TODO - needs some serious cleanup
module Google
class APIClient
class Reference
+ MULTIPART_BOUNDARY = "-----------RubyApiMultipartPost".freeze
+
def initialize(options={})
- # We only need this to do lookups on method ID String values
- # It's optional, but method ID lookups will fail if the client is
- # omitted.
- @client = options[:client]
- @version = options[:version] || 'v1'
+ self.connection = options[:connection] || Faraday.default_connection
+ self.authorization = options[:authorization]
self.api_method = options[:api_method]
+
self.parameters = options[:parameters] || {}
# 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]
- self.headers = options[:headers] || []
- if options[:body]
+
+ self.headers = options[:headers] || {}
+ if options[:media]
+ self.initialize_media_upload
+ elsif options[:body]
self.body = options[:body]
- elsif options[:merged_body]
- self.merged_body = options[:merged_body]
elsif options[:body_object]
- if options[:body_object].respond_to?(:to_json)
- serialized_body = options[:body_object].to_json
- elsif options[:body_object].respond_to?(:to_hash)
- serialized_body = JSON.generate(options[:body_object].to_hash)
- else
- raise TypeError,
- 'Could not convert body object to JSON.' +
- 'Must respond to :to_json or :to_hash.'
- end
- self.merged_body = serialized_body
+ self.headers['Content-Type'] ||= 'application/json'
+ self.body = serialize_body(options[:body_object])
else
- self.merged_body = ''
+ self.body = ''
end
unless self.api_method
self.http_method = options[:http_method] || 'GET'
self.uri = options[:uri]
unless self.parameters.empty?
- self.uri.query_values =
- (self.uri.query_values || {}).merge(self.parameters)
+ self.uri.query = Addressable::URI.form_encode(self.parameters)
end
end
end
+ def initialize_media_upload
+ 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.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]))
+ env = {
+ :request_headers => {'Content-Type' => "multipart/related;boundary=#{MULTIPART_BOUNDARY}"},
+ :request => { :boundary => MULTIPART_BOUNDARY }
+ }
+ multipart = Faraday::Request::Multipart.new
+ self.body = multipart.create_multipart(env, [
+ [nil,Faraday::UploadIO.new(metadata, 'application/json', 'file.json')],
+ [nil, self.media]])
+ self.headers.update(env[:request_headers])
+ 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
+ else
+ raise ArgumentError, "Invalid uploadType for media"
+ end
+ end
+
+ def serialize_body(body)
+ return body.to_json if body.respond_to?(:to_json)
+ return MultiJson.dump(options[:body_object].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
+
+ def media
+ return @media
+ end
+
+ def media=(media)
+ @media = (media)
+ end
+
+ def upload_type
+ return self.parameters['uploadType'] || self.parameters['upload_type']
+ end
+
+ def authorization
+ return @authorization
+ end
+
+ def authorization=(new_authorization)
+ @authorization = new_authorization
+ end
+
+ def connection
+ return @connection
+ end
+
+ def connection=(new_connection)
+ if new_connection.kind_of?(Faraday::Connection)
+ @connection = new_connection
+ else
+ raise TypeError,
+ "Expected Faraday::Connection, got #{new_connection.class}."
+ end
+ end
+
def api_method
return @api_method
end
if new_api_method.kind_of?(Google::APIClient::Method) ||
new_api_method == nil
@api_method = new_api_method
- elsif new_api_method.respond_to?(:to_str) ||
- new_api_method.kind_of?(Symbol)
- unless @client
- raise ArgumentError,
- "API method lookup impossible without client instance."
- end
- new_api_method = new_api_method.to_s
- # This method of guessing the API is unreliable. This will fail for
- # APIs where the first segment of the RPC name does not match the
- # service name. However, this is a fallback mechanism anyway.
- # Developers should be passing in a reference to the method, rather
- # than passing in a string or symbol. This should raise an error
- # in the case of a mismatch.
- api = new_api_method[/^([^.]+)\./, 1]
- @api_method = @client.discovered_method(
- new_api_method, api, @version
- )
- if @api_method
- # Ditch the client reference, we won't need it again.
- @client = nil
- else
- raise ArgumentError, "API method could not be found."
- end
else
raise TypeError,
"Expected Google::APIClient::Method, got #{new_api_method.class}."
end
def parameters=(new_parameters)
- # No type-checking needed, the Method class handles this.
- @parameters = new_parameters
+ @parameters = Hash[new_parameters]
end
def body
end
def body=(new_body)
- if new_body.respond_to?(:each)
- @body = new_body
- else
- raise TypeError, "Expected body to respond to :each."
- end
- end
-
- def merged_body
- return (self.body.inject(StringIO.new) do |accu, chunk|
- accu.write(chunk)
- accu
- end).string
- end
-
- def merged_body=(new_merged_body)
- if new_merged_body.respond_to?(:string)
- new_merged_body = new_merged_body.string
- elsif new_merged_body.respond_to?(:to_str)
- new_merged_body = new_merged_body.to_str
- else
- raise TypeError,
- "Expected String or StringIO, got #{new_merged_body.class}."
- end
- self.body = [new_merged_body]
+ @body = new_body
end
def headers
- return @headers ||= []
+ return @headers ||= {}
end
def headers=(new_headers)
def http_method=(new_http_method)
if new_http_method.kind_of?(Symbol)
- @http_method = new_http_method.to_s.upcase
+ @http_method = new_http_method.to_s.downcase.to_sym
elsif new_http_method.respond_to?(:to_str)
- @http_method = new_http_method.to_str.upcase
+ @http_method = new_http_method.to_s.downcase.to_sym
else
raise TypeError,
"Expected String or Symbol, got #{new_http_method.class}."
@uri = Addressable::URI.parse(new_uri)
end
- def to_request
- if self.api_method
- return self.api_method.generate_request(
- self.parameters, self.merged_body, self.headers
+ def to_http_request
+ request = (
+ if self.api_method
+ self.api_method.generate_request(
+ self.parameters, self.body, self.headers, :connection => self.connection
+ )
+ else
+ self.connection.build_request(self.http_method) do |req|
+ req.url(self.uri.to_str)
+ req.headers.update(self.headers)
+ req.body = self.body
+ end
+ end)
+
+ if self.authorization.respond_to?(:generate_authenticated_request)
+ request = self.authorization.generate_authenticated_request(
+ :request => request,
+ :connection => self.connection
)
- else
- return [self.http_method, self.uri, self.headers, self.body]
end
+ return request
end
def to_hash
end
options[:headers] = self.headers
options[:body] = self.body
+ options[:connection] = self.connection
+ unless self.authorization.nil?
+ options[:authorization] = self.authorization
+ end
return options
end
end