Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conform URL to MutipartPartConvertible #97

Merged
merged 4 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
version: 2
enable-beta-ecosystems: true
updates:
- package-ecosystem: "github-actions"
directory: "/"
Expand All @@ -11,14 +10,3 @@ updates:
dependencies:
patterns:
- "*"
- package-ecosystem: "swift"
directory: "/"
schedule:
interval: "daily"
open-pull-requests-limit: 6
allow:
- dependency-type: all
groups:
all-dependencies:
patterns:
- "*"
3 changes: 2 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ on:

jobs:
unit-tests:
uses: vapor/ci/.github/workflows/run-unit-tests.yml@reusable-workflows
uses: vapor/ci/.github/workflows/run-unit-tests.yml@main
secrets: inherit
2 changes: 2 additions & 0 deletions [email protected]
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ let package = Package(
.product(name: "Collections", package: "swift-collections"),
],
swiftSettings: [
.enableUpcomingFeature("ExistentialAny"),
.enableExperimentalFeature("StrictConcurrency=complete"),
]
),
Expand All @@ -34,6 +35,7 @@ let package = Package(
.target(name: "MultipartKit"),
],
swiftSettings: [
.enableUpcomingFeature("ExistentialAny"),
.enableExperimentalFeature("StrictConcurrency=complete"),
]
),
Expand Down
15 changes: 3 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<a href="https://docs.vapor.codes/4.0/"><img src="https://design.vapor.codes/images/readthedocs.svg" alt="Documentation"></a>
<a href="https://discord.gg/vapor"><img src="https://design.vapor.codes/images/discordchat.svg" alt="Team Chat"></a>
<a href="LICENSE"><img src="https://design.vapor.codes/images/mitlicense.svg" alt="MIT License"></a>
<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>
<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>
<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>
<a href="https://swift.org"><img src="https://design.vapor.codes/images/swift57up.svg" alt="Swift 5.7+"></a>
</p>
Expand All @@ -18,18 +18,9 @@

### Installation

The table below shows a list of MultipartKit major releases alongside their compatible NIO and Swift versions.

|Version|NIO|Swift|SPM|
|---|---|---|---|
|4.0|2.2|5.4+|`from: "4.0.0"`|
|3.0|1.0|4.0+|`from: "3.0.0"`|
|2.0|N/A|3.1+|`from: "2.0.0"`|
|1.0|N/A|3.1+|`from: "1.0.0"`|

Use the SPM string to easily include the dependency in your `Package.swift` file.

Add MultiPartKit to your package dependencies:
Add MultipartKit to your package dependencies:
MahdiBM marked this conversation as resolved.
Show resolved Hide resolved

```swift
dependencies: [
Expand All @@ -38,7 +29,7 @@ dependencies: [
]
```

Add MultiPartKit to your target's dependencies:
Add MultipartKit to your target's dependencies:

```swift
targets: [
Expand Down
4 changes: 3 additions & 1 deletion Sources/MultipartKit/Docs.docc/index.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# ``MultipartKit``

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.
Parser, serializer, and `Codable` support for `multipart/form-data`.

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.

### Multipart Form Data

Expand Down
2 changes: 1 addition & 1 deletion Sources/MultipartKit/Docs.docc/theme-settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"theme": {
"aside": { "border-radius": "6px", "border-style": "double", "border-width": "3px" },
"aside": { "border-radius": "16px", "border-style": "double", "border-width": "3px" },
"border-radius": "0",
"button": { "border-radius": "16px", "border-width": "1px", "border-style": "solid" },
"code": { "border-radius": "16px", "border-width": "1px", "border-style": "solid" },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
extension FormDataDecoder {
struct Decoder {
let codingPath: [CodingKey]
let codingPath: [any CodingKey]
let data: MultipartFormData
let userInfo: [CodingUserInfoKey: Any]
let previousCodingPath : [CodingKey]?
let previousType: Decodable.Type?
let previousCodingPath: [any CodingKey]?
let previousType: (any Decodable.Type)?

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

func unkeyedContainer() throws -> UnkeyedDecodingContainer {
func unkeyedContainer() throws -> any UnkeyedDecodingContainer {
guard let array = data.array else {
throw decodingError(expectedType: "array")
}
return FormDataDecoder.UnkeyedContainer(data: array, decoder: self)
}

func singleValueContainer() throws -> SingleValueDecodingContainer {
func singleValueContainer() throws -> any SingleValueDecodingContainer {
self
}
}

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

private extension FormDataDecoder.Decoder {
func decodingError(expectedType: String) -> Error {
func decodingError(expectedType: String) -> any Error {
let encounteredType: Any.Type
let encounteredTypeDescription: String

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ extension FormDataDecoder.KeyedContainer: KeyedDecodingContainerProtocol {
data.keys.compactMap(K.init(stringValue:))
}

var codingPath: [CodingKey] {
var codingPath: [any CodingKey] {
decoder.codingPath
}

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

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

func nestedUnkeyedContainer(forKey key: K) throws -> UnkeyedDecodingContainer {
func nestedUnkeyedContainer(forKey key: K) throws -> any UnkeyedDecodingContainer {
try decoderForKey(key).unkeyedContainer()
}

func superDecoder() throws -> Decoder {
func superDecoder() throws -> any Decoder {
try decoderForKey(BasicCodingKey.super)
}

func superDecoder(forKey key: K) throws -> Decoder {
func superDecoder(forKey key: K) throws -> any Decoder {
try decoderForKey(key)
}

func decoderForKey(_ key: CodingKey) throws -> FormDataDecoder.Decoder {
func decoderForKey(_ key: any CodingKey) throws -> FormDataDecoder.Decoder {
decoder.nested(at: key, with: try getValue(forKey: key))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ extension FormDataDecoder.Decoder: SingleValueDecodingContainer {
func decode<T: Decodable>(_: T.Type = T.self) throws -> T {
guard
let part = data.part,
let Convertible = T.self as? MultipartPartConvertible.Type
let Convertible = T.self as? any MultipartPartConvertible.Type
else {
guard previousCodingPath?.count != codingPath.count || previousType != T.self else {
throw DecodingError.dataCorrupted(.init(codingPath: codingPath, debugDescription: "Decoding caught in recursion loop"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ extension FormDataDecoder {
}

extension FormDataDecoder.UnkeyedContainer: UnkeyedDecodingContainer {
var codingPath: [CodingKey] {
var codingPath: [any CodingKey] {
decoder.codingPath
}
var count: Int? { data.count }
var index: CodingKey { BasicCodingKey.index(currentIndex) }
var index: any CodingKey { BasicCodingKey.index(currentIndex) }
var isAtEnd: Bool { currentIndex >= data.count }

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

mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer {
mutating func nestedUnkeyedContainer() throws -> any UnkeyedDecodingContainer {
try decoderAtIndex().unkeyedContainer()
}

mutating func superDecoder() throws -> Decoder {
mutating func superDecoder() throws -> any Decoder {
try decoderAtIndex()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public struct FormDataDecoder: Sendable {
let nestingDepth: Int

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

/// Creates a new `FormDataDecoder`.
/// - Parameter nestingDepth: maximum allowed nesting depth of the decoded structure. Defaults to 8.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
extension FormDataEncoder {
struct Encoder {
let codingPath: [CodingKey]
let codingPath: [any CodingKey]
let storage = Storage()
let userInfo: [CodingUserInfoKey: Any]
}
Expand All @@ -13,19 +13,19 @@ extension FormDataEncoder.Encoder: Encoder {
return .init(container)
}

func unkeyedContainer() -> UnkeyedEncodingContainer {
func unkeyedContainer() -> any UnkeyedEncodingContainer {
let container = FormDataEncoder.UnkeyedContainer(encoder: self)
storage.dataContainer = container.dataContainer
return container
}

func singleValueContainer() -> SingleValueEncodingContainer {
func singleValueContainer() -> any SingleValueEncodingContainer {
self
}
}

extension FormDataEncoder.Encoder {
func nested(at key: CodingKey) -> FormDataEncoder.Encoder {
func nested(at key: any CodingKey) -> FormDataEncoder.Encoder {
.init(codingPath: codingPath + [key], userInfo: userInfo)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ extension FormDataEncoder {
}

extension FormDataEncoder.KeyedContainer: KeyedEncodingContainerProtocol {
var codingPath: [CodingKey] {
var codingPath: [any CodingKey] {
encoder.codingPath
}

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

func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
func nestedUnkeyedContainer(forKey key: Key) -> any UnkeyedEncodingContainer {
encoderForKey(key).unkeyedContainer()
}

func superEncoder() -> Encoder {
func superEncoder() -> any Encoder {
encoderForKey(BasicCodingKey.super)
}

func superEncoder(forKey key: Key) -> Encoder {
func superEncoder(forKey key: Key) -> any Encoder {
encoderForKey(key)
}

func encoderForKey(_ key: CodingKey) -> FormDataEncoder.Encoder {
func encoderForKey(_ key: any CodingKey) -> FormDataEncoder.Encoder {
let encoder = self.encoder.nested(at: key)
dataContainer.value[key.stringValue] = encoder.storage
return encoder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ extension FormDataEncoder.Encoder: SingleValueEncodingContainer {

func encode<T: Encodable>(_ value: T) throws {
if
let convertible = value as? MultipartPartConvertible,
let convertible = value as? any MultipartPartConvertible,
let part = convertible.multipart
{
storage.dataContainer = SingleValueDataContainer(part: part)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ extension FormDataEncoder {
}

extension FormDataEncoder.UnkeyedContainer: UnkeyedEncodingContainer {
var codingPath: [CodingKey] {
var codingPath: [any CodingKey] {
encoder.codingPath
}

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

func nestedUnkeyedContainer() -> UnkeyedEncodingContainer {
func nestedUnkeyedContainer() -> any UnkeyedEncodingContainer {
nextEncoder().unkeyedContainer()
}

func superEncoder() -> Encoder {
func superEncoder() -> any Encoder {
nextEncoder()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import NIOCore
public struct FormDataEncoder: Sendable {

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

/// Creates a new `FormDataEncoder`.
public init() { }
Expand Down
2 changes: 1 addition & 1 deletion Sources/MultipartKit/FormDataEncoder/Storage.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Collections

final class Storage {
var dataContainer: DataContainer? = nil
var dataContainer: (any DataContainer)? = nil
var data: MultipartFormData? {
dataContainer?.data
}
Expand Down
9 changes: 2 additions & 7 deletions Sources/MultipartKit/MultipartPart.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,11 @@ public struct MultipartPart: Equatable, Sendable {
extension Array where Element == MultipartPart {
/// Returns the first `MultipartPart` with matching name attribute in `"Content-Disposition"` header.
public func firstPart(named name: String) -> MultipartPart? {
for el in self {
if el.name == name {
return el
}
}
return nil
self.first { $0.name == name }
}

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