Continue internal shuffling...
[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 :parameters, :headers
30       attr_accessor :api_client, :connection, :api_method, :version ,:media, :authorization, :authenticated, :body
31       
32       def initialize(options={})
33         @parameters = Hash[options[:parameters] || {}]
34         @headers = Faraday::Utils::Headers.new
35         self.api_client = options[:api_client]
36         self.headers.merge!(options[:headers]) unless options[:headers].nil?
37         self.api_method = options[:api_method]
38         self.version = options[:version]
39         self.authenticated = options[:authenticated]
40         self.authorization = options[:authorization]
41         
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         
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         end
62       end
63
64       def upload_type
65         return self.parameters['uploadType'] || self.parameters['upload_type']
66       end
67
68       def http_method
69         return @http_method ||= self.api_method.http_method.to_s.downcase.to_sym
70       end
71
72       def http_method=(new_http_method)
73         if new_http_method.kind_of?(Symbol)
74           @http_method = new_http_method.to_s.downcase.to_sym
75         elsif new_http_method.respond_to?(:to_str)
76           @http_method = new_http_method.to_s.downcase.to_sym
77         else
78           raise TypeError,
79             "Expected String or Symbol, got #{new_http_method.class}."
80         end
81       end
82
83       def uri
84         return @uri ||= self.api_method.generate_uri(self.parameters)
85       end
86
87       def uri=(new_uri)
88         @uri = Addressable::URI.parse(new_uri)
89         @parameters.update(@uri.query_values) unless @uri.query_values.nil?
90       end
91
92       def send(connection)
93         response = connection.app.call(self.to_env(connection))        
94         self.process_http_response(response)
95       end
96       
97       def to_http_request
98         if self.api_client
99           self.headers['User-Agent'] ||= '' + self.api_client.user_agent unless self.api_client.user_agent.nil?
100           self.parameters['key'] ||= self.api_client.key unless self.api_client.key.nil?
101           self.parameters['userIp'] ||= self.api_client.user_ip unless self.api_client.user_ip.nil?
102           self.api_method = self.api_client.resolve_method(self.api_method, self.version) unless self.api_method.nil?
103         end
104         request = ( 
105           if self.uri
106             unless self.parameters.empty?
107               self.uri.query = Addressable::URI.form_encode(self.parameters)
108             end
109             [self.http_method, self.uri.to_s, self.headers, self.body]
110           else
111             self.api_method.generate_request(self.parameters, self.body, self.headers)
112           end)
113       end
114
115       def to_hash
116         options = {}
117         if self.api_method
118           options[:api_method] = self.api_method
119           options[:parameters] = self.parameters
120         else
121           options[:http_method] = self.http_method
122           options[:uri] = self.uri
123         end
124         options[:headers] = self.headers
125         options[:body] = self.body
126         options[:media] = self.media
127         unless self.authorization.nil?
128           options[:authorization] = self.authorization
129         end
130         return options
131       end
132       
133       def to_env(connection)
134         method, uri, headers, body = self.to_http_request
135         http_request = connection.build_request(method) do |req|
136           req.url(uri)
137           req.headers.update(headers)
138           req.body = body
139         end
140
141         if self.authorization.respond_to?(:generate_authenticated_request)
142           http_request = self.authorization.generate_authenticated_request(
143             :request => http_request,
144             :connection => connection
145           )
146         end
147
148         request_env = http_request.to_env(connection)
149       end
150       
151       def process_http_response(response)
152         Result.new(self, response)
153       end
154       
155       protected
156       
157       def initialize_media_upload(options)
158         self.media = options[:media]
159         case self.upload_type
160         when "media"
161           if options[:body] || options[:body_object] 
162             raise ArgumentError, "Can not specify body & body object for simple uploads"
163           end
164           self.headers['Content-Type'] ||= self.media.content_type
165           self.body = self.media
166         when "multipart"
167           unless options[:body_object] 
168             raise ArgumentError, "Multipart requested but no body object"              
169           end
170           metadata = StringIO.new(serialize_body(options[:body_object]))
171           build_multipart([Faraday::UploadIO.new(metadata, 'application/json', 'file.json'), self.media])
172         when "resumable"
173           file_length = self.media.length
174           self.headers['X-Upload-Content-Type'] = self.media.content_type
175           self.headers['X-Upload-Content-Length'] = file_length.to_s
176           if options[:body_object]
177             self.headers['Content-Type'] ||= 'application/json'
178             self.body = serialize_body(options[:body_object]) 
179           else
180             self.body = ''
181           end
182         end
183       end
184       
185       def build_multipart(parts, mime_type = 'multipart/related', boundary = MULTIPART_BOUNDARY) 
186         env = {
187           :request_headers => {'Content-Type' => "#{mime_type};boundary=#{boundary}"},
188           :request => { :boundary => boundary }
189         }
190         multipart = Faraday::Request::Multipart.new
191         self.body = multipart.create_multipart(env, parts.map {|part| [nil, part]})
192         self.headers.update(env[:request_headers])
193       end
194       
195       def serialize_body(body)
196         return body.to_json if body.respond_to?(:to_json)
197         return MultiJson.dump(options[:body_object].to_hash) if body.respond_to?(:to_hash)
198         raise TypeError, 'Could not convert body object to JSON.' +
199                          'Must respond to :to_json or :to_hash.'
200       end
201
202     end
203   
204     class Reference < Request
205     end
206   end
207 end