1 # Copyright 2010 Google Inc.
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
7 # http://www.apache.org/licenses/LICENSE-2.0
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.
16 require 'addressable/uri'
17 require 'addressable/template'
18 require 'extlib/inflection'
20 module Google #:nodoc:
21 class APIClient #:nodoc:
22 class ValidationError < StandardError
26 def initialize(service_name, service_version, service_description)
28 @version = service_version
29 @description = service_description
30 metaclass = (class <<self; self; end)
31 self.resources.each do |resource|
32 method_name = Extlib::Inflection.underscore(resource.name).to_sym
33 if !self.respond_to?(method_name)
34 metaclass.send(:define_method, method_name) { resource }
37 self.methods.each do |method|
38 method_name = Extlib::Inflection.underscore(method.name).to_sym
39 if !self.respond_to?(method_name)
40 metaclass.send(:define_method, method_name) { method }
45 attr_reader :name, :version, :description
48 return @base ||= Addressable::URI.parse(self.description['baseUrl'])
52 return @resources ||= (
53 (self.description['resources'] || []).inject([]) do |accu, (k, v)|
54 accu << ::Google::APIClient::Resource.new(self.base, k, v)
62 (self.description['methods'] || []).inject([]) do |accu, (k, v)|
63 accu << ::Google::APIClient::Method.new(self.base, k, v)
70 return @hash ||= (begin
72 self.methods.each do |method|
73 methods_hash[method.rpc_name] = method
75 self.resources.each do |resource|
76 methods_hash.merge!(resource.to_h)
83 # Returns a <code>String</code> representation of the service's state.
85 # @return [String] The service's state, as a <code>String</code>.
88 "#<%s:%#0x NAME:%s>", self.class.to_s, self.object_id, self.name
94 def initialize(base, resource_name, resource_description)
97 @description = resource_description
98 metaclass = (class <<self; self; end)
99 self.resources.each do |resource|
100 method_name = Extlib::Inflection.underscore(resource.name).to_sym
101 if !self.respond_to?(method_name)
102 metaclass.send(:define_method, method_name) { resource }
105 self.methods.each do |method|
106 method_name = Extlib::Inflection.underscore(method.name).to_sym
107 if !self.respond_to?(method_name)
108 metaclass.send(:define_method, method_name) { method }
113 attr_reader :name, :description, :base
116 return @resources ||= (
117 (self.description['resources'] || []).inject([]) do |accu, (k, v)|
118 accu << ::Google::APIClient::Resource.new(self.base, k, v)
125 return @methods ||= (
126 (self.description['methods'] || []).inject([]) do |accu, (k, v)|
127 accu << ::Google::APIClient::Method.new(self.base, k, v)
134 return @hash ||= (begin
136 self.methods.each do |method|
137 methods_hash[method.rpc_name] = method
139 self.resources.each do |resource|
140 methods_hash.merge!(resource.to_h)
147 # Returns a <code>String</code> representation of the resource's state.
149 # @return [String] The resource's state, as a <code>String</code>.
152 "#<%s:%#0x NAME:%s>", self.class.to_s, self.object_id, self.name
158 def initialize(base, method_name, method_description)
161 @description = method_description
164 attr_reader :name, :description, :base
167 return self.description['rpcName']
171 return @uri_template ||=
172 Addressable::Template.new(base + self.description['pathUrl'])
175 def normalize_parameters(parameters={})
176 # Convert keys to Strings when appropriate
177 if parameters.kind_of?(Hash) || parameters.kind_of?(Array)
178 parameters = parameters.inject({}) do |accu, (k, v)|
179 k = k.to_s if k.kind_of?(Symbol)
180 k = k.to_str if k.respond_to?(:to_str)
181 unless k.kind_of?(String)
182 raise TypeError, "Expected String, got #{k.class}."
189 "Expected Hash or Array, got #{parameters.class}."
194 def generate_uri(parameters={})
195 parameters = self.normalize_parameters(parameters)
196 self.validate_parameters(parameters)
197 template_variables = self.uri_template.variables
198 uri = self.uri_template.expand(parameters)
199 query_parameters = parameters.reject do |k, v|
200 template_variables.include?(k)
202 if query_parameters.size > 0
203 uri.query_values = (uri.query_values || {}).merge(query_parameters)
205 # Normalization is necessary because of undesirable percent-escaping
206 # during URI template expansion
210 def generate_request(parameters={}, body='', headers=[])
211 method = self.description['httpMethod'] || 'GET'
212 uri = self.generate_uri(parameters)
213 return [method, uri.to_str, headers, [body]]
216 def validate_parameters(parameters={})
217 parameters = self.normalize_parameters(parameters)
218 parameter_description = self.description['parameters'] || {}
219 required_variables = Hash[parameter_description.select do |k, v|
222 missing_variables = required_variables - parameters.keys
223 if missing_variables.size > 0
225 "Missing required parameters: #{missing_variables.join(', ')}."
227 parameters.each do |k, v|
228 if parameter_description[k]
229 pattern = parameter_description[k]['pattern']
231 regexp = Regexp.new("^#{pattern}$")
234 "Parameter '#{k}' has an invalid value: #{v}. " +
235 "Must match: /^#{pattern}$/."
244 # Returns a <code>String</code> representation of the method's state.
246 # @return [String] The method's state, as a <code>String</code>.
249 "#<%s:%#0x NAME:%s>", self.class.to_s, self.object_id, self.rpc_name