google-api-client 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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