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

Add encoding function that returns Data. Fixes #94 #95

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
2 changes: 2 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ let package = Package(
name: "MultipartKitTests",
dependencies: [
.target(name: "MultipartKit"),
], resources: [
.copy("Utilities/image.jpeg"),
]
),
]
Expand Down
17 changes: 17 additions & 0 deletions Sources/MultipartKit/FormDataDecoder/FormDataDecoder.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Foundation
import NIOCore
import NIOHTTP1

Expand Down Expand Up @@ -37,6 +38,22 @@ public struct FormDataDecoder: Sendable {
try decode(D.self, from: ByteBuffer(string: data), boundary: boundary)
}

/// Decodes a `Decodable` item from `Data` using the supplied boundary.
///
/// let foo = try FormDataDecoder().decode(Foo.self, from: "...", boundary: "123")
///
/// - Parameters:
/// - decodable: Generic `Decodable` type.
/// - data: String to decode.
/// - boundary: Multipart boundary to used in the decoding.
/// - Throws: Any errors decoding the model with `Codable` or parsing the data.
/// - Returns: An instance of the decoded type `D`.
public func decode<D: Decodable>(_ decodable: D.Type, from data: Data, boundary: String) throws -> D {
var buffer = ByteBufferAllocator().buffer(capacity: data.count)
buffer.writeBytes(data)
andrew804 marked this conversation as resolved.
Show resolved Hide resolved
return try decode(D.self, from: buffer, boundary: boundary)
}

/// Decodes a `Decodable` item from `Data` using the supplied boundary.
///
/// let foo = try FormDataDecoder().decode(Foo.self, from: data, boundary: "123")
Expand Down
15 changes: 15 additions & 0 deletions Sources/MultipartKit/FormDataEncoder/FormDataEncoder.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Foundation
import NIOCore

/// Encodes `Encodable` items to `multipart/form-data` encoded `Data`.
Expand Down Expand Up @@ -26,6 +27,20 @@ public struct FormDataEncoder: Sendable {
public func encode<E: Encodable>(_ encodable: E, boundary: String) throws -> String {
try MultipartSerializer().serialize(parts: parts(from: encodable), boundary: boundary)
}

/// Encodes an `Encodable` item to `Data` using the supplied boundary.
///
/// let a = Foo(string: "a", int: 42, double: 3.14, array: [1, 2, 3])
/// let data = try FormDataEncoder().encodeToData(a, boundary: "123")
///
/// - parameters:
/// - encodable: Generic `Encodable` item.
/// - boundary: Multipart boundary to use for encoding. This must not appear anywhere in the encoded data.
/// - throws: Any errors encoding the model with `Codable` or serializing the data.
/// - returns: `multipart/form-data`-encoded `String`.
andrew804 marked this conversation as resolved.
Show resolved Hide resolved
public func encodeToData<E: Encodable>(_ encodable: E, boundary: String) throws -> Data {
andrew804 marked this conversation as resolved.
Show resolved Hide resolved
try MultipartSerializer().serializeToData(parts: parts(from: encodable), boundary: boundary)
}

/// Encodes an `Encodable` item into a `ByteBuffer` using the supplied boundary.
///
Expand Down
19 changes: 18 additions & 1 deletion Sources/MultipartKit/MultipartSerializer.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Foundation
import NIOCore

/// Serializes `MultipartForm`s to `Data`.
Expand All @@ -14,7 +15,7 @@ public final class MultipartSerializer: Sendable {
/// print(data) // multipart-encoded
///
/// - parameters:
/// - parts: One or more `MultipartPart`s to serialize into `Data`.
/// - parts: One or more `MultipartPart`s to serialize into `String`.
/// - boundary: Multipart boundary to use for encoding. This must not appear anywhere in the encoded data.
/// - throws: Any errors that may occur during serialization.
/// - returns: `multipart`-encoded `Data`.
Expand All @@ -23,6 +24,22 @@ public final class MultipartSerializer: Sendable {
try self.serialize(parts: parts, boundary: boundary, into: &buffer)
return String(decoding: buffer.readableBytesView, as: UTF8.self)
}

/// Serializes the `MultipartForm` to data.
///
/// let data = try MultipartSerializer().serializeToData(parts: [part], boundary: "123")
/// print(data) // multipart-encoded
///
/// - parameters:
/// - parts: One or more `MultipartPart`s to serialize into `Data`.
/// - boundary: Multipart boundary to use for encoding. This must not appear anywhere in the encoded data.
/// - throws: Any errors that may occur during serialization.
/// - returns: `multipart`-encoded `Data`.
public func serializeToData(parts: [MultipartPart], boundary: String) throws -> Data {
var buffer = ByteBufferAllocator().buffer(capacity: 0)
try self.serialize(parts: parts, boundary: boundary, into: &buffer)
return Data(buffer.readableBytesView)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import NIOFoundationCompat and use Data(buffer: buffer, byteTransferStrategy: .automatic).

And while you're at it, change the String version to use String(buffer: buffer) 🙂

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used ByteBuffer(data: data) instead but assume that’s the same.

}

/// Serializes the `MultipartForm` into a `ByteBuffer`.
///
Expand Down
36 changes: 36 additions & 0 deletions Tests/MultipartKitTests/MultipartTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,42 @@ class MultipartTests: XCTestCase {
}
}

func testFormDataCodingToFromStringWithJPEG() throws {
let folder = #filePath.split(separator: "/").dropLast().joined(separator: "/")
let path = "/" + folder + "/Utilities/image.jpeg"
andrew804 marked this conversation as resolved.
Show resolved Hide resolved
let originalData = try Data(contentsOf: URL(fileURLWithPath: path))

struct ObjectToEncode: Codable {
let data: Data
}

let object = ObjectToEncode(data: originalData)
let boundary = UUID().uuidString

let encoded = try FormDataEncoder().encode(object, boundary: boundary)
let decoded = try FormDataDecoder().decode(ObjectToEncode.self, from: encoded, boundary: boundary)

XCTAssertEqual(originalData, decoded.data)
}

func testFormDataCodingToFromDataWithJPEG() throws {
let folder = #filePath.split(separator: "/").dropLast().joined(separator: "/")
let path = "/" + folder + "/Utilities/image.jpeg"
let originalData = try Data(contentsOf: URL(fileURLWithPath: path))

struct ObjectToEncode: Codable {
let data: Data
}

let object = ObjectToEncode(data: originalData)
let boundary = UUID().uuidString

let encoded = try FormDataEncoder().encodeToData(object, boundary: boundary)
let decoded = try FormDataDecoder().decode(ObjectToEncode.self, from: encoded, boundary: boundary)

XCTAssertEqual(originalData, decoded.data)
}

func testDocBlocks() throws {
do {
/// Content-Type: multipart/form-data; boundary=123
Expand Down
Binary file added Tests/MultipartKitTests/Utilities/image.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading