From 36741ff2286f65c0800fe3eaa34b4ee68b414c85 Mon Sep 17 00:00:00 2001 From: Iceman Date: Tue, 10 Dec 2024 17:46:54 +0900 Subject: [PATCH 1/9] New target architecture --- Package.resolved | 247 +++++++++++++++++- Package.swift | 46 +++- Sources/CallableKit/Macros.swift | 5 + Sources/CallableKit/ServiceTransport.swift | 7 + .../HummingbirdTransport.swift | 24 ++ Sources/CallableKitMacros/CallableMacro.swift | 12 + Sources/CallableKitMacros/MacroMain.swift | 8 + .../VaporTransport.swift | 30 +++ Sources/Codegen/Codegen.swift | 4 +- .../GenerateHBProvider.swift | 0 .../GenerateMiddleware.swift | 0 .../GenerateSwiftClient.swift | 0 .../GenerateTS/GenerateTS+commonlib.swift | 0 .../GenerateTSClient.swift | 0 .../GenerateVaporProvider.swift | 0 .../Generator.swift | 0 .../{CallableKit => CodegenImpl}/Runner.swift | 0 .../ServiceProtocolScanner.swift | 0 .../{CallableKit => CodegenImpl}/Utils.swift | 0 example/Package.resolved | 77 +++--- example/Package.swift | 37 ++- example/Sources/Client/Gen/Echo.gen.swift | 24 ++ .../Sources/HBServer/Gen/Account.gen.swift | 4 +- example/Sources/HBServer/Gen/Echo.gen.swift | 56 +++- .../Gen/HBToServiceBridgeProtocol.gen.swift | 28 +- .../Sources/VaporServer/Gen/Echo.gen.swift | 55 ++++ 26 files changed, 583 insertions(+), 81 deletions(-) create mode 100644 Sources/CallableKit/Macros.swift create mode 100644 Sources/CallableKit/ServiceTransport.swift create mode 100644 Sources/CallableKitHummingbirdTransport/HummingbirdTransport.swift create mode 100644 Sources/CallableKitMacros/CallableMacro.swift create mode 100644 Sources/CallableKitMacros/MacroMain.swift create mode 100644 Sources/CallableKitVaporTransport/VaporTransport.swift rename Sources/{CallableKit => CodegenImpl}/GenerateHBProvider.swift (100%) rename Sources/{CallableKit => CodegenImpl}/GenerateMiddleware.swift (100%) rename Sources/{CallableKit => CodegenImpl}/GenerateSwiftClient.swift (100%) rename Sources/{CallableKit => CodegenImpl}/GenerateTS/GenerateTS+commonlib.swift (100%) rename Sources/{CallableKit => CodegenImpl}/GenerateTSClient.swift (100%) rename Sources/{CallableKit => CodegenImpl}/GenerateVaporProvider.swift (100%) rename Sources/{CallableKit => CodegenImpl}/Generator.swift (100%) rename Sources/{CallableKit => CodegenImpl}/Runner.swift (100%) rename Sources/{CallableKit => CodegenImpl}/ServiceProtocolScanner.swift (100%) rename Sources/{CallableKit => CodegenImpl}/Utils.swift (100%) 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..c64d090 100644 --- a/Package.swift +++ b/Package.swift @@ -1,36 +1,68 @@ -// 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"]), ], 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/Macros.swift b/Sources/CallableKit/Macros.swift new file mode 100644 index 0000000..45ecce3 --- /dev/null +++ b/Sources/CallableKit/Macros.swift @@ -0,0 +1,5 @@ +@attached( + peer, + names: arbitrary +) +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..7589b85 --- /dev/null +++ b/Sources/CallableKit/ServiceTransport.swift @@ -0,0 +1,7 @@ +public protocol ServiceTransport { + associatedtype Service + func register( + path: String, + methodSelector: @escaping @Sendable (Service.Type) -> (Service) -> (Request) async throws -> Response + ) +} diff --git a/Sources/CallableKitHummingbirdTransport/HummingbirdTransport.swift b/Sources/CallableKitHummingbirdTransport/HummingbirdTransport.swift new file mode 100644 index 0000000..51e513e --- /dev/null +++ b/Sources/CallableKitHummingbirdTransport/HummingbirdTransport.swift @@ -0,0 +1,24 @@ +import CallableKit +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 context.requestDecoder.decode(Request.self, from: request, context: context) + let service = serviceBuilder(request, context) + let serviceResponse = try await methodSelector(Service.self)(service)(serviceRequest) + return try context.responseEncoder.encode(serviceResponse, from: request, context: context) + } + } +} diff --git a/Sources/CallableKitMacros/CallableMacro.swift b/Sources/CallableKitMacros/CallableMacro.swift new file mode 100644 index 0000000..4344d34 --- /dev/null +++ b/Sources/CallableKitMacros/CallableMacro.swift @@ -0,0 +1,12 @@ +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] { + [] + } +} 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..9730bbc --- /dev/null +++ b/Sources/CallableKitVaporTransport/VaporTransport.swift @@ -0,0 +1,30 @@ +import CallableKit +import Vapor + +public struct VaporTransport: ServiceTransport { + public init(router: Router, serviceBuilder: @escaping @Sendable (Request) -> Service) { + self.router = router + self.serviceBuilder = serviceBuilder + } + + public var router: Router + public var serviceBuilder: @Sendable (Request) -> Service + + public func register( + path: String, + methodSelector: @escaping @Sendable (Service.Type) -> (Service) -> (Request) async throws -> Response + ) { + router.post(PathComponent(stringLiteral: path)) { [serviceBuilder] request in + let serviceRequest = try request.content.decode(Request.self) + let service = serviceBuilder(request) + let serviceResponse = try await methodSelector(Service.self)(service)(serviceRequest) + + var buffer = request.byteBufferAllocator.buffer(capacity: 0) + var headers = HTTPHeaders() + let encoder = try ContentConfiguration.global.requireEncoder(for: .json) + try encoder.encode(serviceResponse, to: &buffer, headers: &headers) + + return Vapor.Response(status: .ok, headers: headers, body: .init(buffer: buffer)) + } + } +} diff --git a/Sources/Codegen/Codegen.swift b/Sources/Codegen/Codegen.swift index cec4a2b..392aca4 100644 --- a/Sources/Codegen/Codegen.swift +++ b/Sources/Codegen/Codegen.swift @@ -1,5 +1,5 @@ import ArgumentParser -import CallableKit +import CodegenImpl import Foundation @main struct Codegen: ParsableCommand { @@ -45,7 +45,7 @@ import Foundation } } -extension URL: ExpressibleByArgument { +extension URL: ArgumentParser.ExpressibleByArgument { public init?(argument: String) { self = URL(fileURLWithPath: argument) } diff --git a/Sources/CallableKit/GenerateHBProvider.swift b/Sources/CodegenImpl/GenerateHBProvider.swift similarity index 100% rename from Sources/CallableKit/GenerateHBProvider.swift rename to Sources/CodegenImpl/GenerateHBProvider.swift diff --git a/Sources/CallableKit/GenerateMiddleware.swift b/Sources/CodegenImpl/GenerateMiddleware.swift similarity index 100% rename from Sources/CallableKit/GenerateMiddleware.swift rename to Sources/CodegenImpl/GenerateMiddleware.swift 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/GenerateVaporProvider.swift b/Sources/CodegenImpl/GenerateVaporProvider.swift similarity index 100% rename from Sources/CallableKit/GenerateVaporProvider.swift rename to Sources/CodegenImpl/GenerateVaporProvider.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 100% rename from Sources/CallableKit/Runner.swift rename to Sources/CodegenImpl/Runner.swift 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..6d0d155 100644 --- a/example/Package.resolved +++ b/example/Package.resolved @@ -1,4 +1,5 @@ { + "originHash" : "543649a938992cad2bc79245271956b22892b0dad095b3021a2d1e3b29afe9b1", "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,8 +222,8 @@ "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" } }, { @@ -243,6 +235,15 @@ "version" : "510.0.1" } }, + { + "identity" : "swift-system", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-system.git", + "state" : { + "revision" : "c8a44d836fe7913603e246acab7c528c2e780168", + "version" : "1.4.0" + } + }, { "identity" : "swifttypereader", "kind" : "remoteSourceControl", @@ -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..9c47a03 100644 --- a/example/Package.swift +++ b/example/Package.swift @@ -1,14 +1,35 @@ -// 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( @@ -28,9 +49,7 @@ let package = Package( .product(name: "Vapor", package: "vapor"), "Service", ], - swiftSettings: [ - .unsafeFlags(["-strict-concurrency=complete"]), - ] + swiftSettings: swiftSettings() ), .executableTarget( name: "HBServer", @@ -38,9 +57,7 @@ let package = Package( .product(name: "Hummingbird", package: "hummingbird"), "Service", ], - swiftSettings: [ - .unsafeFlags(["-strict-concurrency=complete"]), - ] + swiftSettings: swiftSettings() ), .executableTarget( name: "Client", diff --git a/example/Sources/Client/Gen/Echo.gen.swift b/example/Sources/Client/Gen/Echo.gen.swift index 2c423de..227228e 100644 --- a/example/Sources/Client/Gen/Echo.gen.swift +++ b/example/Sources/Client/Gen/Echo.gen.swift @@ -44,3 +44,27 @@ extension StubClientProtocol { EchoServiceStub(client: self) } } + +protocol ServiceTransport { + associatedtype Service + func register( + path: String, + methodSelector: @escaping @Sendable (Service.Type) -> (Service) -> (Request) async throws -> Response + ) +} + +func configureEcho( + transport: some ServiceTransport +) { + transport.register(path: "Echo/hello", methodSelector: { $0.hello }) + transport.register(path: "Echo/tommorow", methodSelector: { $0.tommorow }) +} + +struct URLSessionTransport: ServiceTransport { + func register( + path: String, + methodSelector: @escaping @Sendable (Service.Type) -> (Service) -> (Request) async throws -> Response + ) where Request : Decodable, Response : Encodable { + + } +} diff --git a/example/Sources/HBServer/Gen/Account.gen.swift b/example/Sources/HBServer/Gen/Account.gen.swift index c54c16f..a194f75 100644 --- a/example/Sources/HBServer/Gen/Account.gen.swift +++ b/example/Sources/HBServer/Gen/Account.gen.swift @@ -3,8 +3,8 @@ import Hummingbird func configureAccountService( bridge: some HBToServiceBridgeProtocol = .default, - builder serviceBuilder: @escaping @Sendable (HBRequest) async throws -> some AccountServiceProtocol, - router: some HBRouterMethods + builder serviceBuilder: @escaping @Sendable (Request) async throws -> some AccountServiceProtocol, + router: some RouterMethods ) { 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 index 39e1a6c..a29ec36 100644 --- a/example/Sources/HBServer/Gen/Echo.gen.swift +++ b/example/Sources/HBServer/Gen/Echo.gen.swift @@ -3,8 +3,8 @@ import Hummingbird func configureEchoService( bridge: some HBToServiceBridgeProtocol = .default, - builder serviceBuilder: @escaping @Sendable (HBRequest) async throws -> some EchoServiceProtocol, - router: some HBRouterMethods + builder serviceBuilder: @escaping @Sendable (Request) async throws -> some EchoServiceProtocol, + router: some RouterMethods ) { router.post("Echo/hello", use: bridge.makeHandler(serviceBuilder, { $0.hello })) router.post("Echo/tommorow", use: bridge.makeHandler(serviceBuilder, { $0.tommorow })) @@ -16,4 +16,56 @@ func configureEchoService( 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 })) + + router.post(RouterPath.init(stringLiteral: "foo")) { (request: Request, context) in + return "" + } +} + + +protocol ServiceTransport { + associatedtype Service + func register( + path: String, + methodSelector: @escaping @Sendable (Service.Type) -> (Service) -> (Request) async throws -> Response + ) +} + +func configureEcho( + transport: some ServiceTransport +) { + transport.register(path: "Echo/hello", methodSelector: { $0.hello }) + transport.register(path: "Echo/tommorow", methodSelector: { $0.tommorow }) +} + +import Service +import Foundation + +func main() async throws { + let router = Router() + + configureEcho(transport: HummingbirdTransport(router: router) { _, _ in + makeEchoService() + }) + + + let app = Application(router: router) + try await app.run() +} + +struct HummingbirdTransport: ServiceTransport { + var router: Router + var serviceBuilder: @Sendable (Request, Router.Context) -> Service + + 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 context.requestDecoder.decode(Request.self, from: request, context: context) + let service = serviceBuilder(request, context) + let serviceResponse = try await methodSelector(Service.self)(service)(serviceRequest) + return try context.responseEncoder.encode(serviceResponse, from: request, context: context) + } + } } diff --git a/example/Sources/HBServer/Gen/HBToServiceBridgeProtocol.gen.swift b/example/Sources/HBServer/Gen/HBToServiceBridgeProtocol.gen.swift index ae3ecf1..383b2c7 100644 --- a/example/Sources/HBServer/Gen/HBToServiceBridgeProtocol.gen.swift +++ b/example/Sources/HBServer/Gen/HBToServiceBridgeProtocol.gen.swift @@ -2,36 +2,36 @@ import Hummingbird protocol HBToServiceBridgeProtocol { func makeHandler( - _ serviceBuilder: @Sendable @escaping (HBRequest) async throws -> Service, + _ serviceBuilder: @Sendable @escaping (Request) async throws -> Service, _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> (Req) async throws -> Res - ) -> @Sendable (HBRequest) async throws -> HBResponse + ) -> @Sendable (Request) async throws -> Response where Req: Decodable & Sendable, Res: Encodable & Sendable func makeHandler( - _ serviceBuilder: @Sendable @escaping (HBRequest) async throws -> Service, + _ serviceBuilder: @Sendable @escaping (Request) async throws -> Service, _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> () async throws -> Res - ) -> @Sendable (HBRequest) async throws -> HBResponse + ) -> @Sendable (Request) async throws -> Response where Res: Encodable & Sendable func makeHandler( - _ serviceBuilder: @Sendable @escaping (HBRequest) async throws -> Service, + _ serviceBuilder: @Sendable @escaping (Request) async throws -> Service, _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> (Req) async throws -> Void - ) -> @Sendable (HBRequest) async throws -> HBResponse + ) -> @Sendable (Request) async throws -> Response where Req: Decodable & Sendable func makeHandler( - _ serviceBuilder: @Sendable @escaping (HBRequest) async throws -> Service, + _ serviceBuilder: @Sendable @escaping (Request) async throws -> Service, _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> () async throws -> Void - ) -> @Sendable (HBRequest) async throws -> HBResponse + ) -> @Sendable (Request) async throws -> Response } private struct _Empty: Codable, Sendable {} extension HBToServiceBridgeProtocol { func makeHandler( - _ serviceBuilder: @Sendable @escaping (HBRequest) async throws -> Service, + _ serviceBuilder: @Sendable @escaping (Request) async throws -> Service, _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> () async throws -> Res - ) -> @Sendable (HBRequest) async throws -> HBResponse + ) -> @Sendable (Request) async throws -> Response where Res: Encodable & Sendable { makeHandler(serviceBuilder) { (serviceType: Service.Type) in @@ -44,9 +44,9 @@ extension HBToServiceBridgeProtocol { } func makeHandler( - _ serviceBuilder: @Sendable @escaping (HBRequest) async throws -> Service, + _ serviceBuilder: @Sendable @escaping (Request) async throws -> Service, _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> (Req) async throws -> Void - ) -> @Sendable (HBRequest) async throws -> HBResponse + ) -> @Sendable (Request) async throws -> Response where Req: Decodable & Sendable { makeHandler(serviceBuilder) { (serviceType: Service.Type) in @@ -60,9 +60,9 @@ extension HBToServiceBridgeProtocol { } func makeHandler( - _ serviceBuilder: @Sendable @escaping (HBRequest) async throws -> Service, + _ serviceBuilder: @Sendable @escaping (Request) async throws -> Service, _ methodSelector: @Sendable @escaping (Service.Type) -> (Service) -> () async throws -> Void - ) -> @Sendable (HBRequest) async throws -> HBResponse + ) -> @Sendable (Request) async throws -> Response { makeHandler(serviceBuilder) { (serviceType: Service.Type) in { (service: Service) in diff --git a/example/Sources/VaporServer/Gen/Echo.gen.swift b/example/Sources/VaporServer/Gen/Echo.gen.swift index dcd649b..dea23ef 100644 --- a/example/Sources/VaporServer/Gen/Echo.gen.swift +++ b/example/Sources/VaporServer/Gen/Echo.gen.swift @@ -24,3 +24,58 @@ struct EchoServiceProvider { + associatedtype Service + func register( + path: String, + methodSelector: @escaping @Sendable (Service.Type) -> (Service) -> (Request) async throws -> Response + ) +} + +func configureEcho( + transport: some ServiceTransport +) { + transport.register(path: "Echo/hello", methodSelector: { $0.hello }) + transport.register(path: "Echo/tommorow", methodSelector: { $0.tommorow }) +} + +extension ServiceTransport { + +} + +import Service +import Foundation + +func main() async throws { + let app = try await Application.make() + + configureEcho(transport: VaporTransport(router: app.routes) { _ in + makeEchoService() + }) + + try await app.execute() +} + +struct VaporTransport: ServiceTransport { + var router: Router + var serviceBuilder: @Sendable (Request) -> Service + + func register( + path: String, + methodSelector: @escaping @Sendable (Service.Type) -> (Service) -> (Request) async throws -> Response + ) { + router.post(PathComponent(stringLiteral: path)) { [serviceBuilder] request in + let serviceRequest = try request.content.decode(Request.self) + let service = serviceBuilder(request) + let serviceResponse = try await methodSelector(Service.self)(service)(serviceRequest) + + var buffer = request.byteBufferAllocator.buffer(capacity: 0) + var headers = HTTPHeaders() + let encoder = try ContentConfiguration.global.requireEncoder(for: .json) + try encoder.encode(serviceResponse, to: &buffer, headers: &headers) + + return Vapor.Response(status: .ok, headers: headers, body: .init(buffer: buffer)) + } + } +} From 5c5e46c19b9e16a1ec37325251b543327dfecf55 Mon Sep 17 00:00:00 2001 From: Iceman Date: Wed, 11 Dec 2024 16:47:28 +0900 Subject: [PATCH 2/9] Basic Callable macro implementation --- Sources/CallableKit/Macros.swift | 2 +- Sources/CallableKitMacros/CallableMacro.swift | 33 ++++++++++++++++++- example/Package.resolved | 8 ++--- example/Package.swift | 5 ++- example/Sources/APIDefinition/Account.swift | 2 ++ example/Sources/APIDefinition/Echo.swift | 2 ++ 6 files changed, 45 insertions(+), 7 deletions(-) diff --git a/Sources/CallableKit/Macros.swift b/Sources/CallableKit/Macros.swift index 45ecce3..5b1b273 100644 --- a/Sources/CallableKit/Macros.swift +++ b/Sources/CallableKit/Macros.swift @@ -1,5 +1,5 @@ @attached( peer, - names: arbitrary + names: prefixed(configure) ) public macro Callable() = #externalMacro(module: "CallableKitMacros", type: "CallableMacro") diff --git a/Sources/CallableKitMacros/CallableMacro.swift b/Sources/CallableKitMacros/CallableMacro.swift index 4344d34..9ad4ac5 100644 --- a/Sources/CallableKitMacros/CallableMacro.swift +++ b/Sources/CallableKitMacros/CallableMacro.swift @@ -7,6 +7,37 @@ public struct CallableMacro: PeerMacro { 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/example/Package.resolved b/example/Package.resolved index 6d0d155..a5c1c5a 100644 --- a/example/Package.resolved +++ b/example/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "543649a938992cad2bc79245271956b22892b0dad095b3021a2d1e3b29afe9b1", + "originHash" : "72222c8a5eb70b05fc61636aee7c555f6591c06635ec1a72b200e8f1ece362cb", "pins" : [ { "identity" : "async-http-client", @@ -229,10 +229,10 @@ { "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" } }, { diff --git a/example/Package.swift b/example/Package.swift index 9c47a03..acb83b5 100644 --- a/example/Package.swift +++ b/example/Package.swift @@ -34,7 +34,10 @@ let package = Package( targets: [ .target( name: "APIDefinition", - dependencies: ["OtherDependency"] + dependencies: [ + .product(name: "CallableKit", package: "CallableKit"), + "OtherDependency", + ] ), .target(name: "OtherDependency"), .target( 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 From 838c570c06852ab49be17e0e0c91202a6bc0844a Mon Sep 17 00:00:00 2001 From: Iceman Date: Wed, 11 Dec 2024 18:44:58 +0900 Subject: [PATCH 3/9] use new HumminbirdTransport --- Package.swift | 2 + Sources/CallableKit/ServiceTransport.swift | 61 +++++++++++++++ example/Package.swift | 2 + .../Sources/HBServer/Gen/Account.gen.swift | 10 --- example/Sources/HBServer/Gen/Echo.gen.swift | 71 ----------------- .../Gen/HBJSONServiceBridge.gen.swift | 56 -------------- .../Gen/HBToServiceBridgeProtocol.gen.swift | 76 ------------------- example/Sources/HBServer/Main.swift | 60 ++++++++------- 8 files changed, 96 insertions(+), 242 deletions(-) delete mode 100644 example/Sources/HBServer/Gen/Account.gen.swift delete mode 100644 example/Sources/HBServer/Gen/Echo.gen.swift delete mode 100644 example/Sources/HBServer/Gen/HBJSONServiceBridge.gen.swift delete mode 100644 example/Sources/HBServer/Gen/HBToServiceBridgeProtocol.gen.swift diff --git a/Package.swift b/Package.swift index c64d090..eba8dc8 100644 --- a/Package.swift +++ b/Package.swift @@ -9,6 +9,8 @@ let package = Package( 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"), diff --git a/Sources/CallableKit/ServiceTransport.swift b/Sources/CallableKit/ServiceTransport.swift index 7589b85..16c406b 100644 --- a/Sources/CallableKit/ServiceTransport.swift +++ b/Sources/CallableKit/ServiceTransport.swift @@ -1,7 +1,68 @@ 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 + { () -> 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 + { () -> Void in + try await methodSelector(serviceType)(service)() + } + } + } + } +} diff --git a/example/Package.swift b/example/Package.swift index acb83b5..7d88e48 100644 --- a/example/Package.swift +++ b/example/Package.swift @@ -50,6 +50,7 @@ let package = Package( name: "VaporServer", dependencies: [ .product(name: "Vapor", package: "vapor"), + .product(name: "CallableKitVaporTransport", package: "CallableKit"), "Service", ], swiftSettings: swiftSettings() @@ -58,6 +59,7 @@ let package = Package( name: "HBServer", dependencies: [ .product(name: "Hummingbird", package: "hummingbird"), + .product(name: "CallableKitHummingbirdTransport", package: "CallableKit"), "Service", ], swiftSettings: swiftSettings() diff --git a/example/Sources/HBServer/Gen/Account.gen.swift b/example/Sources/HBServer/Gen/Account.gen.swift deleted file mode 100644 index a194f75..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 (Request) async throws -> some AccountServiceProtocol, - router: some RouterMethods -) { - 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 a29ec36..0000000 --- a/example/Sources/HBServer/Gen/Echo.gen.swift +++ /dev/null @@ -1,71 +0,0 @@ -import APIDefinition -import Hummingbird - -func configureEchoService( - bridge: some HBToServiceBridgeProtocol = .default, - builder serviceBuilder: @escaping @Sendable (Request) async throws -> some EchoServiceProtocol, - router: some RouterMethods -) { - 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 })) - - router.post(RouterPath.init(stringLiteral: "foo")) { (request: Request, context) in - return "" - } -} - - -protocol ServiceTransport { - associatedtype Service - func register( - path: String, - methodSelector: @escaping @Sendable (Service.Type) -> (Service) -> (Request) async throws -> Response - ) -} - -func configureEcho( - transport: some ServiceTransport -) { - transport.register(path: "Echo/hello", methodSelector: { $0.hello }) - transport.register(path: "Echo/tommorow", methodSelector: { $0.tommorow }) -} - -import Service -import Foundation - -func main() async throws { - let router = Router() - - configureEcho(transport: HummingbirdTransport(router: router) { _, _ in - makeEchoService() - }) - - - let app = Application(router: router) - try await app.run() -} - -struct HummingbirdTransport: ServiceTransport { - var router: Router - var serviceBuilder: @Sendable (Request, Router.Context) -> Service - - 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 context.requestDecoder.decode(Request.self, from: request, context: context) - let service = serviceBuilder(request, context) - let serviceResponse = try await methodSelector(Service.self)(service)(serviceRequest) - return try context.responseEncoder.encode(serviceResponse, from: request, context: context) - } - } -} 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 383b2c7..0000000 --- a/example/Sources/HBServer/Gen/HBToServiceBridgeProtocol.gen.swift +++ /dev/null @@ -1,76 +0,0 @@ -import Hummingbird - -protocol HBToServiceBridgeProtocol { - 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 HBToServiceBridgeProtocol { - 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/HBServer/Main.swift b/example/Sources/HBServer/Main.swift index 834b0fe..a043cb7 100644 --- a/example/Sources/HBServer/Main.swift +++ b/example/Sources/HBServer/Main.swift @@ -1,45 +1,47 @@ -import Foundation +import APIDefinition +import CallableKitHummingbirdTransport +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))) + return try context.responseEncoder.encode(errorFrame, from: input, context: context) } } } @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.run() } } From 13ce40d357e47d7516fba28d7fabbef5028841d8 Mon Sep 17 00:00:00 2001 From: Iceman Date: Wed, 11 Dec 2024 18:49:15 +0900 Subject: [PATCH 4/9] use new VaporTransport --- .../VaporTransport.swift | 6 +- .../Plugins/CodegenPlugin/CodegenPlugin.swift | 2 - .../Sources/VaporServer/Gen/Account.gen.swift | 17 ---- .../Sources/VaporServer/Gen/Echo.gen.swift | 81 ------------------- .../Gen/VaporJSONServiceBridge.gen.swift | 53 ------------ .../VaporToServiceBridgeProtocol.gen.swift | 76 ----------------- example/Sources/VaporServer/main.swift | 55 +++++++------ 7 files changed, 32 insertions(+), 258 deletions(-) delete mode 100644 example/Sources/VaporServer/Gen/Account.gen.swift delete mode 100644 example/Sources/VaporServer/Gen/Echo.gen.swift delete mode 100644 example/Sources/VaporServer/Gen/VaporJSONServiceBridge.gen.swift delete mode 100644 example/Sources/VaporServer/Gen/VaporToServiceBridgeProtocol.gen.swift diff --git a/Sources/CallableKitVaporTransport/VaporTransport.swift b/Sources/CallableKitVaporTransport/VaporTransport.swift index 9730bbc..f97f3af 100644 --- a/Sources/CallableKitVaporTransport/VaporTransport.swift +++ b/Sources/CallableKitVaporTransport/VaporTransport.swift @@ -1,13 +1,13 @@ import CallableKit import Vapor -public struct VaporTransport: ServiceTransport { - public init(router: Router, serviceBuilder: @escaping @Sendable (Request) -> Service) { +public struct VaporTransport: ServiceTransport { + public init(router: any RoutesBuilder, serviceBuilder: @escaping @Sendable (Request) -> Service) { self.router = router self.serviceBuilder = serviceBuilder } - public var router: Router + public var router: any RoutesBuilder public var serviceBuilder: @Sendable (Request) -> Service public func register( 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/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 dea23ef..0000000 --- a/example/Sources/VaporServer/Gen/Echo.gen.swift +++ /dev/null @@ -1,81 +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 })) - } - } -} - -protocol ServiceTransport { - associatedtype Service - func register( - path: String, - methodSelector: @escaping @Sendable (Service.Type) -> (Service) -> (Request) async throws -> Response - ) -} - -func configureEcho( - transport: some ServiceTransport -) { - transport.register(path: "Echo/hello", methodSelector: { $0.hello }) - transport.register(path: "Echo/tommorow", methodSelector: { $0.tommorow }) -} - -extension ServiceTransport { - -} - -import Service -import Foundation - -func main() async throws { - let app = try await Application.make() - - configureEcho(transport: VaporTransport(router: app.routes) { _ in - makeEchoService() - }) - - try await app.execute() -} - -struct VaporTransport: ServiceTransport { - var router: Router - var serviceBuilder: @Sendable (Request) -> Service - - func register( - path: String, - methodSelector: @escaping @Sendable (Service.Type) -> (Service) -> (Request) async throws -> Response - ) { - router.post(PathComponent(stringLiteral: path)) { [serviceBuilder] request in - let serviceRequest = try request.content.decode(Request.self) - let service = serviceBuilder(request) - let serviceResponse = try await methodSelector(Service.self)(service)(serviceRequest) - - var buffer = request.byteBufferAllocator.buffer(capacity: 0) - var headers = HTTPHeaders() - let encoder = try ContentConfiguration.global.requireEncoder(for: .json) - try encoder.encode(serviceResponse, to: &buffer, headers: &headers) - - return Vapor.Response(status: .ok, headers: headers, body: .init(buffer: buffer)) - } - } -} 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..8f1f221 100644 --- a/example/Sources/VaporServer/main.swift +++ b/example/Sources/VaporServer/main.swift @@ -1,34 +1,37 @@ +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 routes.register(collection: echoProvider) - try routes.register(collection: accountProvider) } - -try app.run() From 51174fda4a7deb366ad260b91d0a55dd6ae7a477 Mon Sep 17 00:00:00 2001 From: Iceman Date: Thu, 12 Dec 2024 11:12:09 +0900 Subject: [PATCH 5/9] fix small mistakes --- Sources/CallableKit/ServiceTransport.swift | 5 +++-- example/Sources/Client/Main.swift | 6 +++++- example/Sources/HBServer/Main.swift | 2 +- example/Sources/VaporServer/main.swift | 1 + 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Sources/CallableKit/ServiceTransport.swift b/Sources/CallableKit/ServiceTransport.swift index 16c406b..d73a689 100644 --- a/Sources/CallableKit/ServiceTransport.swift +++ b/Sources/CallableKit/ServiceTransport.swift @@ -46,7 +46,7 @@ extension ServiceTransport { ) { register(path: path) { (serviceType) in { (service: Service) in - { () -> Response in + { (_: _Empty) -> Response in return try await methodSelector(serviceType)(service)() } } @@ -59,8 +59,9 @@ extension ServiceTransport { ) { register(path: path) { (serviceType) in { (service: Service) in - { () -> Void in + { (_: _Empty) -> _Empty in try await methodSelector(serviceType)(service)() + return _Empty() } } } 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/Main.swift b/example/Sources/HBServer/Main.swift index a043cb7..f088426 100644 --- a/example/Sources/HBServer/Main.swift +++ b/example/Sources/HBServer/Main.swift @@ -42,6 +42,6 @@ struct ErrorMiddleware: MiddlewareProtocol { logger: logger ) - try await app.run() + try await app.runService() } } diff --git a/example/Sources/VaporServer/main.swift b/example/Sources/VaporServer/main.swift index 8f1f221..4743e39 100644 --- a/example/Sources/VaporServer/main.swift +++ b/example/Sources/VaporServer/main.swift @@ -33,5 +33,6 @@ import Service } try await app.execute() + try await app.asyncShutdown() } } From 8111a05e6f59f04f6d331a08dee492ed99d10298 Mon Sep 17 00:00:00 2001 From: Iceman Date: Thu, 12 Dec 2024 11:39:36 +0900 Subject: [PATCH 6/9] Fix backword compatibility --- .../HummingbirdTransport.swift | 19 +++++++++++-- .../VaporTransport.swift | 27 ++++++++++++++----- example/Sources/Client/Gen/Account.gen.swift | 1 + example/Sources/Client/Gen/Echo.gen.swift | 25 +---------------- example/Sources/HBServer/Main.swift | 5 +++- example/test.sh | 4 ++- 6 files changed, 47 insertions(+), 34 deletions(-) diff --git a/Sources/CallableKitHummingbirdTransport/HummingbirdTransport.swift b/Sources/CallableKitHummingbirdTransport/HummingbirdTransport.swift index 51e513e..af39840 100644 --- a/Sources/CallableKitHummingbirdTransport/HummingbirdTransport.swift +++ b/Sources/CallableKitHummingbirdTransport/HummingbirdTransport.swift @@ -1,4 +1,5 @@ import CallableKit +import Foundation import Hummingbird public struct HummingbirdTransport: ServiceTransport { @@ -15,10 +16,24 @@ public struct HummingbirdTransport: ServiceTrans methodSelector: @escaping @Sendable (Service.Type) -> (Service) -> (Request) async throws -> Response ) { router.post(RouterPath(path)) { [serviceBuilder] request, context in - let serviceRequest = try await context.requestDecoder.decode(Request.self, from: request, context: context) + 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) - return try context.responseEncoder.encode(serviceResponse, from: request, context: context) + 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/CallableKitVaporTransport/VaporTransport.swift b/Sources/CallableKitVaporTransport/VaporTransport.swift index f97f3af..70312a5 100644 --- a/Sources/CallableKitVaporTransport/VaporTransport.swift +++ b/Sources/CallableKitVaporTransport/VaporTransport.swift @@ -14,17 +14,32 @@ public struct VaporTransport: ServiceTransport { path: String, methodSelector: @escaping @Sendable (Service.Type) -> (Service) -> (Request) async throws -> Response ) { - router.post(PathComponent(stringLiteral: path)) { [serviceBuilder] request in - let serviceRequest = try request.content.decode(Request.self) + 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 buffer = request.byteBufferAllocator.buffer(capacity: 0) var headers = HTTPHeaders() - let encoder = try ContentConfiguration.global.requireEncoder(for: .json) - try encoder.encode(serviceResponse, to: &buffer, headers: &headers) + 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)) + 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/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 227228e..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 { @@ -44,27 +45,3 @@ extension StubClientProtocol { EchoServiceStub(client: self) } } - -protocol ServiceTransport { - associatedtype Service - func register( - path: String, - methodSelector: @escaping @Sendable (Service.Type) -> (Service) -> (Request) async throws -> Response - ) -} - -func configureEcho( - transport: some ServiceTransport -) { - transport.register(path: "Echo/hello", methodSelector: { $0.hello }) - transport.register(path: "Echo/tommorow", methodSelector: { $0.tommorow }) -} - -struct URLSessionTransport: ServiceTransport { - func register( - path: String, - methodSelector: @escaping @Sendable (Service.Type) -> (Service) -> (Request) async throws -> Response - ) where Request : Decodable, Response : Encodable { - - } -} diff --git a/example/Sources/HBServer/Main.swift b/example/Sources/HBServer/Main.swift index f088426..45b5a53 100644 --- a/example/Sources/HBServer/Main.swift +++ b/example/Sources/HBServer/Main.swift @@ -1,5 +1,6 @@ import APIDefinition import CallableKitHummingbirdTransport +import Foundation import Logging import Hummingbird import Service @@ -15,7 +16,9 @@ struct ErrorMiddleware: MiddlewareProtocol { } catch { context.logger.error("\(error)") let errorFrame = ErrorFrame(errorMessage: "\(error)") - return try context.responseEncoder.encode(errorFrame, from: input, context: context) + var response = try JSONEncoder().encode(errorFrame, from: input, context: context) + response.headers[.cacheControl] = "no-store" + return response } } } diff --git a/example/test.sh b/example/test.sh index e956ff1..58d7b74 100755 --- a/example/test.sh +++ b/example/test.sh @@ -23,7 +23,9 @@ 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 cd TSClient From cdb5812b1cd7388e15eb589022793bb15f89f784 Mon Sep 17 00:00:00 2001 From: Iceman Date: Thu, 12 Dec 2024 12:11:19 +0900 Subject: [PATCH 7/9] Remove server code generation --- Sources/Codegen/Codegen.swift | 12 - Sources/CodegenImpl/GenerateHBProvider.swift | 204 ----------------- Sources/CodegenImpl/GenerateMiddleware.swift | 58 ----- .../CodegenImpl/GenerateVaporProvider.swift | 208 ------------------ Sources/CodegenImpl/Runner.swift | 35 +-- 5 files changed, 1 insertion(+), 516 deletions(-) delete mode 100644 Sources/CodegenImpl/GenerateHBProvider.swift delete mode 100644 Sources/CodegenImpl/GenerateMiddleware.swift delete mode 100644 Sources/CodegenImpl/GenerateVaporProvider.swift diff --git a/Sources/Codegen/Codegen.swift b/Sources/Codegen/Codegen.swift index 392aca4..11eb7d4 100644 --- a/Sources/Codegen/Codegen.swift +++ b/Sources/Codegen/Codegen.swift @@ -6,15 +6,6 @@ import Foundation @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, diff --git a/Sources/CodegenImpl/GenerateHBProvider.swift b/Sources/CodegenImpl/GenerateHBProvider.swift deleted file mode 100644 index 4684cc7..0000000 --- a/Sources/CodegenImpl/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/CodegenImpl/GenerateMiddleware.swift b/Sources/CodegenImpl/GenerateMiddleware.swift deleted file mode 100644 index e1bee07..0000000 --- a/Sources/CodegenImpl/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/CodegenImpl/GenerateVaporProvider.swift b/Sources/CodegenImpl/GenerateVaporProvider.swift deleted file mode 100644 index b96e337..0000000 --- a/Sources/CodegenImpl/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/CodegenImpl/Runner.swift b/Sources/CodegenImpl/Runner.swift index 7783717..f35e132 100644 --- a/Sources/CodegenImpl/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, From f60634931ea5e6dc6fe6e036b139c98531798170 Mon Sep 17 00:00:00 2001 From: Iceman Date: Thu, 12 Dec 2024 14:32:48 +0900 Subject: [PATCH 8/9] Stop to fix the swift and node version --- .github/workflows/test.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) 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 From 6a88485efca8ee8d45cefdc32cf8b54776c6dc31 Mon Sep 17 00:00:00 2001 From: Iceman Date: Thu, 12 Dec 2024 14:59:11 +0900 Subject: [PATCH 9/9] Fix compile error on Linux and avoid slow test comile --- Sources/CallableKitMacros/CallableMacro.swift | 1 + example/test.sh | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Sources/CallableKitMacros/CallableMacro.swift b/Sources/CallableKitMacros/CallableMacro.swift index 9ad4ac5..3d5ff91 100644 --- a/Sources/CallableKitMacros/CallableMacro.swift +++ b/Sources/CallableKitMacros/CallableMacro.swift @@ -1,3 +1,4 @@ +import Foundation import SwiftSyntax import SwiftSyntaxMacros diff --git a/example/test.sh b/example/test.sh index 58d7b74..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 " @@ -27,6 +27,6 @@ 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