From 3a9d58108af35baf4cbaba576ecfeaf6b41af9cb Mon Sep 17 00:00:00 2001 From: Bob Aman Date: Thu, 16 Sep 2010 23:40:08 +0000 Subject: [PATCH] Signed requests can now be generated and transmitted by the client. Example code: require 'google/api_client' client = Google::APIClient.new(:service => 'buzz') client.authorization.fetch_temporary_credential!( :additional_parameters => { 'scope' => 'https://www.googleapis.com/auth/buzz' } ) client.authorization.authorization_uri # Redirect user here client.authorization.fetch_token_credential!(:verifier => '12345') response = client.execute( 'buzz.activities.list', 'scope' => '@self', 'userId' => '@me', 'alt' => 'json' ) status, headers, body = response git-svn-id: https://google-api-ruby-client.googlecode.com/svn/trunk@34 c1d61fac-ed7f-fcc1-18f7-ff78120a04ef --- lib/google/api_client.rb | 61 +++++++----- lib/google/api_client/discovery/service.rb | 104 +++++++++++++++++---- 2 files changed, 123 insertions(+), 42 deletions(-) diff --git a/lib/google/api_client.rb b/lib/google/api_client.rb index 378c56025..d2c1bb584 100644 --- a/lib/google/api_client.rb +++ b/lib/google/api_client.rb @@ -132,29 +132,44 @@ module Google #:nodoc: return nil end - def build_request(*args, &block) - if !args.empty? || block - if args.size != 2 - raise ArgumentError, "wrong number of arguments (#{args.size} for 2)" + def discovered_method(rpc_name, service_version='1.0') + for service in self.discovered_services + # This looks kinda weird, but is not a real problem because there's + # almost always only one service, and this is memoized anyhow. + if service.version.to_s == service_version.to_s + return service.to_h[rpc_name] if service.to_h[rpc_name] end - rpc_name = args[0] - if !rpc_name.kind_of?(String) - raise TypeError, "Expected String, got #{rpc_name.class}." - end - parameters = args[1] - if !parameters.kind_of?(Hash) - raise TypeError, "Expected Hash, got #{parameters.class}." - end - - signature_needed = false - if signature_needed - request = self.sign_request(request) - end - return args - else - require 'google/api_client/discovery/method_builder' - return ::Google::APIClient::MethodBuilder.new(self, :build_request) end + return nil + end + + def generate_request( + api_method, parameters={}, body='', headers=[], options={}) + options={ + :signed => true, + :service_version => '1.0' + }.merge(options) + if api_method.kind_of?(String) + api_method = self.discovered_method( + api_method, options[:service_version] + ) + elsif !api_method.kind_of?(::Google::APIClient::Service) + raise TypeError, + "Expected String or Google::APIClient::Service, " + + "got #{api_method.class}." + end + request = api_method.generate_request(parameters, body, headers) + if options[:signed] + request = self.sign_request(request) + end + return request + end + + def execute(api_method, parameters={}, body='', headers=[], options={}) + request = self.generate_request( + api_method, parameters, body, headers, options + ) + return self.transmit_request(request) end def transmit_request(request) @@ -172,10 +187,6 @@ module Google #:nodoc: '#generate_authenticated_request.' end end - - def execute_signed_request(*args, &block) - return self.transmit_request(self.build_request(*args, &block)) - end end end diff --git a/lib/google/api_client/discovery/service.rb b/lib/google/api_client/discovery/service.rb index 0db554fb4..3f89b3688 100644 --- a/lib/google/api_client/discovery/service.rb +++ b/lib/google/api_client/discovery/service.rb @@ -66,6 +66,19 @@ module Google #:nodoc: ) end + def to_h + return @hash ||= (begin + methods_hash = {} + self.methods.each do |method| + methods_hash[method.rpc_name] = method + end + self.resources.each do |resource| + methods_hash.merge!(resource.to_h) + end + methods_hash + end) + end + ## # Returns a String representation of the service's state. # @@ -117,6 +130,19 @@ module Google #:nodoc: ) end + def to_h + return @hash ||= (begin + methods_hash = {} + self.methods.each do |method| + methods_hash[method.rpc_name] = method + end + self.resources.each do |resource| + methods_hash.merge!(resource.to_h) + end + methods_hash + end) + end + ## # Returns a String representation of the resource's state. # @@ -137,27 +163,38 @@ module Google #:nodoc: attr_reader :name, :description, :base + def rpc_name + return self.description['rpcName'] + end + def uri_template - return Addressable::Template.new(base + self.description['pathUrl']) + return @uri_template ||= + Addressable::Template.new(base + self.description['pathUrl']) + end + + def normalize_parameters(parameters={}) + # Convert keys to Strings when appropriate + if parameters.kind_of?(Hash) || parameters.kind_of?(Array) + parameters = parameters.inject({}) do |accu, (k, v)| + k = k.to_s if k.kind_of?(Symbol) + k = k.to_str if k.respond_to?(:to_str) + unless k.kind_of?(String) + raise TypeError, "Expected String, got #{k.class}." + end + accu[k] = v + accu + end + else + raise TypeError, + "Expected Hash or Array, got #{parameters.class}." + end + return parameters end def generate_uri(parameters={}) - # Convert keys to Strings when appropriate - parameters = parameters.inject({}) do |accu, (k, v)| - k = k.to_s if k.kind_of?(Symbol) - k = k.to_str if k.respond_to?(:to_str) - unless k.kind_of?(String) - raise TypeError, "Expected String, got #{k.class}." - end - accu[k] = v - accu - end + parameters = self.normalize_parameters(parameters) + self.validate_parameters(parameters) template_variables = self.uri_template.variables - missing_variables = template_variables - parameters.keys - if missing_variables.size > 0 - raise ArgumentError, - "Missing required parameters: #{missing_variables.join(', ')}." - end uri = self.uri_template.expand(parameters) query_parameters = parameters.reject do |k, v| template_variables.include?(k) @@ -170,13 +207,46 @@ module Google #:nodoc: return uri.normalize end + def generate_request(parameters={}, body='', headers=[]) + method = self.description['httpMethod'] || 'GET' + uri = self.generate_uri(parameters) + return [method, uri.to_str, headers, [body]] + end + + def validate_parameters(parameters={}) + parameters = self.normalize_parameters(parameters) + parameter_description = self.description['parameters'] || {} + required_variables = Hash[parameter_description.select do |k, v| + v['required'] + end].keys + missing_variables = required_variables - parameters.keys + if missing_variables.size > 0 + raise ArgumentError, + "Missing required parameters: #{missing_variables.join(', ')}." + end + parameters.each do |k, v| + if parameter_description[k] + pattern = parameter_description[k]['pattern'] + if pattern + regexp = Regexp.new("^#{pattern}$") + if v !~ regexp + raise ArgumentError, + "Parameter '#{k}' has an invalid value: #{v}. " + + "Must match: /^#{pattern}$/." + end + end + end + end + return nil + end + ## # Returns a String representation of the method's state. # # @return [String] The method's state, as a String. def inspect sprintf( - "#<%s:%#0x NAME:%s>", self.class.to_s, self.object_id, self.name + "#<%s:%#0x NAME:%s>", self.class.to_s, self.object_id, self.rpc_name ) end end