Skip to content

Commit

Permalink
MultipartKit V5 (#100)
Browse files Browse the repository at this point in the history
* Start making the parser async

* Make header parsing work

* Add body parsing support

* Housekeeping

* Add more complex example test

* Move error throwing up one level

* Apply suggestions and add binary data test

* Add sync parsing and serialising

* Wip

* Make encoding work again

* Start generifying stuff

* Make encoders work with generics

* Finish up en/decoding

* Remove NIO and add some docs

* Fix imports and rename some files

* Fix imports again

* Remove unnecessary Sendable conformances

* Add iterator method to parse collated parts

* 🤦‍♂️

* Add some tests and move errors to own files

* Housekeeping

* More housekeeping

* Add separate sequence for collated parts streaming

* Address feedback

* Reduce temporary allocations

* Fix oversight

* Apply suggestions

* Move collation parsing to the original sequence

* Fix test
  • Loading branch information
ptoffy authored Jan 8, 2025
1 parent 3498e60 commit beea78b
Show file tree
Hide file tree
Showing 37 changed files with 1,878 additions and 1,626 deletions.
70 changes: 70 additions & 0 deletions .swift-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{
"fileScopedDeclarationPrivacy": {
"accessLevel": "private"
},
"indentation": {
"spaces": 4
},
"indentConditionalCompilationBlocks": true,
"indentSwitchCaseLabels": false,
"lineBreakAroundMultilineExpressionChainComponents": false,
"lineBreakBeforeControlFlowKeywords": false,
"lineBreakBeforeEachArgument": false,
"lineBreakBeforeEachGenericRequirement": false,
"lineLength": 140,
"maximumBlankLines": 1,
"multiElementCollectionTrailingCommas": true,
"noAssignmentInExpressions": {
"allowedFunctions": [
"XCTAssertNoThrow"
]
},
"prioritizeKeepingFunctionOutputTogether": false,
"respectsExistingLineBreaks": true,
"rules": {
"AllPublicDeclarationsHaveDocumentation": false,
"AlwaysUseLiteralForEmptyCollectionInit": false,
"AlwaysUseLowerCamelCase": true,
"AmbiguousTrailingClosureOverload": true,
"BeginDocumentationCommentWithOneLineSummary": false,
"DoNotUseSemicolons": true,
"DontRepeatTypeInStaticProperties": true,
"FileScopedDeclarationPrivacy": true,
"FullyIndirectEnum": true,
"GroupNumericLiterals": true,
"IdentifiersMustBeASCII": true,
"NeverForceUnwrap": false,
"NeverUseForceTry": false,
"NeverUseImplicitlyUnwrappedOptionals": false,
"NoAccessLevelOnExtensionDeclaration": true,
"NoAssignmentInExpressions": true,
"NoBlockComments": true,
"NoCasesWithOnlyFallthrough": true,
"NoEmptyTrailingClosureParentheses": true,
"NoLabelsInCasePatterns": true,
"NoLeadingUnderscores": false,
"NoParensAroundConditions": true,
"NoPlaygroundLiterals": true,
"NoVoidReturnOnFunctionSignature": true,
"OmitExplicitReturns": false,
"OneCasePerLine": true,
"OneVariableDeclarationPerLine": true,
"OnlyOneTrailingClosureArgument": true,
"OrderedImports": true,
"ReplaceForEachWithForLoop": true,
"ReturnVoidInsteadOfEmptyTuple": true,
"TypeNamesShouldBeCapitalized": true,
"UseEarlyExits": false,
"UseExplicitNilCheckInConditions": true,
"UseLetInEveryBoundCaseVariable": true,
"UseShorthandTypeNames": true,
"UseSingleLinePropertyGetter": true,
"UseSynthesizedInitializer": true,
"UseTripleSlashForDocumentationComments": true,
"UseWhereClausesInForLoops": false,
"ValidateDocumentationComments": false
},
"spacesAroundRangeFormationOperators": false,
"tabWidth": 4,
"version": 1
}
13 changes: 7 additions & 6 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,35 +1,36 @@
// swift-tools-version:5.7
// swift-tools-version:6.0
import PackageDescription

let package = Package(
name: "multipart-kit",
platforms: [
.macOS(.v10_15),
.macCatalyst(.v13),
.iOS(.v13),
.tvOS(.v13),
.watchOS(.v6),
.visionOS(.v1),
],
products: [
.library(name: "MultipartKit", targets: ["MultipartKit"]),
.library(name: "MultipartKit", targets: ["MultipartKit"])
],
dependencies: [
.package(url: "https://github.com/apple/swift-nio.git", from: "2.65.0"),
.package(url: "https://github.com/apple/swift-http-types.git", from: "1.3.0"),
.package(url: "https://github.com/apple/swift-collections.git", from: "1.1.0"),
],
targets: [
.target(
name: "MultipartKit",
dependencies: [
.product(name: "NIO", package: "swift-nio"),
.product(name: "NIOHTTP1", package: "swift-nio"),
.product(name: "HTTPTypes", package: "swift-http-types"),
.product(name: "Collections", package: "swift-collections"),
],
exclude: ["Docs.docc"]
),
.testTarget(
name: "MultipartKitTests",
dependencies: [
.target(name: "MultipartKit"),
.target(name: "MultipartKit")
]
),
]
Expand Down
43 changes: 0 additions & 43 deletions [email protected]

This file was deleted.

11 changes: 0 additions & 11 deletions Sources/MultipartKit/Deprecated/MultipartError.swift

This file was deleted.

13 changes: 0 additions & 13 deletions Sources/MultipartKit/Exports.swift

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
extension FormDataDecoder {
struct Decoder {
struct Decoder<Body: MultipartPartBodyElement> {
let codingPath: [any CodingKey]
let data: MultipartFormData
let userInfo: [CodingUserInfoKey: Any]
let data: MultipartFormData<Body>
let sendableUserInfo: [CodingUserInfoKey: any Sendable]
let previousCodingPath: [any CodingKey]?
let previousType: (any Decodable.Type)?

init(codingPath: [any CodingKey], data: MultipartFormData, userInfo: [CodingUserInfoKey: Any], previousCodingPath: [any CodingKey]? = nil, previousType: (any Decodable.Type)? = nil) {
var userInfo: [CodingUserInfoKey: Any] { sendableUserInfo }

init(
codingPath: [any CodingKey],
data: MultipartFormData<Body>,
userInfo: [CodingUserInfoKey: any Sendable] = [:],
previousCodingPath: [any CodingKey]? = nil,
previousType: (any Decodable.Type)? = nil
) {
self.codingPath = codingPath
self.data = data
self.userInfo = userInfo
self.sendableUserInfo = userInfo
self.previousCodingPath = previousCodingPath
self.previousType = previousType
}
Expand Down Expand Up @@ -37,41 +45,42 @@ extension FormDataDecoder.Decoder: Decoder {
}

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

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

switch data {
case .nestingDepthExceeded:
return DecodingError.dataCorrupted(.init(
codingPath: codingPath,
debugDescription: "Nesting depth exceeded while expecting \(expectedType).",
underlyingError: nil
))
return DecodingError.dataCorrupted(
.init(
codingPath: codingPath,
debugDescription: "Nesting depth exceeded while expecting \(expectedType).",
underlyingError: nil
))
case .array:
encounteredType = [MultipartFormData].self
encounteredType = [MultipartFormData<Body>].self
encounteredTypeDescription = "array"
case .keyed:
encounteredType = MultipartFormData.Keyed.self
encounteredType = MultipartFormData<Body>.Keyed.self
encounteredTypeDescription = "dictionary"
case .single:
encounteredType = MultipartPart.self
encounteredType = MultipartPart<Body>.self
encounteredTypeDescription = "single value"
}

return DecodingError.typeMismatch(
encounteredType,
.init(
codingPath: codingPath,
debugDescription: "Expected \(expectedType) but encountered \(encounteredTypeDescription).",
underlyingError: nil
)
.init(
codingPath: codingPath,
debugDescription: "Expected \(expectedType) but encountered \(encounteredTypeDescription).",
underlyingError: nil
)
)
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
extension FormDataDecoder {
struct KeyedContainer<K: CodingKey> {
let data: MultipartFormData.Keyed
let decoder: FormDataDecoder.Decoder
struct KeyedContainer<K: CodingKey, Body: MultipartPartBodyElement> {
let data: MultipartFormData<Body>.Keyed
let decoder: FormDataDecoder.Decoder<Body>
}
}

Expand All @@ -18,10 +18,11 @@ extension FormDataDecoder.KeyedContainer: KeyedDecodingContainerProtocol {
data.keys.contains(key.stringValue)
}

func getValue(forKey key: any CodingKey) throws -> MultipartFormData {
func getValue(forKey key: any CodingKey) throws -> MultipartFormData<Body> {
guard let value = data[key.stringValue] else {
throw DecodingError.keyNotFound(
key, .init(
key,
.init(
codingPath: codingPath,
debugDescription: "No value associated with key \"\(key.stringValue)\"."
)
Expand Down Expand Up @@ -54,7 +55,7 @@ extension FormDataDecoder.KeyedContainer: KeyedDecodingContainerProtocol {
try decoderForKey(key)
}

func decoderForKey(_ key: any CodingKey) throws -> FormDataDecoder.Decoder {
func decoderForKey(_ key: any CodingKey) throws -> FormDataDecoder.Decoder<Body> {
decoder.nested(at: key, with: try getValue(forKey: key))
}
}
Loading

0 comments on commit beea78b

Please sign in to comment.