Skip to content

Commit

Permalink
Create PartParams class to allow multipart posts with JSON content an…
Browse files Browse the repository at this point in the history
…d file upload
  • Loading branch information
jeremyisr committed Sep 9, 2019
1 parent f4ae96e commit 0fca48f
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 1 deletion.
1 change: 1 addition & 0 deletions lib/faraday/autoload.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class Request
extend AutoloadHelper
autoload_all 'faraday/request',
UrlEncoded: 'url_encoded',
PartParams: 'part_params',
Multipart: 'multipart',
Retry: 'retry',
Authorization: 'authorization',
Expand Down
10 changes: 9 additions & 1 deletion lib/faraday/request/multipart.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -61,6 +61,14 @@ def create_multipart(env, params)
body
end

def part(boundary, key, value)
if value.is_a?(Faraday::Request::PartParams)
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}"
Expand Down
19 changes: 19 additions & 0 deletions lib/faraday/request/part_params.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

module Faraday
class Request
# Allows multipart posts while specifying headers of a part
class PartParams
def initialize(value, headers)
@value = value
@headers = headers
end

def to_part(boundary, key)
Faraday::Parts::Part.new(boundary, key, value, headers)
end

attr_reader :value, :headers
end
end
end
84 changes: 84 additions & 0 deletions spec/faraday/request/multipart_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,90 @@
end
end

context 'when providing json and IO content in the same payload' do
let(:io_content) { StringIO.new('io-content') }
let(:json_content) do
{
b: 1,
c: 2
}.to_json
end

let(:payload) do
{
json_content: Faraday::Request::PartParams.new(
json_content,
'Content-Type' => 'application/json'
),
io_content: Faraday::UploadIO.new(io_content, '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_content')
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_content)

part_io, body_io = result.part('io_content')
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_content.string)
end
end

context 'when providing json and IO content in the same payload' do
let(:io_content) { StringIO.new('io-content') }
let(:json_content) do
{
b: 1,
c: 2
}.to_json
end

let(:payload) do
{
json_content: Faraday::Request::PartParams.new(
json_content,
'Content-Type' => 'application/json'
),
io_content: Faraday::UploadIO.new(io_content, '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_content')
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_content)

part_io, body_io = result.part('io_content')
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_content.string)
end
end

context 'when multipart objects in array param' do
let(:payload) do
{
Expand Down

0 comments on commit 0fca48f

Please sign in to comment.