Major update, primarily to add pagination support.
* Added Reference objects to encapsulate API calls. * Added Result objects to encapsulate API responses. * Changed the return value of APIClient#execute to Result. * Changed the method signature of APIClient#execute to support named params. * Added APIClient#execute! which throws exceptions on error. * Added automatic parsing code to better allow for complex nested structures. * Added error parser. * Added module for pagination in parsers.
This commit is contained in:
		
							parent
							
								
									a656c13862
								
							
						
					
					
						commit
						f336ab34a7
					
				|  | @ -447,7 +447,7 @@ HTML | |||
|           method.upcase! | ||||
|           request = [method, uri.to_str, headers, [request_body]] | ||||
|           request = client.generate_authenticated_request(:request => request) | ||||
|           response = client.transmit_request(request) | ||||
|           response = client.transmit(request) | ||||
|           status, headers, body = response | ||||
|           puts body | ||||
|           exit(0) | ||||
|  | @ -477,10 +477,13 @@ HTML | |||
|             parameters['xoauth_requestor_id'] = options[:requestor_id] | ||||
|           end | ||||
|           begin | ||||
|             response = client.execute( | ||||
|               method, parameters, request_body, headers | ||||
|             result = client.execute( | ||||
|               :api_method => method, | ||||
|               :parameters => parameters, | ||||
|               :merged_body => request_body, | ||||
|               :headers => headers | ||||
|             ) | ||||
|             status, headers, body = response | ||||
|             status, headers, body = result.response | ||||
|             puts body | ||||
|             exit(0) | ||||
|           rescue ArgumentError => e | ||||
|  |  | |||
|  | @ -75,10 +75,10 @@ get '/oauth2callback' do | |||
| end | ||||
| 
 | ||||
| get '/' do | ||||
|   response = @client.execute( | ||||
|   result = @client.execute( | ||||
|     @buzz.activities.list, | ||||
|     'userId' => '@me', 'scope' => '@consumption', 'alt'=> 'json' | ||||
|     {'userId' => '@me', 'scope' => '@consumption', 'alt'=> 'json'} | ||||
|   ) | ||||
|   status, headers, body = response | ||||
|   [status, {'Content-Type' => 'application/json'}, body] | ||||
|   status, _, _ = result.response | ||||
|   [status, {'Content-Type' => 'application/json'}, JSON.generate(result.data)] | ||||
| end | ||||
|  |  | |||
|  | @ -20,6 +20,8 @@ require 'stringio' | |||
| require 'google/api_client/errors' | ||||
| require 'google/api_client/environment' | ||||
| require 'google/api_client/discovery' | ||||
| require 'google/api_client/reference' | ||||
| require 'google/api_client/result' | ||||
| 
 | ||||
| module Google | ||||
|   # TODO(bobaman): Document all this stuff. | ||||
|  | @ -65,16 +67,6 @@ module Google | |||
|         'google-api-ruby-client/' + VERSION::STRING + | ||||
|         ' ' + ENV::OS_VERSION | ||||
|       ).strip | ||||
|       # This is mostly a default for the sake of convenience. | ||||
|       # Unlike most other options, this one may be nil, so we check for | ||||
|       # the presence of the key rather than checking the value. | ||||
|       if options.has_key?("parser") | ||||
|         self.parser = options["parser"] | ||||
|       else | ||||
|         require 'google/api_client/parsers/json_parser' | ||||
|         # NOTE: Do not rely on this default value, as it may change | ||||
|         self.parser = Google::APIClient::JSONParser | ||||
|       end | ||||
|       # The writer method understands a few Symbols and will generate useful | ||||
|       # default authentication mechanisms. | ||||
|       self.authorization = options["authorization"] || :oauth_2 | ||||
|  | @ -94,33 +86,6 @@ module Google | |||
|       return self | ||||
|     end | ||||
| 
 | ||||
| 
 | ||||
|     ## | ||||
|     # Returns the parser used by the client. | ||||
|     # | ||||
|     # @return [#serialize, #parse] | ||||
|     #   The parser used by the client.  Any object that implements both a | ||||
|     #   <code>#serialize</code> and a <code>#parse</code> method may be used. | ||||
|     #   If <code>nil</code>, no parsing will be done. | ||||
|     attr_reader :parser | ||||
| 
 | ||||
|     ## | ||||
|     # Sets the parser used by the client. | ||||
|     # | ||||
|     # @param [#serialize, #parse] new_parser | ||||
|     #   The parser used by the client.  Any object that implements both a | ||||
|     #   <code>#serialize</code> and a <code>#parse</code> method may be used. | ||||
|     #   If <code>nil</code>, no parsing will be done. | ||||
|     def parser=(new_parser) | ||||
|       if new_parser && | ||||
|           !new_parser.respond_to?(:serialize) && | ||||
|           !new_parser.respond_to?(:parse) | ||||
|         raise TypeError, | ||||
|           'Expected parser object to respond to #serialize and #parse.' | ||||
|       end | ||||
|       @parser = new_parser | ||||
|     end | ||||
| 
 | ||||
|     ## | ||||
|     # Returns the authorization mechanism used by the client. | ||||
|     # | ||||
|  | @ -280,7 +245,7 @@ module Google | |||
|           "Expected String or StringIO, got #{discovery_document.class}." | ||||
|       end | ||||
|       @discovery_documents["#{api}:#{version}"] = | ||||
|         JSON.parse(discovery_document) | ||||
|         ::JSON.parse(discovery_document) | ||||
|     end | ||||
| 
 | ||||
|     ## | ||||
|  | @ -291,16 +256,21 @@ module Google | |||
|       return @directory_document ||= (begin | ||||
|         request_uri = self.directory_uri | ||||
|         request = ['GET', request_uri, [], []] | ||||
|         response = self.transmit_request(request) | ||||
|         response = self.transmit(request) | ||||
|         status, headers, body = response | ||||
|         if status == 200 # TODO(bobaman) Better status code handling? | ||||
|           merged_body = StringIO.new | ||||
|           body.each do |chunk| | ||||
|             merged_body.write(chunk) | ||||
|           merged_body = body.inject(StringIO.new) do |accu, chunk| | ||||
|             accu.write(chunk) | ||||
|             accu | ||||
|           end | ||||
|           merged_body.rewind | ||||
|           JSON.parse(merged_body.string) | ||||
|         else | ||||
|           ::JSON.parse(merged_body.string) | ||||
|         elsif status >= 400 && status < 500 | ||||
|           raise ClientError, | ||||
|             "Could not retrieve discovery document at: #{request_uri}" | ||||
|         elsif status >= 500 && status < 600 | ||||
|           raise ServerError, | ||||
|             "Could not retrieve discovery document at: #{request_uri}" | ||||
|         elsif status > 600 | ||||
|           raise TransmissionError, | ||||
|             "Could not retrieve discovery document at: #{request_uri}" | ||||
|         end | ||||
|  | @ -319,16 +289,21 @@ module Google | |||
|       return @discovery_documents["#{api}:#{version}"] ||= (begin | ||||
|         request_uri = self.discovery_uri(api, version) | ||||
|         request = ['GET', request_uri, [], []] | ||||
|         response = self.transmit_request(request) | ||||
|         response = self.transmit(request) | ||||
|         status, headers, body = response | ||||
|         if status == 200 # TODO(bobaman) Better status code handling? | ||||
|           merged_body = StringIO.new | ||||
|           body.each do |chunk| | ||||
|             merged_body.write(chunk) | ||||
|           merged_body = body.inject(StringIO.new) do |accu, chunk| | ||||
|             accu.write(chunk) | ||||
|             accu | ||||
|           end | ||||
|           merged_body.rewind | ||||
|           JSON.parse(merged_body.string) | ||||
|         else | ||||
|           ::JSON.parse(merged_body.string) | ||||
|         elsif status >= 400 && status < 500 | ||||
|           raise ClientError, | ||||
|             "Could not retrieve discovery document at: #{request_uri}" | ||||
|         elsif status >= 500 && status < 600 | ||||
|           raise ServerError, | ||||
|             "Could not retrieve discovery document at: #{request_uri}" | ||||
|         elsif status > 600 | ||||
|           raise TransmissionError, | ||||
|             "Could not retrieve discovery document at: #{request_uri}" | ||||
|         end | ||||
|  | @ -344,7 +319,7 @@ module Google | |||
|         document_base = self.directory_uri | ||||
|         if self.directory_document && self.directory_document['items'] | ||||
|           self.directory_document['items'].map do |discovery_document| | ||||
|             ::Google::APIClient::API.new( | ||||
|             Google::APIClient::API.new( | ||||
|               document_base, | ||||
|               discovery_document | ||||
|             ) | ||||
|  | @ -373,7 +348,7 @@ module Google | |||
|         document_base = self.discovery_uri(api, version) | ||||
|         discovery_document = self.discovery_document(api, version) | ||||
|         if document_base && discovery_document | ||||
|           ::Google::APIClient::API.new( | ||||
|           Google::APIClient::API.new( | ||||
|             document_base, | ||||
|             discovery_document | ||||
|           ) | ||||
|  | @ -442,8 +417,6 @@ module Google | |||
|     #   - <code>:version</code> —  | ||||
|     #     The service version.  Only used if <code>api_method</code> is a | ||||
|     #     <code>String</code>.  Defaults to <code>'v1'</code>. | ||||
|     #   - <code>:parser</code> —  | ||||
|     #     The parser for the response. | ||||
|     #   - <code>:authorization</code> —  | ||||
|     #     The authorization mechanism for the response.  Used only if | ||||
|     #     <code>:authenticated</code> is <code>true</code>. | ||||
|  | @ -457,17 +430,20 @@ module Google | |||
|     # | ||||
|     # @example | ||||
|     #   request = client.generate_request( | ||||
|     #     'chili.activities.list', | ||||
|     #     {'scope' => '@self', 'userId' => '@me', 'alt' => 'json'} | ||||
|     #     :api_method => 'chili.activities.list', | ||||
|     #     :parameters => | ||||
|     #       {'scope' => '@self', 'userId' => '@me', 'alt' => 'json'} | ||||
|     #   ) | ||||
|     #   method, uri, headers, body = request | ||||
|     def generate_request( | ||||
|         api_method, parameters={}, body='', headers=[], options={}) | ||||
|     def generate_request(options={}) | ||||
|       # Note: The merge method on a Hash object will coerce an API Reference | ||||
|       # object into a Hash and merge with the default options. | ||||
|       options={ | ||||
|         :parser => self.parser, | ||||
|         :version => 'v1', | ||||
|         :authorization => self.authorization | ||||
|       }.merge(options) | ||||
|       # The Reference object is going to need this to do method ID lookups. | ||||
|       options[:client] = self | ||||
|       # The default value for the :authenticated option depends on whether an | ||||
|       # authorization mechanism has been set. | ||||
|       if options[:authorization] | ||||
|  | @ -475,27 +451,8 @@ module Google | |||
|       else | ||||
|         options = {:authenticated => false}.merge(options) | ||||
|       end | ||||
|       if api_method.kind_of?(String) || api_method.kind_of?(Symbol) | ||||
|         api_method = api_method.to_s | ||||
|         # This method of guessing the API is unreliable. This will fail for | ||||
|         # APIs where the first segment of the RPC name does not match the | ||||
|         # service name. However, this is a fallback mechanism anyway. | ||||
|         # Developers should be passing in a reference to the method, rather | ||||
|         # than passing in a string or symbol. This should raise an error | ||||
|         # in the case of a mismatch. | ||||
|         api = api_method[/^([^.]+)\./, 1] | ||||
|         api_method = self.discovered_method( | ||||
|           api_method, api, options[:version] | ||||
|         ) | ||||
|       elsif !api_method.kind_of?(::Google::APIClient::Method) | ||||
|         raise TypeError, | ||||
|           "Expected String, Symbol, or Google::APIClient::Method, " + | ||||
|           "got #{api_method.class}." | ||||
|       end | ||||
|       unless api_method | ||||
|         raise ArgumentError, "API method could not be found." | ||||
|       end | ||||
|       request = api_method.generate_request(parameters, body, headers) | ||||
|       reference = Google::APIClient::Reference.new(options) | ||||
|       request = reference.to_request | ||||
|       if options[:authenticated] | ||||
|         request = self.generate_authenticated_request(:request => request) | ||||
|       end | ||||
|  | @ -503,47 +460,13 @@ module Google | |||
|     end | ||||
| 
 | ||||
|     ## | ||||
|     # Generates a request and transmits it. | ||||
|     # Signs a request using the current authorization mechanism. | ||||
|     # | ||||
|     # @param [Google::APIClient::Method, String] api_method | ||||
|     #   The method object or the RPC name of the method being executed. | ||||
|     # @param [Hash, Array] parameters | ||||
|     #   The parameters to send to the method. | ||||
|     # @param [String] body The body of the request. | ||||
|     # @param [Hash, Array] headers The HTTP headers for the request. | ||||
|     # @param [Hash] options | ||||
|     #   The configuration parameters for the request. | ||||
|     #   - <code>:version</code> —  | ||||
|     #     The service version.  Only used if <code>api_method</code> is a | ||||
|     #     <code>String</code>.  Defaults to <code>'v1'</code>. | ||||
|     #   - <code>:adapter</code> —  | ||||
|     #     The HTTP adapter. | ||||
|     #   - <code>:parser</code> —  | ||||
|     #     The parser for the response. | ||||
|     #   - <code>:authorization</code> —  | ||||
|     #     The authorization mechanism for the response.  Used only if | ||||
|     #     <code>:authenticated</code> is <code>true</code>. | ||||
|     #   - <code>:authenticated</code> —  | ||||
|     #     <code>true</code> if the request must be signed or otherwise | ||||
|     #     authenticated, <code>false</code> | ||||
|     #     otherwise.  Defaults to <code>true</code>. | ||||
|     # @param [Hash] options The options to pass through. | ||||
|     # | ||||
|     # @return [Array] The response from the API. | ||||
|     # | ||||
|     # @example | ||||
|     #   response = client.execute( | ||||
|     #     'chili.activities.list', | ||||
|     #     {'scope' => '@self', 'userId' => '@me', 'alt' => 'json'} | ||||
|     #   ) | ||||
|     #   status, headers, body = response | ||||
|     def execute(api_method, parameters={}, body='', headers=[], options={}) | ||||
|       request = self.generate_request( | ||||
|         api_method, parameters, body, headers, options | ||||
|       ) | ||||
|       return self.transmit_request( | ||||
|         request, | ||||
|         options[:adapter] || self.http_adapter | ||||
|       ) | ||||
|     # @return [Array] The signed or otherwise authenticated request. | ||||
|     def generate_authenticated_request(options={}) | ||||
|       return authorization.generate_authenticated_request(options) | ||||
|     end | ||||
| 
 | ||||
|     ## | ||||
|  | @ -553,7 +476,7 @@ module Google | |||
|     # @param [#transmit] adapter The HTTP adapter. | ||||
|     # | ||||
|     # @return [Array] The response from the server. | ||||
|     def transmit_request(request, adapter=self.http_adapter) | ||||
|     def transmit(request, adapter=self.http_adapter) | ||||
|       if self.user_agent != nil | ||||
|         # If there's no User-Agent header, set one. | ||||
|         method, uri, headers, body = request | ||||
|  | @ -577,13 +500,90 @@ module Google | |||
|     end | ||||
| 
 | ||||
|     ## | ||||
|     # Signs a request using the current authorization mechanism. | ||||
|     # Executes a request, wrapping it in a Result object. | ||||
|     # | ||||
|     # @param [Hash] options The options to pass through. | ||||
|     # @param [Google::APIClient::Method, String] api_method | ||||
|     #   The method object or the RPC name of the method being executed. | ||||
|     # @param [Hash, Array] parameters | ||||
|     #   The parameters to send to the method. | ||||
|     # @param [String] body The body of the request. | ||||
|     # @param [Hash, Array] headers The HTTP headers for the request. | ||||
|     # @param [Hash] options | ||||
|     #   The configuration parameters for the request. | ||||
|     #   - <code>:version</code> —  | ||||
|     #     The service version.  Only used if <code>api_method</code> is a | ||||
|     #     <code>String</code>.  Defaults to <code>'v1'</code>. | ||||
|     #   - <code>:adapter</code> —  | ||||
|     #     The HTTP adapter. | ||||
|     #   - <code>:authorization</code> —  | ||||
|     #     The authorization mechanism for the response.  Used only if | ||||
|     #     <code>:authenticated</code> is <code>true</code>. | ||||
|     #   - <code>:authenticated</code> —  | ||||
|     #     <code>true</code> if the request must be signed or otherwise | ||||
|     #     authenticated, <code>false</code> | ||||
|     #     otherwise.  Defaults to <code>true</code>. | ||||
|     # | ||||
|     # @return [Array] The signed or otherwise authenticated request. | ||||
|     def generate_authenticated_request(options={}) | ||||
|       return authorization.generate_authenticated_request(options) | ||||
|     # @return [Array] The response from the API. | ||||
|     # | ||||
|     # @example | ||||
|     #   request = client.generate_request( | ||||
|     #     :api_method => 'chili.activities.list', | ||||
|     #     :parameters => | ||||
|     #       {'scope' => '@self', 'userId' => '@me', 'alt' => 'json'} | ||||
|     #   ) | ||||
|     def execute(*params) | ||||
|       # This block of code allows us to accept multiple parameter passing | ||||
|       # styles, and maintaining backwards compatibility. | ||||
|       if params.last.respond_to?(:to_hash) && params.size != 2 | ||||
|         # Hash options are tricky. If we get two arguments, it's ambiguous | ||||
|         # whether to treat them as API parameters or Hash options, but since | ||||
|         # it's rare to need to pass in options, we must assume that the | ||||
|         # developer wanted to pass API parameters. Prefer using named | ||||
|         # parameters to avoid this issue. Unnamed parameters should be | ||||
|         # considered syntactic sugar. | ||||
|         options = params.pop | ||||
|       else | ||||
|         options = {} | ||||
|       end | ||||
|       options[:api_method] = params.shift if params.size > 0 | ||||
|       options[:parameters] = params.shift if params.size > 0 | ||||
|       options[:merged_body] = params.shift if params.size > 0 | ||||
|       options[:headers] = params.shift if params.size > 0 | ||||
|       options[:client] = self | ||||
| 
 | ||||
|       reference = Google::APIClient::Reference.new(options) | ||||
|       request = self.generate_request(reference) | ||||
|       response = self.transmit( | ||||
|         request, | ||||
|         options[:adapter] || self.http_adapter | ||||
|       ) | ||||
|       return Google::APIClient::Result.new(reference, request, response) | ||||
|     end | ||||
| 
 | ||||
|     ## | ||||
|     # Same as Google::APIClient#execute, but raises an exception if there was | ||||
|     # an error. | ||||
|     # | ||||
|     # @see Google::APIClient#execute | ||||
|     def execute!(*params) | ||||
|       result = self.execute(*params) | ||||
|       status, _, _ = result.response | ||||
|       if result.data.respond_to?(:error) | ||||
|         # You're going to get a terrible error message if the response isn't | ||||
|         # parsed successfully as an error. | ||||
|         error_message = result.data.error | ||||
|       end | ||||
|       if status >= 400 && status < 500 | ||||
|         raise ClientError, | ||||
|           error_message || "A client error has occurred." | ||||
|       elsif status >= 500 && status < 600 | ||||
|         raise ServerError, | ||||
|           error_message || "A server error has occurred." | ||||
|       elsif status > 600 | ||||
|         raise TransmissionError, | ||||
|           error_message || "A transmission error has occurred." | ||||
|       end | ||||
|       return result | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -144,7 +144,7 @@ module Google | |||
|       def resources | ||||
|         return @resources ||= ( | ||||
|           (@discovery_document['resources'] || []).inject([]) do |accu, (k, v)| | ||||
|             accu << ::Google::APIClient::Resource.new(self.method_base, k, v) | ||||
|             accu << Google::APIClient::Resource.new(self.method_base, k, v) | ||||
|             accu | ||||
|           end | ||||
|         ) | ||||
|  | @ -158,7 +158,7 @@ module Google | |||
|       def methods | ||||
|         return @methods ||= ( | ||||
|           (@discovery_document['methods'] || []).inject([]) do |accu, (k, v)| | ||||
|             accu << ::Google::APIClient::Method.new(self.method_base, k, v) | ||||
|             accu << Google::APIClient::Method.new(self.method_base, k, v) | ||||
|             accu | ||||
|           end | ||||
|         ) | ||||
|  | @ -271,7 +271,7 @@ module Google | |||
|       def resources | ||||
|         return @resources ||= ( | ||||
|           (@discovery_document['resources'] || []).inject([]) do |accu, (k, v)| | ||||
|             accu << ::Google::APIClient::Resource.new(self.method_base, k, v) | ||||
|             accu << Google::APIClient::Resource.new(self.method_base, k, v) | ||||
|             accu | ||||
|           end | ||||
|         ) | ||||
|  | @ -284,7 +284,7 @@ module Google | |||
|       def methods | ||||
|         return @methods ||= ( | ||||
|           (@discovery_document['methods'] || []).inject([]) do |accu, (k, v)| | ||||
|             accu << ::Google::APIClient::Method.new(self.method_base, k, v) | ||||
|             accu << Google::APIClient::Method.new(self.method_base, k, v) | ||||
|             accu | ||||
|           end | ||||
|         ) | ||||
|  | @ -378,6 +378,14 @@ module Google | |||
|         return @discovery_document['id'] | ||||
|       end | ||||
| 
 | ||||
|       ## | ||||
|       # Returns the HTTP method or 'GET' if none is specified. | ||||
|       # | ||||
|       # @return [String] The HTTP method that will be used in the request. | ||||
|       def http_method | ||||
|         return @discovery_document['httpMethod'] || 'GET' | ||||
|       end | ||||
| 
 | ||||
|       ## | ||||
|       # Returns the URI template for the method.  A parameter list can be | ||||
|       # used to expand this into a URI. | ||||
|  | @ -465,7 +473,7 @@ module Google | |||
|         if !headers.kind_of?(Array) && !headers.kind_of?(Hash) | ||||
|           raise TypeError, "Expected Hash or Array, got #{headers.class}." | ||||
|         end | ||||
|         method = @discovery_document['httpMethod'] || 'GET' | ||||
|         method = self.http_method | ||||
|         uri = self.generate_uri(parameters) | ||||
|         headers = headers.to_a if headers.kind_of?(Hash) | ||||
|         return [method, uri.to_str, headers, [body]] | ||||
|  |  | |||
|  | @ -26,5 +26,15 @@ module Google | |||
|     # invalid parameter values. | ||||
|     class ValidationError < StandardError | ||||
|     end | ||||
| 
 | ||||
|     ## | ||||
|     # A 4xx class HTTP error occurred. | ||||
|     class ClientError < TransmissionError | ||||
|     end | ||||
| 
 | ||||
|     ## | ||||
|     # A 5xx class HTTP error occurred. | ||||
|     class ServerError < TransmissionError | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -0,0 +1,59 @@ | |||
| # Copyright 2010 Google Inc. | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #      http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| 
 | ||||
| 
 | ||||
| require 'json' | ||||
| 
 | ||||
| module Google | ||||
|   class APIClient | ||||
|     module Parser | ||||
|       def content_type(content_type) | ||||
|         @@content_type_mapping ||= {} | ||||
|         @@content_type_mapping[content_type] = self | ||||
|       end | ||||
| 
 | ||||
|       def self.match_content_type(content_type) | ||||
|         # TODO(bobaman): Do this more efficiently. | ||||
|         mime_type_regexp = /^([^\/]+)(?:\/([^+]+\+)?([^;]+))?(?:;.*)?$/ | ||||
|         if @@content_type_mapping[content_type] | ||||
|           # Exact match | ||||
|           return @@content_type_mapping[content_type] | ||||
|         else | ||||
|           media_type, extension, sub_type = | ||||
|             content_type.scan(mime_type_regexp)[0] | ||||
|           for pattern, parser in @@content_type_mapping | ||||
|             # We want to match on subtype first | ||||
|             pattern_media_type, pattern_extension, pattern_sub_type = | ||||
|               pattern.scan(mime_type_regexp)[0] | ||||
|             next if pattern_extension != nil | ||||
|             if media_type == pattern_media_type && sub_type == pattern_sub_type | ||||
|               return parser | ||||
|             end | ||||
|           end | ||||
|           for pattern, parser in @@content_type_mapping | ||||
|             # We failed to match on the subtype | ||||
|             # Try to match only on the media type | ||||
|             pattern_media_type, pattern_extension, pattern_sub_type = | ||||
|               pattern.scan(mime_type_regexp)[0] | ||||
|             next if pattern_extension != nil || pattern_sub_type != nil | ||||
|             if media_type == pattern_media_type | ||||
|               return parser | ||||
|             end | ||||
|           end | ||||
|         end | ||||
|         return nil | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | @ -0,0 +1,34 @@ | |||
| # Copyright 2010 Google Inc. | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #      http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| 
 | ||||
| 
 | ||||
| require 'google/api_client/parsers/json_parser' | ||||
| 
 | ||||
| module Google | ||||
|   class APIClient | ||||
|     module JSON | ||||
|       ## | ||||
|       # A module which provides a parser for error responses. | ||||
|       class ErrorParser | ||||
|         include Google::APIClient::JSONParser | ||||
| 
 | ||||
|         matches_fields 'error' | ||||
| 
 | ||||
|         def error | ||||
|           return self['error']['message'] | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | @ -0,0 +1,40 @@ | |||
| # Copyright 2010 Google Inc. | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #      http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| 
 | ||||
| 
 | ||||
| require 'google/api_client/parsers/json_parser' | ||||
| 
 | ||||
| module Google | ||||
|   class APIClient | ||||
|     module JSON | ||||
|       ## | ||||
|       # A module which provides a paginated parser. | ||||
|       module Pagination | ||||
|         def self.included(parser) | ||||
|           parser.class_eval do | ||||
|             include Google::APIClient::JSONParser | ||||
|           end | ||||
|         end | ||||
| 
 | ||||
|         def next_page_token | ||||
|           return self["nextPageToken"] | ||||
|         end | ||||
| 
 | ||||
|         def prev_page_token | ||||
|           return self["prevPageToken"] | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | @ -14,27 +14,105 @@ | |||
| 
 | ||||
| 
 | ||||
| require 'json' | ||||
| require 'google/api_client/parser' | ||||
| 
 | ||||
| module Google | ||||
|   class APIClient | ||||
|     ## | ||||
|     # Provides a consistent interface by which to parse request and response | ||||
|     # content. | ||||
|     # TODO(mattpok): ensure floats, URLs, dates are parsed correctly | ||||
|     # Provides a module which all other parsers should include. | ||||
|     module JSONParser | ||||
|       extend Parser | ||||
|       content_type 'application/json' | ||||
| 
 | ||||
|       def self.serialize(hash) | ||||
|         # JSON parser used can accept arrays as well, but we will limit | ||||
|         # to only allow hash to JSON string parsing to keep a simple interface | ||||
|         unless hash.instance_of? Hash | ||||
|           raise ArgumentError, | ||||
|             "JSON generate expected a Hash but got a #{hash.class}." | ||||
|       module Matcher | ||||
|         def conditions | ||||
|           @conditions ||= [] | ||||
|         end | ||||
| 
 | ||||
|         def matches_kind(kind) | ||||
|           self.matches_field_value(:kind, kind) | ||||
|         end | ||||
| 
 | ||||
|         def matches_fields(fields) | ||||
|           self.conditions << [:fields, fields] | ||||
|         end | ||||
| 
 | ||||
|         def matches_field_value(field, value) | ||||
|           self.conditions << [:field_value, field, value] | ||||
|         end | ||||
|         return JSON.generate(hash) | ||||
|       end | ||||
| 
 | ||||
|       def self.parse(json_string) | ||||
|         return JSON.parse(json_string) | ||||
|       def self.parsers | ||||
|         @parsers ||= [] | ||||
|       end | ||||
| 
 | ||||
|       ## | ||||
|       # This method ensures that all parsers auto-register themselves. | ||||
|       def self.included(parser) | ||||
|         self.parsers << parser | ||||
|         parser.extend(Matcher) | ||||
|       end | ||||
| 
 | ||||
|       def initialize(data) | ||||
|         @data = data.kind_of?(Hash) ? data : ::JSON.parse(data) | ||||
|       end | ||||
| 
 | ||||
|       def [](key) | ||||
|         return self.json[key] | ||||
|       end | ||||
| 
 | ||||
|       def json | ||||
|         if @data | ||||
|           data = @data | ||||
|         elsif self.respond_to?(:data) | ||||
|           data = self.data | ||||
|         else | ||||
|           raise TypeError, "Parser did not provide access to raw data." | ||||
|         end | ||||
|         return data | ||||
|       end | ||||
| 
 | ||||
|       ## | ||||
|       # Matches a parser to the data. | ||||
|       def self.match(data) | ||||
|         for parser in self.parsers | ||||
|           conditions_met = true | ||||
|           for condition in (parser.conditions.sort_by { |c| c.size }).reverse | ||||
|             condition_type, *params = condition | ||||
|             case condition_type | ||||
|             when :fields | ||||
|               for field in params | ||||
|                 if !data.has_key?(field) | ||||
|                   conditions_met = false | ||||
|                   break | ||||
|                 end | ||||
|               end | ||||
|             when :field_values | ||||
|               field, value = params | ||||
|               if data[field] != value | ||||
|                 conditions_met = false | ||||
|                 break | ||||
|               end | ||||
|             else | ||||
|               raise ArgumentError, "Unknown condition type: #{condition_type}" | ||||
|             end | ||||
|             break if !conditions_met | ||||
|           end | ||||
|           if conditions_met | ||||
|             return parser | ||||
|           end | ||||
|         end | ||||
|         return nil | ||||
|       end | ||||
| 
 | ||||
|       def self.parse(json) | ||||
|         data = json.kind_of?(Hash) ? json : ::JSON.parse(json) | ||||
|         parser = self.match(data) | ||||
|         if parser | ||||
|           return parser.new(data) | ||||
|         else | ||||
|           return data | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|   end | ||||
|  |  | |||
|  | @ -0,0 +1,182 @@ | |||
| # Copyright 2010 Google Inc. | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #      http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| 
 | ||||
| require 'stringio' | ||||
| require 'addressable/uri' | ||||
| require 'google/api_client/discovery' | ||||
| 
 | ||||
| module Google | ||||
|   class APIClient | ||||
|     class Reference | ||||
|       def initialize(options={}) | ||||
|         # We only need this to do lookups on method ID String values | ||||
|         # It's optional, but method ID lookups will fail if the client is | ||||
|         # omitted. | ||||
|         @client = options[:client] | ||||
|         @version = options[:version] || 'v1' | ||||
| 
 | ||||
|         self.api_method = options[:api_method] | ||||
|         self.parameters = options[:parameters] || {} | ||||
|         self.headers = options[:headers] || [] | ||||
|         if options[:body] | ||||
|           self.body = options[:body] | ||||
|         elsif options[:merged_body] | ||||
|           self.merged_body = options[:merged_body] | ||||
|         else | ||||
|           self.merged_body = '' | ||||
|         end | ||||
|         unless self.api_method | ||||
|           self.http_method = options[:http_method] || 'GET' | ||||
|           self.uri = options[:uri] | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       def api_method | ||||
|         return @api_method | ||||
|       end | ||||
| 
 | ||||
|       def api_method=(new_api_method) | ||||
|         if new_api_method.kind_of?(Google::APIClient::Method) || | ||||
|             new_api_method == nil | ||||
|           @api_method = new_api_method | ||||
|         elsif new_api_method.respond_to?(:to_str) || | ||||
|             new_api_method.kind_of?(Symbol) | ||||
|           unless @client | ||||
|             raise ArgumentError, | ||||
|               "API method lookup impossible without client instance." | ||||
|           end | ||||
|           new_api_method = new_api_method.to_s | ||||
|           # This method of guessing the API is unreliable. This will fail for | ||||
|           # APIs where the first segment of the RPC name does not match the | ||||
|           # service name. However, this is a fallback mechanism anyway. | ||||
|           # Developers should be passing in a reference to the method, rather | ||||
|           # than passing in a string or symbol. This should raise an error | ||||
|           # in the case of a mismatch. | ||||
|           api = new_api_method[/^([^.]+)\./, 1] | ||||
|           @api_method = @client.discovered_method( | ||||
|             new_api_method, api, @version | ||||
|           ) | ||||
|           if @api_method | ||||
|             # Ditch the client reference, we won't need it again. | ||||
|             @client = nil | ||||
|           else | ||||
|             raise ArgumentError, "API method could not be found." | ||||
|           end | ||||
|         else | ||||
|           raise TypeError, | ||||
|             "Expected Google::APIClient::Method, got #{new_api_method.class}." | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       def parameters | ||||
|         return @parameters | ||||
|       end | ||||
| 
 | ||||
|       def parameters=(new_parameters) | ||||
|         # No type-checking needed, the Method class handles this. | ||||
|         @parameters = new_parameters | ||||
|       end | ||||
| 
 | ||||
|       def body | ||||
|         return @body | ||||
|       end | ||||
| 
 | ||||
|       def body=(new_body) | ||||
|         if new_body.respond_to?(:each) | ||||
|           @body = new_body | ||||
|         else | ||||
|           raise TypeError, "Expected body to respond to :each." | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       def merged_body | ||||
|         return (self.body.inject(StringIO.new) do |accu, chunk| | ||||
|           accu.write(chunk) | ||||
|           accu | ||||
|         end).string | ||||
|       end | ||||
| 
 | ||||
|       def merged_body=(new_merged_body) | ||||
|         if new_merged_body.respond_to?(:string) | ||||
|           new_merged_body = new_merged_body.string | ||||
|         elsif new_merged_body.respond_to?(:to_str) | ||||
|           new_merged_body = new_merged_body.to_str | ||||
|         else | ||||
|           raise TypeError, | ||||
|             "Expected String or StringIO, got #{new_merged_body.class}." | ||||
|         end | ||||
|         self.body = [new_merged_body] | ||||
|       end | ||||
| 
 | ||||
|       def headers | ||||
|         return @headers ||= [] | ||||
|       end | ||||
| 
 | ||||
|       def headers=(new_headers) | ||||
|         if new_headers.kind_of?(Array) || new_headers.kind_of?(Hash) | ||||
|           @headers = new_headers | ||||
|         else | ||||
|           raise TypeError, "Expected Hash or Array, got #{new_headers.class}." | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       def http_method | ||||
|         return @http_method ||= self.api_method.http_method | ||||
|       end | ||||
| 
 | ||||
|       def http_method=(new_http_method) | ||||
|         if new_http_method.kind_of?(Symbol) | ||||
|           @http_method = new_http_method.to_s.upcase | ||||
|         elsif new_http_method.respond_to?(:to_str) | ||||
|           @http_method = new_http_method.to_str.upcase | ||||
|         else | ||||
|           raise TypeError, | ||||
|             "Expected String or Symbol, got #{new_http_method.class}." | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       def uri | ||||
|         return @uri ||= self.api_method.generate_uri(self.parameters) | ||||
|       end | ||||
| 
 | ||||
|       def uri=(new_uri) | ||||
|         @uri = Addressable::URI.parse(new_uri) | ||||
|       end | ||||
| 
 | ||||
|       def to_request | ||||
|         if self.api_method | ||||
|           return self.api_method.generate_request( | ||||
|             self.parameters, self.merged_body, self.headers | ||||
|           ) | ||||
|         else | ||||
|           return [self.http_method, self.uri, self.headers, self.body] | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       def to_hash | ||||
|         options = {} | ||||
|         if self.api_method | ||||
|           options[:api_method] = self.api_method | ||||
|           options[:parameters] = self.parameters | ||||
|         else | ||||
|           options[:http_method] = self.http_method | ||||
|           options[:uri] = self.uri | ||||
|         end | ||||
|         options[:headers] = self.headers | ||||
|         options[:body] = self.body | ||||
|         return options | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | @ -0,0 +1,116 @@ | |||
| # Copyright 2010 Google Inc. | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #      http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| 
 | ||||
| require 'google/api_client/parsers/json_parser' | ||||
| 
 | ||||
| module Google | ||||
|   class APIClient | ||||
|     ## | ||||
|     # This class wraps a result returned by an API call. | ||||
|     class Result | ||||
|       def initialize(reference, request, response) | ||||
|         @reference = reference | ||||
|         @request = request | ||||
|         @response = response | ||||
|       end | ||||
| 
 | ||||
|       attr_reader :reference | ||||
| 
 | ||||
|       attr_reader :request | ||||
| 
 | ||||
|       attr_reader :response | ||||
| 
 | ||||
|       def status | ||||
|         return @response[0] | ||||
|       end | ||||
| 
 | ||||
|       def headers | ||||
|         return @response[1] | ||||
|       end | ||||
| 
 | ||||
|       def body | ||||
|         return @body ||= (begin | ||||
|           response_body = @response[2] | ||||
|           merged_body = (response_body.inject(StringIO.new) do |accu, chunk| | ||||
|             accu.write(chunk) | ||||
|             accu | ||||
|           end).string | ||||
|         end) | ||||
|       end | ||||
| 
 | ||||
|       def data | ||||
|         return @data ||= (begin | ||||
|           _, content_type = self.headers.detect do |h, v| | ||||
|             h.downcase == 'Content-Type'.downcase | ||||
|           end | ||||
|           parser_type = | ||||
|             Google::APIClient::Parser.match_content_type(content_type) | ||||
|           parser_type.parse(self.body) | ||||
|         end) | ||||
|       end | ||||
| 
 | ||||
|       def pagination_type | ||||
|         return :token | ||||
|       end | ||||
| 
 | ||||
|       def page_token_param | ||||
|         return "pageToken" | ||||
|       end | ||||
| 
 | ||||
|       def next_page_token | ||||
|         if self.data.respond_to?(:next_page_token) | ||||
|           return self.data.next_page_token | ||||
|         elsif self.data.respond_to?(:[]) | ||||
|           return self.data["nextPageToken"] | ||||
|         else | ||||
|           raise TypeError, "Data object did not respond to #next_page_token." | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       def next_page | ||||
|         merged_parameters = Hash[self.reference.parameters].merge({ | ||||
|           self.page_token_param => self.next_page_token | ||||
|         }) | ||||
|         # Because References can be coerced to Hashes, we can merge them, | ||||
|         # preserving all context except the API method parameters that we're | ||||
|         # using for pagination. | ||||
|         return Google::APIClient::Reference.new( | ||||
|           Hash[self.reference].merge(:parameters => merged_parameters) | ||||
|         ) | ||||
|       end | ||||
| 
 | ||||
|       def prev_page_token | ||||
|         if self.data.respond_to?(:prev_page_token) | ||||
|           return self.data.prev_page_token | ||||
|         elsif self.data.respond_to?(:[]) | ||||
|           return self.data["prevPageToken"] | ||||
|         else | ||||
|           raise TypeError, "Data object did not respond to #next_page_token." | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       def prev_page | ||||
|         merged_parameters = Hash[self.reference.parameters].merge({ | ||||
|           self.page_token_param => self.prev_page_token | ||||
|         }) | ||||
|         # Because References can be coerced to Hashes, we can merge them, | ||||
|         # preserving all context except the API method parameters that we're | ||||
|         # using for pagination. | ||||
|         return Google::APIClient::Reference.new( | ||||
|           Hash[self.reference].merge(:parameters => merged_parameters) | ||||
|         ) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | @ -16,8 +16,8 @@ | |||
| module Google | ||||
|   class APIClient | ||||
|     module VERSION | ||||
|       MAJOR = 0 | ||||
|       MINOR = 2 | ||||
|       MAJOR = 1 | ||||
|       MINOR = 0 | ||||
|       TINY  = 0 | ||||
| 
 | ||||
|       STRING = [MAJOR, MINOR, TINY].join('.') | ||||
|  |  | |||
|  | @ -66,6 +66,9 @@ describe Google::APIClient do | |||
|   describe 'with the prediction API' do | ||||
|     before do | ||||
|       @client.authorization = nil | ||||
|       # The prediction API no longer exposes a v1, so we have to be | ||||
|       # careful about looking up the wrong API version. | ||||
|       @prediction = @client.discovered_api('prediction', 'v1.2') | ||||
|     end | ||||
| 
 | ||||
|     it 'should correctly determine the discovery URI' do | ||||
|  | @ -74,45 +77,39 @@ describe Google::APIClient do | |||
|     end | ||||
| 
 | ||||
|     it 'should correctly generate API objects' do | ||||
|       @client.discovered_api('prediction').name.should == 'prediction' | ||||
|       @client.discovered_api('prediction').version.should == 'v1' | ||||
|       @client.discovered_api(:prediction).name.should == 'prediction' | ||||
|       @client.discovered_api(:prediction).version.should == 'v1' | ||||
|       @client.discovered_api('prediction', 'v1.2').name.should == 'prediction' | ||||
|       @client.discovered_api('prediction', 'v1.2').version.should == 'v1.2' | ||||
|       @client.discovered_api(:prediction, 'v1.2').name.should == 'prediction' | ||||
|       @client.discovered_api(:prediction, 'v1.2').version.should == 'v1.2' | ||||
|     end | ||||
| 
 | ||||
|     it 'should discover methods' do | ||||
|       @client.discovered_method( | ||||
|         'prediction.training.insert', 'prediction' | ||||
|         'prediction.training.insert', 'prediction', 'v1.2' | ||||
|       ).name.should == 'insert' | ||||
|       @client.discovered_method( | ||||
|         :'prediction.training.insert', :prediction | ||||
|         :'prediction.training.insert', :prediction, 'v1.2' | ||||
|       ).name.should == 'insert' | ||||
|     end | ||||
| 
 | ||||
|     it 'should discover methods' do | ||||
|       @client.discovered_method( | ||||
|         'prediction.training.delete', 'prediction', 'v1.1' | ||||
|         'prediction.training.delete', 'prediction', 'v1.2' | ||||
|       ).name.should == 'delete' | ||||
|     end | ||||
| 
 | ||||
|     it 'should not find methods that are not in the discovery document' do | ||||
|       @client.discovered_method( | ||||
|         'prediction.training.delete', 'prediction', 'v1' | ||||
|       ).should == nil | ||||
|       @client.discovered_method( | ||||
|         'prediction.bogus', 'prediction', 'v1' | ||||
|         'prediction.bogus', 'prediction', 'v1.2' | ||||
|       ).should == nil | ||||
|     end | ||||
| 
 | ||||
|     it 'should raise an error for bogus methods' do | ||||
|       (lambda do | ||||
|         @client.discovered_method(42, 'prediction', 'v1') | ||||
|         @client.discovered_method(42, 'prediction', 'v1.2') | ||||
|       end).should raise_error(TypeError) | ||||
|     end | ||||
| 
 | ||||
|     it 'should raise an error for bogus methods' do | ||||
|       (lambda do | ||||
|         @client.generate_request(@client.discovered_api('prediction')) | ||||
|         @client.generate_request(@client.discovered_api('prediction', 'v1.2')) | ||||
|       end).should raise_error(TypeError) | ||||
|     end | ||||
| 
 | ||||
|  | @ -123,49 +120,50 @@ describe Google::APIClient do | |||
| 
 | ||||
|     it 'should generate valid requests' do | ||||
|       request = @client.generate_request( | ||||
|         'prediction.training.insert', | ||||
|         {'data' => '12345', } | ||||
|         :api_method => @prediction.training.insert, | ||||
|         :parameters => {'data' => '12345', } | ||||
|       ) | ||||
|       method, uri, headers, body = request | ||||
|       method.should == 'POST' | ||||
|       uri.should == | ||||
|         'https://www.googleapis.com/prediction/v1/training?data=12345' | ||||
|         'https://www.googleapis.com/prediction/v1.2/training?data=12345' | ||||
|       (headers.inject({}) { |h,(k,v)| h[k]=v; h }).should == {} | ||||
|       body.should respond_to(:each) | ||||
|     end | ||||
| 
 | ||||
|     it 'should generate requests against the correct URIs' do | ||||
|       request = @client.generate_request( | ||||
|         :'prediction.training.insert', | ||||
|         {'data' => '12345'} | ||||
|         :api_method => @prediction.training.insert, | ||||
|         :parameters => {'data' => '12345'} | ||||
|       ) | ||||
|       method, uri, headers, body = request | ||||
|       uri.should == | ||||
|         'https://www.googleapis.com/prediction/v1/training?data=12345' | ||||
|         'https://www.googleapis.com/prediction/v1.2/training?data=12345' | ||||
|     end | ||||
| 
 | ||||
|     it 'should generate requests against the correct URIs' do | ||||
|       prediction = @client.discovered_api('prediction', 'v1') | ||||
|       request = @client.generate_request( | ||||
|         prediction.training.insert, | ||||
|         {'data' => '12345'} | ||||
|         :api_method => @prediction.training.insert, | ||||
|         :parameters => {'data' => '12345'} | ||||
|       ) | ||||
|       method, uri, headers, body = request | ||||
|       uri.should == | ||||
|         'https://www.googleapis.com/prediction/v1/training?data=12345' | ||||
|         'https://www.googleapis.com/prediction/v1.2/training?data=12345' | ||||
|     end | ||||
| 
 | ||||
|     it 'should allow modification to the base URIs for testing purposes' do | ||||
|       prediction = @client.discovered_api('prediction', 'v1') | ||||
|       prediction = @client.discovered_api('prediction', 'v1.2') | ||||
|       prediction.method_base = | ||||
|         'https://testing-domain.googleapis.com/prediction/v1/' | ||||
|         'https://testing-domain.googleapis.com/prediction/v1.2/' | ||||
|       request = @client.generate_request( | ||||
|         prediction.training.insert, | ||||
|         {'data' => '123'} | ||||
|         :api_method => prediction.training.insert, | ||||
|         :parameters => {'data' => '123'} | ||||
|       ) | ||||
|       method, uri, headers, body = request | ||||
|       uri.should == | ||||
|         'https://testing-domain.googleapis.com/prediction/v1/training?data=123' | ||||
|       uri.should == ( | ||||
|         'https://testing-domain.googleapis.com/' + | ||||
|         'prediction/v1.2/training?data=123' | ||||
|       ) | ||||
|     end | ||||
| 
 | ||||
|     it 'should generate OAuth 1 requests' do | ||||
|  | @ -173,8 +171,8 @@ describe Google::APIClient do | |||
|       @client.authorization.token_credential_key = '12345' | ||||
|       @client.authorization.token_credential_secret = '12345' | ||||
|       request = @client.generate_request( | ||||
|         'prediction.training.insert', | ||||
|         {'data' => '12345'} | ||||
|         :api_method => @prediction.training.insert, | ||||
|         :parameters => {'data' => '12345'} | ||||
|       ) | ||||
|       method, uri, headers, body = request | ||||
|       headers = headers.inject({}) { |h,(k,v)| h[k]=v; h } | ||||
|  | @ -186,8 +184,8 @@ describe Google::APIClient do | |||
|       @client.authorization = :oauth_2 | ||||
|       @client.authorization.access_token = '12345' | ||||
|       request = @client.generate_request( | ||||
|         'prediction.training.insert', | ||||
|         {'data' => '12345'} | ||||
|         :api_method => @prediction.training.insert, | ||||
|         :parameters => {'data' => '12345'} | ||||
|       ) | ||||
|       method, uri, headers, body = request | ||||
|       headers = headers.inject({}) { |h,(k,v)| h[k]=v; h } | ||||
|  | @ -199,24 +197,47 @@ describe Google::APIClient do | |||
|       @client.authorization = :oauth_1 | ||||
|       @client.authorization.token_credential_key = '12345' | ||||
|       @client.authorization.token_credential_secret = '12345' | ||||
|       response = @client.execute( | ||||
|         'prediction.training.insert', | ||||
|       result = @client.execute( | ||||
|         @prediction.training.insert, | ||||
|         {'data' => '12345'} | ||||
|       ) | ||||
|       status, headers, body = response | ||||
|       status, headers, body = result.response | ||||
|       status.should == 401 | ||||
|     end | ||||
| 
 | ||||
|     it 'should not be able to execute improperly authorized requests' do | ||||
|       @client.authorization = :oauth_2 | ||||
|       @client.authorization.access_token = '12345' | ||||
|       response = @client.execute( | ||||
|         'prediction.training.insert', | ||||
|       result = @client.execute( | ||||
|         @prediction.training.insert, | ||||
|         {'data' => '12345'} | ||||
|       ) | ||||
|       status, headers, body = response | ||||
|       status, headers, body = result.response | ||||
|       status.should == 401 | ||||
|     end | ||||
| 
 | ||||
|     it 'should not be able to execute improperly authorized requests' do | ||||
|       (lambda do | ||||
|         @client.authorization = :oauth_1 | ||||
|         @client.authorization.token_credential_key = '12345' | ||||
|         @client.authorization.token_credential_secret = '12345' | ||||
|         result = @client.execute!( | ||||
|           @prediction.training.insert, | ||||
|           {'data' => '12345'} | ||||
|         ) | ||||
|       end).should raise_error(Google::APIClient::ClientError) | ||||
|     end | ||||
| 
 | ||||
|     it 'should not be able to execute improperly authorized requests' do | ||||
|       (lambda do | ||||
|         @client.authorization = :oauth_2 | ||||
|         @client.authorization.access_token = '12345' | ||||
|         result = @client.execute!( | ||||
|           @prediction.training.insert, | ||||
|           {'data' => '12345'} | ||||
|         ) | ||||
|       end).should raise_error(Google::APIClient::ClientError) | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe 'with the buzz API' do | ||||
|  | @ -251,22 +272,18 @@ describe Google::APIClient do | |||
|     it 'should fail for string RPC names that do not match API name' do | ||||
|       (lambda do | ||||
|         @client.generate_request( | ||||
|           'chili.activities.list', | ||||
|           {'alt' => 'json'}, | ||||
|           '', | ||||
|           [], | ||||
|           {:signed => false} | ||||
|           :api_method => 'chili.activities.list', | ||||
|           :parameters => {'alt' => 'json'}, | ||||
|           :authenticated => false | ||||
|         ) | ||||
|       end).should raise_error(Google::APIClient::TransmissionError) | ||||
|     end | ||||
| 
 | ||||
|     it 'should generate requests against the correct URIs' do | ||||
|       request = @client.generate_request( | ||||
|         @buzz.activities.list, | ||||
|         {'userId' => 'hikingfan', 'scope' => '@public'}, | ||||
|         '', | ||||
|         [], | ||||
|         {:signed => false} | ||||
|         :api_method => @buzz.activities.list, | ||||
|         :parameters => {'userId' => 'hikingfan', 'scope' => '@public'}, | ||||
|         :authenticated => false | ||||
|       ) | ||||
|       method, uri, headers, body = request | ||||
|       uri.should == | ||||
|  | @ -276,11 +293,9 @@ describe Google::APIClient do | |||
|     it 'should correctly validate parameters' do | ||||
|       (lambda do | ||||
|         @client.generate_request( | ||||
|           @buzz.activities.list, | ||||
|           {'alt' => 'json'}, | ||||
|           '', | ||||
|           [], | ||||
|           {:signed => false} | ||||
|           :api_method => @buzz.activities.list, | ||||
|           :parameters => {'alt' => 'json'}, | ||||
|           :authenticated => false | ||||
|         ) | ||||
|       end).should raise_error(ArgumentError) | ||||
|     end | ||||
|  | @ -288,26 +303,33 @@ describe Google::APIClient do | |||
|     it 'should correctly validate parameters' do | ||||
|       (lambda do | ||||
|         @client.generate_request( | ||||
|           @buzz.activities.list, | ||||
|           {'userId' => 'hikingfan', 'scope' => '@bogus'}, | ||||
|           '', | ||||
|           [], | ||||
|           {:signed => false} | ||||
|           :api_method => @buzz.activities.list, | ||||
|           :parameters => {'userId' => 'hikingfan', 'scope' => '@bogus'}, | ||||
|           :authenticated => false | ||||
|         ) | ||||
|       end).should raise_error(ArgumentError) | ||||
|     end | ||||
| 
 | ||||
|     it 'should be able to execute requests without authorization' do | ||||
|       response = @client.execute( | ||||
|       result = @client.execute( | ||||
|         @buzz.activities.list, | ||||
|         {'alt' => 'json', 'userId' => 'hikingfan', 'scope' => '@public'}, | ||||
|         '', | ||||
|         [], | ||||
|         {:signed => false} | ||||
|         :authenticated => false | ||||
|       ) | ||||
|       status, headers, body = response | ||||
|       status, headers, body = result.response | ||||
|       status.should == 200 | ||||
|     end | ||||
| 
 | ||||
|     it 'should not be able to execute requests without authorization' do | ||||
|       result = @client.execute( | ||||
|         @buzz.activities.list, | ||||
|         'alt' => 'json', 'userId' => '@me', 'scope' => '@self' | ||||
|       ) | ||||
|       status, headers, body = result.response | ||||
|       status.should == 401 | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   describe 'with the latitude API' do | ||||
|  | @ -338,11 +360,8 @@ describe Google::APIClient do | |||
| 
 | ||||
|     it 'should generate requests against the correct URIs' do | ||||
|       request = @client.generate_request( | ||||
|         'latitude.currentLocation.get', | ||||
|         {}, | ||||
|         '', | ||||
|         [], | ||||
|         {:signed => false} | ||||
|         :api_method => 'latitude.currentLocation.get', | ||||
|         :authenticated => false | ||||
|       ) | ||||
|       method, uri, headers, body = request | ||||
|       uri.should == | ||||
|  | @ -351,11 +370,8 @@ describe Google::APIClient do | |||
| 
 | ||||
|     it 'should generate requests against the correct URIs' do | ||||
|       request = @client.generate_request( | ||||
|         @latitude.current_location.get, | ||||
|         {}, | ||||
|         '', | ||||
|         [], | ||||
|         {:signed => false} | ||||
|         :api_method => @latitude.current_location.get, | ||||
|         :authenticated => false | ||||
|       ) | ||||
|       method, uri, headers, body = request | ||||
|       uri.should == | ||||
|  | @ -363,14 +379,11 @@ describe Google::APIClient do | |||
|     end | ||||
| 
 | ||||
|     it 'should not be able to execute requests without authorization' do | ||||
|       response = @client.execute( | ||||
|         'latitude.currentLocation.get', | ||||
|         {}, | ||||
|         '', | ||||
|         [], | ||||
|         {:signed => false} | ||||
|       result = @client.execute( | ||||
|         :api_method => 'latitude.currentLocation.get', | ||||
|         :authenticated => false | ||||
|       ) | ||||
|       status, headers, body = response | ||||
|       status, headers, body = result.response | ||||
|       status.should == 401 | ||||
|     end | ||||
|   end | ||||
|  | @ -403,11 +416,8 @@ describe Google::APIClient do | |||
| 
 | ||||
|     it 'should generate requests against the correct URIs' do | ||||
|       request = @client.generate_request( | ||||
|         'moderator.profiles.get', | ||||
|         {}, | ||||
|         '', | ||||
|         [], | ||||
|         {:signed => false} | ||||
|         :api_method => 'moderator.profiles.get', | ||||
|         :authenticated => false | ||||
|       ) | ||||
|       method, uri, headers, body = request | ||||
|       uri.should == | ||||
|  | @ -416,11 +426,8 @@ describe Google::APIClient do | |||
| 
 | ||||
|     it 'should generate requests against the correct URIs' do | ||||
|       request = @client.generate_request( | ||||
|         @moderator.profiles.get, | ||||
|         {}, | ||||
|         '', | ||||
|         [], | ||||
|         {:signed => false} | ||||
|         :api_method => @moderator.profiles.get, | ||||
|         :authenticated => false | ||||
|       ) | ||||
|       method, uri, headers, body = request | ||||
|       uri.should == | ||||
|  | @ -428,14 +435,14 @@ describe Google::APIClient do | |||
|     end | ||||
| 
 | ||||
|     it 'should not be able to execute requests without authorization' do | ||||
|       response = @client.execute( | ||||
|       result = @client.execute( | ||||
|         'moderator.profiles.get', | ||||
|         {}, | ||||
|         '', | ||||
|         [], | ||||
|         {:signed => false} | ||||
|         {:authenticated => false} | ||||
|       ) | ||||
|       status, headers, body = response | ||||
|       status, headers, body = result.response | ||||
|       status.should == 401 | ||||
|     end | ||||
|   end | ||||
|  |  | |||
|  | @ -16,36 +16,40 @@ require 'spec_helper' | |||
| 
 | ||||
| require 'json' | ||||
| require 'google/api_client/parsers/json_parser' | ||||
| require 'google/api_client/parsers/json/error_parser' | ||||
| require 'google/api_client/parsers/json/pagination' | ||||
| 
 | ||||
| describe Google::APIClient::JSONParser, 'generates json from hash' do | ||||
| describe Google::APIClient::JSONParser, 'with error data' do | ||||
|   before do | ||||
|     @parser = Google::APIClient::JSONParser | ||||
|   end | ||||
| 
 | ||||
|   it 'should translate simple hash to JSON string' do | ||||
|     @parser.serialize('test' => 23).should == '{"test":23}' | ||||
|   end | ||||
| 
 | ||||
|   it 'should translate simple nested into to nested JSON string' do | ||||
|     @parser.serialize({ | ||||
|       'test' => 23, 'test2' => {'foo' => 'baz', 12 => 3.14 } | ||||
|     }).should == | ||||
|       '{"test2":{"12":3.14,"foo":"baz"},"test":23}' | ||||
|   end | ||||
| end | ||||
| 
 | ||||
| describe Google::APIClient::JSONParser, 'parses json string into hash' do | ||||
|   before do | ||||
|     @parser = Google::APIClient::JSONParser | ||||
|   end | ||||
| 
 | ||||
|   it 'should parse simple json string into hash' do | ||||
|     @parser.parse('{"test":23}').should == {'test' => 23} | ||||
|   end | ||||
| 
 | ||||
|   it 'should parse nested json object into hash' do | ||||
|     @parser.parse('{"test":23, "test2":{"bar":"baz", "foo":3.14}}').should == { | ||||
|       'test' => 23, 'test2' => {'bar' => 'baz', 'foo' => 3.14} | ||||
|     @data = { | ||||
|       'error' => { | ||||
|         'code' => 401, | ||||
|         'message' => 'Token invalid - Invalid AuthSub token.', | ||||
|         'errors' => [ | ||||
|           { | ||||
|             'location' => 'Authorization', | ||||
|             'domain' => 'global', | ||||
|             'locationType' => 'header', | ||||
|             'reason' => 'authError', | ||||
|             'message' => 'Token invalid - Invalid AuthSub token.' | ||||
|           } | ||||
|         ] | ||||
|       } | ||||
|     } | ||||
|   end | ||||
| 
 | ||||
|   it 'should correctly match as an error' do | ||||
|     parser = Google::APIClient::JSONParser.match(@data) | ||||
|     parser.should == Google::APIClient::JSON::ErrorParser | ||||
|   end | ||||
| 
 | ||||
|   it 'should be automatically handled as an error when parsed' do | ||||
|     data = Google::APIClient::JSONParser.parse(@data) | ||||
|     data.should be_kind_of(Google::APIClient::JSON::ErrorParser) | ||||
|   end | ||||
| 
 | ||||
|   it 'should correctly expose error message' do | ||||
|     data = Google::APIClient::JSONParser.parse(@data) | ||||
|     data.error.should == 'Token invalid - Invalid AuthSub token.' | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -36,7 +36,7 @@ shared_examples_for 'configurable user agent' do | |||
|   it 'should not allow the user agent to be used with bogus values' do | ||||
|     (lambda do | ||||
|       @client.user_agent = 42 | ||||
|       @client.transmit_request( | ||||
|       @client.transmit( | ||||
|         ['GET', 'http://www.google.com/', [], []] | ||||
|       ) | ||||
|     end).should raise_error(TypeError) | ||||
|  | @ -53,7 +53,7 @@ shared_examples_for 'configurable user agent' do | |||
|       end | ||||
|       [200, [], ['']] | ||||
|     end | ||||
|     @client.transmit_request(request, adapter) | ||||
|     @client.transmit(request, adapter) | ||||
|   end | ||||
| end | ||||
| 
 | ||||
|  | @ -63,11 +63,7 @@ describe Google::APIClient do | |||
|   end | ||||
| 
 | ||||
|   it 'should make its version number available' do | ||||
|     ::Google::APIClient::VERSION::STRING.should be_instance_of(String) | ||||
|   end | ||||
| 
 | ||||
|   it 'should use the default JSON parser' do | ||||
|     @client.parser.should be(Google::APIClient::JSONParser) | ||||
|     Google::APIClient::VERSION::STRING.should be_instance_of(String) | ||||
|   end | ||||
| 
 | ||||
|   it 'should default to OAuth 2' do | ||||
|  | @ -104,26 +100,4 @@ describe Google::APIClient do | |||
|     # TODO | ||||
|     it_should_behave_like 'configurable user agent' | ||||
|   end | ||||
| 
 | ||||
|   describe 'with custom pluggable parser' do | ||||
|     before do | ||||
|       class FakeJsonParser | ||||
|         def serialize(value) | ||||
|           return "42" | ||||
|         end | ||||
| 
 | ||||
|         def parse(value) | ||||
|           return 42 | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       @client.parser = FakeJsonParser.new | ||||
|     end | ||||
| 
 | ||||
|     it 'should use the custom parser' do | ||||
|       @client.parser.should be_instance_of(FakeJsonParser) | ||||
|     end | ||||
| 
 | ||||
|     it_should_behave_like 'configurable user agent' | ||||
|   end | ||||
| end | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue