Skip to content

Commit a31236f

Browse files
authored
Conform URL to MutipartPartConvertible (#97)
* Package and docs updates * General code cleanup * Conform URL to MultipartPartConvertible, add test. * Update CI
1 parent 3c9558f commit a31236f

22 files changed

+137
-99
lines changed

.github/dependabot.yml

-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
version: 2
2-
enable-beta-ecosystems: true
32
updates:
43
- package-ecosystem: "github-actions"
54
directory: "/"
@@ -11,14 +10,3 @@ updates:
1110
dependencies:
1211
patterns:
1312
- "*"
14-
- package-ecosystem: "swift"
15-
directory: "/"
16-
schedule:
17-
interval: "daily"
18-
open-pull-requests-limit: 6
19-
allow:
20-
- dependency-type: all
21-
groups:
22-
all-dependencies:
23-
patterns:
24-
- "*"

.github/workflows/test.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ on:
55

66
jobs:
77
unit-tests:
8-
uses: vapor/ci/.github/workflows/run-unit-tests.yml@reusable-workflows
8+
uses: vapor/ci/.github/workflows/run-unit-tests.yml@main
9+
secrets: inherit

[email protected]

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ let package = Package(
2525
.product(name: "Collections", package: "swift-collections"),
2626
],
2727
swiftSettings: [
28+
.enableUpcomingFeature("ExistentialAny"),
2829
.enableExperimentalFeature("StrictConcurrency=complete"),
2930
]
3031
),
@@ -34,6 +35,7 @@ let package = Package(
3435
.target(name: "MultipartKit"),
3536
],
3637
swiftSettings: [
38+
.enableUpcomingFeature("ExistentialAny"),
3739
.enableExperimentalFeature("StrictConcurrency=complete"),
3840
]
3941
),

README.md

+3-12
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<a href="https://docs.vapor.codes/4.0/"><img src="https://design.vapor.codes/images/readthedocs.svg" alt="Documentation"></a>
1010
<a href="https://discord.gg/vapor"><img src="https://design.vapor.codes/images/discordchat.svg" alt="Team Chat"></a>
1111
<a href="LICENSE"><img src="https://design.vapor.codes/images/mitlicense.svg" alt="MIT License"></a>
12-
<a href="https://github.com/vapor/multipart-kit/actions/workflows/test.yml"><img src="https://img.shields.io/github/actions/workflow/status/vapor/multipart-kit/test.yml?event=push&style=plastic&logo=github&label=test&logoColor=%23ccc" alt="Continuous Integration"></a>
12+
<a href="https://github.com/vapor/multipart-kit/actions/workflows/test.yml"><img src="https://img.shields.io/github/actions/workflow/status/vapor/multipart-kit/test.yml?event=push&style=plastic&logo=github&label=tests&logoColor=%23ccc" alt="Continuous Integration"></a>
1313
<a href="https://codecov.io/github/vapor/multipart-kit"><img src="https://img.shields.io/codecov/c/github/vapor/multipart-kit?style=plastic&logo=codecov&label=Codecov&token=yDzzHja8lt"></a>
1414
<a href="https://swift.org"><img src="https://design.vapor.codes/images/swift57up.svg" alt="Swift 5.7+"></a>
1515
</p>
@@ -18,18 +18,9 @@
1818

1919
### Installation
2020

21-
The table below shows a list of MultipartKit major releases alongside their compatible NIO and Swift versions.
22-
23-
|Version|NIO|Swift|SPM|
24-
|---|---|---|---|
25-
|4.0|2.2|5.4+|`from: "4.0.0"`|
26-
|3.0|1.0|4.0+|`from: "3.0.0"`|
27-
|2.0|N/A|3.1+|`from: "2.0.0"`|
28-
|1.0|N/A|3.1+|`from: "1.0.0"`|
29-
3021
Use the SPM string to easily include the dependency in your `Package.swift` file.
3122

32-
Add MultiPartKit to your package dependencies:
23+
Add MultipartKit to your package dependencies:
3324

3425
```swift
3526
dependencies: [
@@ -38,7 +29,7 @@ dependencies: [
3829
]
3930
```
4031

41-
Add MultiPartKit to your target's dependencies:
32+
Add MultipartKit to your target's dependencies:
4233

4334
```swift
4435
targets: [

Sources/MultipartKit/Docs.docc/index.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# ``MultipartKit``
22

3-
MultipartKit is a Swift package for parsing and serializing multipart/form-data requests. It provides hooks for encoding and decoding requests in Swift. It provides `Codable` support for the special case of the `multipart/form-data` media type through a ``FormDataEncoder`` and ``FormDataDecoder``. The parser delivers its output as it is parsed through callbacks suitable for streaming.
3+
Parser, serializer, and `Codable` support for `multipart/form-data`.
4+
5+
MultipartKit is a Swift package for parsing and serializing `multipart/form-data` requests. It provides hooks for encoding and decoding requests in Swift and `Codable` support for handling `multipart/form-data` data through a ``FormDataEncoder`` and ``FormDataDecoder``. The parser delivers its output as it is parsed through callbacks suitable for streaming.
46

57
### Multipart Form Data
68

Sources/MultipartKit/Docs.docc/theme-settings.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"theme": {
3-
"aside": { "border-radius": "6px", "border-style": "double", "border-width": "3px" },
3+
"aside": { "border-radius": "16px", "border-style": "double", "border-width": "3px" },
44
"border-radius": "0",
55
"button": { "border-radius": "16px", "border-width": "1px", "border-style": "solid" },
66
"code": { "border-radius": "16px", "border-width": "1px", "border-style": "solid" },

Sources/MultipartKit/FormDataDecoder/FormDataDecoder.Decoder.swift

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
extension FormDataDecoder {
22
struct Decoder {
3-
let codingPath: [CodingKey]
3+
let codingPath: [any CodingKey]
44
let data: MultipartFormData
55
let userInfo: [CodingUserInfoKey: Any]
6-
let previousCodingPath : [CodingKey]?
7-
let previousType: Decodable.Type?
6+
let previousCodingPath: [any CodingKey]?
7+
let previousType: (any Decodable.Type)?
88

9-
init(codingPath: [CodingKey], data: MultipartFormData, userInfo: [CodingUserInfoKey: Any], previousCodingPath: [CodingKey]? = nil, previousType: Decodable.Type? = nil) {
9+
init(codingPath: [any CodingKey], data: MultipartFormData, userInfo: [CodingUserInfoKey: Any], previousCodingPath: [any CodingKey]? = nil, previousType: (any Decodable.Type)? = nil) {
1010
self.codingPath = codingPath
1111
self.data = data
1212
self.userInfo = userInfo
@@ -24,26 +24,26 @@ extension FormDataDecoder.Decoder: Decoder {
2424
return KeyedDecodingContainer(FormDataDecoder.KeyedContainer(data: dictionary, decoder: self))
2525
}
2626

27-
func unkeyedContainer() throws -> UnkeyedDecodingContainer {
27+
func unkeyedContainer() throws -> any UnkeyedDecodingContainer {
2828
guard let array = data.array else {
2929
throw decodingError(expectedType: "array")
3030
}
3131
return FormDataDecoder.UnkeyedContainer(data: array, decoder: self)
3232
}
3333

34-
func singleValueContainer() throws -> SingleValueDecodingContainer {
34+
func singleValueContainer() throws -> any SingleValueDecodingContainer {
3535
self
3636
}
3737
}
3838

3939
extension FormDataDecoder.Decoder {
40-
func nested(at key: CodingKey, with data: MultipartFormData) -> Self {
40+
func nested(at key: any CodingKey, with data: MultipartFormData) -> Self {
4141
.init(codingPath: codingPath + [key], data: data, userInfo: userInfo)
4242
}
4343
}
4444

4545
private extension FormDataDecoder.Decoder {
46-
func decodingError(expectedType: String) -> Error {
46+
func decodingError(expectedType: String) -> any Error {
4747
let encounteredType: Any.Type
4848
let encounteredTypeDescription: String
4949

Sources/MultipartKit/FormDataDecoder/FormDataDecoder.KeyedContainer.swift

+6-6
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ extension FormDataDecoder.KeyedContainer: KeyedDecodingContainerProtocol {
1010
data.keys.compactMap(K.init(stringValue:))
1111
}
1212

13-
var codingPath: [CodingKey] {
13+
var codingPath: [any CodingKey] {
1414
decoder.codingPath
1515
}
1616

1717
func contains(_ key: K) -> Bool {
1818
data.keys.contains(key.stringValue)
1919
}
2020

21-
func getValue(forKey key: CodingKey) throws -> MultipartFormData {
21+
func getValue(forKey key: any CodingKey) throws -> MultipartFormData {
2222
guard let value = data[key.stringValue] else {
2323
throw DecodingError.keyNotFound(
2424
key, .init(
@@ -42,19 +42,19 @@ extension FormDataDecoder.KeyedContainer: KeyedDecodingContainerProtocol {
4242
try decoderForKey(key).container(keyedBy: keyType)
4343
}
4444

45-
func nestedUnkeyedContainer(forKey key: K) throws -> UnkeyedDecodingContainer {
45+
func nestedUnkeyedContainer(forKey key: K) throws -> any UnkeyedDecodingContainer {
4646
try decoderForKey(key).unkeyedContainer()
4747
}
4848

49-
func superDecoder() throws -> Decoder {
49+
func superDecoder() throws -> any Decoder {
5050
try decoderForKey(BasicCodingKey.super)
5151
}
5252

53-
func superDecoder(forKey key: K) throws -> Decoder {
53+
func superDecoder(forKey key: K) throws -> any Decoder {
5454
try decoderForKey(key)
5555
}
5656

57-
func decoderForKey(_ key: CodingKey) throws -> FormDataDecoder.Decoder {
57+
func decoderForKey(_ key: any CodingKey) throws -> FormDataDecoder.Decoder {
5858
decoder.nested(at: key, with: try getValue(forKey: key))
5959
}
6060
}

Sources/MultipartKit/FormDataDecoder/FormDataDecoder.SingleValueContainer.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ extension FormDataDecoder.Decoder: SingleValueDecodingContainer {
66
func decode<T: Decodable>(_: T.Type = T.self) throws -> T {
77
guard
88
let part = data.part,
9-
let Convertible = T.self as? MultipartPartConvertible.Type
9+
let Convertible = T.self as? any MultipartPartConvertible.Type
1010
else {
1111
guard previousCodingPath?.count != codingPath.count || previousType != T.self else {
1212
throw DecodingError.dataCorrupted(.init(codingPath: codingPath, debugDescription: "Decoding caught in recursion loop"))

Sources/MultipartKit/FormDataDecoder/FormDataDecoder.UnkeyedContainer.swift

+4-4
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ extension FormDataDecoder {
77
}
88

99
extension FormDataDecoder.UnkeyedContainer: UnkeyedDecodingContainer {
10-
var codingPath: [CodingKey] {
10+
var codingPath: [any CodingKey] {
1111
decoder.codingPath
1212
}
1313
var count: Int? { data.count }
14-
var index: CodingKey { BasicCodingKey.index(currentIndex) }
14+
var index: any CodingKey { BasicCodingKey.index(currentIndex) }
1515
var isAtEnd: Bool { currentIndex >= data.count }
1616

1717
mutating func decodeNil() throws -> Bool {
@@ -26,11 +26,11 @@ extension FormDataDecoder.UnkeyedContainer: UnkeyedDecodingContainer {
2626
try decoderAtIndex().container(keyedBy: keyType)
2727
}
2828

29-
mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer {
29+
mutating func nestedUnkeyedContainer() throws -> any UnkeyedDecodingContainer {
3030
try decoderAtIndex().unkeyedContainer()
3131
}
3232

33-
mutating func superDecoder() throws -> Decoder {
33+
mutating func superDecoder() throws -> any Decoder {
3434
try decoderAtIndex()
3535
}
3636

Sources/MultipartKit/FormDataDecoder/FormDataDecoder.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public struct FormDataDecoder: Sendable {
1515
let nestingDepth: Int
1616

1717
/// Any contextual information set by the user for decoding.
18-
public var userInfo: [CodingUserInfoKey: Sendable] = [:]
18+
public var userInfo: [CodingUserInfoKey: any Sendable] = [:]
1919

2020
/// Creates a new `FormDataDecoder`.
2121
/// - Parameter nestingDepth: maximum allowed nesting depth of the decoded structure. Defaults to 8.

Sources/MultipartKit/FormDataEncoder/FormDataEncoder.Encoder.swift

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
extension FormDataEncoder {
22
struct Encoder {
3-
let codingPath: [CodingKey]
3+
let codingPath: [any CodingKey]
44
let storage = Storage()
55
let userInfo: [CodingUserInfoKey: Any]
66
}
@@ -13,19 +13,19 @@ extension FormDataEncoder.Encoder: Encoder {
1313
return .init(container)
1414
}
1515

16-
func unkeyedContainer() -> UnkeyedEncodingContainer {
16+
func unkeyedContainer() -> any UnkeyedEncodingContainer {
1717
let container = FormDataEncoder.UnkeyedContainer(encoder: self)
1818
storage.dataContainer = container.dataContainer
1919
return container
2020
}
2121

22-
func singleValueContainer() -> SingleValueEncodingContainer {
22+
func singleValueContainer() -> any SingleValueEncodingContainer {
2323
self
2424
}
2525
}
2626

2727
extension FormDataEncoder.Encoder {
28-
func nested(at key: CodingKey) -> FormDataEncoder.Encoder {
28+
func nested(at key: any CodingKey) -> FormDataEncoder.Encoder {
2929
.init(codingPath: codingPath + [key], userInfo: userInfo)
3030
}
3131
}

Sources/MultipartKit/FormDataEncoder/FormDataEncoder.KeyedContainer.swift

+5-5
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ extension FormDataEncoder {
66
}
77

88
extension FormDataEncoder.KeyedContainer: KeyedEncodingContainerProtocol {
9-
var codingPath: [CodingKey] {
9+
var codingPath: [any CodingKey] {
1010
encoder.codingPath
1111
}
1212

@@ -22,19 +22,19 @@ extension FormDataEncoder.KeyedContainer: KeyedEncodingContainerProtocol {
2222
encoderForKey(key).container(keyedBy: keyType)
2323
}
2424

25-
func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
25+
func nestedUnkeyedContainer(forKey key: Key) -> any UnkeyedEncodingContainer {
2626
encoderForKey(key).unkeyedContainer()
2727
}
2828

29-
func superEncoder() -> Encoder {
29+
func superEncoder() -> any Encoder {
3030
encoderForKey(BasicCodingKey.super)
3131
}
3232

33-
func superEncoder(forKey key: Key) -> Encoder {
33+
func superEncoder(forKey key: Key) -> any Encoder {
3434
encoderForKey(key)
3535
}
3636

37-
func encoderForKey(_ key: CodingKey) -> FormDataEncoder.Encoder {
37+
func encoderForKey(_ key: any CodingKey) -> FormDataEncoder.Encoder {
3838
let encoder = self.encoder.nested(at: key)
3939
dataContainer.value[key.stringValue] = encoder.storage
4040
return encoder

Sources/MultipartKit/FormDataEncoder/FormDataEncoder.SingleValueContainer.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ extension FormDataEncoder.Encoder: SingleValueEncodingContainer {
55

66
func encode<T: Encodable>(_ value: T) throws {
77
if
8-
let convertible = value as? MultipartPartConvertible,
8+
let convertible = value as? any MultipartPartConvertible,
99
let part = convertible.multipart
1010
{
1111
storage.dataContainer = SingleValueDataContainer(part: part)

Sources/MultipartKit/FormDataEncoder/FormDataEncoder.UnkeyedContainer.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ extension FormDataEncoder {
66
}
77

88
extension FormDataEncoder.UnkeyedContainer: UnkeyedEncodingContainer {
9-
var codingPath: [CodingKey] {
9+
var codingPath: [any CodingKey] {
1010
encoder.codingPath
1111
}
1212

@@ -26,11 +26,11 @@ extension FormDataEncoder.UnkeyedContainer: UnkeyedEncodingContainer {
2626
nextEncoder().container(keyedBy: keyType)
2727
}
2828

29-
func nestedUnkeyedContainer() -> UnkeyedEncodingContainer {
29+
func nestedUnkeyedContainer() -> any UnkeyedEncodingContainer {
3030
nextEncoder().unkeyedContainer()
3131
}
3232

33-
func superEncoder() -> Encoder {
33+
func superEncoder() -> any Encoder {
3434
nextEncoder()
3535
}
3636

Sources/MultipartKit/FormDataEncoder/FormDataEncoder.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import NIOCore
88
public struct FormDataEncoder: Sendable {
99

1010
/// Any contextual information set by the user for encoding.
11-
public var userInfo: [CodingUserInfoKey: Sendable] = [:]
11+
public var userInfo: [CodingUserInfoKey: any Sendable] = [:]
1212

1313
/// Creates a new `FormDataEncoder`.
1414
public init() { }

Sources/MultipartKit/FormDataEncoder/Storage.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import Collections
22

33
final class Storage {
4-
var dataContainer: DataContainer? = nil
4+
var dataContainer: (any DataContainer)? = nil
55
var data: MultipartFormData? {
66
dataContainer?.data
77
}

Sources/MultipartKit/MultipartPart.swift

+2-7
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,11 @@ public struct MultipartPart: Equatable, Sendable {
5353
extension Array where Element == MultipartPart {
5454
/// Returns the first `MultipartPart` with matching name attribute in `"Content-Disposition"` header.
5555
public func firstPart(named name: String) -> MultipartPart? {
56-
for el in self {
57-
if el.name == name {
58-
return el
59-
}
60-
}
61-
return nil
56+
self.first { $0.name == name }
6257
}
6358

6459
/// Returns all `MultipartPart`s with matching name attribute in `"Content-Disposition"` header.
6560
public func allParts(named name: String) -> [MultipartPart] {
66-
filter { $0.name == name }
61+
self.filter { $0.name == name }
6762
}
6863
}

0 commit comments

Comments
 (0)