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'
19 require 'google/api_client/errors'
24 # A method that has been described by a discovery document.
28 # Creates a description of a particular method.
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.
37 # @return [Google::APIClient::Method] The constructed method object.
38 def initialize(method_base, method_name, discovery_document)
39 @method_base = method_base
41 @discovery_document = discovery_document
45 # Returns the identifier for the method.
47 # @return [String] The method identifier.
51 # Returns the parsed section of the discovery document that applies to
54 # @return [Hash] The method description.
55 attr_reader :description
58 # Returns the base URI for the method.
60 # @return [Addressable::URI]
61 # The base URI that this method will be joined to.
62 attr_reader :method_base
65 # Updates the method with the new base.
67 # @param [Addressable::URI, #to_str, String] new_base
68 # The new base URI to use for the method.
69 def method_base=(new_method_base)
70 @method_base = Addressable::URI.parse(new_method_base)
75 # Returns the method ID.
77 # @return [String] The method identifier.
79 return @discovery_document['id']
83 # Returns the HTTP method or 'GET' if none is specified.
85 # @return [String] The HTTP method that will be used in the request.
87 return @discovery_document['httpMethod'] || 'GET'
91 # Returns the URI template for the method. A parameter list can be
92 # used to expand this into a URI.
94 # @return [Addressable::Template] The URI template.
96 # TODO(bobaman) We shouldn't be calling #to_s here, this should be
97 # a join operation on a URI, but we have to treat these as Strings
98 # because of the way the discovery document provides the URIs.
99 # This should be fixed soon.
100 return @uri_template ||= Addressable::Template.new(
101 self.method_base + @discovery_document['path']
106 # Normalizes parameters, converting to the appropriate types.
108 # @param [Hash, Array] parameters
109 # The parameters to normalize.
111 # @return [Hash] The normalized parameters.
112 def normalize_parameters(parameters={})
113 # Convert keys to Strings when appropriate
114 if parameters.kind_of?(Hash) || parameters.kind_of?(Array)
115 # Is a Hash or an Array a better return type? Do we ever need to
116 # worry about the same parameter being sent twice with different
118 parameters = parameters.inject({}) do |accu, (k, v)|
119 k = k.to_s if k.kind_of?(Symbol)
120 k = k.to_str if k.respond_to?(:to_str)
121 unless k.kind_of?(String)
122 raise TypeError, "Expected String, got #{k.class}."
129 "Expected Hash or Array, got #{parameters.class}."
135 # Expands the method's URI template using a parameter list.
137 # @param [Hash, Array] parameters
138 # The parameter list to use.
140 # @return [Addressable::URI] The URI after expansion.
141 def generate_uri(parameters={})
142 parameters = self.normalize_parameters(parameters)
143 self.validate_parameters(parameters)
144 template_variables = self.uri_template.variables
145 uri = self.uri_template.expand(parameters)
146 query_parameters = parameters.reject do |k, v|
147 template_variables.include?(k)
149 if query_parameters.size > 0
150 uri.query_values = (uri.query_values || {}).merge(query_parameters)
152 # Normalization is necessary because of undesirable percent-escaping
153 # during URI template expansion
158 # Generates an HTTP request for this method.
160 # @param [Hash, Array] parameters
161 # The parameters to send.
162 # @param [String, StringIO] body The body for the HTTP request.
163 # @param [Hash, Array] headers The HTTP headers for the request.
165 # @return [Array] The generated HTTP request.
166 def generate_request(parameters={}, body='', headers=[])
167 if body.respond_to?(:string)
169 elsif body.respond_to?(:to_str)
172 raise TypeError, "Expected String or StringIO, got #{body.class}."
174 if !headers.kind_of?(Array) && !headers.kind_of?(Hash)
175 raise TypeError, "Expected Hash or Array, got #{headers.class}."
177 method = self.http_method
178 uri = self.generate_uri(parameters)
179 headers = headers.to_a if headers.kind_of?(Hash)
180 return [method, uri.to_str, headers, [body]]
184 # Returns a <code>Hash</code> of the parameter descriptions for
187 # @return [Hash] The parameter descriptions.
188 def parameter_descriptions
189 @parameter_descriptions ||= (
190 @discovery_document['parameters'] || {}
191 ).inject({}) { |h,(k,v)| h[k]=v; h }
195 # Returns an <code>Array</code> of the parameters for this method.
197 # @return [Array] The parameters.
200 @discovery_document['parameters'] || {}
201 ).inject({}) { |h,(k,v)| h[k]=v; h }).keys
205 # Returns an <code>Array</code> of the required parameters for this
208 # @return [Array] The required parameters.
211 # # A list of all required parameters.
212 # method.required_parameters
213 def required_parameters
214 @required_parameters ||= ((self.parameter_descriptions.select do |k, v|
216 end).inject({}) { |h,(k,v)| h[k]=v; h }).keys
220 # Returns an <code>Array</code> of the optional parameters for this
223 # @return [Array] The optional parameters.
226 # # A list of all optional parameters.
227 # method.optional_parameters
228 def optional_parameters
229 @optional_parameters ||= ((self.parameter_descriptions.reject do |k, v|
231 end).inject({}) { |h,(k,v)| h[k]=v; h }).keys
235 # Verifies that the parameters are valid for this method. Raises an
236 # exception if validation fails.
238 # @param [Hash, Array] parameters
239 # The parameters to verify.
241 # @return [NilClass] <code>nil</code> if validation passes.
242 def validate_parameters(parameters={})
243 parameters = self.normalize_parameters(parameters)
244 required_variables = ((self.parameter_descriptions.select do |k, v|
246 end).inject({}) { |h,(k,v)| h[k]=v; h }).keys
247 missing_variables = required_variables - parameters.keys
248 if missing_variables.size > 0
250 "Missing required parameters: #{missing_variables.join(', ')}."
252 parameters.each do |k, v|
253 if self.parameter_descriptions[k]
254 enum = self.parameter_descriptions[k]['enum']
255 if enum && !enum.include?(v)
257 "Parameter '#{k}' has an invalid value: #{v}. " +
258 "Must be one of #{enum.inspect}."
260 pattern = self.parameter_descriptions[k]['pattern']
262 regexp = Regexp.new("^#{pattern}$")
265 "Parameter '#{k}' has an invalid value: #{v}. " +
266 "Must match: /^#{pattern}$/."
275 # Returns a <code>String</code> representation of the method's state.
277 # @return [String] The method's state, as a <code>String</code>.
281 self.class.to_s, self.object_id, self.id