Resolving issues introduced by Faraday dependency upgrade.
[arvados.git] / lib / google / api_client / reference.rb
1 # Copyright 2010 Google Inc.
2 #
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
6 #
7 #      http://www.apache.org/licenses/LICENSE-2.0
8 #
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.
14
15
16 gem 'faraday', '~> 0.8.1'
17 require 'faraday'
18 require 'faraday/utils'
19 require 'multi_json'
20 require 'addressable/uri'
21 require 'stringio'
22 require 'google/api_client/discovery'
23
24 # TODO - needs some serious cleanup
25
26 module Google
27   class APIClient
28     class Reference
29       MULTIPART_BOUNDARY = "-----------RubyApiMultipartPost".freeze
30
31       def initialize(options={})
32         # We only need this to do lookups on method ID String values
33         # It's optional, but method ID lookups will fail if the client is
34         # omitted.
35         @client = options[:client]
36         @version = options[:version] || 'v1'
37
38         self.connection = options[:connection] || Faraday.default_connection
39         self.authorization = options[:authorization]
40         self.api_method = options[:api_method]
41         self.parameters = options[:parameters] || {}
42         # These parameters are handled differently because they're not
43         # parameters to the API method, but rather to the API system.
44         self.parameters['key'] ||= options[:key] if options[:key]
45         self.parameters['userIp'] ||= options[:user_ip] if options[:user_ip]
46         self.headers = options[:headers] || {}
47         if options[:media]
48           self.media = options[:media]
49           upload_type = parameters['uploadType'] || parameters['upload_type']
50           case upload_type
51           when "media"
52             if options[:body] || options[:body_object]
53               raise ArgumentError, "Can not specify body & body object for simple uploads"
54             end
55             self.headers['Content-Type'] ||= self.media.content_type
56             self.body = self.media
57           when "multipart"
58             unless options[:body_object]
59               raise ArgumentError, "Multipart requested but no body object"
60             end
61             # This is all a bit of a hack due to signet requiring body to be a string
62             # Ideally, update signet to delay serialization so we can just pass
63             # streams all the way down through to the HTTP lib
64             metadata = StringIO.new(serialize_body(options[:body_object]))
65             env = {
66               :request_headers => {'Content-Type' => "multipart/related;boundary=#{MULTIPART_BOUNDARY}"},
67               :request => { :boundary => MULTIPART_BOUNDARY }
68             }
69             multipart = Faraday::Request::Multipart.new
70             self.body = multipart.create_multipart(env, [
71               [nil,Faraday::UploadIO.new(metadata, 'application/json', 'file.json')],
72               [nil, self.media]])
73             self.headers.update(env[:request_headers])
74           when "resumable"
75             file_length = self.media.length
76             self.headers['X-Upload-Content-Type'] = self.media.content_type
77             self.headers['X-Upload-Content-Length'] = file_length.to_s
78             if options[:body_object]
79               self.headers['Content-Type'] ||= 'application/json'
80               self.body = serialize_body(options[:body_object])
81             else
82               self.body = ''
83             end
84           else
85             raise ArgumentError, "Invalid uploadType for media"
86           end
87         elsif options[:body]
88           self.body = options[:body]
89         elsif options[:body_object]
90           self.headers['Content-Type'] ||= 'application/json'
91           self.body = serialize_body(options[:body_object])
92         else
93           self.body = ''
94         end
95         unless self.api_method
96           self.http_method = options[:http_method] || 'GET'
97           self.uri = options[:uri]
98           unless self.parameters.empty?
99             self.uri.query_values =
100               (self.uri.query_values || {}).merge(self.parameters)
101           end
102         end
103       end
104
105       def serialize_body(body)
106         return body.to_json if body.respond_to?(:to_json)
107         return MultiJson.dump(options[:body_object].to_hash) if body.respond_to?(:to_hash)
108         raise TypeError, 'Could not convert body object to JSON.' +
109                          'Must respond to :to_json or :to_hash.'
110       end
111
112       def media
113         return @media
114       end
115
116       def media=(media)
117         @media = (media)
118       end
119
120       def authorization
121         return @authorization
122       end
123
124       def authorization=(new_authorization)
125         @authorization = new_authorization
126       end
127
128       def connection
129         return @connection
130       end
131
132       def connection=(new_connection)
133         if new_connection.kind_of?(Faraday::Connection)
134           @connection = new_connection
135         else
136           raise TypeError,
137             "Expected Faraday::Connection, got #{new_connection.class}."
138         end
139       end
140
141       def api_method
142         return @api_method
143       end
144
145       def api_method=(new_api_method)
146         if new_api_method.kind_of?(Google::APIClient::Method) ||
147             new_api_method == nil
148           @api_method = new_api_method
149         elsif new_api_method.respond_to?(:to_str) ||
150             new_api_method.kind_of?(Symbol)
151           unless @client
152             raise ArgumentError,
153               "API method lookup impossible without client instance."
154           end
155           new_api_method = new_api_method.to_s
156           # This method of guessing the API is unreliable. This will fail for
157           # APIs where the first segment of the RPC name does not match the
158           # service name. However, this is a fallback mechanism anyway.
159           # Developers should be passing in a reference to the method, rather
160           # than passing in a string or symbol. This should raise an error
161           # in the case of a mismatch.
162           api = new_api_method[/^([^.]+)\./, 1]
163           @api_method = @client.discovered_method(
164             new_api_method, api, @version
165           )
166           if @api_method
167             # Ditch the client reference, we won't need it again.
168             @client = nil
169           else
170             raise ArgumentError, "API method could not be found."
171           end
172         else
173           raise TypeError,
174             "Expected Google::APIClient::Method, got #{new_api_method.class}."
175         end
176       end
177
178       def parameters
179         return @parameters
180       end
181
182       def parameters=(new_parameters)
183         # No type-checking needed, the Method class handles this.
184         @parameters = new_parameters
185       end
186
187       def body
188         return @body
189       end
190
191       def body=(new_body)
192         if new_body.respond_to?(:to_str)
193           @body = new_body.to_str
194         elsif new_body.respond_to?(:read)
195           @body = new_body.read()
196         elsif new_body.respond_to?(:inject)
197           @body = (new_body.inject(StringIO.new) do |accu, chunk|
198             accu.write(chunk)
199             accu
200           end).string
201         else
202           raise TypeError, "Expected body to be String, IO, or Enumerable chunks."
203         end
204       end
205
206       def headers
207         return @headers ||= {}
208       end
209
210       def headers=(new_headers)
211         if new_headers.kind_of?(Array) || new_headers.kind_of?(Hash)
212           @headers = new_headers
213         else
214           raise TypeError, "Expected Hash or Array, got #{new_headers.class}."
215         end
216       end
217
218       def http_method
219         return @http_method ||= self.api_method.http_method
220       end
221
222       def http_method=(new_http_method)
223         if new_http_method.kind_of?(Symbol)
224           @http_method = new_http_method.to_s.upcase
225         elsif new_http_method.respond_to?(:to_str)
226           @http_method = new_http_method.to_str.upcase
227         else
228           raise TypeError,
229             "Expected String or Symbol, got #{new_http_method.class}."
230         end
231       end
232
233       def uri
234         return @uri ||= self.api_method.generate_uri(self.parameters)
235       end
236
237       def uri=(new_uri)
238         @uri = Addressable::URI.parse(new_uri)
239       end
240
241       def to_request
242         if self.api_method
243           return self.api_method.generate_request(
244             self.parameters, self.body, self.headers,
245             :connection => self.connection
246           )
247         else
248           return self.connection.build_request(
249             self.http_method.to_s.downcase.to_sym
250           ) do |req|
251             req.url(Addressable::URI.parse(self.uri).normalize.to_s)
252             req.headers = Faraday::Utils::Headers.new(self.headers)
253             req.body = self.body
254           end
255         end
256       end
257
258       def to_hash
259         options = {}
260         if self.api_method
261           options[:api_method] = self.api_method
262           options[:parameters] = self.parameters
263         else
264           options[:http_method] = self.http_method
265           options[:uri] = self.uri
266         end
267         options[:headers] = self.headers
268         options[:body] = self.body
269         options[:connection] = self.connection
270         options[:authorization] = self.authorization unless self.authorization.nil?
271         return options
272       end
273     end
274   end
275 end