Skip to content

Commit f7c5f88

Browse files
committed
Add content_type option to Multipart for multipart/related and mixed
Multipart#initialize now accepts a content_type: keyword argument, defaulting to "multipart/form-data". This enables multipart/related, multipart/mixed, and other multipart content types without requiring users to manually construct the body. Closes #1.
1 parent 5723106 commit f7c5f88

4 files changed

Lines changed: 44 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- `Multipart` accepts a `content_type:` keyword to support `multipart/related`,
13+
`multipart/mixed`, and other multipart content types. Defaults to
14+
`multipart/form-data`.
15+
([#1](https://github.com/httprb/form_data/issues/1))
1216
- `FormData::File#close` for closing file handles opened from String paths or
1317
Pathnames. When a File is created from an existing IO, `close` is a no-op.
1418
([#27](https://github.com/httprb/form_data/issues/27))

lib/http/form_data/multipart.rb

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ module FormData
1212
class Multipart
1313
include Readable
1414

15+
# Default MIME type for multipart form data
16+
DEFAULT_CONTENT_TYPE = "multipart/form-data"
17+
1518
# Returns the multipart boundary string
1619
#
1720
# @example
@@ -23,14 +26,19 @@ class Multipart
2326

2427
# Creates a new Multipart form data instance
2528
#
26-
# @example
29+
# @example Basic form data
2730
# Multipart.new({ foo: "bar" })
2831
#
32+
# @example With custom content type
33+
# Multipart.new(parts, content_type: "multipart/related")
34+
#
2935
# @api public
3036
# @param [Enumerable, Hash, #to_h] data form data key-value pairs
3137
# @param [String] boundary custom boundary string
32-
def initialize(data, boundary: self.class.generate_boundary)
33-
@boundary = boundary.to_s.freeze
38+
# @param [String] content_type MIME type for the Content-Type header
39+
def initialize(data, boundary: self.class.generate_boundary, content_type: DEFAULT_CONTENT_TYPE)
40+
@boundary = boundary.to_s.freeze
41+
@content_type = content_type
3442
@io = CompositeIO.new(parts(data).flat_map { |part| [glue, part] } << tail)
3543
end
3644

@@ -54,7 +62,7 @@ def self.generate_boundary
5462
# @api public
5563
# @return [String]
5664
def content_type
57-
"multipart/form-data; boundary=#{@boundary}"
65+
"#{@content_type}; boundary=#{@boundary}"
5866
end
5967

6068
# Returns form data content size for Content-Length

sig/http/form_data/multipart.rbs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@ module HTTP
33
class Multipart
44
include Readable
55

6+
DEFAULT_CONTENT_TYPE: String
7+
68
# Returns the multipart boundary string
79
attr_reader boundary: String
810

11+
@content_type: _ToS
12+
913
# Creates a new Multipart form data instance
10-
def initialize: (untyped data, ?boundary: _ToS) -> void
14+
def initialize: (untyped data, ?boundary: _ToS, ?content_type: _ToS) -> void
1115

1216
# Generates a boundary string for multipart form data
1317
def self.generate_boundary: () -> String

test/http/form_data/multipart_test.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,29 @@ def test_content_type_with_user_defined_boundary
171171
assert_equal "multipart/form-data; boundary=my-boundary", form_data.content_type
172172
end
173173

174+
def test_content_type_with_custom_type
175+
form_data = HTTP::FormData::Multipart.new(@params, boundary: "b", content_type: "multipart/related")
176+
177+
assert_equal "multipart/related; boundary=b", form_data.content_type
178+
end
179+
180+
def test_content_type_with_multipart_mixed
181+
form_data = HTTP::FormData::Multipart.new(@params, boundary: "b", content_type: "multipart/mixed")
182+
183+
assert_equal "multipart/mixed; boundary=b", form_data.content_type
184+
end
185+
186+
def test_content_type_default_is_form_data
187+
assert_equal "multipart/form-data", HTTP::FormData::Multipart::DEFAULT_CONTENT_TYPE
188+
end
189+
190+
def test_content_type_converts_to_string
191+
form_data = HTTP::FormData::Multipart.new(@params, boundary: "b", content_type: :"multipart/related")
192+
193+
assert_equal "multipart/related; boundary=b", form_data.content_type
194+
assert_instance_of String, form_data.content_type
195+
end
196+
174197
def test_content_length
175198
assert_equal @form_data.to_s.bytesize, @form_data.content_length
176199
end

0 commit comments

Comments
 (0)