diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6e8f217..54c63f7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,16 +9,15 @@ on: jobs: build_and_test: - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest] defaults: run: working-directory: example steps: - - uses: swift-actions/setup-swift@v1 - - uses: actions/setup-node@v3 - with: - node-version: 20 - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: npm ci working-directory: example/TSClient - run: ./test.sh vapor diff --git a/Package.resolved b/Package.resolved index fbed636..56713c3 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,24 @@ { + "originHash" : "47b101acb5dce72e2df300bb54213e799129143e7ff1f087c6474d20f149c22f", "pins" : [ + { + "identity" : "async-http-client", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swift-server/async-http-client.git", + "state" : { + "revision" : "2119f0d9cc1b334e25447fe43d3693c0e60e6234", + "version" : "1.24.0" + } + }, + { + "identity" : "async-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/async-kit.git", + "state" : { + "revision" : "e048c8ee94967e8d8a1c2ec0e1156d6f7fa34d31", + "version" : "1.20.0" + } + }, { "identity" : "codabletotypescript", "kind" : "remoteSourceControl", @@ -9,6 +28,51 @@ "version" : "2.11.0" } }, + { + "identity" : "console-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/console-kit.git", + "state" : { + "revision" : "966d89ae64cd71c652a1e981bc971de59d64f13d", + "version" : "4.15.1" + } + }, + { + "identity" : "hummingbird", + "kind" : "remoteSourceControl", + "location" : "https://github.com/hummingbird-project/hummingbird.git", + "state" : { + "revision" : "70b0714b4c4a192a51a9ddcc691fcf2ab3bc9141", + "version" : "2.5.0" + } + }, + { + "identity" : "multipart-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/multipart-kit.git", + "state" : { + "revision" : "a31236f24bfd2ea2f520a74575881f6731d7ae68", + "version" : "4.7.0" + } + }, + { + "identity" : "routing-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/routing-kit.git", + "state" : { + "revision" : "8c9a227476555c55837e569be71944e02a056b72", + "version" : "4.9.1" + } + }, + { + "identity" : "swift-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-algorithms.git", + "state" : { + "revision" : "f6919dfc309e7f1b56224378b11e28bab5bccc42", + "version" : "1.2.0" + } + }, { "identity" : "swift-argument-parser", "kind" : "remoteSourceControl", @@ -18,22 +82,175 @@ "version" : "1.2.2" } }, + { + "identity" : "swift-asn1", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-asn1.git", + "state" : { + "revision" : "7faebca1ea4f9aaf0cda1cef7c43aecd2311ddf6", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-async-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-async-algorithms.git", + "state" : { + "revision" : "5c8bd186f48c16af0775972700626f0b74588278", + "version" : "1.0.2" + } + }, + { + "identity" : "swift-atomics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-atomics.git", + "state" : { + "revision" : "cd142fd2f64be2100422d658e7411e39489da985", + "version" : "1.2.0" + } + }, { "identity" : "swift-collections", "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-collections", "state" : { - "revision" : "937e904258d22af6e447a0b72c0bc67583ef64a2", - "version" : "1.0.4" + "revision" : "671108c96644956dddcd89dd59c203dcdb36cec7", + "version" : "1.1.4" + } + }, + { + "identity" : "swift-crypto", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-crypto.git", + "state" : { + "revision" : "ff0f781cf7c6a22d52957e50b104f5768b50c779", + "version" : "3.10.0" + } + }, + { + "identity" : "swift-distributed-tracing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-distributed-tracing.git", + "state" : { + "revision" : "6483d340853a944c96dbcc28b27dd10b6c581703", + "version" : "1.1.2" + } + }, + { + "identity" : "swift-http-types", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-http-types.git", + "state" : { + "revision" : "ef18d829e8b92d731ad27bb81583edd2094d1ce3", + "version" : "1.3.1" + } + }, + { + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log.git", + "state" : { + "revision" : "96a2f8a0fa41e9e09af4585e2724c4e825410b91", + "version" : "1.6.2" + } + }, + { + "identity" : "swift-metrics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-metrics.git", + "state" : { + "revision" : "e0165b53d49b413dd987526b641e05e246782685", + "version" : "2.5.0" + } + }, + { + "identity" : "swift-nio", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio.git", + "state" : { + "revision" : "dca6594f65308c761a9c409e09fbf35f48d50d34", + "version" : "2.77.0" + } + }, + { + "identity" : "swift-nio-extras", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-extras.git", + "state" : { + "revision" : "2e9746cfc57554f70b650b021b6ae4738abef3e6", + "version" : "1.24.1" + } + }, + { + "identity" : "swift-nio-http2", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-http2.git", + "state" : { + "revision" : "eaa71bb6ae082eee5a07407b1ad0cbd8f48f9dca", + "version" : "1.34.1" + } + }, + { + "identity" : "swift-nio-ssl", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-ssl.git", + "state" : { + "revision" : "c7e95421334b1068490b5d41314a50e70bab23d1", + "version" : "2.29.0" + } + }, + { + "identity" : "swift-nio-transport-services", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-transport-services.git", + "state" : { + "revision" : "bbd5e63cf949b7db0c9edaf7a21e141c52afe214", + "version" : "1.23.0" + } + }, + { + "identity" : "swift-numerics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-numerics.git", + "state" : { + "revision" : "0a5bc04095a675662cf24757cc0640aa2204253b", + "version" : "1.0.2" + } + }, + { + "identity" : "swift-service-context", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-service-context.git", + "state" : { + "revision" : "0c62c5b4601d6c125050b5c3a97f20cce881d32b", + "version" : "1.1.0" + } + }, + { + "identity" : "swift-service-lifecycle", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swift-server/swift-service-lifecycle.git", + "state" : { + "revision" : "f70b838872863396a25694d8b19fe58bcd0b7903", + "version" : "2.6.2" } }, { "identity" : "swift-syntax", "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-syntax", + "location" : "https://github.com/swiftlang/swift-syntax.git", + "state" : { + "revision" : "0687f71944021d616d34d922343dcef086855920", + "version" : "600.0.1" + } + }, + { + "identity" : "swift-system", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-system.git", "state" : { - "revision" : "fa8f95c2d536d6620cc2f504ebe8a6167c9fc2dd", - "version" : "510.0.1" + "revision" : "c8a44d836fe7913603e246acab7c528c2e780168", + "version" : "1.4.0" } }, { @@ -53,7 +270,25 @@ "revision" : "c64e4a060a3daa9f8c964699ca4d8210235df20b", "version" : "1.8.7" } + }, + { + "identity" : "vapor", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/vapor.git", + "state" : { + "revision" : "3aeeb6fab5112fb10ce699b5a80207e5cf571c32", + "version" : "4.106.7" + } + }, + { + "identity" : "websocket-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/websocket-kit.git", + "state" : { + "revision" : "4232d34efa49f633ba61afde365d3896fc7f8740", + "version" : "2.15.0" + } } ], - "version" : 2 + "version" : 3 } diff --git a/Package.swift b/Package.swift index 9b1af8a..eba8dc8 100644 --- a/Package.swift +++ b/Package.swift @@ -1,36 +1,70 @@ -// swift-tools-version: 5.9 +// swift-tools-version: 5.10 +import CompilerPluginSupport import PackageDescription let package = Package( name: "CallableKit", - platforms: [.macOS(.v13)], + platforms: [.macOS(.v14)], products: [ .executable(name: "codegen", targets: ["Codegen"]), .library(name: "CallableKit", targets: ["CallableKit"]), + .library(name: "CallableKitVaporTransport", targets: ["CallableKitVaporTransport"]), + .library(name: "CallableKitHummingbirdTransport", targets: ["CallableKitHummingbirdTransport"]), ], dependencies: [ .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.2"), - .package(url: "https://github.com/omochi/CodableToTypeScript", from: "2.11.0"), - .package(url: "https://github.com/omochi/SwiftTypeReader", from: "2.8.0"), + .package(url: "https://github.com/swiftlang/swift-syntax.git", from: "600.0.1"), + .package(url: "https://github.com/omochi/CodableToTypeScript.git", from: "2.11.0"), + .package(url: "https://github.com/omochi/SwiftTypeReader.git", from: "2.8.0"), + .package(url: "https://github.com/vapor/vapor.git", from: "4.106.7"), + .package(url: "https://github.com/hummingbird-project/hummingbird.git", from: "2.5.0"), ], targets: [ .executableTarget( name: "Codegen", dependencies: [ .product(name: "ArgumentParser", package: "swift-argument-parser"), - "CallableKit" + "CodegenImpl", ] ), .target( - name: "CallableKit", + name: "CodegenImpl", dependencies: [ "CodableToTypeScript", - "SwiftTypeReader" + "SwiftTypeReader", ], swiftSettings: [ .enableUpcomingFeature("BareSlashRegexLiterals"), ] + ), + .target( + name: "CallableKit", + dependencies: [ + "CallableKitMacros", + ] + ), + .macro( + name: "CallableKitMacros", + dependencies: [ + .product(name: "SwiftCompilerPlugin", package: "swift-syntax"), + .product(name: "SwiftDiagnostics", package: "swift-syntax"), + .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), + ] + ), + .target( + name: "CallableKitVaporTransport", + dependencies: [ + .product(name: "Vapor", package: "vapor"), + "CallableKit", + ] + ), + .target( + name: "CallableKitHummingbirdTransport", + dependencies: [ + .product(name: "Hummingbird", package: "hummingbird"), + "CallableKit", + ] ) ] ) diff --git a/Sources/CallableKit/GenerateHBProvider.swift b/Sources/CallableKit/GenerateHBProvider.swift deleted file mode 100644 index 4684cc7..0000000 --- a/Sources/CallableKit/GenerateHBProvider.swift +++ /dev/null @@ -1,204 +0,0 @@ -import Foundation -import SwiftTypeReader - -struct GenerateHBProvider { - var definitionModule: String - var srcDirectory: URL - var dstDirectory: URL - var dependencies: [URL] - - private func generateHBToServiceBridgeProtocol() -> String { - """ -import Hummingbird - -protocol HBToServiceBridgeProtocol { - func makeHandler( - _ serviceBuilder: @Sendable @escaping (HBRequest) async throws -> Service, - _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> (Req) async throws -> Res - ) -> @Sendable (HBRequest) async throws -> HBResponse - where Req: Decodable & Sendable, Res: Encodable & Sendable - - func makeHandler( - _ serviceBuilder: @Sendable @escaping (HBRequest) async throws -> Service, - _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> () async throws -> Res - ) -> @Sendable (HBRequest) async throws -> HBResponse - where Res: Encodable & Sendable - - func makeHandler( - _ serviceBuilder: @Sendable @escaping (HBRequest) async throws -> Service, - _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> (Req) async throws -> Void - ) -> @Sendable (HBRequest) async throws -> HBResponse - where Req: Decodable & Sendable - - func makeHandler( - _ serviceBuilder: @Sendable @escaping (HBRequest) async throws -> Service, - _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> () async throws -> Void - ) -> @Sendable (HBRequest) async throws -> HBResponse -} - -private struct _Empty: Codable, Sendable {} - -extension HBToServiceBridgeProtocol { - func makeHandler( - _ serviceBuilder: @Sendable @escaping (HBRequest) async throws -> Service, - _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> () async throws -> Res - ) -> @Sendable (HBRequest) async throws -> HBResponse - where Res: Encodable & Sendable - { - makeHandler(serviceBuilder) { (serviceType: Service.Type) in - { (service: Service) in - { (_: _Empty) -> Res in - try await methodSelector(serviceType)(service)() - } - } - } - } - - func makeHandler( - _ serviceBuilder: @Sendable @escaping (HBRequest) async throws -> Service, - _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> (Req) async throws -> Void - ) -> @Sendable (HBRequest) async throws -> HBResponse - where Req: Decodable & Sendable - { - makeHandler(serviceBuilder) { (serviceType: Service.Type) in - { (service: Service) in - { (req: Req) -> _Empty in - try await methodSelector(serviceType)(service)(req) - return _Empty() - } - } - } - } - - func makeHandler( - _ serviceBuilder: @Sendable @escaping (HBRequest) async throws -> Service, - _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> () async throws -> Void - ) -> @Sendable (HBRequest) async throws -> HBResponse - { - makeHandler(serviceBuilder) { (serviceType: Service.Type) in - { (service: Service) in - { (_: _Empty) -> _Empty in - try await methodSelector(serviceType)(service)() - return _Empty() - } - } - } - } -} - -""" - } - - private func generateHBJSONServiceBridge() -> String { -""" -import Foundation -import NIOCore -import NIOFoundationCompat -import Hummingbird - -extension HBToServiceBridgeProtocol where Self == HBJSONServiceBridge { - static var `default`: HBJSONServiceBridge { HBJSONServiceBridge() } -} - -struct HBJSONServiceBridge: HBToServiceBridgeProtocol { - func makeHandler( - _ serviceBuilder: @Sendable @escaping (HBRequest) async throws -> Service, - _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> (Req) async throws -> Res - ) -> @Sendable (HBRequest) async throws -> HBResponse - where Req: Decodable & Sendable, Res: Encodable & Sendable - { - HBJSONServiceHandler(serviceBuilder: serviceBuilder, methodSelector: methodSelector) - .callAsFunction(request:) - } -} - -private struct HBJSONServiceHandler: Sendable where Req: Decodable & Sendable, Res: Encodable & Sendable { - var serviceBuilder: @Sendable (HBRequest) async throws -> Service - var methodSelector: @Sendable (Service.Type) -> (Service) -> (Req) async throws -> Res - - @Sendable func callAsFunction(request: HBRequest) async throws -> HBResponse { - let service = try await serviceBuilder(request) - guard let body = request.body.buffer else { - throw HBHTTPError(.badRequest, message: "no body") - } - let rpcRequest = try makeDecoder().decode(Req.self, from: body) - let rpcResponse = try await methodSelector(Service.self)(service)(rpcRequest) - return try makeResponse(status: .ok, body: rpcResponse) - } - - private func makeResponse(status: HTTPResponseStatus, body: R) throws -> HBResponse { - let body = try makeEncoder().encodeAsByteBuffer(body, allocator: .init()) - let headers = HTTPHeaders([ - ("Content-Type", "application/json"), - ("Cache-Control", "no-store"), - ]) - return HBResponse(status: status, headers: headers, body: .byteBuffer(body)) - } -} - -private func makeDecoder() -> JSONDecoder { - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .millisecondsSince1970 - return decoder -} - -private func makeEncoder() -> JSONEncoder { - let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .millisecondsSince1970 - return encoder -} - -""" - } - - private func processFile(file: Generator.InputFile) throws -> String? { - var providers: [String] = [] - for stype in file.types.compactMap(ServiceProtocolScanner.scan) { - providers.append(""" -func configure\(stype.serviceName)Service( - bridge: some HBToServiceBridgeProtocol = .default, - builder serviceBuilder: @escaping @Sendable (HBRequest) async throws -> some \(stype.name), - router: some HBRouterMethods -) { -\(stype.functions.map { """ - router.post("\(stype.serviceName)/\($0.name)", use: bridge.makeHandler(serviceBuilder, { $0.\($0.name) })) -""" }.joined(separator: "\n")) -} - -""") - } - if providers.isEmpty { return nil } - - return """ -import \(definitionModule) -import Hummingbird - -\(providers.joined()) -""" - } - - func run() throws { - var g = Generator(definitionModule: definitionModule, srcDirectory: srcDirectory, dstDirectory: dstDirectory, dependencies: dependencies) - g.isOutputFileName = { $0.hasSuffix(".gen.swift") } - - try g.run { input, write in - try write(file: .init( - name: "HBToServiceBridgeProtocol.gen.swift", - content: generateHBToServiceBridgeProtocol() - )) - try write(file: .init( - name: "HBJSONServiceBridge.gen.swift", - content: generateHBJSONServiceBridge() - )) - - for inputFile in input.files { - guard let generated = try processFile(file: inputFile) else { continue } - let outputFile = URL(fileURLWithPath: inputFile.file.lastPathComponent.replacingOccurrences(of: ".swift", with: ".gen.swift")).lastPathComponent - try write(file: .init( - name: outputFile, - content: generated - )) - } - } - } -} diff --git a/Sources/CallableKit/GenerateMiddleware.swift b/Sources/CallableKit/GenerateMiddleware.swift deleted file mode 100644 index e1bee07..0000000 --- a/Sources/CallableKit/GenerateMiddleware.swift +++ /dev/null @@ -1,58 +0,0 @@ -import Foundation -import SwiftTypeReader - -struct GenerateMiddleware { - var definitionModule: String - var srcDirectory: URL - var dstDirectory: URL - var dependencies: [URL] - - private func processFile(file: Generator.InputFile) throws -> String? { - var providers: [String] = [] - for stype in file.types.compactMap(ServiceProtocolScanner.scan) { - providers.append(""" -protocol \(stype.serviceName)ServiceMiddlewareProtocol: \(stype.serviceName)ServiceProtocol { - associatedtype Output - associatedtype Next: \(stype.serviceName)ServiceProtocol - func prepare() async throws -> Output - var next: (Output) -> Next { get } -} - -extension \(stype.serviceName)ServiceMiddlewareProtocol { -\(stype.functions.map { f in - let req = f.request.map { "\($0.argName): \($0.typeName)" } ?? "" - let res = f.response.map { " -> \($0.typeName)" } ?? "" - return """ - func \(f.name)(\(req)) async throws\(res) { - try await next(try await prepare()).\(f.name)(\(f.request.map { "\($0.argName): \($0.argName)" } ?? "")) - } -""" }.joined(separator: "\n")) -} - -""") - } - if providers.isEmpty { return nil } - - return """ -import \(definitionModule) - -\(providers.joined()) -""" - } - - func run() throws { - var g = Generator(definitionModule: definitionModule, srcDirectory: srcDirectory, dstDirectory: dstDirectory, dependencies: dependencies) - g.isOutputFileName = { $0.hasSuffix(".gen.swift") } - - try g.run { input, write in - for inputFile in input.files { - guard let generated = try processFile(file: inputFile) else { continue } - let outputFile = URL(fileURLWithPath: inputFile.file.lastPathComponent.replacingOccurrences(of: ".swift", with: "Middleware.gen.swift")).lastPathComponent - try write(file: .init( - name: outputFile, - content: generated - )) - } - } - } -} diff --git a/Sources/CallableKit/GenerateVaporProvider.swift b/Sources/CallableKit/GenerateVaporProvider.swift deleted file mode 100644 index b96e337..0000000 --- a/Sources/CallableKit/GenerateVaporProvider.swift +++ /dev/null @@ -1,208 +0,0 @@ -import Foundation -import SwiftTypeReader - -struct GenerateVaporProvider { - var definitionModule: String - var srcDirectory: URL - var dstDirectory: URL - var dependencies: [URL] - - private func generateVaporToServiceBridgeProtocol() -> String { - """ -import Vapor - -protocol VaporToServiceBridgeProtocol { - func makeHandler( - _ serviceBuilder: @Sendable @escaping (Request) async throws -> Service, - _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> (Req) async throws -> Res - ) -> @Sendable (Request) async throws -> Response - where Req: Decodable & Sendable, Res: Encodable & Sendable - - func makeHandler( - _ serviceBuilder: @Sendable @escaping (Request) async throws -> Service, - _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> () async throws -> Res - ) -> @Sendable (Request) async throws -> Response - where Res: Encodable & Sendable - - func makeHandler( - _ serviceBuilder: @Sendable @escaping (Request) async throws -> Service, - _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> (Req) async throws -> Void - ) -> @Sendable (Request) async throws -> Response - where Req: Decodable & Sendable - - func makeHandler( - _ serviceBuilder: @Sendable @escaping (Request) async throws -> Service, - _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> () async throws -> Void - ) -> @Sendable (Request) async throws -> Response -} - -private struct _Empty: Codable, Sendable {} - -extension VaporToServiceBridgeProtocol { - func makeHandler( - _ serviceBuilder: @Sendable @escaping (Request) async throws -> Service, - _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> () async throws -> Res - ) -> @Sendable (Request) async throws -> Response - where Res: Encodable & Sendable - { - makeHandler(serviceBuilder) { (serviceType: Service.Type) in - { (service: Service) in - { (_: _Empty) -> Res in - try await methodSelector(serviceType)(service)() - } - } - } - } - - func makeHandler( - _ serviceBuilder: @Sendable @escaping (Request) async throws -> Service, - _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> (Req) async throws -> Void - ) -> @Sendable (Request) async throws -> Response - where Req: Decodable & Sendable - { - makeHandler(serviceBuilder) { (serviceType: Service.Type) in - { (service: Service) in - { (req: Req) -> _Empty in - try await methodSelector(serviceType)(service)(req) - return _Empty() - } - } - } - } - - func makeHandler( - _ serviceBuilder: @Sendable @escaping (Request) async throws -> Service, - _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> () async throws -> Void - ) -> @Sendable (Request) async throws -> Response - { - makeHandler(serviceBuilder) { (serviceType: Service.Type) in - { (service: Service) in - { (_: _Empty) -> _Empty in - try await methodSelector(serviceType)(service)() - return _Empty() - } - } - } - } -} - -""" - } - - private func generateVaporJSONServiceBridge() -> String { -""" -import Foundation -import Vapor - -extension VaporToServiceBridgeProtocol where Self == VaporJSONServiceBridge { - static var `default`: VaporJSONServiceBridge { VaporJSONServiceBridge() } -} - -struct VaporJSONServiceBridge: VaporToServiceBridgeProtocol { - func makeHandler( - _ serviceBuilder: @Sendable @escaping (Request) async throws -> Service, - _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> (Req) async throws -> Res - ) -> @Sendable (Request) async throws -> Response - where Req: Decodable & Sendable, Res: Encodable & Sendable - { - VaporJSONServiceHandler(serviceBuilder: serviceBuilder, methodSelector: methodSelector) - .callAsFunction(request:) - } -} - -private struct VaporJSONServiceHandler: Sendable where Req: Decodable & Sendable, Res: Encodable & Sendable { - var serviceBuilder: @Sendable (Request) async throws -> Service - var methodSelector: @Sendable (Service.Type) -> (Service) -> (Req) async throws -> Res - - @Sendable func callAsFunction(request: Request) async throws -> Response { - let service = try await serviceBuilder(request) - guard let body = request.body.data else { - throw Abort(.badRequest, reason: "no body") - } - let rpcRequest = try makeDecoder().decode(Req.self, from: body) - let rpcResponse = try await methodSelector(Service.self)(service)(rpcRequest) - return try makeResponse(status: .ok, body: rpcResponse) - } - - private func makeResponse(status: HTTPResponseStatus, body: R) throws -> Response { - let body = try makeEncoder().encode(body) - var headers = HTTPHeaders() - headers.contentType = .json - headers.cacheControl = .init(noStore: true) - return Response(status: status, headers: headers, body: .init(data: body)) - } -} - -private func makeDecoder() -> JSONDecoder { - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .millisecondsSince1970 - return decoder -} - -private func makeEncoder() -> JSONEncoder { - let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .millisecondsSince1970 - return encoder -} - -""" - } - - private func processFile(file: Generator.InputFile) throws -> String? { - var providers: [String] = [] - for stype in file.types.compactMap(ServiceProtocolScanner.scan) { - providers.append(""" -struct \(stype.serviceName)ServiceProvider: RouteCollection { - var bridge: Bridge - var serviceBuilder: @Sendable (Request) async throws -> Service - init(bridge: Bridge = .default, builder: @Sendable @escaping (Request) async throws -> Service) { - self.bridge = bridge - self.serviceBuilder = builder - } - - func boot(routes: any RoutesBuilder) throws { - routes.group("\(stype.serviceName)") { group in -\(stype.functions.map { """ - group.post("\($0.name)", use: bridge.makeHandler(serviceBuilder, { $0.\($0.name) })) -""" }.joined(separator: "\n")) - } - } -} - -""") - } - if providers.isEmpty { return nil } - - return """ -import \(definitionModule) -import Vapor - -\(providers.joined()) -""" - } - - func run() throws { - var g = Generator(definitionModule: definitionModule, srcDirectory: srcDirectory, dstDirectory: dstDirectory, dependencies: dependencies) - g.isOutputFileName = { $0.hasSuffix(".gen.swift") } - - try g.run { input, write in - try write(file: .init( - name: "VaporToServiceBridgeProtocol.gen.swift", - content: generateVaporToServiceBridgeProtocol() - )) - try write(file: .init( - name: "VaporJSONServiceBridge.gen.swift", - content: generateVaporJSONServiceBridge() - )) - - for inputFile in input.files { - guard let generated = try processFile(file: inputFile) else { continue } - let outputFile = URL(fileURLWithPath: inputFile.file.lastPathComponent.replacingOccurrences(of: ".swift", with: ".gen.swift")).lastPathComponent - try write(file: .init( - name: outputFile, - content: generated - )) - } - } - } -} diff --git a/Sources/CallableKit/Macros.swift b/Sources/CallableKit/Macros.swift new file mode 100644 index 0000000..5b1b273 --- /dev/null +++ b/Sources/CallableKit/Macros.swift @@ -0,0 +1,5 @@ +@attached( + peer, + names: prefixed(configure) +) +public macro Callable() = #externalMacro(module: "CallableKitMacros", type: "CallableMacro") diff --git a/Sources/CallableKit/ServiceTransport.swift b/Sources/CallableKit/ServiceTransport.swift new file mode 100644 index 0000000..d73a689 --- /dev/null +++ b/Sources/CallableKit/ServiceTransport.swift @@ -0,0 +1,69 @@ +public protocol ServiceTransport { + associatedtype Service + + func register( + path: String, + methodSelector: @escaping @Sendable (Service.Type) -> (Service) -> (Request) async throws -> Void + ) + + func register( + path: String, + methodSelector: @escaping @Sendable (Service.Type) -> (Service) -> () async throws -> Response + ) + + func register( + path: String, + methodSelector: @escaping @Sendable (Service.Type) -> (Service) -> () async throws -> Void + ) + + func register( + path: String, + methodSelector: @escaping @Sendable (Service.Type) -> (Service) -> (Request) async throws -> Response + ) +} + +fileprivate struct _Empty: Codable, Sendable { +} + +extension ServiceTransport { + public func register( + path: String, + methodSelector: @escaping @Sendable (Service.Type) -> (Service) -> (Request) async throws -> Void + ) { + register(path: path) { (serviceType) in + { (service: Service) in + { (request: Request) -> _Empty in + try await methodSelector(serviceType)(service)(request) + return _Empty() + } + } + } + } + + public func register( + path: String, + methodSelector: @escaping @Sendable (Service.Type) -> (Service) -> () async throws -> Response + ) { + register(path: path) { (serviceType) in + { (service: Service) in + { (_: _Empty) -> Response in + return try await methodSelector(serviceType)(service)() + } + } + } + } + + public func register( + path: String, + methodSelector: @escaping @Sendable (Service.Type) -> (Service) -> () async throws -> Void + ) { + register(path: path) { (serviceType) in + { (service: Service) in + { (_: _Empty) -> _Empty in + try await methodSelector(serviceType)(service)() + return _Empty() + } + } + } + } +} diff --git a/Sources/CallableKitHummingbirdTransport/HummingbirdTransport.swift b/Sources/CallableKitHummingbirdTransport/HummingbirdTransport.swift new file mode 100644 index 0000000..af39840 --- /dev/null +++ b/Sources/CallableKitHummingbirdTransport/HummingbirdTransport.swift @@ -0,0 +1,39 @@ +import CallableKit +import Foundation +import Hummingbird + +public struct HummingbirdTransport: ServiceTransport { + public init(router: Router, serviceBuilder: @escaping @Sendable (Request, Router.Context) -> Service) { + self.router = router + self.serviceBuilder = serviceBuilder + } + + public var router: Router + public var serviceBuilder: @Sendable (Request, Router.Context) -> Service + + public func register( + path: String, + methodSelector: @escaping @Sendable (Service.Type) -> (Service) -> (Request) async throws -> Response + ) { + router.post(RouterPath(path)) { [serviceBuilder] request, context in + let serviceRequest = try await makeDecoder().decode(Request.self, from: request, context: context) + let service = serviceBuilder(request, context) + let serviceResponse = try await methodSelector(Service.self)(service)(serviceRequest) + var response = try makeEncoder().encode(serviceResponse, from: request, context: context) + response.headers[.cacheControl] = "no-store" + return response + } + } +} + +private func makeDecoder() -> JSONDecoder { + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .millisecondsSince1970 + return decoder +} + +private func makeEncoder() -> JSONEncoder { + let encoder = JSONEncoder() + encoder.dateEncodingStrategy = .millisecondsSince1970 + return encoder +} diff --git a/Sources/CallableKitMacros/CallableMacro.swift b/Sources/CallableKitMacros/CallableMacro.swift new file mode 100644 index 0000000..3d5ff91 --- /dev/null +++ b/Sources/CallableKitMacros/CallableMacro.swift @@ -0,0 +1,44 @@ +import Foundation +import SwiftSyntax +import SwiftSyntaxMacros + +public struct CallableMacro: PeerMacro { + public static func expansion( + of node: AttributeSyntax, + providingPeersOf declaration: some DeclSyntaxProtocol, + in context: some MacroExpansionContext + ) throws -> [DeclSyntax] { + guard let `protocol` = declaration.as(ProtocolDeclSyntax.self) else { + throw MacroExpansionErrorMessage("@Callable can only be attached to protocols.") + } + + let protocolName = `protocol`.name.trimmedDescription + let serviceName = protocolName.replacingOccurrences(of: "ServiceProtocol", with: "") + + let functions = `protocol`.memberBlock.members.compactMap { item in + return item.decl.as(FunctionDeclSyntax.self) + } + + let configureFunc = try FunctionDeclSyntax(""" + public func configure\(raw: protocolName)<\(raw: serviceName): \(raw: protocolName)>( + transport: some ServiceTransport<\(raw: serviceName)> + ) + """) { + for function in functions { + FunctionCallExprSyntax( + callee: "transport.register" as ExprSyntax, + trailingClosure: ClosureExprSyntax { + "$0.\(function.name)" as ExprSyntax + } + ) { + LabeledExprSyntax( + label: "path", + expression: "\(serviceName)/\(function.name)".makeLiteralSyntax() + ) + } + } + } + + return [DeclSyntax(configureFunc)] + } +} diff --git a/Sources/CallableKitMacros/MacroMain.swift b/Sources/CallableKitMacros/MacroMain.swift new file mode 100644 index 0000000..32cf52f --- /dev/null +++ b/Sources/CallableKitMacros/MacroMain.swift @@ -0,0 +1,8 @@ +import SwiftCompilerPlugin +import SwiftSyntaxMacros + +@main struct MacroMain: CompilerPlugin { + let providingMacros: [any Macro.Type] = [ + CallableMacro.self, + ] +} diff --git a/Sources/CallableKitVaporTransport/VaporTransport.swift b/Sources/CallableKitVaporTransport/VaporTransport.swift new file mode 100644 index 0000000..70312a5 --- /dev/null +++ b/Sources/CallableKitVaporTransport/VaporTransport.swift @@ -0,0 +1,45 @@ +import CallableKit +import Vapor + +public struct VaporTransport: ServiceTransport { + public init(router: any RoutesBuilder, serviceBuilder: @escaping @Sendable (Request) -> Service) { + self.router = router + self.serviceBuilder = serviceBuilder + } + + public var router: any RoutesBuilder + public var serviceBuilder: @Sendable (Request) -> Service + + public func register( + path: String, + methodSelector: @escaping @Sendable (Service.Type) -> (Service) -> (Request) async throws -> Response + ) { + router.post(path.pathComponents) { [serviceBuilder] request in + guard let body = request.body.data else { + throw Abort(.badRequest, reason: "no body") + } + let serviceRequest = try makeDecoder().decode(Request.self, from: body) + let service = serviceBuilder(request) + let serviceResponse = try await methodSelector(Service.self)(service)(serviceRequest) + + var headers = HTTPHeaders() + headers.cacheControl = .init(noStore: true) + var buffer = request.byteBufferAllocator.buffer(capacity: 0) + try makeEncoder().encode(serviceResponse, to: &buffer, headers: &headers, userInfo: [:]) + + return Vapor.Response(status: .ok, headers: headers, body: .init(buffer: buffer, byteBufferAllocator: request.byteBufferAllocator)) + } + } +} + +private func makeDecoder() -> JSONDecoder { + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .millisecondsSince1970 + return decoder +} + +private func makeEncoder() -> JSONEncoder { + let encoder = JSONEncoder() + encoder.dateEncodingStrategy = .millisecondsSince1970 + return encoder +} diff --git a/Sources/Codegen/Codegen.swift b/Sources/Codegen/Codegen.swift index cec4a2b..11eb7d4 100644 --- a/Sources/Codegen/Codegen.swift +++ b/Sources/Codegen/Codegen.swift @@ -1,20 +1,11 @@ import ArgumentParser -import CallableKit +import CodegenImpl import Foundation @main struct Codegen: ParsableCommand { @Option(help: "generate client stub", completion: .directory) var client_out: URL? - @Option(help: "generate service middleware code", completion: .directory) - var middleware_out: URL? - - @Option(help: "generate routing code for vipor", completion: .directory) - var vapor_out: URL? - - @Option(help: "generate routing code for hummingbird", completion: .directory) - var hb_out: URL? - @Option(help: "generate client stub for typescript", completion: .directory) var ts_out: URL? @@ -34,9 +25,6 @@ import Foundation try Runner( definitionDirectory: definitionDirectory, clientOut: client_out, - middlewareOut: middleware_out, - vaporOut: vapor_out, - hbOut: hb_out, tsOut: ts_out, module: module, dependencies: dependency, @@ -45,7 +33,7 @@ import Foundation } } -extension URL: ExpressibleByArgument { +extension URL: ArgumentParser.ExpressibleByArgument { public init?(argument: String) { self = URL(fileURLWithPath: argument) } diff --git a/Sources/CallableKit/GenerateSwiftClient.swift b/Sources/CodegenImpl/GenerateSwiftClient.swift similarity index 100% rename from Sources/CallableKit/GenerateSwiftClient.swift rename to Sources/CodegenImpl/GenerateSwiftClient.swift diff --git a/Sources/CallableKit/GenerateTS/GenerateTS+commonlib.swift b/Sources/CodegenImpl/GenerateTS/GenerateTS+commonlib.swift similarity index 100% rename from Sources/CallableKit/GenerateTS/GenerateTS+commonlib.swift rename to Sources/CodegenImpl/GenerateTS/GenerateTS+commonlib.swift diff --git a/Sources/CallableKit/GenerateTSClient.swift b/Sources/CodegenImpl/GenerateTSClient.swift similarity index 100% rename from Sources/CallableKit/GenerateTSClient.swift rename to Sources/CodegenImpl/GenerateTSClient.swift diff --git a/Sources/CallableKit/Generator.swift b/Sources/CodegenImpl/Generator.swift similarity index 100% rename from Sources/CallableKit/Generator.swift rename to Sources/CodegenImpl/Generator.swift diff --git a/Sources/CallableKit/Runner.swift b/Sources/CodegenImpl/Runner.swift similarity index 52% rename from Sources/CallableKit/Runner.swift rename to Sources/CodegenImpl/Runner.swift index 7783717..f35e132 100644 --- a/Sources/CallableKit/Runner.swift +++ b/Sources/CodegenImpl/Runner.swift @@ -1,12 +1,9 @@ import Foundation public struct Runner { - public init(definitionDirectory: URL, clientOut: URL? = nil, middlewareOut: URL? = nil, vaporOut: URL? = nil, hbOut: URL? = nil, tsOut: URL? = nil, module: String? = nil, dependencies: [URL] = [], nextjs: Bool) { + public init(definitionDirectory: URL, clientOut: URL? = nil, tsOut: URL? = nil, module: String? = nil, dependencies: [URL] = [], nextjs: Bool = false) { self.definitionDirectory = definitionDirectory self.clientOut = clientOut - self.middlewareOut = middlewareOut - self.vaporOut = vaporOut - self.hbOut = hbOut self.tsOut = tsOut self.module = module self.dependencies = dependencies @@ -15,9 +12,6 @@ public struct Runner { public var definitionDirectory: URL public var clientOut: URL? - public var middlewareOut: URL? - public var vaporOut: URL? - public var hbOut: URL? public var tsOut: URL? public var module: String? public var dependencies: [URL] = [] @@ -37,33 +31,6 @@ public struct Runner { ).run() } - if let middlewareOut { - try GenerateMiddleware( - definitionModule: module, - srcDirectory: definitionDirectory, - dstDirectory: middlewareOut, - dependencies: dependencies - ).run() - } - - if let vaporOut { - try GenerateVaporProvider( - definitionModule: module, - srcDirectory: definitionDirectory, - dstDirectory: vaporOut, - dependencies: dependencies - ).run() - } - - if let hbOut { - try GenerateHBProvider( - definitionModule: module, - srcDirectory: definitionDirectory, - dstDirectory: hbOut, - dependencies: dependencies - ).run() - } - if let tsOut { try GenerateTSClient( definitionModule: module, diff --git a/Sources/CallableKit/ServiceProtocolScanner.swift b/Sources/CodegenImpl/ServiceProtocolScanner.swift similarity index 100% rename from Sources/CallableKit/ServiceProtocolScanner.swift rename to Sources/CodegenImpl/ServiceProtocolScanner.swift diff --git a/Sources/CallableKit/Utils.swift b/Sources/CodegenImpl/Utils.swift similarity index 100% rename from Sources/CallableKit/Utils.swift rename to Sources/CodegenImpl/Utils.swift diff --git a/example/Package.resolved b/example/Package.resolved index eed9e84..a5c1c5a 100644 --- a/example/Package.resolved +++ b/example/Package.resolved @@ -1,4 +1,5 @@ { + "originHash" : "72222c8a5eb70b05fc61636aee7c555f6591c06635ec1a72b200e8f1ece362cb", "pins" : [ { "identity" : "async-http-client", @@ -32,8 +33,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/vapor/console-kit.git", "state" : { - "revision" : "f4ef965dadd999f7e4687053153c97b8b320819c", - "version" : "4.10.1" + "revision" : "966d89ae64cd71c652a1e981bc971de59d64f13d", + "version" : "4.15.1" } }, { @@ -41,17 +42,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/hummingbird-project/hummingbird", "state" : { - "revision" : "3c7f440cfac5ca060544f38b2083f171f9a9ee2d", - "version" : "1.9.0" - } - }, - { - "identity" : "hummingbird-core", - "kind" : "remoteSourceControl", - "location" : "https://github.com/hummingbird-project/hummingbird-core.git", - "state" : { - "revision" : "ca84a9bae1bbbf1015c9ec69f4fb46324f2b19a1", - "version" : "1.4.0" + "revision" : "70b0714b4c4a192a51a9ddcc691fcf2ab3bc9141", + "version" : "2.5.0" } }, { @@ -68,8 +60,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/vapor/routing-kit.git", "state" : { - "revision" : "ffac7b3a127ce1e85fb232f1a6271164628809ad", - "version" : "4.6.0" + "revision" : "8c9a227476555c55837e569be71944e02a056b72", + "version" : "4.9.1" } }, { @@ -91,21 +83,21 @@ } }, { - "identity" : "swift-atomics", + "identity" : "swift-async-algorithms", "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-atomics.git", + "location" : "https://github.com/apple/swift-async-algorithms.git", "state" : { - "revision" : "cd142fd2f64be2100422d658e7411e39489da985", - "version" : "1.2.0" + "revision" : "5c8bd186f48c16af0775972700626f0b74588278", + "version" : "1.0.2" } }, { - "identity" : "swift-backtrace", + "identity" : "swift-atomics", "kind" : "remoteSourceControl", - "location" : "https://github.com/swift-server/swift-backtrace.git", + "location" : "https://github.com/apple/swift-atomics.git", "state" : { - "revision" : "f25620d5d05e2f1ba27154b40cafea2b67566956", - "version" : "1.3.3" + "revision" : "cd142fd2f64be2100422d658e7411e39489da985", + "version" : "1.2.0" } }, { @@ -113,8 +105,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-collections", "state" : { - "revision" : "937e904258d22af6e447a0b72c0bc67583ef64a2", - "version" : "1.0.4" + "revision" : "671108c96644956dddcd89dd59c203dcdb36cec7", + "version" : "1.1.4" } }, { @@ -158,8 +150,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-metrics.git", "state" : { - "revision" : "9b39d811a83cf18b79d7d5513b06f8b290198b10", - "version" : "2.3.3" + "revision" : "e0165b53d49b413dd987526b641e05e246782685", + "version" : "2.5.0" } }, { @@ -167,8 +159,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio.git", "state" : { - "revision" : "3db5c4aeee8100d2db6f1eaf3864afdad5dc68fd", - "version" : "2.59.0" + "revision" : "dca6594f65308c761a9c409e09fbf35f48d50d34", + "version" : "2.77.0" } }, { @@ -176,8 +168,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-extras.git", "state" : { - "revision" : "798c962495593a23fdea0c0c63fd55571d8dff51", - "version" : "1.20.0" + "revision" : "2e9746cfc57554f70b650b021b6ae4738abef3e6", + "version" : "1.24.1" } }, { @@ -203,8 +195,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-transport-services.git", "state" : { - "revision" : "e7403c35ca6bb539a7ca353b91cc2d8ec0362d58", - "version" : "1.19.0" + "revision" : "bbd5e63cf949b7db0c9edaf7a21e141c52afe214", + "version" : "1.23.0" } }, { @@ -230,17 +222,26 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/swift-server/swift-service-lifecycle.git", "state" : { - "revision" : "22363fed316cd9942b56bcd1a1df8875df79b794", - "version" : "1.0.0-alpha.11" + "revision" : "f70b838872863396a25694d8b19fe58bcd0b7903", + "version" : "2.6.2" } }, { "identity" : "swift-syntax", "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-syntax", + "location" : "https://github.com/swiftlang/swift-syntax.git", "state" : { - "revision" : "fa8f95c2d536d6620cc2f504ebe8a6167c9fc2dd", - "version" : "510.0.1" + "revision" : "0687f71944021d616d34d922343dcef086855920", + "version" : "600.0.1" + } + }, + { + "identity" : "swift-system", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-system.git", + "state" : { + "revision" : "c8a44d836fe7913603e246acab7c528c2e780168", + "version" : "1.4.0" } }, { @@ -266,8 +267,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/vapor/vapor", "state" : { - "revision" : "0fa646e517dff34aa5d0ae12d221021ec07a801d", - "version" : "4.85.1" + "revision" : "3aeeb6fab5112fb10ce699b5a80207e5cf571c32", + "version" : "4.106.7" } }, { @@ -280,5 +281,5 @@ } } ], - "version" : 2 + "version" : 3 } diff --git a/example/Package.swift b/example/Package.swift index cb3f0f4..7d88e48 100644 --- a/example/Package.swift +++ b/example/Package.swift @@ -1,19 +1,43 @@ -// swift-tools-version: 5.8 +// swift-tools-version: 5.10 import PackageDescription +func swiftSettings() -> [SwiftSetting] { + return [ + .enableUpcomingFeature("ForwardTrailingClosures"), + .enableUpcomingFeature("ConciseMagicFile"), + .enableUpcomingFeature("BareSlashRegexLiterals"), + .enableUpcomingFeature("IsolatedDefaultValues"), + .enableUpcomingFeature("DeprecateApplicationMain"), + .enableUpcomingFeature("GlobalConcurrency"), + .enableUpcomingFeature("DynamicActorIsolation"), +// .enableUpcomingFeature("InternalImportsByDefault"), + .enableUpcomingFeature("DisableOutwardActorInference"), + .enableUpcomingFeature("ImportObjcForwardDeclarations"), + .enableUpcomingFeature("GlobalActorIsolatedTypesUsability"), + .enableUpcomingFeature("MemberImportVisibility"), +// .enableUpcomingFeature("InferSendableFromCaptures"), + .enableUpcomingFeature("RegionBasedIsolation"), + .enableUpcomingFeature("ExistentialAny"), + .enableUpcomingFeature("StrictConcurrency"), + ] +} + let package = Package( name: "MyApplication", - platforms: [.macOS(.v13)], + platforms: [.macOS(.v14)], dependencies: [ .package(path: "../"), - .package(url: "https://github.com/vapor/vapor.git", from: "4.85.1"), - .package(url: "https://github.com/hummingbird-project/hummingbird.git", from: "1.9.0"), + .package(url: "https://github.com/vapor/vapor.git", from: "4.106.7"), + .package(url: "https://github.com/hummingbird-project/hummingbird.git", from: "2.5.0"), ], targets: [ .target( name: "APIDefinition", - dependencies: ["OtherDependency"] + dependencies: [ + .product(name: "CallableKit", package: "CallableKit"), + "OtherDependency", + ] ), .target(name: "OtherDependency"), .target( @@ -26,21 +50,19 @@ let package = Package( name: "VaporServer", dependencies: [ .product(name: "Vapor", package: "vapor"), + .product(name: "CallableKitVaporTransport", package: "CallableKit"), "Service", ], - swiftSettings: [ - .unsafeFlags(["-strict-concurrency=complete"]), - ] + swiftSettings: swiftSettings() ), .executableTarget( name: "HBServer", dependencies: [ .product(name: "Hummingbird", package: "hummingbird"), + .product(name: "CallableKitHummingbirdTransport", package: "CallableKit"), "Service", ], - swiftSettings: [ - .unsafeFlags(["-strict-concurrency=complete"]), - ] + swiftSettings: swiftSettings() ), .executableTarget( name: "Client", diff --git a/example/Plugins/CodegenPlugin/CodegenPlugin.swift b/example/Plugins/CodegenPlugin/CodegenPlugin.swift index 294b07a..c197f53 100644 --- a/example/Plugins/CodegenPlugin/CodegenPlugin.swift +++ b/example/Plugins/CodegenPlugin/CodegenPlugin.swift @@ -13,8 +13,6 @@ struct CodegenPlugin: CommandPlugin { let arguments: [String] = [ "Sources/APIDefinition", "--client_out", "Sources/Client/Gen", - "--vapor_out", "Sources/VaporServer/Gen", - "--hb_out", "Sources/HBServer/Gen", "--ts_out", "TSClient/src/Gen", "--dependency", "Sources/OtherDependency", ] diff --git a/example/Sources/APIDefinition/Account.swift b/example/Sources/APIDefinition/Account.swift index 77c148a..2c78dbb 100644 --- a/example/Sources/APIDefinition/Account.swift +++ b/example/Sources/APIDefinition/Account.swift @@ -1,6 +1,8 @@ +import CallableKit import Foundation import OtherDependency +@Callable public protocol AccountServiceProtocol { func signin(request: AccountSignin.Request) async throws -> CodableResult> } diff --git a/example/Sources/APIDefinition/Echo.swift b/example/Sources/APIDefinition/Echo.swift index 5c4e3ca..a6678d7 100644 --- a/example/Sources/APIDefinition/Echo.swift +++ b/example/Sources/APIDefinition/Echo.swift @@ -1,5 +1,7 @@ +import CallableKit import Foundation +@Callable public protocol EchoServiceProtocol { func hello(request: EchoHelloRequest) async throws -> EchoHelloResponse diff --git a/example/Sources/Client/Gen/Account.gen.swift b/example/Sources/Client/Gen/Account.gen.swift index 3f1d16b..cb35a06 100644 --- a/example/Sources/Client/Gen/Account.gen.swift +++ b/example/Sources/Client/Gen/Account.gen.swift @@ -1,4 +1,5 @@ import APIDefinition +import CallableKit import Foundation import OtherDependency diff --git a/example/Sources/Client/Gen/Echo.gen.swift b/example/Sources/Client/Gen/Echo.gen.swift index 2c423de..c96459e 100644 --- a/example/Sources/Client/Gen/Echo.gen.swift +++ b/example/Sources/Client/Gen/Echo.gen.swift @@ -1,4 +1,5 @@ import APIDefinition +import CallableKit import Foundation public struct EchoServiceStub: EchoServiceProtocol, Sendable { diff --git a/example/Sources/Client/Main.swift b/example/Sources/Client/Main.swift index 3199558..351dd0a 100644 --- a/example/Sources/Client/Main.swift +++ b/example/Sources/Client/Main.swift @@ -16,7 +16,11 @@ struct ErrorFrame: Decodable, CustomStringConvertible, LocalizedError { request.addValue("Bearer xxxxxxxxxxxx", forHTTPHeaderField: "Authorization") }, mapResponseError: { error in - throw try JSONDecoder().decode(ErrorFrame.self, from: error.body) + do { + throw try JSONDecoder().decode(ErrorFrame.self, from: error.body) + } catch let decodingError { + throw ErrorFrame(errorMessage: "\(decodingError), body=\(String(data: error.body, encoding: .utf8) ?? "")") + } } ) diff --git a/example/Sources/HBServer/Gen/Account.gen.swift b/example/Sources/HBServer/Gen/Account.gen.swift deleted file mode 100644 index c54c16f..0000000 --- a/example/Sources/HBServer/Gen/Account.gen.swift +++ /dev/null @@ -1,10 +0,0 @@ -import APIDefinition -import Hummingbird - -func configureAccountService( - bridge: some HBToServiceBridgeProtocol = .default, - builder serviceBuilder: @escaping @Sendable (HBRequest) async throws -> some AccountServiceProtocol, - router: some HBRouterMethods -) { - router.post("Account/signin", use: bridge.makeHandler(serviceBuilder, { $0.signin })) -} diff --git a/example/Sources/HBServer/Gen/Echo.gen.swift b/example/Sources/HBServer/Gen/Echo.gen.swift deleted file mode 100644 index 39e1a6c..0000000 --- a/example/Sources/HBServer/Gen/Echo.gen.swift +++ /dev/null @@ -1,19 +0,0 @@ -import APIDefinition -import Hummingbird - -func configureEchoService( - bridge: some HBToServiceBridgeProtocol = .default, - builder serviceBuilder: @escaping @Sendable (HBRequest) async throws -> some EchoServiceProtocol, - router: some HBRouterMethods -) { - router.post("Echo/hello", use: bridge.makeHandler(serviceBuilder, { $0.hello })) - router.post("Echo/tommorow", use: bridge.makeHandler(serviceBuilder, { $0.tommorow })) - router.post("Echo/testTypicalEntity", use: bridge.makeHandler(serviceBuilder, { $0.testTypicalEntity })) - router.post("Echo/testComplexType", use: bridge.makeHandler(serviceBuilder, { $0.testComplexType })) - router.post("Echo/emptyRequestAndResponse", use: bridge.makeHandler(serviceBuilder, { $0.emptyRequestAndResponse })) - router.post("Echo/testTypeAliasToRawRepr", use: bridge.makeHandler(serviceBuilder, { $0.testTypeAliasToRawRepr })) - router.post("Echo/testRawRepr", use: bridge.makeHandler(serviceBuilder, { $0.testRawRepr })) - router.post("Echo/testRawRepr2", use: bridge.makeHandler(serviceBuilder, { $0.testRawRepr2 })) - router.post("Echo/testRawRepr3", use: bridge.makeHandler(serviceBuilder, { $0.testRawRepr3 })) - router.post("Echo/testRawRepr4", use: bridge.makeHandler(serviceBuilder, { $0.testRawRepr4 })) -} diff --git a/example/Sources/HBServer/Gen/HBJSONServiceBridge.gen.swift b/example/Sources/HBServer/Gen/HBJSONServiceBridge.gen.swift deleted file mode 100644 index 5721f6a..0000000 --- a/example/Sources/HBServer/Gen/HBJSONServiceBridge.gen.swift +++ /dev/null @@ -1,56 +0,0 @@ -import Foundation -import NIOCore -import NIOFoundationCompat -import Hummingbird - -extension HBToServiceBridgeProtocol where Self == HBJSONServiceBridge { - static var `default`: HBJSONServiceBridge { HBJSONServiceBridge() } -} - -struct HBJSONServiceBridge: HBToServiceBridgeProtocol { - func makeHandler( - _ serviceBuilder: @Sendable @escaping (HBRequest) async throws -> Service, - _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> (Req) async throws -> Res - ) -> @Sendable (HBRequest) async throws -> HBResponse - where Req: Decodable & Sendable, Res: Encodable & Sendable - { - HBJSONServiceHandler(serviceBuilder: serviceBuilder, methodSelector: methodSelector) - .callAsFunction(request:) - } -} - -private struct HBJSONServiceHandler: Sendable where Req: Decodable & Sendable, Res: Encodable & Sendable { - var serviceBuilder: @Sendable (HBRequest) async throws -> Service - var methodSelector: @Sendable (Service.Type) -> (Service) -> (Req) async throws -> Res - - @Sendable func callAsFunction(request: HBRequest) async throws -> HBResponse { - let service = try await serviceBuilder(request) - guard let body = request.body.buffer else { - throw HBHTTPError(.badRequest, message: "no body") - } - let rpcRequest = try makeDecoder().decode(Req.self, from: body) - let rpcResponse = try await methodSelector(Service.self)(service)(rpcRequest) - return try makeResponse(status: .ok, body: rpcResponse) - } - - private func makeResponse(status: HTTPResponseStatus, body: R) throws -> HBResponse { - let body = try makeEncoder().encodeAsByteBuffer(body, allocator: .init()) - let headers = HTTPHeaders([ - ("Content-Type", "application/json"), - ("Cache-Control", "no-store"), - ]) - return HBResponse(status: status, headers: headers, body: .byteBuffer(body)) - } -} - -private func makeDecoder() -> JSONDecoder { - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .millisecondsSince1970 - return decoder -} - -private func makeEncoder() -> JSONEncoder { - let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .millisecondsSince1970 - return encoder -} diff --git a/example/Sources/HBServer/Gen/HBToServiceBridgeProtocol.gen.swift b/example/Sources/HBServer/Gen/HBToServiceBridgeProtocol.gen.swift deleted file mode 100644 index ae3ecf1..0000000 --- a/example/Sources/HBServer/Gen/HBToServiceBridgeProtocol.gen.swift +++ /dev/null @@ -1,76 +0,0 @@ -import Hummingbird - -protocol HBToServiceBridgeProtocol { - func makeHandler( - _ serviceBuilder: @Sendable @escaping (HBRequest) async throws -> Service, - _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> (Req) async throws -> Res - ) -> @Sendable (HBRequest) async throws -> HBResponse - where Req: Decodable & Sendable, Res: Encodable & Sendable - - func makeHandler( - _ serviceBuilder: @Sendable @escaping (HBRequest) async throws -> Service, - _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> () async throws -> Res - ) -> @Sendable (HBRequest) async throws -> HBResponse - where Res: Encodable & Sendable - - func makeHandler( - _ serviceBuilder: @Sendable @escaping (HBRequest) async throws -> Service, - _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> (Req) async throws -> Void - ) -> @Sendable (HBRequest) async throws -> HBResponse - where Req: Decodable & Sendable - - func makeHandler( - _ serviceBuilder: @Sendable @escaping (HBRequest) async throws -> Service, - _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> () async throws -> Void - ) -> @Sendable (HBRequest) async throws -> HBResponse -} - -private struct _Empty: Codable, Sendable {} - -extension HBToServiceBridgeProtocol { - func makeHandler( - _ serviceBuilder: @Sendable @escaping (HBRequest) async throws -> Service, - _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> () async throws -> Res - ) -> @Sendable (HBRequest) async throws -> HBResponse - where Res: Encodable & Sendable - { - makeHandler(serviceBuilder) { (serviceType: Service.Type) in - { (service: Service) in - { (_: _Empty) -> Res in - try await methodSelector(serviceType)(service)() - } - } - } - } - - func makeHandler( - _ serviceBuilder: @Sendable @escaping (HBRequest) async throws -> Service, - _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> (Req) async throws -> Void - ) -> @Sendable (HBRequest) async throws -> HBResponse - where Req: Decodable & Sendable - { - makeHandler(serviceBuilder) { (serviceType: Service.Type) in - { (service: Service) in - { (req: Req) -> _Empty in - try await methodSelector(serviceType)(service)(req) - return _Empty() - } - } - } - } - - func makeHandler( - _ serviceBuilder: @Sendable @escaping (HBRequest) async throws -> Service, - _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> () async throws -> Void - ) -> @Sendable (HBRequest) async throws -> HBResponse - { - makeHandler(serviceBuilder) { (serviceType: Service.Type) in - { (service: Service) in - { (_: _Empty) -> _Empty in - try await methodSelector(serviceType)(service)() - return _Empty() - } - } - } - } -} diff --git a/example/Sources/HBServer/Main.swift b/example/Sources/HBServer/Main.swift index 834b0fe..45b5a53 100644 --- a/example/Sources/HBServer/Main.swift +++ b/example/Sources/HBServer/Main.swift @@ -1,45 +1,50 @@ +import APIDefinition +import CallableKitHummingbirdTransport import Foundation +import Logging import Hummingbird -import NIOFoundationCompat import Service -struct ErrorMiddleware: HBMiddleware { - func apply(to request: HBRequest, next: HBResponder) -> EventLoopFuture { - next.respond(to: request).flatMapErrorThrowing { error in - request.logger.error("\(error)") - struct ErrorFrame: Encodable { - var errorMessage: String - } +struct ErrorFrame: Encodable { + var errorMessage: String +} + +struct ErrorMiddleware: MiddlewareProtocol { + func handle(_ input: Request, context: Context, next: (Request, Context) async throws -> Response) async throws -> Response { + do { + return try await next(input, context) + } catch { + context.logger.error("\(error)") let errorFrame = ErrorFrame(errorMessage: "\(error)") - var headers = HTTPHeaders() - headers.add(name: "Content-Type", value: "application/json") - headers.add(name: "Cache-Control", value: "no-store") - let body = (try? JSONEncoder().encode(errorFrame)) ?? .init() - return HBResponse(status: .internalServerError, headers: headers, body: .byteBuffer(.init(data: body))) + var response = try JSONEncoder().encode(errorFrame, from: input, context: context) + response.headers[.cacheControl] = "no-store" + return response } } } @main struct Main { static func main() async throws { - let app = HBApplication( - configuration: .init( - address: .hostname("127.0.0.1", port: 8080), - logLevel: .error - ) - ) - defer { app.stop() } - - let router = app.router.group().add(middleware: ErrorMiddleware()) + let router = Router() + router.add(middleware: ErrorMiddleware()) - configureAccountService(builder: { req in + configureAccountServiceProtocol(transport: HummingbirdTransport(router: router) { _, _ in makeAccountService() - }, router: router) - - configureEchoService(builder: { req in + }) + configureEchoServiceProtocol(transport: HummingbirdTransport(router: router) { _, _ in makeEchoService() - }, router: router) + }) + + var logger = Logger(label: "Hummingbird") + logger.logLevel = .error + let app = Application( + router: router, + configuration: .init( + address: .hostname("127.0.0.1", port: 8080) + ), + logger: logger + ) - try await app.asyncRun() + try await app.runService() } } diff --git a/example/Sources/VaporServer/Gen/Account.gen.swift b/example/Sources/VaporServer/Gen/Account.gen.swift deleted file mode 100644 index 109ccf0..0000000 --- a/example/Sources/VaporServer/Gen/Account.gen.swift +++ /dev/null @@ -1,17 +0,0 @@ -import APIDefinition -import Vapor - -struct AccountServiceProvider: RouteCollection { - var bridge: Bridge - var serviceBuilder: @Sendable (Request) async throws -> Service - init(bridge: Bridge = .default, builder: @Sendable @escaping (Request) async throws -> Service) { - self.bridge = bridge - self.serviceBuilder = builder - } - - func boot(routes: any RoutesBuilder) throws { - routes.group("Account") { group in - group.post("signin", use: bridge.makeHandler(serviceBuilder, { $0.signin })) - } - } -} diff --git a/example/Sources/VaporServer/Gen/Echo.gen.swift b/example/Sources/VaporServer/Gen/Echo.gen.swift deleted file mode 100644 index dcd649b..0000000 --- a/example/Sources/VaporServer/Gen/Echo.gen.swift +++ /dev/null @@ -1,26 +0,0 @@ -import APIDefinition -import Vapor - -struct EchoServiceProvider: RouteCollection { - var bridge: Bridge - var serviceBuilder: @Sendable (Request) async throws -> Service - init(bridge: Bridge = .default, builder: @Sendable @escaping (Request) async throws -> Service) { - self.bridge = bridge - self.serviceBuilder = builder - } - - func boot(routes: any RoutesBuilder) throws { - routes.group("Echo") { group in - group.post("hello", use: bridge.makeHandler(serviceBuilder, { $0.hello })) - group.post("tommorow", use: bridge.makeHandler(serviceBuilder, { $0.tommorow })) - group.post("testTypicalEntity", use: bridge.makeHandler(serviceBuilder, { $0.testTypicalEntity })) - group.post("testComplexType", use: bridge.makeHandler(serviceBuilder, { $0.testComplexType })) - group.post("emptyRequestAndResponse", use: bridge.makeHandler(serviceBuilder, { $0.emptyRequestAndResponse })) - group.post("testTypeAliasToRawRepr", use: bridge.makeHandler(serviceBuilder, { $0.testTypeAliasToRawRepr })) - group.post("testRawRepr", use: bridge.makeHandler(serviceBuilder, { $0.testRawRepr })) - group.post("testRawRepr2", use: bridge.makeHandler(serviceBuilder, { $0.testRawRepr2 })) - group.post("testRawRepr3", use: bridge.makeHandler(serviceBuilder, { $0.testRawRepr3 })) - group.post("testRawRepr4", use: bridge.makeHandler(serviceBuilder, { $0.testRawRepr4 })) - } - } -} diff --git a/example/Sources/VaporServer/Gen/VaporJSONServiceBridge.gen.swift b/example/Sources/VaporServer/Gen/VaporJSONServiceBridge.gen.swift deleted file mode 100644 index e013403..0000000 --- a/example/Sources/VaporServer/Gen/VaporJSONServiceBridge.gen.swift +++ /dev/null @@ -1,53 +0,0 @@ -import Foundation -import Vapor - -extension VaporToServiceBridgeProtocol where Self == VaporJSONServiceBridge { - static var `default`: VaporJSONServiceBridge { VaporJSONServiceBridge() } -} - -struct VaporJSONServiceBridge: VaporToServiceBridgeProtocol { - func makeHandler( - _ serviceBuilder: @Sendable @escaping (Request) async throws -> Service, - _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> (Req) async throws -> Res - ) -> @Sendable (Request) async throws -> Response - where Req: Decodable & Sendable, Res: Encodable & Sendable - { - VaporJSONServiceHandler(serviceBuilder: serviceBuilder, methodSelector: methodSelector) - .callAsFunction(request:) - } -} - -private struct VaporJSONServiceHandler: Sendable where Req: Decodable & Sendable, Res: Encodable & Sendable { - var serviceBuilder: @Sendable (Request) async throws -> Service - var methodSelector: @Sendable (Service.Type) -> (Service) -> (Req) async throws -> Res - - @Sendable func callAsFunction(request: Request) async throws -> Response { - let service = try await serviceBuilder(request) - guard let body = request.body.data else { - throw Abort(.badRequest, reason: "no body") - } - let rpcRequest = try makeDecoder().decode(Req.self, from: body) - let rpcResponse = try await methodSelector(Service.self)(service)(rpcRequest) - return try makeResponse(status: .ok, body: rpcResponse) - } - - private func makeResponse(status: HTTPResponseStatus, body: R) throws -> Response { - let body = try makeEncoder().encode(body) - var headers = HTTPHeaders() - headers.contentType = .json - headers.cacheControl = .init(noStore: true) - return Response(status: status, headers: headers, body: .init(data: body)) - } -} - -private func makeDecoder() -> JSONDecoder { - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .millisecondsSince1970 - return decoder -} - -private func makeEncoder() -> JSONEncoder { - let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .millisecondsSince1970 - return encoder -} diff --git a/example/Sources/VaporServer/Gen/VaporToServiceBridgeProtocol.gen.swift b/example/Sources/VaporServer/Gen/VaporToServiceBridgeProtocol.gen.swift deleted file mode 100644 index 122bc3b..0000000 --- a/example/Sources/VaporServer/Gen/VaporToServiceBridgeProtocol.gen.swift +++ /dev/null @@ -1,76 +0,0 @@ -import Vapor - -protocol VaporToServiceBridgeProtocol { - func makeHandler( - _ serviceBuilder: @Sendable @escaping (Request) async throws -> Service, - _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> (Req) async throws -> Res - ) -> @Sendable (Request) async throws -> Response - where Req: Decodable & Sendable, Res: Encodable & Sendable - - func makeHandler( - _ serviceBuilder: @Sendable @escaping (Request) async throws -> Service, - _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> () async throws -> Res - ) -> @Sendable (Request) async throws -> Response - where Res: Encodable & Sendable - - func makeHandler( - _ serviceBuilder: @Sendable @escaping (Request) async throws -> Service, - _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> (Req) async throws -> Void - ) -> @Sendable (Request) async throws -> Response - where Req: Decodable & Sendable - - func makeHandler( - _ serviceBuilder: @Sendable @escaping (Request) async throws -> Service, - _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> () async throws -> Void - ) -> @Sendable (Request) async throws -> Response -} - -private struct _Empty: Codable, Sendable {} - -extension VaporToServiceBridgeProtocol { - func makeHandler( - _ serviceBuilder: @Sendable @escaping (Request) async throws -> Service, - _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> () async throws -> Res - ) -> @Sendable (Request) async throws -> Response - where Res: Encodable & Sendable - { - makeHandler(serviceBuilder) { (serviceType: Service.Type) in - { (service: Service) in - { (_: _Empty) -> Res in - try await methodSelector(serviceType)(service)() - } - } - } - } - - func makeHandler( - _ serviceBuilder: @Sendable @escaping (Request) async throws -> Service, - _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> (Req) async throws -> Void - ) -> @Sendable (Request) async throws -> Response - where Req: Decodable & Sendable - { - makeHandler(serviceBuilder) { (serviceType: Service.Type) in - { (service: Service) in - { (req: Req) -> _Empty in - try await methodSelector(serviceType)(service)(req) - return _Empty() - } - } - } - } - - func makeHandler( - _ serviceBuilder: @Sendable @escaping (Request) async throws -> Service, - _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> () async throws -> Void - ) -> @Sendable (Request) async throws -> Response - { - makeHandler(serviceBuilder) { (serviceType: Service.Type) in - { (service: Service) in - { (_: _Empty) -> _Empty in - try await methodSelector(serviceType)(service)() - return _Empty() - } - } - } - } -} diff --git a/example/Sources/VaporServer/main.swift b/example/Sources/VaporServer/main.swift index 536b041..4743e39 100644 --- a/example/Sources/VaporServer/main.swift +++ b/example/Sources/VaporServer/main.swift @@ -1,34 +1,38 @@ +import APIDefinition +import CallableKitVaporTransport import Vapor import Service -let app = Application() -defer { app.shutdown() } +@main struct Main { + static func main() async throws { + let app = try await Application.make() -app.logger.logLevel = .error -let logger = app.logger + app.logger.logLevel = .error + let logger = app.logger -let myErrorMiddleware = ErrorMiddleware { _, error in - logger.error("\(error)") - struct ErrorFrame: Encodable { - var errorMessage: String - } - let errorFrame = ErrorFrame(errorMessage: "\(error)") - var headers = HTTPHeaders() - headers.contentType = .json - headers.cacheControl = .init(noStore: true) - let body = (try? JSONEncoder().encode(errorFrame)) ?? .init() - return Response(status: .internalServerError, headers: headers, body: .init(data: body)) -} + let myErrorMiddleware = ErrorMiddleware { _, error in + logger.error("\(error)") + struct ErrorFrame: Encodable { + var errorMessage: String + } + let errorFrame = ErrorFrame(errorMessage: "\(error)") + var headers = HTTPHeaders() + headers.contentType = .json + headers.cacheControl = .init(noStore: true) + let body = (try? JSONEncoder().encode(errorFrame)) ?? .init() + return Response(status: .internalServerError, headers: headers, body: .init(data: body)) + } -try app.group(myErrorMiddleware) { routes in - let echoProvider = EchoServiceProvider { req in - makeEchoService() - } - let accountProvider = AccountServiceProvider { req in - makeAccountService() + app.group(myErrorMiddleware) { routes in + configureAccountServiceProtocol(transport: VaporTransport(router: routes) { _ in + makeAccountService() + }) + configureEchoServiceProtocol(transport: VaporTransport(router: routes) { _ in + makeEchoService() + }) + } + + try await app.execute() + try await app.asyncShutdown() } - try routes.register(collection: echoProvider) - try routes.register(collection: accountProvider) } - -try app.run() diff --git a/example/test.sh b/example/test.sh index e956ff1..afc35cb 100755 --- a/example/test.sh +++ b/example/test.sh @@ -2,15 +2,15 @@ set -o pipefail swift package --allow-writing-to-package-directory codegen -swift build -c release +swift build APP=${1:-} case ${APP} in "vapor") - .build/release/VaporServer & + .build/debug/VaporServer & ;; "hummingbird") - .build/release/HBServer & + .build/debug/HBServer & ;; *) echo "$0 " @@ -23,8 +23,10 @@ function finally { } trap finally EXIT -sleep 0.2 +until curl -s -o /dev/null http://localhost:8080/; do + sleep 0.1 +done -.build/release/Client +.build/debug/Client cd TSClient npm install && npm run run