Skip to content

Commit

Permalink
Add support for indexed arrays
Browse files Browse the repository at this point in the history
  • Loading branch information
siemensikkema committed Sep 1, 2021
1 parent 1b07f24 commit 95056cb
Show file tree
Hide file tree
Showing 2 changed files with 188 additions and 27 deletions.
29 changes: 25 additions & 4 deletions Sources/MultipartKit/MultipartFormData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ extension MultipartFormData {
private static func namedParts(from data: MultipartFormData, path: String? = nil) -> [MultipartPart] {
switch data {
case .array(let array):
return array.flatMap { namedParts(from: $0, path: path.map { "\($0)[]" }) }
return array.enumerated().flatMap { offset, element in
namedParts(from: element, path: path.map { "\($0)[\(offset)]" }) }
case .single(var part):
part.name = path
return [part]
Expand All @@ -65,6 +66,7 @@ extension MultipartFormData {
return []
}
}
}

private enum MultipartFormDataError: Error {
case nestingLevelExceeded
Expand All @@ -88,10 +90,29 @@ private extension MultipartFormData {
data.insert(part, at: path.dropFirst(), remainingNestingDepth: remainingNestingDepth - 1)
}

func insertingPart(at index: Int?) -> MultipartFormData {
var array = self.array ?? []
let count = array.count
let index = index ?? count

switch index {
case count:
array.append(.empty)
case 0..<count:
break
default:
// ignore indices outside the range of 0...count
return self
}

insertPart(into: &array[index])
return .array(array)
}

if head.isEmpty {
var tail = MultipartFormData.empty
insertPart(into: &tail)
return .array((array ?? []) + [tail])
return insertingPart(at: nil)
} else if let index = Int(head) {
return insertingPart(at: index)
} else {
var dictionary = self.dictionary ?? [:]
insertPart(into: &dictionary[String(head), default: .empty])
Expand Down
186 changes: 163 additions & 23 deletions Tests/MultipartKitTests/MultipartKitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -116,15 +116,15 @@ class MultipartTests: XCTestCase {
\r
3.14\r
--hello\r
Content-Disposition: form-data; name="array[]"\r
Content-Disposition: form-data; name="array[0]"\r
\r
1\r
--hello\r
Content-Disposition: form-data; name="array[]"\r
Content-Disposition: form-data; name="array[1]"\r
\r
2\r
--hello\r
Content-Disposition: form-data; name="array[]"\r
Content-Disposition: form-data; name="array[2]"\r
\r
3\r
--hello\r
Expand Down Expand Up @@ -415,64 +415,204 @@ class MultipartTests: XCTestCase {
}

func testNestedEncode() throws {
struct Foo: Encodable {
struct Bar: Encodable {
let baz: Int
struct Formdata: Encodable, Equatable {
struct NestedFormdata: Encodable, Equatable {
struct AnotherNestedFormdata: Encodable, Equatable {
let int: Int
let string: String
let strings: [String]
}
let int: String
let string: Int
let strings: [String]
let anotherNestedFormdata: AnotherNestedFormdata
let anotherNestedFormdataList: [AnotherNestedFormdata]
}
let bar: Bar
let bars: [Bar]
let nestedFormdata: [NestedFormdata]
}

let encoder = FormDataEncoder()
let data = try encoder.encode(Foo(bar: .init(baz: 1), bars: [.init(baz: 2), .init(baz: 3)]), boundary: "-")
let data = try encoder.encode(Formdata(nestedFormdata: [
.init(
int: "1",
string: 1,
strings: ["2", "3"],
anotherNestedFormdata: .init(int: 4, string: "5", strings: ["6", "7"]),
anotherNestedFormdataList: [
.init(int: 10, string: "11", strings: ["12", "13"]),
.init(int: 20, string: "21", strings: ["22", "33"])
])
]), boundary: "-")
let expected = """
---\r
Content-Disposition: form-data; name="bar[baz]"\r
Content-Disposition: form-data; name="nestedFormdata[0][int]"\r
\r
1\r
---\r
Content-Disposition: form-data; name="bars[][baz]"\r
Content-Disposition: form-data; name="nestedFormdata[0][string]"\r
\r
1\r
---\r
Content-Disposition: form-data; name="nestedFormdata[0][strings][0]"\r
\r
2\r
---\r
Content-Disposition: form-data; name="bars[][baz]"\r
Content-Disposition: form-data; name="nestedFormdata[0][strings][1]"\r
\r
3\r
---\r
Content-Disposition: form-data; name="nestedFormdata[0][anotherNestedFormdata][int]"\r
\r
4\r
---\r
Content-Disposition: form-data; name="nestedFormdata[0][anotherNestedFormdata][string]"\r
\r
5\r
---\r
Content-Disposition: form-data; name="nestedFormdata[0][anotherNestedFormdata][strings][0]"\r
\r
6\r
---\r
Content-Disposition: form-data; name="nestedFormdata[0][anotherNestedFormdata][strings][1]"\r
\r
7\r
---\r
Content-Disposition: form-data; name="nestedFormdata[0][anotherNestedFormdataList][0][int]"\r
\r
10\r
---\r
Content-Disposition: form-data; name="nestedFormdata[0][anotherNestedFormdataList][0][string]"\r
\r
11\r
---\r
Content-Disposition: form-data; name="nestedFormdata[0][anotherNestedFormdataList][0][strings][0]"\r
\r
12\r
---\r
Content-Disposition: form-data; name="nestedFormdata[0][anotherNestedFormdataList][0][strings][1]"\r
\r
13\r
---\r
Content-Disposition: form-data; name="nestedFormdata[0][anotherNestedFormdataList][1][int]"\r
\r
20\r
---\r
Content-Disposition: form-data; name="nestedFormdata[0][anotherNestedFormdataList][1][string]"\r
\r
21\r
---\r
Content-Disposition: form-data; name="nestedFormdata[0][anotherNestedFormdataList][1][strings][0]"\r
\r
22\r
---\r
Content-Disposition: form-data; name="nestedFormdata[0][anotherNestedFormdataList][1][strings][1]"\r
\r
33\r
-----\r\n
"""

XCTAssertEqual(data, expected)
}

func testNestedDecode() throws {
struct Foo: Decodable, Equatable {
struct Bar: Decodable, Equatable {
let baz: Int
struct Formdata: Decodable, Equatable {
struct NestedFormdata: Decodable, Equatable {
struct AnotherNestedFormdata: Decodable, Equatable {
let int: Int
let string: String
let strings: [String]
}
let int: String
let string: Int
let strings: [String]
let anotherNestedFormdata: AnotherNestedFormdata
let anotherNestedFormdataList: [AnotherNestedFormdata]
}
let bar: Bar
let bars: [Bar]
let nestedFormdata: [NestedFormdata]
}

let data = """
---\r
Content-Disposition: form-data; name="bar[baz]"\r
Content-Disposition: form-data; name="nestedFormdata[0][int]"\r
\r
1\r
---\r
Content-Disposition: form-data; name="nestedFormdata[0][string]"\r
\r
1\r
---\r
Content-Disposition: form-data; name="bars[][baz]"\r
Content-Disposition: form-data; name="nestedFormdata[0][strings][0]"\r
\r
2\r
---\r
Content-Disposition: form-data; name="bars[][baz]"\r
Content-Disposition: form-data; name="nestedFormdata[0][strings][1]"\r
\r
3\r
---\r
Content-Disposition: form-data; name="nestedFormdata[0][anotherNestedFormdata][int]"\r
\r
4\r
---\r
Content-Disposition: form-data; name="nestedFormdata[0][anotherNestedFormdata][string]"\r
\r
5\r
---\r
Content-Disposition: form-data; name="nestedFormdata[0][anotherNestedFormdata][strings][0]"\r
\r
6\r
---\r
Content-Disposition: form-data; name="nestedFormdata[0][anotherNestedFormdata][strings][1]"\r
\r
7\r
---\r
Content-Disposition: form-data; name="nestedFormdata[0][anotherNestedFormdataList][0][int]"\r
\r
10\r
---\r
Content-Disposition: form-data; name="nestedFormdata[0][anotherNestedFormdataList][0][string]"\r
\r
11\r
---\r
Content-Disposition: form-data; name="nestedFormdata[0][anotherNestedFormdataList][0][strings][0]"\r
\r
12\r
---\r
Content-Disposition: form-data; name="nestedFormdata[0][anotherNestedFormdataList][0][strings][1]"\r
\r
13\r
---\r
Content-Disposition: form-data; name="nestedFormdata[0][anotherNestedFormdataList][1][int]"\r
\r
20\r
---\r
Content-Disposition: form-data; name="nestedFormdata[0][anotherNestedFormdataList][1][string]"\r
\r
21\r
---\r
Content-Disposition: form-data; name="nestedFormdata[0][anotherNestedFormdataList][1][strings][0]"\r
\r
22\r
---\r
Content-Disposition: form-data; name="nestedFormdata[0][anotherNestedFormdataList][1][strings][1]"\r
\r
33\r
-----\r\n
"""

let decoder = FormDataDecoder()
let foo = try decoder.decode(Foo.self, from: data, boundary: "-")

XCTAssertEqual(foo, Foo(bar: .init(baz: 1), bars: [.init(baz: 2), .init(baz: 3)]))
let formdata = try decoder.decode(Formdata.self, from: data, boundary: "-")

XCTAssertEqual(formdata, Formdata(nestedFormdata: [
.init(
int: "1",
string: 1,
strings: ["2", "3"],
anotherNestedFormdata: .init(int: 4, string: "5", strings: ["6", "7"]),
anotherNestedFormdataList: [
.init(int: 10, string: "11", strings: ["12", "13"]),
.init(int: 20, string: "21", strings: ["22", "33"])
])
]))
}

func testDecodingSingleValue() throws {
Expand Down

0 comments on commit 95056cb

Please sign in to comment.