Begin consolidation of request building in reference. Further changes coming to simpl...
[arvados.git] / lib / google / api_client / reference.rb
index 0a24b0c5930fdab04871252addda73bb4d6f2d9d..4bf41f4e40826992b9a0fce3427057fa2dd1cfe3 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-
-require 'stringio'
-require 'json'
+require 'faraday'
+require 'faraday/utils'
+require 'multi_json'
+require 'compat/multi_json'
 require 'addressable/uri'
+require 'stringio'
 require 'google/api_client/discovery'
 
+# TODO - needs some serious cleanup
 
 module Google
   class APIClient
     class Reference
+      MULTIPART_BOUNDARY = "-----------RubyApiMultipartPost".freeze
+
       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.connection = options[:connection] || Faraday.default_connection
+        self.authorization = options[:authorization]
         self.api_method = options[:api_method]
+        
         self.parameters = options[:parameters] || {}
         # These parameters are handled differently because they're not
         # parameters to the API method, but rather to the API system.
         self.parameters['key'] ||= options[:key] if options[:key]
         self.parameters['userIp'] ||= options[:user_ip] if options[:user_ip]
-        self.headers = options[:headers] || []
-        if options[:body]
+
+        self.headers = options[:headers] || {}
+        if options[:media]
+          self.initialize_media_upload
+        elsif options[:body]
           self.body = options[:body]
-        elsif options[:merged_body]
-          self.merged_body = options[:merged_body]
         elsif options[:body_object]
-          if options[:body_object].respond_to?(:to_json)
-            serialized_body = options[:body_object].to_json
-          elsif options[:body_object].respond_to?(:to_hash)
-            serialized_body = JSON.generate(options[:body_object].to_hash)
-          else
-            raise TypeError,
-              'Could not convert body object to JSON.' +
-              'Must respond to :to_json or :to_hash.'
-          end
-          self.merged_body = serialized_body
+          self.headers['Content-Type'] ||= 'application/json'
+          self.body = serialize_body(options[:body_object])
         else
-          self.merged_body = ''
+          self.body = ''
         end
         unless self.api_method
           self.http_method = options[:http_method] || 'GET'
           self.uri = options[:uri]
           unless self.parameters.empty?
-            self.uri.query_values =
-              (self.uri.query_values || {}).merge(self.parameters)
+            self.uri.query = Addressable::URI.form_encode(self.parameters)
           end
         end
       end
 
+      def initialize_media_upload
+        self.media = options[:media]
+        case self.upload_type
+        when "media"
+          if options[:body] || options[:body_object] 
+            raise ArgumentError, "Can not specify body & body object for simple uploads"
+          end
+          self.headers['Content-Type'] ||= self.media.content_type
+          self.body = self.media
+        when "multipart"
+          unless options[:body_object] 
+            raise ArgumentError, "Multipart requested but no body object"              
+          end
+          metadata = StringIO.new(serialize_body(options[:body_object]))
+          env = {
+            :request_headers => {'Content-Type' => "multipart/related;boundary=#{MULTIPART_BOUNDARY}"},
+            :request => { :boundary => MULTIPART_BOUNDARY }
+          }
+          multipart = Faraday::Request::Multipart.new
+          self.body = multipart.create_multipart(env, [
+            [nil,Faraday::UploadIO.new(metadata, 'application/json', 'file.json')], 
+            [nil, self.media]])
+          self.headers.update(env[:request_headers])
+        when "resumable"
+          file_length = self.media.length
+          self.headers['X-Upload-Content-Type'] = self.media.content_type
+          self.headers['X-Upload-Content-Length'] = file_length.to_s
+          if options[:body_object]
+            self.headers['Content-Type'] ||= 'application/json'
+            self.body = serialize_body(options[:body_object]) 
+          else
+            self.body = ''
+          end
+        else
+          raise ArgumentError, "Invalid uploadType for media"
+        end
+      end
+
+      def serialize_body(body)
+        return body.to_json if body.respond_to?(:to_json)
+        return MultiJson.dump(options[:body_object].to_hash) if body.respond_to?(:to_hash)
+        raise TypeError, 'Could not convert body object to JSON.' +
+                         'Must respond to :to_json or :to_hash.'
+      end
+
+      def media
+        return @media
+      end
+
+      def media=(media)
+        @media = (media)
+      end
+      
+      def upload_type
+        return self.parameters['uploadType'] || self.parameters['upload_type']
+      end
+
+      def authorization
+        return @authorization
+      end
+
+      def authorization=(new_authorization)
+        @authorization = new_authorization
+      end
+
+      def connection
+        return @connection
+      end
+
+      def connection=(new_connection)
+        if new_connection.kind_of?(Faraday::Connection)
+          @connection = new_connection
+        else
+          raise TypeError,
+            "Expected Faraday::Connection, got #{new_connection.class}."
+        end
+      end
+
       def api_method
         return @api_method
       end
@@ -72,29 +145,6 @@ module Google
         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}."
@@ -106,8 +156,7 @@ module Google
       end
 
       def parameters=(new_parameters)
-        # No type-checking needed, the Method class handles this.
-        @parameters = new_parameters
+        @parameters = Hash[new_parameters]
       end
 
       def body
@@ -115,34 +164,11 @@ module Google
       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]
+        @body = new_body
       end
 
       def headers
-        return @headers ||= []
+        return @headers ||= {}
       end
 
       def headers=(new_headers)
@@ -159,9 +185,9 @@ module Google
 
       def http_method=(new_http_method)
         if new_http_method.kind_of?(Symbol)
-          @http_method = new_http_method.to_s.upcase
+          @http_method = new_http_method.to_s.downcase.to_sym
         elsif new_http_method.respond_to?(:to_str)
-          @http_method = new_http_method.to_str.upcase
+          @http_method = new_http_method.to_s.downcase.to_sym
         else
           raise TypeError,
             "Expected String or Symbol, got #{new_http_method.class}."
@@ -176,14 +202,27 @@ module Google
         @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
+      def to_http_request
+        request = ( 
+          if self.api_method
+            self.api_method.generate_request(
+              self.parameters, self.body, self.headers, :connection => self.connection
+            )
+          else
+            self.connection.build_request(self.http_method) do |req|
+              req.url(self.uri.to_str)
+              req.headers.update(self.headers)
+              req.body = self.body
+            end
+          end)
+        
+        if self.authorization.respond_to?(:generate_authenticated_request)
+          request = self.authorization.generate_authenticated_request(
+            :request => request,
+            :connection => self.connection
           )
-        else
-          return [self.http_method, self.uri, self.headers, self.body]
         end
+        return request
       end
 
       def to_hash
@@ -197,6 +236,10 @@ module Google
         end
         options[:headers] = self.headers
         options[:body] = self.body
+        options[:connection] = self.connection
+        unless self.authorization.nil?
+          options[:authorization] = self.authorization
+        end
         return options
       end
     end