Unify processing of api/resumable/batch requests
[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 module Google
24   class APIClient
25
26     class Request
27       MULTIPART_BOUNDARY = "-----------RubyApiMultipartPost".freeze
28       
29       attr_reader :connection, :parameters,  :api_method, :headers
30       attr_accessor :media, :authorization, :body
31       
32       def initialize(options={})
33
34         self.connection = options[:connection] || Faraday.default_connection
35         self.authorization = options[:authorization]
36         self.api_method = options[:api_method]
37         
38         @parameters = Hash[options[:parameters] || {}]
39         # These parameters are handled differently because they're not
40         # parameters to the API method, but rather to the API system.
41         self.parameters['key'] ||= options[:key] if options[:key]
42         self.parameters['userIp'] ||= options[:user_ip] if options[:user_ip]
43
44         @headers = Faraday::Utils::Headers.new
45         self.headers.merge!(options[:headers]) if options[:headers]
46         
47         if options[:media]
48           self.initialize_media_upload(options)
49         elsif options[:body]
50           self.body = options[:body]
51         elsif options[:body_object]
52           self.headers['Content-Type'] ||= 'application/json'
53           self.body = serialize_body(options[:body_object])
54         else
55           self.body = ''
56         end
57         
58         unless self.api_method
59           self.http_method = options[:http_method] || 'GET'
60           self.uri = options[:uri]
61           unless self.parameters.empty?
62             self.uri.query = Addressable::URI.form_encode(self.parameters)
63           end
64         end
65       end
66
67       def initialize_media_upload(options)
68         self.media = options[:media]
69         case self.upload_type
70         when "media"
71           if options[:body] || options[:body_object] 
72             raise ArgumentError, "Can not specify body & body object for simple uploads"
73           end
74           self.headers['Content-Type'] ||= self.media.content_type
75           self.body = self.media
76         when "multipart"
77           unless options[:body_object] 
78             raise ArgumentError, "Multipart requested but no body object"              
79           end
80           metadata = StringIO.new(serialize_body(options[:body_object]))
81           build_multipart([Faraday::UploadIO.new(metadata, 'application/json', 'file.json'), self.media])
82         when "resumable"
83           file_length = self.media.length
84           self.headers['X-Upload-Content-Type'] = self.media.content_type
85           self.headers['X-Upload-Content-Length'] = file_length.to_s
86           if options[:body_object]
87             self.headers['Content-Type'] ||= 'application/json'
88             self.body = serialize_body(options[:body_object]) 
89           else
90             self.body = ''
91           end
92         end
93       end
94       
95       def build_multipart(parts, mime_type = 'multipart/related', boundary = MULTIPART_BOUNDARY) 
96         env = {
97           :request_headers => {'Content-Type' => "#{mime_type};boundary=#{boundary}"},
98           :request => { :boundary => boundary }
99         }
100         multipart = Faraday::Request::Multipart.new
101         self.body = multipart.create_multipart(env, parts.map {|part| [nil, part]})
102         self.headers.update(env[:request_headers])
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 upload_type
113         return self.parameters['uploadType'] || self.parameters['upload_type']
114       end
115
116       def connection=(new_connection)
117         if new_connection.kind_of?(Faraday::Connection)
118           @connection = new_connection
119         else
120           raise TypeError,
121             "Expected Faraday::Connection, got #{new_connection.class}."
122         end
123       end
124
125       def api_method=(new_api_method)
126         if new_api_method.kind_of?(Google::APIClient::Method) ||
127             new_api_method == nil
128           @api_method = new_api_method
129         else
130           raise TypeError,
131             "Expected Google::APIClient::Method, got #{new_api_method.class}."
132         end
133       end
134
135       def http_method
136         return @http_method ||= self.api_method.http_method.to_s.downcase.to_sym
137       end
138
139       def http_method=(new_http_method)
140         if new_http_method.kind_of?(Symbol)
141           @http_method = new_http_method.to_s.downcase.to_sym
142         elsif new_http_method.respond_to?(:to_str)
143           @http_method = new_http_method.to_s.downcase.to_sym
144         else
145           raise TypeError,
146             "Expected String or Symbol, got #{new_http_method.class}."
147         end
148       end
149
150       def uri
151         return @uri ||= self.api_method.generate_uri(self.parameters)
152       end
153
154       def uri=(new_uri)
155         @uri = Addressable::URI.parse(new_uri)
156       end
157
158       def to_http_request
159         request = ( 
160           if self.uri
161             self.connection.build_request(self.http_method) do |req|
162               req.url(self.uri.to_str)
163               req.headers.update(self.headers)
164               req.body = self.body
165             end
166           else
167             self.api_method.generate_request(
168               self.parameters, self.body, self.headers, :connection => self.connection
169             )
170           end)
171         
172         if self.authorization.respond_to?(:generate_authenticated_request)
173           request = self.authorization.generate_authenticated_request(
174             :request => request,
175             :connection => self.connection
176           )
177         end
178         return request
179       end
180
181       def to_hash
182         options = {}
183         if self.api_method
184           options[:api_method] = self.api_method
185           options[:parameters] = self.parameters
186         else
187           options[:http_method] = self.http_method
188           options[:uri] = self.uri
189         end
190         options[:headers] = self.headers
191         options[:body] = self.body
192         options[:connection] = self.connection
193         options[:media] = self.media
194         unless self.authorization.nil?
195           options[:authorization] = self.authorization
196         end
197         return options
198       end
199       
200       def process_response(response)
201         Result.new(self, response)
202       end
203     end
204   
205     class Reference < Request
206     end
207   end
208 end