Skip to content

Commit

Permalink
routes coding
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuawright11 committed May 28, 2024
1 parent 8e98077 commit 5e50e4a
Show file tree
Hide file tree
Showing 30 changed files with 349 additions and 278 deletions.
12 changes: 12 additions & 0 deletions Papyrus/Sources/Coders.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
enum Coders {

// MARK: HTTP Body

static var defaultHTTPBodyEncoder: HTTPBodyEncoder = .json()
static var defaultHTTPBodyDecoder: HTTPBodyDecoder = .json()

// MARK: Query

static var defaultQueryEncoder = URLEncodedFormEncoder()
static var defaultQueryDecoder = URLEncodedFormDecoder()
}
9 changes: 9 additions & 0 deletions Papyrus/Sources/Extensions/String+Multipart.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
extension String {
static func randomMultipartBoundary() -> String {
let first = UInt32.random(in: UInt32.min...UInt32.max)
let second = UInt32.random(in: UInt32.min...UInt32.max)
return String(format: "papyrus.boundary.%08x%08x", first, second)
}

static let crlf = "\r\n"
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,6 @@ extension URLSession: HTTPService {
}
#endif
}

public func request(_ req: Request, completionHandler: @escaping (Response) -> Void) {
let urlRequest = req.urlRequest
dataTask(with: urlRequest) {
completionHandler(_Response(request: urlRequest, response: $1, error: $2, body: $0))
}.resume()
}
}

// MARK: `Response` Conformance
Expand Down
53 changes: 53 additions & 0 deletions Papyrus/Sources/HTTPBodyDecoder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import Foundation

public protocol HTTPBodyDecoder: KeyMappable {
func decode<D: Decodable>(_ type: D.Type, from: Data) throws -> D
}

// MARK: application/json

extension HTTPBodyDecoder where Self == JSONDecoder {
public static func json(_ decoder: JSONDecoder = JSONDecoder()) -> Self {
decoder
}
}

extension JSONDecoder: HTTPBodyDecoder {
public func with(keyMapping: KeyMapping) -> Self {
let new = JSONDecoder()
new.userInfo = userInfo
new.dataDecodingStrategy = dataDecodingStrategy
new.dateDecodingStrategy = dateDecodingStrategy
new.nonConformingFloatDecodingStrategy = nonConformingFloatDecodingStrategy
#if os(Linux)
#else
if #available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) {
new.assumesTopLevelDictionary = assumesTopLevelDictionary
new.allowsJSON5 = allowsJSON5
}
#endif
new.keyDecodingStrategy = keyMapping.jsonDecodingStrategy
return new as! Self
}
}

// MARK: application/x-www-form-urlencoded

extension HTTPBodyDecoder where Self == URLEncodedFormDecoder {
public static func urlForm(_ decoder: URLEncodedFormDecoder = URLEncodedFormDecoder()) -> Self {
decoder
}
}

extension URLEncodedFormDecoder: HTTPBodyDecoder {
public func decode<D: Decodable>(_ type: D.Type, from data: Data) throws -> D {
let string = String(decoding: data, as: UTF8.self)
return try decode(type, from: string)
}

public func with(keyMapping: KeyMapping) -> Self {
var copy = self
copy.keyMapping = keyMapping
return copy
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import Foundation

public protocol RequestEncoder: KeyMappable {
public protocol HTTPBodyEncoder: KeyMappable {
var contentType: String { get }
func encode<E: Encodable>(_ value: E) throws -> Data
}

// MARK: application/json

extension RequestEncoder where Self == JSONEncoder {
public static func json(_ encoder: JSONEncoder) -> Self {
extension HTTPBodyEncoder where Self == JSONEncoder {
public static func json(_ encoder: JSONEncoder = JSONEncoder()) -> Self {
encoder
}
}

extension JSONEncoder: RequestEncoder {
extension JSONEncoder: HTTPBodyEncoder {
public var contentType: String { "application/json" }

public func with(keyMapping: KeyMapping) -> Self {
Expand All @@ -30,13 +30,13 @@ extension JSONEncoder: RequestEncoder {

// MARK: application/x-www-form-urlencoded

extension RequestEncoder where Self == URLEncodedFormEncoder {
public static func urlForm(_ encoder: URLEncodedFormEncoder) -> Self {
extension HTTPBodyEncoder where Self == URLEncodedFormEncoder {
public static func urlForm(_ encoder: URLEncodedFormEncoder = URLEncodedFormEncoder()) -> Self {
encoder
}
}

extension URLEncodedFormEncoder: RequestEncoder {
extension URLEncodedFormEncoder: HTTPBodyEncoder {
public var contentType: String { "application/x-www-form-urlencoded" }

public func encode<E: Encodable>(_ value: E) throws -> Data {
Expand All @@ -57,8 +57,8 @@ extension URLEncodedFormEncoder: RequestEncoder {

// MARK: multipart/form-data

extension RequestEncoder where Self == MultipartEncoder {
public static func multipart(_ encoder: MultipartEncoder) -> Self {
extension HTTPBodyEncoder where Self == MultipartEncoder {
public static func multipart(_ encoder: MultipartEncoder = MultipartEncoder()) -> Self {
encoder
}
}
3 changes: 0 additions & 3 deletions Papyrus/Sources/HTTPService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,4 @@ public protocol HTTPService {

/// Concurrency based API
func request(_ req: Request) async -> Response

/// Callback based API
func request(_ req: Request, completionHandler: @escaping (Response) -> Void)
}
12 changes: 12 additions & 0 deletions Papyrus/Sources/KeyMappable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Foundation

public protocol KeyMappable {
func with(keyMapping: KeyMapping) -> Self
}

extension KeyMappable {
func with(keyMapping: KeyMapping?) -> Self {
guard let keyMapping else { return self }
return with(keyMapping: keyMapping)
}
}
4 changes: 0 additions & 4 deletions Papyrus/Sources/KeyMapping.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import Foundation

public protocol KeyMappable {
func with(keyMapping: KeyMapping) -> Self
}

/// Represents the mapping between your type's property names and
/// their corresponding request field key.
public enum KeyMapping {
Expand Down
12 changes: 6 additions & 6 deletions Papyrus/Sources/Macros.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,22 @@ public macro Mock() = #externalMacro(module: "PapyrusPlugin", type: "MockMacro")
public macro Headers(_ headers: [String: String]) = #externalMacro(module: "PapyrusPlugin", type: "DecoratorMacro")

@attached(peer)
public macro JSON(encoder: JSONEncoder = JSONEncoder(), decoder: JSONDecoder = JSONDecoder()) = #externalMacro(module: "PapyrusPlugin", type: "DecoratorMacro")
public macro KeyMapping(_ mapping: KeyMapping) = #externalMacro(module: "PapyrusPlugin", type: "DecoratorMacro")

@attached(peer)
public macro URLForm(_ encoder: URLEncodedFormEncoder = URLEncodedFormEncoder()) = #externalMacro(module: "PapyrusPlugin", type: "DecoratorMacro")
public macro Authorization(_ value: RequestBuilder.AuthorizationHeader) = #externalMacro(module: "PapyrusPlugin", type: "DecoratorMacro")

@attached(peer)
public macro Multipart(_ encoder: MultipartEncoder = MultipartEncoder()) = #externalMacro(module: "PapyrusPlugin", type: "DecoratorMacro")
public macro JSON(encoder: JSONEncoder = JSONEncoder(), decoder: JSONDecoder = JSONDecoder()) = #externalMacro(module: "PapyrusPlugin", type: "DecoratorMacro")

@attached(peer)
public macro Converter(encoder: RequestEncoder, decoder: ResponseDecoder) = #externalMacro(module: "PapyrusPlugin", type: "DecoratorMacro")
public macro URLForm(_ encoder: URLEncodedFormEncoder = URLEncodedFormEncoder()) = #externalMacro(module: "PapyrusPlugin", type: "DecoratorMacro")

@attached(peer)
public macro KeyMapping(_ mapping: KeyMapping) = #externalMacro(module: "PapyrusPlugin", type: "DecoratorMacro")
public macro Multipart(_ encoder: MultipartEncoder = MultipartEncoder()) = #externalMacro(module: "PapyrusPlugin", type: "DecoratorMacro")

@attached(peer)
public macro Authorization(_ value: RequestBuilder.AuthorizationHeader) = #externalMacro(module: "PapyrusPlugin", type: "DecoratorMacro")
public macro Coder(encoder: HTTPBodyEncoder, decoder: HTTPBodyDecoder) = #externalMacro(module: "PapyrusPlugin", type: "DecoratorMacro")

// MARK: Function attributes

Expand Down
18 changes: 18 additions & 0 deletions Papyrus/Sources/Multipart/MultipartDecoder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Foundation

public struct MultipartDecoder: HTTPBodyDecoder {
public let boundary: String

public init(boundary: String? = nil) {
self.boundary = boundary ?? .randomMultipartBoundary()
}

public func with(keyMapping: KeyMapping) -> MultipartDecoder {
// KeyMapping isn't relevant since each part has already encoded data.
self
}

public func decode<D>(_ type: D.Type, from: Data) throws -> D where D : Decodable {
fatalError("multipart decoding isn't supported, yet")
}
}
11 changes: 2 additions & 9 deletions Papyrus/Sources/Multipart/MultipartEncoder.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import Foundation

public struct MultipartEncoder: RequestEncoder {
public struct MultipartEncoder: HTTPBodyEncoder {
public var contentType: String { "multipart/form-data; boundary=\(boundary)" }
public let boundary: String
private let crlf = "\r\n"

public init(boundary: String? = nil) {
self.boundary = boundary ?? MultipartEncoder.randomBoundary()
self.boundary = boundary ?? .randomMultipartBoundary()
}

public func with(keyMapping: KeyMapping) -> MultipartEncoder {
Expand Down Expand Up @@ -47,11 +47,4 @@ public struct MultipartEncoder: RequestEncoder {
let string = headers.map { "\($0): \($1)\(crlf)" }.sorted().joined() + crlf
return Data(string.utf8)
}

private static func randomBoundary() -> String {
let first = UInt32.random(in: UInt32.min...UInt32.max)
let second = UInt32.random(in: UInt32.min...UInt32.max)

return String(format: "papyrus.boundary.%08x%08x", first, second)
}
}
107 changes: 0 additions & 107 deletions Papyrus/Sources/PapyrusRouter.swift

This file was deleted.

0 comments on commit 5e50e4a

Please sign in to comment.