google-api-client 0.2.0 → 0.3.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.
@@ -0,0 +1,272 @@
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
+ require 'addressable/uri'
17
+
18
+ require 'google/inflection'
19
+ require 'google/api_client/discovery/resource'
20
+ require 'google/api_client/discovery/method'
21
+
22
+ module Google
23
+ class APIClient
24
+ ##
25
+ # A service that has been described by a discovery document.
26
+ class API
27
+
28
+ ##
29
+ # Creates a description of a particular version of a service.
30
+ #
31
+ # @param [String] api
32
+ # The identifier for the service. Note that while this frequently
33
+ # matches the first segment of all of the service's RPC names, this
34
+ # should not be assumed. There is no requirement that these match.
35
+ # @param [String] version
36
+ # The identifier for the service version.
37
+ # @param [Hash] api_description
38
+ # The section of the discovery document that applies to this service
39
+ # version.
40
+ #
41
+ # @return [Google::APIClient::API] The constructed service object.
42
+ def initialize(document_base, discovery_document)
43
+ @document_base = Addressable::URI.parse(document_base)
44
+ @discovery_document = discovery_document
45
+ metaclass = (class <<self; self; end)
46
+ self.resources.each do |resource|
47
+ method_name = Google::INFLECTOR.underscore(resource.name).to_sym
48
+ if !self.respond_to?(method_name)
49
+ metaclass.send(:define_method, method_name) { resource }
50
+ end
51
+ end
52
+ self.methods.each do |method|
53
+ method_name = Google::INFLECTOR.underscore(method.name).to_sym
54
+ if !self.respond_to?(method_name)
55
+ metaclass.send(:define_method, method_name) { method }
56
+ end
57
+ end
58
+ end
59
+
60
+ ##
61
+ # Returns the id of the service.
62
+ #
63
+ # @return [String] The service id.
64
+ def id
65
+ return (
66
+ @discovery_document['id'] ||
67
+ "#{self.name}:#{self.version}"
68
+ )
69
+ end
70
+
71
+ ##
72
+ # Returns the identifier for the service.
73
+ #
74
+ # @return [String] The service identifier.
75
+ def name
76
+ return @discovery_document['name']
77
+ end
78
+
79
+ ##
80
+ # Returns the version of the service.
81
+ #
82
+ # @return [String] The service version.
83
+ def version
84
+ return @discovery_document['version']
85
+ end
86
+
87
+ ##
88
+ # Returns a human-readable title for the API.
89
+ #
90
+ # @return [Hash] The API title.
91
+ def title
92
+ return @discovery_document['title']
93
+ end
94
+
95
+ ##
96
+ # Returns a human-readable description of the API.
97
+ #
98
+ # @return [Hash] The API description.
99
+ def description
100
+ return @discovery_document['description']
101
+ end
102
+
103
+ ##
104
+ # Returns a URI for the API documentation.
105
+ #
106
+ # @return [Hash] The API documentation.
107
+ def documentation
108
+ return Addressable::URI.parse(@discovery_document['documentationLink'])
109
+ end
110
+
111
+ ##
112
+ # Returns true if this is the preferred version of this API.
113
+ #
114
+ # @return [TrueClass, FalseClass]
115
+ # Whether or not this is the preferred version of this API.
116
+ def preferred
117
+ return !!@discovery_document['preferred']
118
+ end
119
+
120
+ ##
121
+ # Returns the list of API features.
122
+ #
123
+ # @return [Array]
124
+ # The features supported by this API.
125
+ def features
126
+ return @discovery_document['features'] || []
127
+ end
128
+
129
+ ##
130
+ # Returns true if this API uses a data wrapper.
131
+ #
132
+ # @return [TrueClass, FalseClass]
133
+ # Whether or not this API uses a data wrapper.
134
+ def data_wrapper?
135
+ return self.features.include?('dataWrapper')
136
+ end
137
+
138
+ ##
139
+ # Returns the base URI for the discovery document.
140
+ #
141
+ # @return [Addressable::URI] The base URI.
142
+ attr_reader :document_base
143
+
144
+ ##
145
+ # Returns the base URI for this version of the service.
146
+ #
147
+ # @return [Addressable::URI] The base URI that methods are joined to.
148
+ def method_base
149
+ if @discovery_document['basePath']
150
+ return @method_base ||= (
151
+ self.document_base +
152
+ Addressable::URI.parse(@discovery_document['basePath'])
153
+ ).normalize
154
+ else
155
+ return nil
156
+ end
157
+ end
158
+
159
+ ##
160
+ # Updates the hierarchy of resources and methods with the new base.
161
+ #
162
+ # @param [Addressable::URI, #to_str, String] new_base
163
+ # The new base URI to use for the service.
164
+ def method_base=(new_method_base)
165
+ @method_base = Addressable::URI.parse(new_method_base)
166
+ self.resources.each do |resource|
167
+ resource.method_base = @method_base
168
+ end
169
+ self.methods.each do |method|
170
+ method.method_base = @method_base
171
+ end
172
+ end
173
+
174
+ ##
175
+ # A list of schemas available for this version of the API.
176
+ #
177
+ # @return [Hash] A list of {Google::APIClient::Schema} objects.
178
+ def schemas
179
+ return @schemas ||= (
180
+ (@discovery_document['schemas'] || []).inject({}) do |accu, (k, v)|
181
+ accu[k] = Google::APIClient::Schema.parse(self, v)
182
+ accu
183
+ end
184
+ )
185
+ end
186
+
187
+ ##
188
+ # Returns a schema for a kind value.
189
+ #
190
+ # @return [Google::APIClient::Schema] The associated Schema object.
191
+ def schema_for_kind(kind)
192
+ api_name, schema_name = kind.split('#', 2)
193
+ if api_name != self.name
194
+ raise ArgumentError,
195
+ "The kind does not match this API. " +
196
+ "Expected '#{self.name}', got '#{api_name}'."
197
+ end
198
+ for k, v in self.schemas
199
+ return v if k.downcase == schema_name.downcase
200
+ end
201
+ return nil
202
+ end
203
+
204
+ ##
205
+ # A list of resources available at the root level of this version of the
206
+ # API.
207
+ #
208
+ # @return [Array] A list of {Google::APIClient::Resource} objects.
209
+ def resources
210
+ return @resources ||= (
211
+ (@discovery_document['resources'] || []).inject([]) do |accu, (k, v)|
212
+ accu << Google::APIClient::Resource.new(
213
+ self, self.method_base, k, v
214
+ )
215
+ accu
216
+ end
217
+ )
218
+ end
219
+
220
+ ##
221
+ # A list of methods available at the root level of this version of the
222
+ # API.
223
+ #
224
+ # @return [Array] A list of {Google::APIClient::Method} objects.
225
+ def methods
226
+ return @methods ||= (
227
+ (@discovery_document['methods'] || []).inject([]) do |accu, (k, v)|
228
+ accu << Google::APIClient::Method.new(self, self.method_base, k, v)
229
+ accu
230
+ end
231
+ )
232
+ end
233
+
234
+ ##
235
+ # Allows deep inspection of the discovery document.
236
+ def [](key)
237
+ return @discovery_document[key]
238
+ end
239
+
240
+ ##
241
+ # Converts the service to a flat mapping of RPC names and method objects.
242
+ #
243
+ # @return [Hash] All methods available on the service.
244
+ #
245
+ # @example
246
+ # # Discover available methods
247
+ # method_names = client.discovered_api('buzz').to_h.keys
248
+ def to_h
249
+ return @hash ||= (begin
250
+ methods_hash = {}
251
+ self.methods.each do |method|
252
+ methods_hash[method.id] = method
253
+ end
254
+ self.resources.each do |resource|
255
+ methods_hash.merge!(resource.to_h)
256
+ end
257
+ methods_hash
258
+ end)
259
+ end
260
+
261
+ ##
262
+ # Returns a <code>String</code> representation of the service's state.
263
+ #
264
+ # @return [String] The service's state, as a <code>String</code>.
265
+ def inspect
266
+ sprintf(
267
+ "#<%s:%#0x ID:%s>", self.class.to_s, self.object_id, self.id
268
+ )
269
+ end
270
+ end
271
+ end
272
+ end
@@ -0,0 +1,313 @@
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
+ require 'addressable/uri'
17
+ require 'addressable/template'
18
+
19
+ require 'google/api_client/errors'
20
+
21
+ module Google
22
+ class APIClient
23
+ ##
24
+ # A method that has been described by a discovery document.
25
+ class Method
26
+
27
+ ##
28
+ # Creates a description of a particular method.
29
+ #
30
+ # @param [Addressable::URI] method_base
31
+ # The base URI for the service.
32
+ # @param [String] method_name
33
+ # The identifier for the method.
34
+ # @param [Hash] method_description
35
+ # The section of the discovery document that applies to this method.
36
+ #
37
+ # @return [Google::APIClient::Method] The constructed method object.
38
+ def initialize(api, method_base, method_name, discovery_document)
39
+ @api = api
40
+ @method_base = method_base
41
+ @name = method_name
42
+ @discovery_document = discovery_document
43
+ end
44
+
45
+ ##
46
+ # Returns the identifier for the method.
47
+ #
48
+ # @return [String] The method identifier.
49
+ attr_reader :name
50
+
51
+ ##
52
+ # Returns the parsed section of the discovery document that applies to
53
+ # this method.
54
+ #
55
+ # @return [Hash] The method description.
56
+ attr_reader :description
57
+
58
+ ##
59
+ # Returns the base URI for the method.
60
+ #
61
+ # @return [Addressable::URI]
62
+ # The base URI that this method will be joined to.
63
+ attr_reader :method_base
64
+
65
+ ##
66
+ # Updates the method with the new base.
67
+ #
68
+ # @param [Addressable::URI, #to_str, String] new_base
69
+ # The new base URI to use for the method.
70
+ def method_base=(new_method_base)
71
+ @method_base = Addressable::URI.parse(new_method_base)
72
+ @uri_template = nil
73
+ end
74
+
75
+ ##
76
+ # Returns the method ID.
77
+ #
78
+ # @return [String] The method identifier.
79
+ def id
80
+ return @discovery_document['id']
81
+ end
82
+
83
+ ##
84
+ # Returns the HTTP method or 'GET' if none is specified.
85
+ #
86
+ # @return [String] The HTTP method that will be used in the request.
87
+ def http_method
88
+ return @discovery_document['httpMethod'] || 'GET'
89
+ end
90
+
91
+ ##
92
+ # Returns the URI template for the method. A parameter list can be
93
+ # used to expand this into a URI.
94
+ #
95
+ # @return [Addressable::Template] The URI template.
96
+ def uri_template
97
+ # TODO(bobaman) We shouldn't be calling #to_s here, this should be
98
+ # a join operation on a URI, but we have to treat these as Strings
99
+ # because of the way the discovery document provides the URIs.
100
+ # This should be fixed soon.
101
+ return @uri_template ||= Addressable::Template.new(
102
+ self.method_base + @discovery_document['path']
103
+ )
104
+ end
105
+
106
+ ##
107
+ # Returns the Schema object for the method's request, if any.
108
+ #
109
+ # @return [Google::APIClient::Schema] The request schema.
110
+ def request_schema
111
+ if @discovery_document['request']
112
+ schema_name = @discovery_document['request']['$ref']
113
+ return @api.schemas[schema_name]
114
+ else
115
+ return nil
116
+ end
117
+ end
118
+
119
+ ##
120
+ # Returns the Schema object for the method's response, if any.
121
+ #
122
+ # @return [Google::APIClient::Schema] The response schema.
123
+ def response_schema
124
+ if @discovery_document['response']
125
+ schema_name = @discovery_document['response']['$ref']
126
+ return @api.schemas[schema_name]
127
+ else
128
+ return nil
129
+ end
130
+ end
131
+
132
+ ##
133
+ # Normalizes parameters, converting to the appropriate types.
134
+ #
135
+ # @param [Hash, Array] parameters
136
+ # The parameters to normalize.
137
+ #
138
+ # @return [Hash] The normalized parameters.
139
+ def normalize_parameters(parameters={})
140
+ # Convert keys to Strings when appropriate
141
+ if parameters.kind_of?(Hash) || parameters.kind_of?(Array)
142
+ # Is a Hash or an Array a better return type? Do we ever need to
143
+ # worry about the same parameter being sent twice with different
144
+ # values?
145
+ parameters = parameters.inject({}) do |accu, (k, v)|
146
+ k = k.to_s if k.kind_of?(Symbol)
147
+ k = k.to_str if k.respond_to?(:to_str)
148
+ unless k.kind_of?(String)
149
+ raise TypeError, "Expected String, got #{k.class}."
150
+ end
151
+ accu[k] = v
152
+ accu
153
+ end
154
+ else
155
+ raise TypeError,
156
+ "Expected Hash or Array, got #{parameters.class}."
157
+ end
158
+ return parameters
159
+ end
160
+
161
+ ##
162
+ # Expands the method's URI template using a parameter list.
163
+ #
164
+ # @param [Hash, Array] parameters
165
+ # The parameter list to use.
166
+ #
167
+ # @return [Addressable::URI] The URI after expansion.
168
+ def generate_uri(parameters={})
169
+ parameters = self.normalize_parameters(parameters)
170
+ self.validate_parameters(parameters)
171
+ template_variables = self.uri_template.variables
172
+ uri = self.uri_template.expand(parameters)
173
+ query_parameters = parameters.reject do |k, v|
174
+ template_variables.include?(k)
175
+ end
176
+ if query_parameters.size > 0
177
+ uri.query_values = (uri.query_values || {}).merge(query_parameters)
178
+ end
179
+ # Normalization is necessary because of undesirable percent-escaping
180
+ # during URI template expansion
181
+ return uri.normalize
182
+ end
183
+
184
+ ##
185
+ # Generates an HTTP request for this method.
186
+ #
187
+ # @param [Hash, Array] parameters
188
+ # The parameters to send.
189
+ # @param [String, StringIO] body The body for the HTTP request.
190
+ # @param [Hash, Array] headers The HTTP headers for the request.
191
+ #
192
+ # @return [Array] The generated HTTP request.
193
+ def generate_request(parameters={}, body='', headers=[])
194
+ if body.respond_to?(:string)
195
+ body = body.string
196
+ elsif body.respond_to?(:to_str)
197
+ body = body.to_str
198
+ else
199
+ raise TypeError, "Expected String or StringIO, got #{body.class}."
200
+ end
201
+ if !headers.kind_of?(Array) && !headers.kind_of?(Hash)
202
+ raise TypeError, "Expected Hash or Array, got #{headers.class}."
203
+ end
204
+ method = self.http_method
205
+ uri = self.generate_uri(parameters)
206
+ headers = headers.to_a if headers.kind_of?(Hash)
207
+ return [method, uri.to_str, headers, [body]]
208
+ end
209
+
210
+ ##
211
+ # Returns a <code>Hash</code> of the parameter descriptions for
212
+ # this method.
213
+ #
214
+ # @return [Hash] The parameter descriptions.
215
+ def parameter_descriptions
216
+ @parameter_descriptions ||= (
217
+ @discovery_document['parameters'] || {}
218
+ ).inject({}) { |h,(k,v)| h[k]=v; h }
219
+ end
220
+
221
+ ##
222
+ # Returns an <code>Array</code> of the parameters for this method.
223
+ #
224
+ # @return [Array] The parameters.
225
+ def parameters
226
+ @parameters ||= ((
227
+ @discovery_document['parameters'] || {}
228
+ ).inject({}) { |h,(k,v)| h[k]=v; h }).keys
229
+ end
230
+
231
+ ##
232
+ # Returns an <code>Array</code> of the required parameters for this
233
+ # method.
234
+ #
235
+ # @return [Array] The required parameters.
236
+ #
237
+ # @example
238
+ # # A list of all required parameters.
239
+ # method.required_parameters
240
+ def required_parameters
241
+ @required_parameters ||= ((self.parameter_descriptions.select do |k, v|
242
+ v['required']
243
+ end).inject({}) { |h,(k,v)| h[k]=v; h }).keys
244
+ end
245
+
246
+ ##
247
+ # Returns an <code>Array</code> of the optional parameters for this
248
+ # method.
249
+ #
250
+ # @return [Array] The optional parameters.
251
+ #
252
+ # @example
253
+ # # A list of all optional parameters.
254
+ # method.optional_parameters
255
+ def optional_parameters
256
+ @optional_parameters ||= ((self.parameter_descriptions.reject do |k, v|
257
+ v['required']
258
+ end).inject({}) { |h,(k,v)| h[k]=v; h }).keys
259
+ end
260
+
261
+ ##
262
+ # Verifies that the parameters are valid for this method. Raises an
263
+ # exception if validation fails.
264
+ #
265
+ # @param [Hash, Array] parameters
266
+ # The parameters to verify.
267
+ #
268
+ # @return [NilClass] <code>nil</code> if validation passes.
269
+ def validate_parameters(parameters={})
270
+ parameters = self.normalize_parameters(parameters)
271
+ required_variables = ((self.parameter_descriptions.select do |k, v|
272
+ v['required']
273
+ end).inject({}) { |h,(k,v)| h[k]=v; h }).keys
274
+ missing_variables = required_variables - parameters.keys
275
+ if missing_variables.size > 0
276
+ raise ArgumentError,
277
+ "Missing required parameters: #{missing_variables.join(', ')}."
278
+ end
279
+ parameters.each do |k, v|
280
+ if self.parameter_descriptions[k]
281
+ enum = self.parameter_descriptions[k]['enum']
282
+ if enum && !enum.include?(v)
283
+ raise ArgumentError,
284
+ "Parameter '#{k}' has an invalid value: #{v}. " +
285
+ "Must be one of #{enum.inspect}."
286
+ end
287
+ pattern = self.parameter_descriptions[k]['pattern']
288
+ if pattern
289
+ regexp = Regexp.new("^#{pattern}$")
290
+ if v !~ regexp
291
+ raise ArgumentError,
292
+ "Parameter '#{k}' has an invalid value: #{v}. " +
293
+ "Must match: /^#{pattern}$/."
294
+ end
295
+ end
296
+ end
297
+ end
298
+ return nil
299
+ end
300
+
301
+ ##
302
+ # Returns a <code>String</code> representation of the method's state.
303
+ #
304
+ # @return [String] The method's state, as a <code>String</code>.
305
+ def inspect
306
+ sprintf(
307
+ "#<%s:%#0x ID:%s>",
308
+ self.class.to_s, self.object_id, self.id
309
+ )
310
+ end
311
+ end
312
+ end
313
+ end