From a3fb87f86e4a7592fb732dcba0a987887025b01f Mon Sep 17 00:00:00 2001 From: Michael Housh Date: Fri, 30 Jan 2026 17:10:14 -0500 Subject: [PATCH] feat: Removes api routes and controller as they're currently not used. --- Package.swift | 18 -- Sources/ApiController/Interface.swift | 38 --- Sources/ApiController/Live.swift | 129 --------- .../Extensions/ApiController+respond.swift | 74 ++--- .../Middleware/DependenciesMiddleware.swift | 10 +- Sources/App/configure.swift | 5 - Sources/ManualDCore/Routes/ApiRoute.swift | 270 ------------------ Sources/ManualDCore/Routes/SiteRoute.swift | 4 - Sources/Styleguide/Badge.swift | 7 - Sources/Styleguide/Buttons.swift | 26 -- Sources/Styleguide/ElementaryExtensions.swift | 10 - Sources/Styleguide/Icon.swift | 45 --- Sources/Styleguide/Input.swift | 54 ---- Sources/Styleguide/Number.swift | 9 - Tests/ApiRouteTests/ProjectRouteTests.swift | 104 ------- 15 files changed, 42 insertions(+), 761 deletions(-) delete mode 100644 Sources/ApiController/Interface.swift delete mode 100644 Sources/ApiController/Live.swift delete mode 100644 Sources/ManualDCore/Routes/ApiRoute.swift delete mode 100644 Sources/Styleguide/Icon.swift delete mode 100644 Tests/ApiRouteTests/ProjectRouteTests.swift diff --git a/Package.swift b/Package.swift index 17066e6..b277b0d 100644 --- a/Package.swift +++ b/Package.swift @@ -6,7 +6,6 @@ let package = Package( name: "swift-manual-d", products: [ .executable(name: "App", targets: ["App"]), - .library(name: "ApiController", targets: ["ApiController"]), .library(name: "AuthClient", targets: ["AuthClient"]), .library(name: "DatabaseClient", targets: ["DatabaseClient"]), .library(name: "EnvClient", targets: ["EnvClient"]), @@ -38,7 +37,6 @@ let package = Package( .executableTarget( name: "App", dependencies: [ - .target(name: "ApiController"), .target(name: "AuthClient"), .target(name: "DatabaseClient"), .target(name: "ViewController"), @@ -52,22 +50,6 @@ let package = Package( .product(name: "VaporRouting", package: "vapor-routing"), ] ), - .target( - name: "ApiController", - dependencies: [ - .target(name: "DatabaseClient"), - .target(name: "ManualDCore"), - .product(name: "Dependencies", package: "swift-dependencies"), - .product(name: "DependenciesMacros", package: "swift-dependencies"), - .product(name: "Vapor", package: "vapor"), - ] - ), - .testTarget( - name: "ApiRouteTests", - dependencies: [ - .target(name: "ManualDCore") - ] - ), .target( name: "AuthClient", dependencies: [ diff --git a/Sources/ApiController/Interface.swift b/Sources/ApiController/Interface.swift deleted file mode 100644 index 58efd83..0000000 --- a/Sources/ApiController/Interface.swift +++ /dev/null @@ -1,38 +0,0 @@ -import Dependencies -import DependenciesMacros -import Logging -import ManualDCore - -extension DependencyValues { - public var apiController: ApiController { - get { self[ApiController.self] } - set { self[ApiController.self] = newValue } - } -} - -@DependencyClient -public struct ApiController: Sendable { - public var json: @Sendable (Request) async throws -> (any Encodable)? -} - -extension ApiController { - public struct Request: Sendable { - public let route: SiteRoute.Api - public let logger: Logger - - public init(route: SiteRoute.Api, logger: Logger) { - self.route = route - self.logger = logger - } - } -} - -extension ApiController: DependencyKey { - public static let testValue = Self() - - public static let liveValue = Self( - json: { request in - try await request.route.respond(logger: request.logger) - } - ) -} diff --git a/Sources/ApiController/Live.swift b/Sources/ApiController/Live.swift deleted file mode 100644 index 625d798..0000000 --- a/Sources/ApiController/Live.swift +++ /dev/null @@ -1,129 +0,0 @@ -import DatabaseClient -import Dependencies -import Logging -import ManualDCore - -extension SiteRoute.Api { - func respond(logger: Logger) async throws -> (any Encodable)? { - switch self { - case .project(let route): - return try await route.respond(logger: logger) - case .room(let route): - return try await route.respond(logger: logger) - case .equipment(let route): - return try await route.respond(logger: logger) - case .componentLoss(let route): - return try await route.respond(logger: logger) - } - } -} - -extension SiteRoute.Api.ProjectRoute { - - func respond(logger: Logger) async throws -> (any Encodable)? { - @Dependency(\.database) var database - - switch self { - case .create(let request): - // return try await database.projects.create(request) - // FIX: - fatalError() - case .delete(let id): - try await database.projects.delete(id) - return nil - case .detail(let id, let route): - switch route { - case .index: - return try await database.projects.detail(id) - case .completedSteps: - // FIX: - fatalError() - - } - case .get(let id): - guard let project = try await database.projects.get(id) else { - logger.error("Project not found for id: \(id)") - throw ApiError("Project not found.") - } - return project - case .index: - // FIX: Fix to return projects. - return [Project]() - } - } -} - -extension SiteRoute.Api.RoomRoute { - - func respond(logger: Logger) async throws -> (any Encodable)? { - @Dependency(\.database) var database - - switch self { - case .create(let request): - return try await database.rooms.create(request) - case .delete(let id): - try await database.rooms.delete(id) - return nil - case .get(let id): - guard let room = try await database.rooms.get(id) else { - logger.error("Room not found for id: \(id)") - throw ApiError("Room not found.") - } - return room - } - } -} - -extension SiteRoute.Api.EquipmentRoute { - - func respond(logger: Logger) async throws -> (any Encodable)? { - @Dependency(\.database) var database - - switch self { - case .create(let request): - return try await database.equipment.create(request) - case .delete(let id): - try await database.equipment.delete(id) - return nil - case .fetch(let projectID): - return try await database.equipment.fetch(projectID) - case .get(let id): - guard let room = try await database.equipment.get(id) else { - logger.error("Equipment not found for id: \(id)") - throw ApiError("Equipment not found.") - } - return room - } - } -} - -extension SiteRoute.Api.ComponentLossRoute { - - func respond(logger: Logger) async throws -> (any Encodable)? { - @Dependency(\.database) var database - - switch self { - case .create(let request): - return try await database.componentLosses.create(request) - case .delete(let id): - try await database.componentLosses.delete(id) - return nil - case .fetch(let projectID): - return try await database.componentLosses.fetch(projectID) - case .get(let id): - guard let room = try await database.componentLosses.get(id) else { - logger.error("Component loss not found for id: \(id)") - throw ApiError("Component loss not found.") - } - return room - } - } -} - -public struct ApiError: Error { - let message: String - - init(_ message: String) { - self.message = message - } -} diff --git a/Sources/App/Extensions/ApiController+respond.swift b/Sources/App/Extensions/ApiController+respond.swift index e0786aa..15f993d 100644 --- a/Sources/App/Extensions/ApiController+respond.swift +++ b/Sources/App/Extensions/ApiController+respond.swift @@ -1,37 +1,37 @@ -import ApiController -import ManualDCore -import Vapor - -extension ApiController { - - func respond(_ route: SiteRoute.Api, request: Vapor.Request) async throws - -> any AsyncResponseEncodable - { - guard let encodable = try await json(.init(route: route, logger: request.logger)) else { - return HTTPStatus.ok - } - return AnyJSONResponse(value: encodable) - } -} - -struct AnyJSONResponse: AsyncResponseEncodable { - public var headers: HTTPHeaders = ["Content-Type": "application/json"] - let value: any Encodable - - init(additionalHeaders: HTTPHeaders = [:], value: any Encodable) { - if additionalHeaders.contains(name: .contentType) { - self.headers = additionalHeaders - } else { - headers.add(contentsOf: additionalHeaders) - } - self.value = value - } - - func encodeResponse(for request: Request) async throws -> Response { - try Response( - status: .ok, - headers: headers, - body: .init(data: JSONEncoder().encode(value)) - ) - } -} +// import ApiController +// import ManualDCore +// import Vapor +// +// extension ApiController { +// +// func respond(_ route: SiteRoute.Api, request: Vapor.Request) async throws +// -> any AsyncResponseEncodable +// { +// guard let encodable = try await json(.init(route: route, logger: request.logger)) else { +// return HTTPStatus.ok +// } +// return AnyJSONResponse(value: encodable) +// } +// } +// +// struct AnyJSONResponse: AsyncResponseEncodable { +// public var headers: HTTPHeaders = ["Content-Type": "application/json"] +// let value: any Encodable +// +// init(additionalHeaders: HTTPHeaders = [:], value: any Encodable) { +// if additionalHeaders.contains(name: .contentType) { +// self.headers = additionalHeaders +// } else { +// headers.add(contentsOf: additionalHeaders) +// } +// self.value = value +// } +// +// func encodeResponse(for request: Request) async throws -> Response { +// try Response( +// status: .ok, +// headers: headers, +// body: .init(data: JSONEncoder().encode(value)) +// ) +// } +// } diff --git a/Sources/App/Middleware/DependenciesMiddleware.swift b/Sources/App/Middleware/DependenciesMiddleware.swift index 909d159..1d83091 100644 --- a/Sources/App/Middleware/DependenciesMiddleware.swift +++ b/Sources/App/Middleware/DependenciesMiddleware.swift @@ -1,4 +1,4 @@ -import ApiController +// import ApiController import AuthClient import DatabaseClient import Dependencies @@ -12,17 +12,17 @@ import ViewController struct DependenciesMiddleware: AsyncMiddleware { private let values: DependencyValues.Continuation - private let apiController: ApiController + // private let apiController: ApiController private let database: DatabaseClient private let viewController: ViewController init( database: DatabaseClient, - apiController: ApiController = .liveValue, + // apiController: ApiController = .liveValue, viewController: ViewController = .liveValue ) { self.values = withEscapedDependencies { $0 } - self.apiController = apiController + // self.apiController = apiController self.database = database self.viewController = viewController } @@ -30,7 +30,7 @@ struct DependenciesMiddleware: AsyncMiddleware { func respond(to request: Request, chainingTo next: any AsyncResponder) async throws -> Response { try await values.yield { try await withDependencies { - $0.apiController = apiController + // $0.apiController = apiController $0.auth = .live(on: request) $0.database = database // $0.dateFormatter = .liveValue diff --git a/Sources/App/configure.swift b/Sources/App/configure.swift index 06f5041..7241f2b 100644 --- a/Sources/App/configure.swift +++ b/Sources/App/configure.swift @@ -100,8 +100,6 @@ extension SiteRoute { fileprivate func middleware() -> [any Middleware]? { switch self { - case .api: - return nil case .health: return nil case .view(let route): @@ -117,13 +115,10 @@ private func siteHandler( request: Request, route: SiteRoute ) async throws -> any AsyncResponseEncodable { - @Dependency(\.apiController) var apiController @Dependency(\.viewController) var viewController @Dependency(\.projectClient) var projectClient switch route { - case .api(let route): - return try await apiController.respond(route, request: request) case .health: return HTTPStatus.ok // Generating a pdf return's a `Response` instead of `HTML` like other views, so we diff --git a/Sources/ManualDCore/Routes/ApiRoute.swift b/Sources/ManualDCore/Routes/ApiRoute.swift deleted file mode 100644 index e99a1e1..0000000 --- a/Sources/ManualDCore/Routes/ApiRoute.swift +++ /dev/null @@ -1,270 +0,0 @@ -import CasePathsCore -import Foundation -@preconcurrency import URLRouting - -extension SiteRoute { - /// Represents api routes. - /// - /// The routes return json as opposed to view routes that return html. - public enum Api: Sendable, Equatable { - - case project(Self.ProjectRoute) - case room(Self.RoomRoute) - case equipment(Self.EquipmentRoute) - case componentLoss(Self.ComponentLossRoute) - - public static let rootPath = Path { - "api" - "v1" - } - - public static let router = OneOf { - Route(.case(Self.project)) { - rootPath - ProjectRoute.router - } - Route(.case(Self.room)) { - rootPath - RoomRoute.router - } - Route(.case(Self.equipment)) { - rootPath - EquipmentRoute.router - } - Route(.case(Self.componentLoss)) { - rootPath - ComponentLossRoute.router - } - } - - } - -} - -extension SiteRoute.Api { - public enum ProjectRoute: Sendable, Equatable { - case create(Project.Create) - case delete(id: Project.ID) - case detail(id: Project.ID, route: DetailRoute) - case get(id: Project.ID) - case index - - static let rootPath = "projects" - - public static let router = OneOf { - Route(.case(Self.create)) { - Path { rootPath } - Method.post - Body(.json(Project.Create.self)) - } - Route(.case(Self.delete(id:))) { - Path { - rootPath - Project.ID.parser() - } - Method.delete - } - Route(.case(Self.get(id:))) { - Path { - rootPath - Project.ID.parser() - } - Method.get - } - Route(.case(Self.index)) { - Path { rootPath } - Method.get - } - Route(.case(Self.detail(id:route:))) { - Path { - rootPath - Project.ID.parser() - } - DetailRoute.router - } - } - } -} - -extension SiteRoute.Api.ProjectRoute { - public enum DetailRoute: Equatable, Sendable { - case index - case completedSteps - - static let rootPath = "details" - - static let router = OneOf { - Route(.case(Self.index)) { - Path { rootPath } - Method.get - } - Route(.case(Self.completedSteps)) { - Path { - rootPath - "completed" - } - Method.get - } - } - } -} - -extension SiteRoute.Api { - - public enum RoomRoute: Sendable, Equatable { - case create(Room.Create) - case delete(id: Room.ID) - case get(id: Room.ID) - - static let rootPath = "rooms" - - public static let router = OneOf { - Route(.case(Self.create)) { - Path { rootPath } - Method.post - Body(.json(Room.Create.self)) - } - Route(.case(Self.delete(id:))) { - Path { - rootPath - Room.ID.parser() - } - Method.delete - } - Route(.case(Self.get(id:))) { - Path { - rootPath - Room.ID.parser() - } - Method.get - } - } - } -} - -extension SiteRoute.Api { - - public enum EquipmentRoute: Sendable, Equatable { - case create(EquipmentInfo.Create) - case delete(id: EquipmentInfo.ID) - case fetch(projectID: Project.ID) - case get(id: EquipmentInfo.ID) - - static let rootPath = "equipment" - - public static let router = OneOf { - Route(.case(Self.create)) { - Path { rootPath } - Method.post - Body(.json(EquipmentInfo.Create.self)) - } - Route(.case(Self.delete(id:))) { - Path { - rootPath - EquipmentInfo.ID.parser() - } - Method.delete - } - Route(.case(Self.fetch(projectID:))) { - Path { rootPath } - Method.get - Query { - Field("projectID") { Project.ID.parser() } - } - } - Route(.case(Self.get(id:))) { - Path { - rootPath - EquipmentInfo.ID.parser() - } - Method.get - } - } - } -} - -extension SiteRoute.Api { - - public enum ComponentLossRoute: Sendable, Equatable { - case create(ComponentPressureLoss.Create) - case delete(id: ComponentPressureLoss.ID) - case fetch(projectID: Project.ID) - case get(id: ComponentPressureLoss.ID) - - static let rootPath = "componentLoss" - - public static let router = OneOf { - Route(.case(Self.create)) { - Path { rootPath } - Method.post - Body(.json(ComponentPressureLoss.Create.self)) - } - Route(.case(Self.delete(id:))) { - Path { - rootPath - ComponentPressureLoss.ID.parser() - } - Method.delete - } - Route(.case(Self.fetch(projectID:))) { - Path { rootPath } - Method.get - Query { - Field("projectID") { Project.ID.parser() } - } - } - Route(.case(Self.get(id:))) { - Path { - rootPath - ComponentPressureLoss.ID.parser() - } - Method.get - } - } - } -} - -extension SiteRoute.Api { - public enum EffectiveLengthRoute: Equatable, Sendable { - case create(EquivalentLength.Create) - case delete(id: EquivalentLength.ID) - case fetch(projectID: Project.ID) - case get(id: EquivalentLength.ID) - - static let rootPath = "effectiveLength" - - public static let router = OneOf { - Route(.case(Self.create)) { - Path { - rootPath - "create" - } - Method.post - Body(.json(EquivalentLength.Create.self)) - } - Route(.case(Self.delete(id:))) { - Path { - rootPath - EquivalentLength.ID.parser() - } - Method.delete - } - Route(.case(Self.fetch(projectID:))) { - Path { - rootPath - } - Method.get - Query { - Field("projectID") { Project.ID.parser() } - } - } - Route(.case(Self.get(id:))) { - Path { - rootPath - EquivalentLength.ID.parser() - } - Method.get - } - } - } -} diff --git a/Sources/ManualDCore/Routes/SiteRoute.swift b/Sources/ManualDCore/Routes/SiteRoute.swift index f3a8b91..009a209 100644 --- a/Sources/ManualDCore/Routes/SiteRoute.swift +++ b/Sources/ManualDCore/Routes/SiteRoute.swift @@ -5,14 +5,10 @@ import Foundation public enum SiteRoute: Equatable, Sendable { - case api(Self.Api) case health case view(Self.View) public static let router = OneOf { - Route(.case(Self.api)) { - SiteRoute.Api.router - } Route(.case(Self.health)) { Path { "health" } Method.get diff --git a/Sources/Styleguide/Badge.swift b/Sources/Styleguide/Badge.swift index b1016c8..498d7bd 100644 --- a/Sources/Styleguide/Badge.swift +++ b/Sources/Styleguide/Badge.swift @@ -27,11 +27,4 @@ extension Badge where Inner == Number { self.inner = Number(number, digits: digits) } - public init(number: Tagged) { - self.inner = Number(number.rawValue) - } - - public init(number: Tagged, digits: Int = 2) { - self.inner = Number(number.rawValue, digits: digits) - } } diff --git a/Sources/Styleguide/Buttons.swift b/Sources/Styleguide/Buttons.swift index c5914d2..f421bf7 100644 --- a/Sources/Styleguide/Buttons.swift +++ b/Sources/Styleguide/Buttons.swift @@ -26,32 +26,6 @@ public struct SubmitButton: HTML, Sendable { } } -public struct CancelButton: HTML, Sendable { - let title: String - let type: HTMLAttribute.ButtonType - - public init( - title: String = "Cancel", - type: HTMLAttribute.ButtonType = .button - ) { - self.title = title - self.type = type - } - - public var body: some HTML { - button( - .class( - """ - text-white font-bold text-xl bg-red-500 hover:bg-red-600 px-4 py-2 rounded-lg shadow-lg - """ - ), - .type(type) - ) { - title - } - } -} - public struct EditButton: HTML, Sendable { let title: String? let type: HTMLAttribute.ButtonType diff --git a/Sources/Styleguide/ElementaryExtensions.swift b/Sources/Styleguide/ElementaryExtensions.swift index 9744b1c..ec0f5f1 100644 --- a/Sources/Styleguide/ElementaryExtensions.swift +++ b/Sources/Styleguide/ElementaryExtensions.swift @@ -9,13 +9,6 @@ extension HTMLAttribute where Tag: HTMLTrait.Attributes.href { } } -extension HTMLAttribute where Tag == HTMLTag.form { - - public static func action(route: SiteRoute.View) -> Self { - action(SiteRoute.View.router.path(for: route)) - } -} - extension HTMLAttribute where Tag == HTMLTag.input { public static func value(_ string: String?) -> Self { @@ -42,9 +35,6 @@ extension HTMLAttribute where Tag == HTMLTag.button { } extension HTML where Tag: HTMLTrait.Attributes.Global { - public func badge() -> _AttributedElement { - attributes(.class("badge badge-lg badge-outline")) - } public func hidden(when shouldHide: Bool) -> _AttributedElement { attributes(.class("hidden"), when: shouldHide) diff --git a/Sources/Styleguide/Icon.swift b/Sources/Styleguide/Icon.swift deleted file mode 100644 index 2d816c7..0000000 --- a/Sources/Styleguide/Icon.swift +++ /dev/null @@ -1,45 +0,0 @@ -import Elementary - -// TODO: Remove, using svg's. -public struct Icon: HTML, Sendable { - - let icon: String - - public init(icon: String) { - self.icon = icon - } - - public var body: some HTML { - i(.data("lucide", value: icon)) {} - } -} - -extension Icon { - - public init(_ icon: Key) { - self.init(icon: icon.icon) - } - - public enum Key: String { - - case circlePlus - case close - case doorClosed - case mapPin - case rulerDimensionLine - case squareFunction - case wind - - var icon: String { - switch self { - case .circlePlus: return "circle-plus" - case .close: return "x" - case .doorClosed: return "door-closed" - case .mapPin: return "map-pin" - case .rulerDimensionLine: return "ruler-dimension-line" - case .squareFunction: return "square-function" - case .wind: return rawValue - } - } - } -} diff --git a/Sources/Styleguide/Input.swift b/Sources/Styleguide/Input.swift index 9ba876e..0357b9f 100644 --- a/Sources/Styleguide/Input.swift +++ b/Sources/Styleguide/Input.swift @@ -21,60 +21,6 @@ public struct LabeledInput: HTML, Sendable { } } -public struct Input: HTML, Sendable { - - let id: String? - let name: String? - let placeholder: String - - private var _name: String { - guard let name else { - return id ?? "" - } - return name - } - - init( - id: String? = nil, - name: String? = nil, - placeholder: String - ) { - self.id = id - self.name = name - self.placeholder = placeholder - } - - public init( - id: String, - name: String? = nil, - placeholder: String - ) { - self.id = id - self.name = name - self.placeholder = placeholder - } - - public init( - name: String, - placeholder: String - ) { - self.init(id: nil, name: name, placeholder: placeholder) - } - - public var body: some HTML { - input( - .id(id ?? ""), .name(_name), .placeholder(placeholder), - .class( - """ - input w-full rounded-md bg-white px-3 py-1.5 text-slate-900 outline-1 - -outline-offset-1 outline-slate-300 focus:outline focus:-outline-offset-2 - focus:outline-indigo-600 invalid:border-red-500 out-of-range:border-red-500 - """ - ) - ) - } -} - extension HTMLAttribute where Tag == HTMLTag.input { public static func max(_ value: String) -> Self { diff --git a/Sources/Styleguide/Number.swift b/Sources/Styleguide/Number.swift index ecc466a..691e733 100644 --- a/Sources/Styleguide/Number.swift +++ b/Sources/Styleguide/Number.swift @@ -6,15 +6,6 @@ public struct Number: HTML, Sendable { let fractionDigits: Int let value: Double - // private var formatter: NumberFormatter { - // let formatter = NumberFormatter() - // formatter.maximumFractionDigits = fractionDigits - // formatter.numberStyle = .decimal - // formatter.groupingSize = 3 - // formatter.groupingSeparator = "," - // return formatter - // } - public init( _ value: Double, digits fractionDigits: Int = 2 diff --git a/Tests/ApiRouteTests/ProjectRouteTests.swift b/Tests/ApiRouteTests/ProjectRouteTests.swift deleted file mode 100644 index 25d3fb0..0000000 --- a/Tests/ApiRouteTests/ProjectRouteTests.swift +++ /dev/null @@ -1,104 +0,0 @@ -import Dependencies -import Foundation -import ManualDCore -import Testing -import URLRouting - -@Suite("ProjectRouteTests") -struct ProjectRouteTests { - let router = SiteRoute.Api.router - - @Test - func create() throws { - let json = """ - { - \"name\": \"Test\", - \"streetAddress\": \"1234 Seasme Street\", - \"city\": \"Nowhere\", - \"state\": \"OH\", - \"zipCode\": \"55555\" - } - """ - var request = URLRequestData( - method: "POST", - path: "/api/v1/projects", - body: .init(json.utf8) - ) - let route = try router.parse(&request) - #expect( - route - == .project( - .create( - .init( - name: "Test", - streetAddress: "1234 Seasme Street", - city: "Nowhere", - state: "OH", - zipCode: "55555" - ) - ) - ) - ) - } - - @Test - func delete() throws { - let id = UUID(0) - var request = URLRequestData( - method: "DELETE", - path: "/api/v1/projects/\(id)" - ) - let route = try router.parse(&request) - #expect(route == .project(.delete(id: id))) - } - - @Test - func get() throws { - let id = UUID(0) - var request = URLRequestData( - method: "GET", - path: "/api/v1/projects/\(id)" - ) - let route = try router.parse(&request) - #expect(route == .project(.get(id: id))) - } - - @Test - func index() throws { - var request = URLRequestData( - method: "GET", - path: "/api/v1/projects" - ) - let route = try router.parse(&request) - #expect(route == .project(.index)) - } - - @Test - func formData() throws { - let p = Body { - FormData { - Optionally { - Field("id", default: nil) { EquivalentLength.ID.parser() } - } - Field("name", .string) - Field("type") { EquivalentLength.EffectiveLengthType.parser() } - Many { - Field("straightLengths") { - Int.parser() - } - } - } - .map(.memberwise(SiteRoute.View.ProjectRoute.EquivalentLengthRoute.StepTwo.init)) - } - - var request = URLRequestData( - body: .init( - "name=Test&type=supply&straightLengths=20&straightLengths=10" - .utf8 - ) - ) - let value = try p.parse(&request) - print(value) - #expect(value.straightLengths == [20, 10]) - } -}