From 7dded89b338b8bbddfcc9976d146f1e535a58368 Mon Sep 17 00:00:00 2001 From: Jeremy ISRAEL Date: Tue, 10 Sep 2019 11:12:05 +0200 Subject: [PATCH] Create PartParams class to allow multipart posts with JSON content and IO content in the same request --- lib/faraday.rb | 2 +- lib/faraday/param_part.rb | 25 +++++++++++++++++ lib/faraday/request/multipart.rb | 10 ++++++- spec/faraday/request/multipart_spec.rb | 39 ++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 lib/faraday/param_part.rb diff --git a/lib/faraday.rb b/lib/faraday.rb index e883465c0..9ae0d679a 100644 --- a/lib/faraday.rb +++ b/lib/faraday.rb @@ -160,7 +160,7 @@ def self.default_connection_options=(options) require_libs 'utils', 'options', 'connection', 'rack_builder', 'parameters', 'middleware', 'adapter', 'request', 'response', 'upload_io', - 'error' + 'error', 'param_part' require_lib 'autoload' unless ENV['FARADAY_NO_AUTOLOAD'] end diff --git a/lib/faraday/param_part.rb b/lib/faraday/param_part.rb new file mode 100644 index 000000000..386526ebe --- /dev/null +++ b/lib/faraday/param_part.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Faraday + # Allows multipart posts while specifying headers of a part + class ParamPart + def initialize(value, content_type, content_id = nil) + @value = value + @content_type = content_type + @content_id = content_id + end + + def to_part(boundary, key) + Faraday::Parts::Part.new(boundary, key, value, headers) + end + + def headers + { + 'Content-Type' => content_type, + 'Content-ID' => content_id + } + end + + attr_reader :value, :content_type, :content_id + end +end diff --git a/lib/faraday/request/multipart.rb b/lib/faraday/request/multipart.rb index 09aa6c610..399c434f4 100644 --- a/lib/faraday/request/multipart.rb +++ b/lib/faraday/request/multipart.rb @@ -52,7 +52,7 @@ def has_multipart?(obj) # rubocop:disable Naming/PredicateName def create_multipart(env, params) boundary = env.request.boundary parts = process_params(params) do |key, value| - Faraday::Parts::Part.new(boundary, key, value) + part(boundary, key, value) end parts << Faraday::Parts::EpiloguePart.new(boundary) @@ -61,6 +61,14 @@ def create_multipart(env, params) body end + def part(boundary, key, value) + if value.respond_to?(:to_part) + value.to_part(boundary, key) + else + Faraday::Parts::Part.new(boundary, key, value) + end + end + # @return [String] def unique_boundary "#{DEFAULT_BOUNDARY_PREFIX}-#{SecureRandom.hex}" diff --git a/spec/faraday/request/multipart_spec.rb b/spec/faraday/request/multipart_spec.rb index 3f8979eb0..ffd48b6b9 100644 --- a/spec/faraday/request/multipart_spec.rb +++ b/spec/faraday/request/multipart_spec.rb @@ -68,6 +68,45 @@ end end + context 'when providing json and IO content in the same payload' do + let(:io) { StringIO.new('io-content') } + let(:json) do + { + b: 1, + c: 2 + }.to_json + end + + let(:payload) do + { + json: Faraday::ParamPart.new(json, 'application/json'), + io: Faraday::UploadIO.new(io, 'application/pdf') + } + end + + it_behaves_like 'a multipart request' + + it 'forms a multipart request' do + response = conn.post('/echo', payload) + + boundary = parse_multipart_boundary(response.headers['Content-Type']) + result = parse_multipart(boundary, response.body) + expect(result[:errors]).to be_empty + + part_json, body_json = result.part('json') + expect(part_json).to_not be_nil + expect(part_json.mime).to eq('application/json') + expect(part_json.filename).to be_nil + expect(body_json).to eq(json) + + part_io, body_io = result.part('io') + expect(part_io).to_not be_nil + expect(part_io.mime).to eq('application/pdf') + expect(part_io.filename).to eq('local.path') + expect(body_io).to eq(io.string) + end + end + context 'when multipart objects in array param' do let(:payload) do {