Respect discovered methods with colons in path
[arvados.git] / lib / google / api_client / discovery / method.rb
index b186508185fc57aa60bd482c803e116d7a55b9d4..3a06857c0e3d559f0e4e20813767d0c3dee639a8 100644 (file)
@@ -18,6 +18,7 @@ require 'addressable/template'
 
 require 'google/api_client/errors'
 
+
 module Google
   class APIClient
     ##
@@ -27,11 +28,13 @@ module Google
       ##
       # Creates a description of a particular method.
       #
+      # @param [Google::APIClient::API] api
+      #   The API this method belongs to.
       # @param [Addressable::URI] method_base
       #   The base URI for the service.
       # @param [String] method_name
       #   The identifier for the method.
-      # @param [Hash] method_description
+      # @param [Hash] discovery_document
       #   The section of the discovery document that applies to this method.
       #
       # @return [Google::APIClient::Method] The constructed method object.
@@ -42,18 +45,20 @@ module Google
         @discovery_document = discovery_document
       end
 
+      # @return [String] unparsed discovery document for the method
+      attr_reader :discovery_document
+
       ##
-      # Returns the identifier for the method.
+      # Returns the API this method belongs to.
       #
-      # @return [String] The method identifier.
-      attr_reader :name
+      # @return [Google::APIClient::API] The API this method belongs to.
+      attr_reader :api
 
       ##
-      # Returns the parsed section of the discovery document that applies to
-      # this method.
+      # Returns the identifier for the method.
       #
-      # @return [Hash] The method description.
-      attr_reader :description
+      # @return [String] The method identifier.
+      attr_reader :name
 
       ##
       # Returns the base URI for the method.
@@ -65,13 +70,21 @@ module Google
       ##
       # Updates the method with the new base.
       #
-      # @param [Addressable::URI, #to_str, String] new_base
+      # @param [Addressable::URI, #to_str, String] new_method_base
       #   The new base URI to use for the method.
       def method_base=(new_method_base)
         @method_base = Addressable::URI.parse(new_method_base)
         @uri_template = nil
       end
 
+      ##
+      # Returns a human-readable description of the method.
+      #
+      # @return [Hash] The API description.
+      def description
+        return @discovery_document['description']
+      end
+      
       ##
       # Returns the method ID.
       #
@@ -94,15 +107,23 @@ module Google
       #
       # @return [Addressable::Template] The URI template.
       def uri_template
-        # TODO(bobaman) We shouldn't be calling #to_s here, this should be
-        # a join operation on a URI, but we have to treat these as Strings
-        # because of the way the discovery document provides the URIs.
-        # This should be fixed soon.
         return @uri_template ||= Addressable::Template.new(
-          self.method_base + @discovery_document['path']
+          self.method_base.join(Addressable::URI.parse("./" + @discovery_document['path']))
         )
       end
 
+      ##
+      # Returns media upload information for this method, if supported
+      #
+      # @return [Google::APIClient::MediaUpload] Description of upload endpoints
+      def media_upload
+        if @discovery_document['mediaUpload']
+          return @media_upload ||= Google::APIClient::MediaUpload.new(self, self.method_base, @discovery_document['mediaUpload'])
+        else
+          return nil
+        end
+      end
+
       ##
       # Returns the Schema object for the method's request, if any.
       #
@@ -146,7 +167,7 @@ module Google
             unless k.kind_of?(String)
               raise TypeError, "Expected String, got #{k.class}."
             end
-            accu << [k,v]
+            accu << [k, v]
             accu
           end
         else
@@ -159,52 +180,66 @@ module Google
       ##
       # Expands the method's URI template using a parameter list.
       #
+      # @api private
       # @param [Hash, Array] parameters
       #   The parameter list to use.
       #
       # @return [Addressable::URI] The URI after expansion.
       def generate_uri(parameters={})
         parameters = self.normalize_parameters(parameters)
+        
         self.validate_parameters(parameters)
         template_variables = self.uri_template.variables
-        uri = self.uri_template.expand(parameters)
+        upload_type = parameters.assoc('uploadType') || parameters.assoc('upload_type')
+        if upload_type
+          unless self.media_upload
+            raise ArgumentException, "Media upload not supported for this method"
+          end
+          case upload_type.last
+          when 'media', 'multipart', 'resumable'
+            uri = self.media_upload.uri_template.expand(parameters)
+          else
+            raise ArgumentException, "Invalid uploadType '#{upload_type}'"
+          end
+        else
+          uri = self.uri_template.expand(parameters)
+        end
         query_parameters = parameters.reject do |k, v|
           template_variables.include?(k)
         end
-        if query_parameters.size > 0
-          uri.query_values = (uri.query_values || []) + query_parameters
+        # encode all non-template parameters
+        params = ""
+        unless query_parameters.empty?
+          params = "?" + Addressable::URI.form_encode(query_parameters.sort)
         end
         # Normalization is necessary because of undesirable percent-escaping
         # during URI template expansion
-        return uri.normalize
+        return uri.normalize + params
       end
 
       ##
       # Generates an HTTP request for this method.
       #
+      # @api private
       # @param [Hash, Array] parameters
       #   The parameters to send.
       # @param [String, StringIO] body The body for the HTTP request.
       # @param [Hash, Array] headers The HTTP headers for the request.
+      # @option options [Faraday::Connection] :connection
+      #   The HTTP connection to use.
       #
       # @return [Array] The generated HTTP request.
-      def generate_request(parameters={}, body='', headers=[])
-        if body.respond_to?(:string)
-          body = body.string
-        elsif body.respond_to?(:to_str)
-          body = body.to_str
-        else
-          raise TypeError, "Expected String or StringIO, got #{body.class}."
-        end
+      def generate_request(parameters={}, body='', headers={}, options={})
         if !headers.kind_of?(Array) && !headers.kind_of?(Hash)
           raise TypeError, "Expected Hash or Array, got #{headers.class}."
         end
-        method = self.http_method
+        method = self.http_method.to_s.downcase.to_sym
         uri = self.generate_uri(parameters)
-        headers = headers.to_a if headers.kind_of?(Hash)
-        return [method, uri.to_str, headers, [body]]
+        headers = Faraday::Utils::Headers.new(headers)
+        return [method, uri, headers, body]
       end
 
+
       ##
       # Returns a <code>Hash</code> of the parameter descriptions for
       # this method.
@@ -260,6 +295,7 @@ module Google
       # Verifies that the parameters are valid for this method.  Raises an
       # exception if validation fails.
       #
+      # @api private
       # @param [Hash, Array] parameters
       #   The parameters to verify.
       #
@@ -269,26 +305,42 @@ module Google
         required_variables = ((self.parameter_descriptions.select do |k, v|
           v['required']
         end).inject({}) { |h,(k,v)| h[k]=v; h }).keys
-        missing_variables = required_variables - parameters.map(&:first)
+        missing_variables = required_variables - parameters.map { |(k, _)| k }
         if missing_variables.size > 0
           raise ArgumentError,
             "Missing required parameters: #{missing_variables.join(', ')}."
         end
         parameters.each do |k, v|
-          if self.parameter_descriptions[k]
-            enum = self.parameter_descriptions[k]['enum']
-            if enum && !enum.include?(v)
-              raise ArgumentError,
-                "Parameter '#{k}' has an invalid value: #{v}. " +
-                "Must be one of #{enum.inspect}."
-            end
-            pattern = self.parameter_descriptions[k]['pattern']
-            if pattern
-              regexp = Regexp.new("^#{pattern}$")
-              if v !~ regexp
+          # Handle repeated parameters.
+          if self.parameter_descriptions[k] &&
+              self.parameter_descriptions[k]['repeated'] &&
+              v.kind_of?(Array)
+            # If this is a repeated parameter and we've got an array as a
+            # value, just provide the whole array to the loop below.
+            items = v
+          else
+            # If this is not a repeated parameter, or if it is but we're
+            # being given a single value, wrap the value in an array, so that
+            # the loop below still works for the single element.
+            items = [v]
+          end
+
+          items.each do |item|
+            if self.parameter_descriptions[k]
+              enum = self.parameter_descriptions[k]['enum']
+              if enum && !enum.include?(item)
                 raise ArgumentError,
-                  "Parameter '#{k}' has an invalid value: #{v}. " +
-                  "Must match: /^#{pattern}$/."
+                  "Parameter '#{k}' has an invalid value: #{item}. " +
+                  "Must be one of #{enum.inspect}."
+              end
+              pattern = self.parameter_descriptions[k]['pattern']
+              if pattern
+                regexp = Regexp.new("^#{pattern}$")
+                if item !~ regexp
+                  raise ArgumentError,
+                    "Parameter '#{k}' has an invalid value: #{item}. " +
+                    "Must match: /^#{pattern}$/."
+                end
               end
             end
           end