1 # Copyright 2010 Google Inc.
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
16 gem 'faraday', '~> 0.7.0'
18 require 'faraday/utils'
20 require 'addressable/uri'
22 require 'google/api_client/discovery'
29 MULTIPART_BOUNDARY = "-----------RubyApiMultipartPost".freeze
30 def initialize(options={})
31 # We only need this to do lookups on method ID String values
32 # It's optional, but method ID lookups will fail if the client is
34 @client = options[:client]
35 @version = options[:version] || 'v1'
37 self.connection = options[:connection] || Faraday.default_connection
38 self.api_method = options[:api_method]
39 self.parameters = options[:parameters] || {}
40 # These parameters are handled differently because they're not
41 # parameters to the API method, but rather to the API system.
42 self.parameters['key'] ||= options[:key] if options[:key]
43 self.parameters['userIp'] ||= options[:user_ip] if options[:user_ip]
44 self.headers = options[:headers] || {}
47 self.media = options[:media]
48 upload_type = parameters['uploadType'] || parameters['upload_type']
51 if options[:body] || options[:body_object]
52 raise ArgumentError, "Can not specify body & body object for simple uploads"
54 self.headers['Content-Type'] ||= self.media.content_type
55 self.body = self.media
57 unless options[:body_object]
58 raise ArgumentError, "Multipart requested but no body object"
60 # This is all a bit of a hack due to signet requiring body to be a string
61 # Ideally, update signet to delay serialization so we can just pass
62 # streams all the way down through to the HTTP lib
63 metadata = StringIO.new(serialize_body(options[:body_object]))
65 :request_headers => {'Content-Type' => "multipart/related;boundary=#{MULTIPART_BOUNDARY}"},
66 :request => { :boundary => MULTIPART_BOUNDARY }
68 multipart = Faraday::Request::Multipart.new
69 self.body = multipart.create_multipart(env, [
70 [nil,Faraday::UploadIO.new(metadata, 'application/json', 'file.json')],
72 self.headers.update(env[:request_headers])
74 file_length = self.media.length
75 self.headers['X-Upload-Content-Type'] = self.media.content_type
76 self.headers['X-Upload-Content-Length'] = file_length.to_s
77 if options[:body_object]
78 self.headers['Content-Type'] ||= 'application/json'
79 self.body = serialize_body(options[:body_object])
84 raise ArgumentError, "Invalid uploadType for media"
87 self.body = options[:body]
88 elsif options[:body_object]
89 self.headers['Content-Type'] ||= 'application/json'
90 self.body = serialize_body(options[:body_object])
94 unless self.api_method
95 self.http_method = options[:http_method] || 'GET'
96 self.uri = options[:uri]
97 unless self.parameters.empty?
98 self.uri.query_values =
99 (self.uri.query_values || {}).merge(self.parameters)
104 def serialize_body(body)
105 return body.to_json if body.respond_to?(:to_json)
106 return MultiJson.encode(options[:body_object].to_hash) if body.respond_to?(:to_hash)
107 raise TypeError, 'Could not convert body object to JSON.' +
108 'Must respond to :to_json or :to_hash.'
123 def connection=(new_connection)
124 if new_connection.kind_of?(Faraday::Connection)
125 @connection = new_connection
128 "Expected Faraday::Connection, got #{new_connection.class}."
136 def api_method=(new_api_method)
137 if new_api_method.kind_of?(Google::APIClient::Method) ||
138 new_api_method == nil
139 @api_method = new_api_method
140 elsif new_api_method.respond_to?(:to_str) ||
141 new_api_method.kind_of?(Symbol)
144 "API method lookup impossible without client instance."
146 new_api_method = new_api_method.to_s
147 # This method of guessing the API is unreliable. This will fail for
148 # APIs where the first segment of the RPC name does not match the
149 # service name. However, this is a fallback mechanism anyway.
150 # Developers should be passing in a reference to the method, rather
151 # than passing in a string or symbol. This should raise an error
152 # in the case of a mismatch.
153 api = new_api_method[/^([^.]+)\./, 1]
154 @api_method = @client.discovered_method(
155 new_api_method, api, @version
158 # Ditch the client reference, we won't need it again.
161 raise ArgumentError, "API method could not be found."
165 "Expected Google::APIClient::Method, got #{new_api_method.class}."
173 def parameters=(new_parameters)
174 # No type-checking needed, the Method class handles this.
175 @parameters = new_parameters
183 if new_body.respond_to?(:to_str)
184 @body = new_body.to_str
185 elsif new_body.respond_to?(:read)
186 @body = new_body.read()
187 elsif new_body.respond_to?(:inject)
188 @body = (new_body.inject(StringIO.new) do |accu, chunk|
193 raise TypeError, "Expected body to be String, IO, or Enumerable chunks."
198 return @headers ||= {}
201 def headers=(new_headers)
202 if new_headers.kind_of?(Array) || new_headers.kind_of?(Hash)
203 @headers = new_headers
205 raise TypeError, "Expected Hash or Array, got #{new_headers.class}."
210 return @http_method ||= self.api_method.http_method
213 def http_method=(new_http_method)
214 if new_http_method.kind_of?(Symbol)
215 @http_method = new_http_method.to_s.upcase
216 elsif new_http_method.respond_to?(:to_str)
217 @http_method = new_http_method.to_str.upcase
220 "Expected String or Symbol, got #{new_http_method.class}."
225 return @uri ||= self.api_method.generate_uri(self.parameters)
229 @uri = Addressable::URI.parse(new_uri)
234 return self.api_method.generate_request(
235 self.parameters, self.body, self.headers
238 return Faraday::Request.create(
239 self.http_method.to_s.downcase.to_sym
241 req.url(Addressable::URI.parse(self.uri))
242 req.headers = Faraday::Utils::Headers.new(self.headers)
251 options[:api_method] = self.api_method
252 options[:parameters] = self.parameters
254 options[:http_method] = self.http_method
255 options[:uri] = self.uri
257 options[:headers] = self.headers
258 options[:body] = self.body
259 options[:connection] = self.connection