Begin consolidation of request building in reference. Further changes coming to simpl...
[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 require 'faraday'
16 require 'faraday/utils'
17 require 'multi_json'
18 require 'compat/multi_json'
19 require 'addressable/uri'
20 require 'stringio'
21 require 'google/api_client/discovery'
22
23 # TODO - needs some serious cleanup
24
25 module Google
26   class APIClient
27     class Reference
28       MULTIPART_BOUNDARY = "-----------RubyApiMultipartPost".freeze
29
30       def initialize(options={})
31
32         self.connection = options[:connection] || Faraday.default_connection
33         self.authorization = options[:authorization]
34         self.api_method = options[:api_method]
35         
36         self.parameters = options[:parameters] || {}
37         # These parameters are handled differently because they're not
38         # parameters to the API method, but rather to the API system.
39         self.parameters['key'] ||= options[:key] if options[:key]
40         self.parameters['userIp'] ||= options[:user_ip] if options[:user_ip]
41
42         self.headers = options[:headers] || {}
43         if options[:media]
44           self.initialize_media_upload
45         elsif options[:body]
46           self.body = options[:body]
47         elsif options[:body_object]
48           self.headers['Content-Type'] ||= 'application/json'
49           self.body = serialize_body(options[:body_object])
50         else
51           self.body = ''
52         end
53         unless self.api_method
54           self.http_method = options[:http_method] || 'GET'
55           self.uri = options[:uri]
56           unless self.parameters.empty?
57             self.uri.query = Addressable::URI.form_encode(self.parameters)
58           end
59         end
60       end
61
62       def initialize_media_upload
63         self.media = options[:media]
64         case self.upload_type
65         when "media"
66           if options[:body] || options[:body_object] 
67             raise ArgumentError, "Can not specify body & body object for simple uploads"
68           end
69           self.headers['Content-Type'] ||= self.media.content_type
70           self.body = self.media
71         when "multipart"
72           unless options[:body_object] 
73             raise ArgumentError, "Multipart requested but no body object"              
74           end
75           metadata = StringIO.new(serialize_body(options[:body_object]))
76           env = {
77             :request_headers => {'Content-Type' => "multipart/related;boundary=#{MULTIPART_BOUNDARY}"},
78             :request => { :boundary => MULTIPART_BOUNDARY }
79           }
80           multipart = Faraday::Request::Multipart.new
81           self.body = multipart.create_multipart(env, [
82             [nil,Faraday::UploadIO.new(metadata, 'application/json', 'file.json')], 
83             [nil, self.media]])
84           self.headers.update(env[:request_headers])
85         when "resumable"
86           file_length = self.media.length
87           self.headers['X-Upload-Content-Type'] = self.media.content_type
88           self.headers['X-Upload-Content-Length'] = file_length.to_s
89           if 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         else
96           raise ArgumentError, "Invalid uploadType for media"
97         end
98       end
99
100       def serialize_body(body)
101         return body.to_json if body.respond_to?(:to_json)
102         return MultiJson.dump(options[:body_object].to_hash) if body.respond_to?(:to_hash)
103         raise TypeError, 'Could not convert body object to JSON.' +
104                          'Must respond to :to_json or :to_hash.'
105       end
106
107       def media
108         return @media
109       end
110
111       def media=(media)
112         @media = (media)
113       end
114       
115       def upload_type
116         return self.parameters['uploadType'] || self.parameters['upload_type']
117       end
118
119       def authorization
120         return @authorization
121       end
122
123       def authorization=(new_authorization)
124         @authorization = new_authorization
125       end
126
127       def connection
128         return @connection
129       end
130
131       def connection=(new_connection)
132         if new_connection.kind_of?(Faraday::Connection)
133           @connection = new_connection
134         else
135           raise TypeError,
136             "Expected Faraday::Connection, got #{new_connection.class}."
137         end
138       end
139
140       def api_method
141         return @api_method
142       end
143
144       def api_method=(new_api_method)
145         if new_api_method.kind_of?(Google::APIClient::Method) ||
146             new_api_method == nil
147           @api_method = new_api_method
148         else
149           raise TypeError,
150             "Expected Google::APIClient::Method, got #{new_api_method.class}."
151         end
152       end
153
154       def parameters
155         return @parameters
156       end
157
158       def parameters=(new_parameters)
159         @parameters = Hash[new_parameters]
160       end
161
162       def body
163         return @body
164       end
165
166       def body=(new_body)
167         @body = new_body
168       end
169
170       def headers
171         return @headers ||= {}
172       end
173
174       def headers=(new_headers)
175         if new_headers.kind_of?(Array) || new_headers.kind_of?(Hash)
176           @headers = new_headers
177         else
178           raise TypeError, "Expected Hash or Array, got #{new_headers.class}."
179         end
180       end
181
182       def http_method
183         return @http_method ||= self.api_method.http_method
184       end
185
186       def http_method=(new_http_method)
187         if new_http_method.kind_of?(Symbol)
188           @http_method = new_http_method.to_s.downcase.to_sym
189         elsif new_http_method.respond_to?(:to_str)
190           @http_method = new_http_method.to_s.downcase.to_sym
191         else
192           raise TypeError,
193             "Expected String or Symbol, got #{new_http_method.class}."
194         end
195       end
196
197       def uri
198         return @uri ||= self.api_method.generate_uri(self.parameters)
199       end
200
201       def uri=(new_uri)
202         @uri = Addressable::URI.parse(new_uri)
203       end
204
205       def to_http_request
206         request = ( 
207           if self.api_method
208             self.api_method.generate_request(
209               self.parameters, self.body, self.headers, :connection => self.connection
210             )
211           else
212             self.connection.build_request(self.http_method) do |req|
213               req.url(self.uri.to_str)
214               req.headers.update(self.headers)
215               req.body = self.body
216             end
217           end)
218         
219         if self.authorization.respond_to?(:generate_authenticated_request)
220           request = self.authorization.generate_authenticated_request(
221             :request => request,
222             :connection => self.connection
223           )
224         end
225         return request
226       end
227
228       def to_hash
229         options = {}
230         if self.api_method
231           options[:api_method] = self.api_method
232           options[:parameters] = self.parameters
233         else
234           options[:http_method] = self.http_method
235           options[:uri] = self.uri
236         end
237         options[:headers] = self.headers
238         options[:body] = self.body
239         options[:connection] = self.connection
240         unless self.authorization.nil?
241           options[:authorization] = self.authorization
242         end
243         return options
244       end
245     end
246   end
247 end