Replies: 3 comments 4 replies
-
Well, it's a total punt on actually parsing a import Foundation
import MultipartKit
import NIOFoundationCompat
extension Conversions {
struct Multipart<Value: Codable>: Conversion {
let boundary: String
let decoder: FormDataDecoder
let encoder: FormDataEncoder
init(
_ type: Value.Type,
boundary: String,
decoder: FormDataDecoder,
encoder: FormDataEncoder
) {
self.boundary = boundary
self.decoder = decoder
self.encoder = encoder
}
public func apply(_ input: Data) throws -> Value {
try self.decoder.decode(Value.self, from: ByteBuffer(data: input), boundary: boundary)
}
public func unapply(_ output: Value) throws -> Data {
var buffer = ByteBuffer()
try self.encoder.encode(output, boundary: self.boundary, into: &buffer)
return Data(buffer: buffer)
}
}
}
extension Conversion {
static func multipart<Value>(
_ type: Value.Type,
boundary: String,
decoder: FormDataDecoder = .init(),
encoder: FormDataEncoder = .init()
) -> Self where Self == Conversions.Multipart<Value> {
.init(type, boundary: boundary, decoder: decoder, encoder: encoder)
}
func multipart<Value>(
_ type: Value.Type,
boundary: String,
decoder: FormDataDecoder = .init(),
encoder: FormDataEncoder = .init()
) -> Conversions.Map<Self, Conversions.Multipart<Value>> {
self.map(.multipart(type, boundary: boundary, decoder: decoder, encoder: encoder))
}
} Which then allows me to implement something like the following. I think struct Multipart<Value: Codable>: ParserPrinter {
let boundary: String
init(
_ type: Value.Type,
printingBoundary boundary: String
) {
self.boundary = boundary
}
var body: some ParserPrinter<URLRequestData, Value> {
Headers {
Field("Content-Type") {
"multipart/form-data;boundary=\""
Prefix { $0 != "\"" }.map(.string)
"\""
}
}
.flatMap { boundary in
Body(.multipart(Value.self, boundary: boundary))
}
.printing(self.printing)
}
var printing: some ParserPrinter<URLRequestData, Value> {
ParsePrint {
Skip {
Headers {
Field("Content-Type") {
"multipart/form-data;boundary=\""
self.boundary
"\""
}
}
}
Body(.multipart(Value.self, boundary: self.boundary))
}
}
} I may continue to take passes at this, because taking a transitive dependency on |
Beta Was this translation helpful? Give feedback.
-
I've had a go at trying to generalise this. My approach has been to parse the body in two stages - first parse the raw URLRequestData body into multiple parts, with each part represented as a I have a Route(.case(TestRoute.multipart)) {
Method.post
Path { "upload" }
Multipart(boundary: "abcde12345") {
Part {
PartHeaders {
Field("Content-Type") { "text/plain" }
}
PartBody(DataToSubstring(encoding: .utf8).string)
}
Part {
PartHeaders {
Field("Content-Type") { "application/json" }
}
PartBody(.json(Model.self))
}
}
} The only weird thing is that you still need to provide some boundary value to the multipart parser, but it's only used when printing. This is because even though the boundary value can be parsed from the headers when parsing, this value is discarded after parsing the body into separate parts, so that value is lost. However, it doesn't seem to really matter what the boundary value is when printing as long as its something that isn't part of your content so you can supply any boundary value that makes sense when printing. It does seem like a bit of a strange API though. I'm sure there's a lot that can be improved about my implementation but I've pushed up my WIP here: |
Beta Was this translation helpful? Give feedback.
-
Just circling back to this thread to let you know that we've finally open sourced out multipart routing extensions https://github.com/Shimmur/swift-url-routing-multipart |
Beta Was this translation helpful? Give feedback.
-
I'd love to see if anyone has taken a stab at implementing a
multipart/form-data
parser-printer, either ad-hoc or generally. I love the idea ofswift-url-routing
as a type-safe API client, but it's not too long before you run into the need to makemultipart/form-data
requests.I figure unlike the provided (
application/x-www-form-urlencoded
)FormData
parser, which is merely a parser ofData
, a multipart parser would need to be a parser of all ofURLRequestData
, because it needs access to the body and headers, to capture the boundary string.Speaking of capturing the boundary string, I'm not quite sure how to to do that, for use in parsing the separator in the body.
Anyway, just thought I'd throw up a discussion and see if anyone has anything to give me a head start. If I make headway, I'll follow up.
Beta Was this translation helpful? Give feedback.
All reactions