google-api-client 0.4.7 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,294 +12,16 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
-
16
- gem 'faraday', '~> 0.8.1'
17
- require 'faraday'
18
- require 'faraday/utils'
19
- require 'multi_json'
20
- require 'compat/multi_json'
21
- require 'addressable/uri'
22
- require 'stringio'
23
- require 'google/api_client/discovery'
24
-
25
- # TODO - needs some serious cleanup
15
+ require 'google/api_client/request'
26
16
 
27
17
  module Google
28
18
  class APIClient
29
- class Reference
30
- MULTIPART_BOUNDARY = "-----------RubyApiMultipartPost".freeze
31
-
32
- def initialize(options={})
33
- # We only need this to do lookups on method ID String values
34
- # It's optional, but method ID lookups will fail if the client is
35
- # omitted.
36
- @client = options[:client]
37
- @version = options[:version] || 'v1'
38
-
39
- self.connection = options[:connection] || Faraday.default_connection
40
- self.authorization = options[:authorization]
41
- self.api_method = options[:api_method]
42
- self.parameters = options[:parameters] || {}
43
- # These parameters are handled differently because they're not
44
- # parameters to the API method, but rather to the API system.
45
- if self.parameters.kind_of?(Array)
46
- if options[:key]
47
- self.parameters.reject! { |k, _| k == 'key' }
48
- self.parameters << ['key', options[:key]]
49
- end
50
- if options[:user_ip]
51
- self.parameters.reject! { |k, _| k == 'userIp' }
52
- self.parameters << ['userIp', options[:user_ip]]
53
- end
54
- elsif self.parameters.kind_of?(Hash)
55
- self.parameters['key'] ||= options[:key] if options[:key]
56
- self.parameters['userIp'] ||= options[:user_ip] if options[:user_ip]
57
- # Convert to Array, because they're easier to work with when
58
- # repeated parameters are an issue.
59
- self.parameters = self.parameters.to_a
60
- else
61
- raise TypeError,
62
- "Expected Array or Hash, got #{self.parameters.class}."
63
- end
64
- self.headers = options[:headers] || {}
65
- if options[:media]
66
- self.media = options[:media]
67
- upload_type = self.parameters.find { |(k, _)| ['uploadType', 'upload_type'].include?(k) }.last
68
- case upload_type
69
- when "media"
70
- if options[:body] || options[:body_object]
71
- raise ArgumentError,
72
- "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
- # This is all a bit of a hack due to Signet requiring body to be a
81
- # string. Ideally, update Signet to delay serialization so we can
82
- # just pass streams all the way down through to the HTTP library.
83
- metadata = StringIO.new(serialize_body(options[:body_object]))
84
- env = {
85
- :request_headers => {
86
- 'Content-Type' =>
87
- "multipart/related;boundary=#{MULTIPART_BOUNDARY}"
88
- },
89
- :request => {:boundary => MULTIPART_BOUNDARY}
90
- }
91
- multipart = Faraday::Request::Multipart.new
92
- self.body = multipart.create_multipart(env, [
93
- [nil, Faraday::UploadIO.new(
94
- metadata, 'application/json', 'file.json'
95
- )],
96
- [nil, self.media]])
97
- self.headers.update(env[:request_headers])
98
- when "resumable"
99
- file_length = self.media.length
100
- self.headers['X-Upload-Content-Type'] = self.media.content_type
101
- self.headers['X-Upload-Content-Length'] = file_length.to_s
102
- if options[:body_object]
103
- self.headers['Content-Type'] ||= 'application/json'
104
- self.body = serialize_body(options[:body_object])
105
- else
106
- self.body = ''
107
- end
108
- else
109
- raise ArgumentError, "Invalid uploadType for media."
110
- end
111
- elsif options[:body]
112
- self.body = options[:body]
113
- elsif options[:body_object]
114
- self.headers['Content-Type'] ||= 'application/json'
115
- self.body = serialize_body(options[:body_object])
116
- else
117
- self.body = ''
118
- end
119
- unless self.api_method
120
- self.http_method = options[:http_method] || 'GET'
121
- self.uri = options[:uri]
122
- unless self.parameters.empty?
123
- query_values = (self.uri.query_values(Array) || [])
124
- self.uri.query = Addressable::URI.form_encode(
125
- (query_values + self.parameters).sort
126
- )
127
- self.uri.query = nil if self.uri.query == ""
128
- end
129
- end
130
- end
131
-
132
- def serialize_body(body)
133
- return body.to_json if body.respond_to?(:to_json)
134
- return MultiJson.dump(options[:body_object].to_hash) if body.respond_to?(:to_hash)
135
- raise TypeError, 'Could not convert body object to JSON.' +
136
- 'Must respond to :to_json or :to_hash.'
137
- end
138
-
139
- def media
140
- return @media
141
- end
142
-
143
- def media=(media)
144
- @media = (media)
145
- end
146
-
147
- def authorization
148
- return @authorization
149
- end
150
-
151
- def authorization=(new_authorization)
152
- @authorization = new_authorization
153
- end
154
-
155
- def connection
156
- return @connection
157
- end
158
-
159
- def connection=(new_connection)
160
- if new_connection.kind_of?(Faraday::Connection)
161
- @connection = new_connection
162
- else
163
- raise TypeError,
164
- "Expected Faraday::Connection, got #{new_connection.class}."
165
- end
166
- end
167
-
168
- def api_method
169
- return @api_method
170
- end
171
-
172
- def api_method=(new_api_method)
173
- if new_api_method.kind_of?(Google::APIClient::Method) ||
174
- new_api_method == nil
175
- @api_method = new_api_method
176
- elsif new_api_method.respond_to?(:to_str) ||
177
- new_api_method.kind_of?(Symbol)
178
- unless @client
179
- raise ArgumentError,
180
- "API method lookup impossible without client instance."
181
- end
182
- new_api_method = new_api_method.to_s
183
- # This method of guessing the API is unreliable. This will fail for
184
- # APIs where the first segment of the RPC name does not match the
185
- # service name. However, this is a fallback mechanism anyway.
186
- # Developers should be passing in a reference to the method, rather
187
- # than passing in a string or symbol. This should raise an error
188
- # in the case of a mismatch.
189
- api = new_api_method[/^([^.]+)\./, 1]
190
- @api_method = @client.discovered_method(
191
- new_api_method, api, @version
192
- )
193
- if @api_method
194
- # Ditch the client reference, we won't need it again.
195
- @client = nil
196
- else
197
- raise ArgumentError, "API method could not be found."
198
- end
199
- else
200
- raise TypeError,
201
- "Expected Google::APIClient::Method, got #{new_api_method.class}."
202
- end
203
- end
204
-
205
- def parameters
206
- return @parameters
207
- end
208
-
209
- def parameters=(new_parameters)
210
- # No type-checking needed, the Method class handles this.
211
- @parameters = new_parameters
212
- end
213
-
214
- def body
215
- return @body
216
- end
217
-
218
- def body=(new_body)
219
- if new_body.respond_to?(:to_str)
220
- @body = new_body.to_str
221
- elsif new_body.respond_to?(:read)
222
- @body = new_body.read()
223
- elsif new_body.respond_to?(:inject)
224
- @body = (new_body.inject(StringIO.new) do |accu, chunk|
225
- accu.write(chunk)
226
- accu
227
- end).string
228
- else
229
- raise TypeError,
230
- "Expected body to be String, IO, or Enumerable chunks."
231
- end
232
- end
233
-
234
- def headers
235
- return @headers ||= {}
236
- end
237
-
238
- def headers=(new_headers)
239
- if new_headers.kind_of?(Array) || new_headers.kind_of?(Hash)
240
- @headers = new_headers
241
- else
242
- raise TypeError, "Expected Hash or Array, got #{new_headers.class}."
243
- end
244
- end
245
-
246
- def http_method
247
- return @http_method ||= self.api_method.http_method
248
- end
249
-
250
- def http_method=(new_http_method)
251
- if new_http_method.kind_of?(Symbol)
252
- @http_method = new_http_method.to_s.upcase
253
- elsif new_http_method.respond_to?(:to_str)
254
- @http_method = new_http_method.to_str.upcase
255
- else
256
- raise TypeError,
257
- "Expected String or Symbol, got #{new_http_method.class}."
258
- end
259
- end
260
-
261
- def uri
262
- return @uri ||= self.api_method.generate_uri(self.parameters)
263
- end
264
-
265
- def uri=(new_uri)
266
- @uri = Addressable::URI.parse(new_uri)
267
- end
268
-
269
- def to_request
270
- if self.api_method
271
- return self.api_method.generate_request(
272
- self.parameters, self.body, self.headers,
273
- :connection => self.connection
274
- )
275
- else
276
- return self.connection.build_request(
277
- self.http_method.to_s.downcase.to_sym
278
- ) do |req|
279
- req.url(Addressable::URI.parse(self.uri).normalize.to_s)
280
- req.headers = Faraday::Utils::Headers.new(self.headers)
281
- req.body = self.body
282
- end
283
- end
284
- end
285
-
286
- def to_hash
287
- options = {}
288
- if self.api_method
289
- options[:api_method] = self.api_method
290
- options[:parameters] = self.parameters
291
- else
292
- options[:http_method] = self.http_method
293
- options[:uri] = self.uri
294
- end
295
- options[:headers] = self.headers
296
- options[:body] = self.body
297
- options[:connection] = self.connection
298
- unless self.authorization.nil?
299
- options[:authorization] = self.authorization
300
- end
301
- return options
302
- end
19
+ ##
20
+ # Subclass of Request for backwards compatibility with pre-0.5.0 versions of the library
21
+ #
22
+ # @deprecated
23
+ # use {Google::APIClient::Request} instead
24
+ class Reference < Request
303
25
  end
304
26
  end
305
27
  end
@@ -0,0 +1,336 @@
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
+ ##
27
+ # Represents an API request.
28
+ class Request
29
+ MULTIPART_BOUNDARY = "-----------RubyApiMultipartPost".freeze
30
+
31
+ # @return [Hash] Request parameters
32
+ attr_reader :parameters
33
+ # @return [Hash] Additional HTTP headers
34
+ attr_reader :headers
35
+ # @return [Google::APIClient::Method] API method to invoke
36
+ attr_reader :api_method
37
+ # @return [Google::APIClient::UploadIO] File to upload
38
+ attr_accessor :media
39
+ # @return [#generated_authenticated_request] User credentials
40
+ attr_accessor :authorization
41
+ # @return [TrueClass,FalseClass] True if request should include credentials
42
+ attr_accessor :authenticated
43
+ # @return [#read, #to_str] Request body
44
+ attr_accessor :body
45
+
46
+ ##
47
+ # Build a request
48
+ #
49
+ # @param [Hash] options
50
+ # @option options [Hash, Array] :parameters
51
+ # Request parameters for the API method.
52
+ # @option options [Google::APIClient::Method] :api_method
53
+ # API method to invoke. Either :api_method or :uri must be specified
54
+ # @option options [TrueClass, FalseClass] :authenticated
55
+ # True if request should include credentials. Implicitly true if
56
+ # unspecified and :authorization present
57
+ # @option options [#generate_signed_request] :authorization
58
+ # OAuth credentials
59
+ # @option options [Google::APIClient::UploadIO] :media
60
+ # File to upload, if media upload request
61
+ # @option options [#to_json, #to_hash] :body_object
62
+ # Main body of the API request. Typically hash or object that can
63
+ # be serialized to JSON
64
+ # @option options [#read, #to_str] :body
65
+ # Raw body to send in POST/PUT requests
66
+ # @option options [String, Addressable::URI] :uri
67
+ # URI to request. Either :api_method or :uri must be specified
68
+ # @option options [String, Symbol] :http_method
69
+ # HTTP method when requesting a URI
70
+ def initialize(options={})
71
+ @parameters = Hash[options[:parameters] || {}]
72
+ @headers = Faraday::Utils::Headers.new
73
+ self.headers.merge!(options[:headers]) unless options[:headers].nil?
74
+ self.api_method = options[:api_method]
75
+ self.authenticated = options[:authenticated]
76
+ self.authorization = options[:authorization]
77
+
78
+ # These parameters are handled differently because they're not
79
+ # parameters to the API method, but rather to the API system.
80
+ self.parameters['key'] ||= options[:key] if options[:key]
81
+ self.parameters['userIp'] ||= options[:user_ip] if options[:user_ip]
82
+
83
+ if options[:media]
84
+ self.initialize_media_upload(options)
85
+ elsif options[:body]
86
+ self.body = options[:body]
87
+ elsif options[:body_object]
88
+ self.headers['Content-Type'] ||= 'application/json'
89
+ self.body = serialize_body(options[:body_object])
90
+ else
91
+ self.body = ''
92
+ end
93
+
94
+ unless self.api_method
95
+ self.http_method = options[:http_method] || 'GET'
96
+ self.uri = options[:uri]
97
+ end
98
+ end
99
+
100
+ # @!attribute [r] upload_type
101
+ # @return [String] protocol used for upload
102
+ def upload_type
103
+ return self.parameters['uploadType'] || self.parameters['upload_type']
104
+ end
105
+
106
+ # @!attribute http_method
107
+ # @return [Symbol] HTTP method if invoking a URI
108
+ def http_method
109
+ return @http_method ||= self.api_method.http_method.to_s.downcase.to_sym
110
+ end
111
+
112
+ def http_method=(new_http_method)
113
+ if new_http_method.kind_of?(Symbol)
114
+ @http_method = new_http_method.to_s.downcase.to_sym
115
+ elsif new_http_method.respond_to?(:to_str)
116
+ @http_method = new_http_method.to_s.downcase.to_sym
117
+ else
118
+ raise TypeError,
119
+ "Expected String or Symbol, got #{new_http_method.class}."
120
+ end
121
+ end
122
+
123
+ def api_method=(new_api_method)
124
+ if new_api_method.nil? || new_api_method.kind_of?(Google::APIClient::Method)
125
+ @api_method = new_api_method
126
+ else
127
+ raise TypeError,
128
+ "Expected Google::APIClient::Method, got #{new_api_method.class}."
129
+ end
130
+ end
131
+
132
+ # @!attribute uri
133
+ # @return [Addressable::URI] URI to send request
134
+ def uri
135
+ return @uri ||= self.api_method.generate_uri(self.parameters)
136
+ end
137
+
138
+ def uri=(new_uri)
139
+ @uri = Addressable::URI.parse(new_uri)
140
+ @parameters.update(@uri.query_values) unless @uri.query_values.nil?
141
+ end
142
+
143
+
144
+ # Transmits the request with the given connection
145
+ #
146
+ # @api private
147
+ #
148
+ # @param [Faraday::Connection] connection
149
+ # the connection to transmit with
150
+ #
151
+ # @return [Google::APIClient::Result]
152
+ # result of API request
153
+ def send(connection)
154
+ http_response = connection.app.call(self.to_env(connection))
155
+ result = self.process_http_response(http_response)
156
+
157
+ # Resumamble slightly different than other upload protocols in that it requires at least
158
+ # 2 requests.
159
+ if self.upload_type == 'resumable'
160
+ upload = result.resumable_upload
161
+ unless upload.complete?
162
+ result = upload.send(connection)
163
+ end
164
+ end
165
+ return result
166
+ end
167
+
168
+ # Convert to an HTTP request. Returns components in order of method, URI,
169
+ # request headers, and body
170
+ #
171
+ # @api private
172
+ #
173
+ # @return [Array<(Symbol, Addressable::URI, Hash, [#read,#to_str])>]
174
+ def to_http_request
175
+ request = (
176
+ if self.uri
177
+ unless self.parameters.empty?
178
+ self.uri.query = Addressable::URI.form_encode(self.parameters)
179
+ end
180
+ [self.http_method, self.uri.to_s, self.headers, self.body]
181
+ else
182
+ self.api_method.generate_request(self.parameters, self.body, self.headers)
183
+ end)
184
+ end
185
+
186
+ ##
187
+ # Hashified verison of the API request
188
+ #
189
+ # @return [Hash]
190
+ def to_hash
191
+ options = {}
192
+ if self.api_method
193
+ options[:api_method] = self.api_method
194
+ options[:parameters] = self.parameters
195
+ else
196
+ options[:http_method] = self.http_method
197
+ options[:uri] = self.uri
198
+ end
199
+ options[:headers] = self.headers
200
+ options[:body] = self.body
201
+ options[:media] = self.media
202
+ unless self.authorization.nil?
203
+ options[:authorization] = self.authorization
204
+ end
205
+ return options
206
+ end
207
+
208
+ ##
209
+ # Prepares the request for execution, building a hash of parts
210
+ # suitable for sending to Faraday::Connection.
211
+ #
212
+ # @api private
213
+ #
214
+ # @param [Faraday::Connection] connection
215
+ # Connection for building the request
216
+ #
217
+ # @return [Hash]
218
+ # Encoded request
219
+ def to_env(connection)
220
+ method, uri, headers, body = self.to_http_request
221
+ http_request = connection.build_request(method) do |req|
222
+ req.url(uri)
223
+ req.headers.update(headers)
224
+ req.body = body
225
+ end
226
+
227
+ if self.authorization.respond_to?(:generate_authenticated_request)
228
+ http_request = self.authorization.generate_authenticated_request(
229
+ :request => http_request,
230
+ :connection => connection
231
+ )
232
+ end
233
+
234
+ request_env = http_request.to_env(connection)
235
+ end
236
+
237
+ ##
238
+ # Convert HTTP response to an API Result
239
+ #
240
+ # @api private
241
+ #
242
+ # @param [Faraday::Response] response
243
+ # HTTP response
244
+ #
245
+ # @return [Google::APIClient::Result]
246
+ # Processed API response
247
+ def process_http_response(response)
248
+ Result.new(self, response)
249
+ end
250
+
251
+ protected
252
+
253
+ ##
254
+ # Adjust headers & body for media uploads
255
+ #
256
+ # @api private
257
+ #
258
+ # @param [Hash] options
259
+ # @option options [Hash, Array] :parameters
260
+ # Request parameters for the API method.
261
+ # @option options [Google::APIClient::UploadIO] :media
262
+ # File to upload, if media upload request
263
+ # @option options [#to_json, #to_hash] :body_object
264
+ # Main body of the API request. Typically hash or object that can
265
+ # be serialized to JSON
266
+ # @option options [#read, #to_str] :body
267
+ # Raw body to send in POST/PUT requests
268
+ def initialize_media_upload(options)
269
+ self.media = options[:media]
270
+ case self.upload_type
271
+ when "media"
272
+ if options[:body] || options[:body_object]
273
+ raise ArgumentError, "Can not specify body & body object for simple uploads"
274
+ end
275
+ self.headers['Content-Type'] ||= self.media.content_type
276
+ self.body = self.media
277
+ when "multipart"
278
+ unless options[:body_object]
279
+ raise ArgumentError, "Multipart requested but no body object"
280
+ end
281
+ metadata = StringIO.new(serialize_body(options[:body_object]))
282
+ build_multipart([Faraday::UploadIO.new(metadata, 'application/json', 'file.json'), self.media])
283
+ when "resumable"
284
+ file_length = self.media.length
285
+ self.headers['X-Upload-Content-Type'] = self.media.content_type
286
+ self.headers['X-Upload-Content-Length'] = file_length.to_s
287
+ if options[:body_object]
288
+ self.headers['Content-Type'] ||= 'application/json'
289
+ self.body = serialize_body(options[:body_object])
290
+ else
291
+ self.body = ''
292
+ end
293
+ end
294
+ end
295
+
296
+ ##
297
+ # Assemble a multipart message from a set of parts
298
+ #
299
+ # @api private
300
+ #
301
+ # @param [Array<[#read,#to_str]>] parts
302
+ # Array of parts to encode.
303
+ # @param [String] mime_type
304
+ # MIME type of the message
305
+ # @param [String] boundary
306
+ # Boundary for separating each part of the message
307
+ def build_multipart(parts, mime_type = 'multipart/related', boundary = MULTIPART_BOUNDARY)
308
+ env = {
309
+ :request_headers => {'Content-Type' => "#{mime_type};boundary=#{boundary}"},
310
+ :request => { :boundary => boundary }
311
+ }
312
+ multipart = Faraday::Request::Multipart.new
313
+ self.body = multipart.create_multipart(env, parts.map {|part| [nil, part]})
314
+ self.headers.update(env[:request_headers])
315
+ end
316
+
317
+ ##
318
+ # Serialize body object to JSON
319
+ #
320
+ # @api private
321
+ #
322
+ # @param [#to_json,#to_hash] body
323
+ # object to serialize
324
+ #
325
+ # @return [String]
326
+ # JSON
327
+ def serialize_body(body)
328
+ return body.to_json if body.respond_to?(:to_json)
329
+ return MultiJson.dump(options[:body_object].to_hash) if body.respond_to?(:to_hash)
330
+ raise TypeError, 'Could not convert body object to JSON.' +
331
+ 'Must respond to :to_json or :to_hash.'
332
+ end
333
+
334
+ end
335
+ end
336
+ end