diff --git a/Package.swift b/Package.swift index 0d95980..ebf54d5 100644 --- a/Package.swift +++ b/Package.swift @@ -11,6 +11,7 @@ let package = Package( .library(name: "CallableKit", targets: ["CallableKit"]), .library(name: "CallableKitVaporTransport", targets: ["CallableKitVaporTransport"]), .library(name: "CallableKitHummingbirdTransport", targets: ["CallableKitHummingbirdTransport"]), + .library(name: "CallableKitURLSessionStub", targets: ["CallableKitURLSessionStub"]), ], dependencies: [ .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.2"), @@ -65,6 +66,12 @@ let package = Package( .product(name: "Hummingbird", package: "hummingbird"), "CallableKit", ] + ), + .target( + name: "CallableKitURLSessionStub", + dependencies: [ + "CallableKit", + ] ) ] ) diff --git a/Sources/CallableKit/CallableKitEmpty.swift b/Sources/CallableKit/CallableKitEmpty.swift new file mode 100644 index 0000000..804917e --- /dev/null +++ b/Sources/CallableKit/CallableKitEmpty.swift @@ -0,0 +1,3 @@ +@usableFromInline internal struct CallableKitEmpty: Codable { + @usableFromInline init() {} +} diff --git a/Sources/CallableKit/Macros.swift b/Sources/CallableKit/Macros.swift index 5b1b273..f59a169 100644 --- a/Sources/CallableKit/Macros.swift +++ b/Sources/CallableKit/Macros.swift @@ -1,5 +1,5 @@ @attached( peer, - names: prefixed(configure) + names: prefixed(configure), suffixed(Stub) ) public macro Callable() = #externalMacro(module: "CallableKitMacros", type: "CallableMacro") diff --git a/Sources/CallableKit/ServiceTransport.swift b/Sources/CallableKit/ServiceTransport.swift index d73a689..34e67f9 100644 --- a/Sources/CallableKit/ServiceTransport.swift +++ b/Sources/CallableKit/ServiceTransport.swift @@ -22,46 +22,43 @@ public protocol ServiceTransport { ) } -fileprivate struct _Empty: Codable, Sendable { -} - extension ServiceTransport { - public func register( + @inlinable 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 + { (request: Request) -> CallableKitEmpty in try await methodSelector(serviceType)(service)(request) - return _Empty() + return CallableKitEmpty() } } } } - public func register( + @inlinable public func register( path: String, methodSelector: @escaping @Sendable (Service.Type) -> (Service) -> () async throws -> Response ) { register(path: path) { (serviceType) in { (service: Service) in - { (_: _Empty) -> Response in + { (_: CallableKitEmpty) -> Response in return try await methodSelector(serviceType)(service)() } } } } - public func register( + @inlinable public func register( path: String, methodSelector: @escaping @Sendable (Service.Type) -> (Service) -> () async throws -> Void ) { register(path: path) { (serviceType) in { (service: Service) in - { (_: _Empty) -> _Empty in + { (_: CallableKitEmpty) -> CallableKitEmpty in try await methodSelector(serviceType)(service)() - return _Empty() + return CallableKitEmpty() } } } diff --git a/Sources/CallableKit/StubClientProtocol.swift b/Sources/CallableKit/StubClientProtocol.swift new file mode 100644 index 0000000..22b42c3 --- /dev/null +++ b/Sources/CallableKit/StubClientProtocol.swift @@ -0,0 +1,47 @@ +public protocol StubClientProtocol: Sendable { + func send( + path: String, + request: Request + ) async throws -> Response + + func send( + path: String, + request: Request + ) async throws + + func send( + path: String + ) async throws -> Response + + func send( + path: String + ) async throws +} + +extension StubClientProtocol { + @inlinable public func send( + path: String, + request: Request + ) async throws -> Response { + try await send(path: path, request: request) + } + + @inlinable public func send( + path: String, + request: Request + ) async throws { + _ = try await send(path: path, request: request) as CallableKitEmpty + } + + @inlinable public func send( + path: String + ) async throws -> Response { + try await send(path: path, request: CallableKitEmpty()) + } + + @inlinable public func send( + path: String + ) async throws { + _ = try await send(path: path, request: CallableKitEmpty()) + } +} diff --git a/Sources/CallableKitMacros/CallableMacro.swift b/Sources/CallableKitMacros/CallableMacro.swift index cd95446..2c259be 100644 --- a/Sources/CallableKitMacros/CallableMacro.swift +++ b/Sources/CallableKitMacros/CallableMacro.swift @@ -39,7 +39,32 @@ public struct CallableMacro: PeerMacro { } } - return [DeclSyntax(configureFunc)] + let stubStruct = try StructDeclSyntax("public struct \(raw: protocolName)Stub: \(raw: protocolName), Sendable") { + VariableDeclSyntax( + modifiers: [.init(name: .keyword(.private))], + .let, + name: "client" as PatternSyntax, + type: TypeAnnotationSyntax(type: "C" as TypeSyntax) + ) + try InitializerDeclSyntax("public init(client: C)") { + "self.client = client" + } + for function in functions { + function + .with(\.leadingTrivia, []) + .with(\.modifiers, [.init(name: .keyword(.public))]) + .with(\.body, CodeBlockSyntax { + if let param = function.signature.parameterClause.parameters.first { + let argName = param.secondName ?? param.firstName + #"return try await client.send(path: "\#(raw: serviceName)/\#(function.name)", request: \#(argName))"# + } else { + #"return try await client.send(path: "\#(raw: serviceName)/\#(function.name)")"# + } + }) + } + } + + return [DeclSyntax(configureFunc), DeclSyntax(stubStruct)] } } diff --git a/example/Sources/Client/Gen/FoundationHTTPStubClient.gen.swift b/Sources/CallableKitURLSessionStub/URLSessionStubClient.swift similarity index 65% rename from example/Sources/Client/Gen/FoundationHTTPStubClient.gen.swift rename to Sources/CallableKitURLSessionStub/URLSessionStubClient.swift index 3538078..ef3766e 100644 --- a/example/Sources/Client/Gen/FoundationHTTPStubClient.gen.swift +++ b/Sources/CallableKitURLSessionStub/URLSessionStubClient.swift @@ -1,42 +1,43 @@ -#if !DISABLE_FOUNDATION_NETWORKING +import CallableKit import Foundation #if canImport(FoundationNetworking) @preconcurrency import FoundationNetworking #endif -enum FoundationHTTPStubClientError: Error { - case unexpectedState - case unexpectedStatusCode(_ code: Int) -} +public struct URLSessionStubClient: StubClientProtocol { + public enum UnexpectedError: Error { + case state + case statusCode(_ code: Int) + } -struct FoundationHTTPStubResponseError: Error, CustomStringConvertible { - var path: String - var body: Data - var request: URLRequest - var response: HTTPURLResponse - var description: String { - "ResponseError. path=\(path), status=\(response.statusCode)" + public struct ResponseError: Error, CustomStringConvertible { + public var path: String + public var body: Data + public var request: URLRequest + public var response: HTTPURLResponse + public var description: String { + "ResponseError. path=\(path), status=\(response.statusCode)" + } } -} -final class FoundationHTTPStubClient: StubClientProtocol { - private let baseURL: URL - private let session: URLSession - private let onWillSendRequest: (@Sendable (inout URLRequest) async throws -> Void)? - private let mapResponseError: (@Sendable (FoundationHTTPStubResponseError) throws -> Never)? + public var baseURL: URL + public var session: URLSession + public var onWillSendRequest: (@Sendable (inout URLRequest) async throws -> Void)? + public var mapResponseError: (@Sendable (ResponseError) throws -> Never)? - init( + public init( baseURL: URL, + session: URLSession = .init(configuration: .ephemeral), onWillSendRequest: (@Sendable (inout URLRequest) async throws -> Void)? = nil, - mapResponseError: (@Sendable (FoundationHTTPStubResponseError) throws -> Never)? = nil + mapResponseError: (@Sendable (ResponseError) throws -> Never)? = nil ) { self.baseURL = baseURL - session = .init(configuration: .ephemeral) + self.session = session self.onWillSendRequest = onWillSendRequest self.mapResponseError = mapResponseError } - func send( + public func send( path: String, request: Req ) async throws -> Res { @@ -48,7 +49,7 @@ final class FoundationHTTPStubClient: StubClientProtocol { q.addValue("\(body.count)", forHTTPHeaderField: "Content-Length") q.httpBody = body - if let onWillSendRequest = onWillSendRequest { + if let onWillSendRequest { try await onWillSendRequest(&q) } let (data, urlResponse) = try await session.data(for: q) @@ -62,25 +63,25 @@ final class FoundationHTTPStubClient: StubClientProtocol { request: URLRequest ) throws -> Res { guard let urlResponse = response as? HTTPURLResponse else { - throw FoundationHTTPStubClientError.unexpectedState + throw UnexpectedError.state } if 200...299 ~= urlResponse.statusCode { return try makeDecoder().decode(Res.self, from: data) } else if 400...599 ~= urlResponse.statusCode { - let error = FoundationHTTPStubResponseError( + let error = ResponseError( path: path, body: data, request: request, response: urlResponse ) - if let mapResponseError = mapResponseError { + if let mapResponseError { try mapResponseError(error) } else { throw error } } else { - throw FoundationHTTPStubClientError.unexpectedStatusCode(urlResponse.statusCode) + throw UnexpectedError.statusCode(urlResponse.statusCode) } } } @@ -98,13 +99,9 @@ private func makeEncoder() -> JSONEncoder { } #if canImport(FoundationNetworking) -private class TaskBox: @unchecked Sendable { - var task: URLSessionTask? -} - extension URLSession { func data(for request: URLRequest) async throws -> (Data, URLResponse) { - let taskBox = TaskBox() + nonisolated(unsafe) var taskBox: URLSessionTask? return try await withTaskCancellationHandler(operation: { try await withCheckedThrowingContinuation { continuation in let task = dataTask(with: request) { data, response, error in @@ -115,13 +112,12 @@ extension URLSession { } return continuation.resume(returning: (data, response)) } - taskBox.task = task + taskBox = task task.resume() } }, onCancel: { - taskBox.task?.cancel() + taskBox?.cancel() }) } } #endif -#endif diff --git a/Sources/Codegen/Codegen.swift b/Sources/Codegen/Codegen.swift index 11eb7d4..fa8aa3b 100644 --- a/Sources/Codegen/Codegen.swift +++ b/Sources/Codegen/Codegen.swift @@ -3,9 +3,6 @@ import CodegenImpl import Foundation @main struct Codegen: ParsableCommand { - @Option(help: "generate client stub", completion: .directory) - var client_out: URL? - @Option(help: "generate client stub for typescript", completion: .directory) var ts_out: URL? @@ -24,7 +21,6 @@ import Foundation mutating func run() throws { try Runner( definitionDirectory: definitionDirectory, - clientOut: client_out, tsOut: ts_out, module: module, dependencies: dependency, diff --git a/Sources/CodegenImpl/GenerateSwiftClient.swift b/Sources/CodegenImpl/GenerateSwiftClient.swift deleted file mode 100644 index fe6ca64..0000000 --- a/Sources/CodegenImpl/GenerateSwiftClient.swift +++ /dev/null @@ -1,264 +0,0 @@ -import Foundation -import SwiftTypeReader - -struct GenerateSwiftClient { - var definitionModule: String - var srcDirectory: URL - var dstDirectory: URL - var dependencies: [URL] - - private func generateStubClient() -> String { - """ -public protocol StubClientProtocol: Sendable { - func send( - path: String, - request: Req - ) async throws -> Res - - func send( - path: String, - request: Req - ) async throws - - func send( - path: String - ) async throws -> Res - - func send( - path: String - ) async throws -} - -private struct _Empty: Codable {} - -extension StubClientProtocol { - public func send( - path: String, - request: Req - ) async throws -> Res { - try await send(path: path, request: request) - } - - public func send( - path: String, - request: Req - ) async throws { - _ = try await send(path: path, request: request) as _Empty - } - - public func send( - path: String - ) async throws -> Res { - try await send(path: path, request: _Empty()) - } - - public func send( - path: String - ) async throws { - _ = try await send(path: path, request: _Empty()) - } -} - -""" - } - - private func generateFoundationHTTPStubClient() -> String { -#""" -#if !DISABLE_FOUNDATION_NETWORKING -import Foundation -#if canImport(FoundationNetworking) -@preconcurrency import FoundationNetworking -#endif - -enum FoundationHTTPStubClientError: Error { - case unexpectedState - case unexpectedStatusCode(_ code: Int) -} - -struct FoundationHTTPStubResponseError: Error, CustomStringConvertible { - var path: String - var body: Data - var request: URLRequest - var response: HTTPURLResponse - var description: String { - "ResponseError. path=\(path), status=\(response.statusCode)" - } -} - -final class FoundationHTTPStubClient: StubClientProtocol { - private let baseURL: URL - private let session: URLSession - private let onWillSendRequest: (@Sendable (inout URLRequest) async throws -> Void)? - private let mapResponseError: (@Sendable (FoundationHTTPStubResponseError) throws -> Never)? - - init( - baseURL: URL, - onWillSendRequest: (@Sendable (inout URLRequest) async throws -> Void)? = nil, - mapResponseError: (@Sendable (FoundationHTTPStubResponseError) throws -> Never)? = nil - ) { - self.baseURL = baseURL - session = .init(configuration: .ephemeral) - self.onWillSendRequest = onWillSendRequest - self.mapResponseError = mapResponseError - } - - func send( - path: String, - request: Req - ) async throws -> Res { - var q = URLRequest(url: baseURL.appendingPathComponent(path)) - q.httpMethod = "POST" - - q.addValue("application/json", forHTTPHeaderField: "Content-Type") - let body = try makeEncoder().encode(request) - q.addValue("\(body.count)", forHTTPHeaderField: "Content-Length") - q.httpBody = body - - if let onWillSendRequest = onWillSendRequest { - try await onWillSendRequest(&q) - } - let (data, urlResponse) = try await session.data(for: q) - return try handleResponse(data: data, response: urlResponse, path: path, request: q) - } - - private func handleResponse( - data: Data, - response: URLResponse, - path: String, - request: URLRequest - ) throws -> Res { - guard let urlResponse = response as? HTTPURLResponse else { - throw FoundationHTTPStubClientError.unexpectedState - } - - if 200...299 ~= urlResponse.statusCode { - return try makeDecoder().decode(Res.self, from: data) - } else if 400...599 ~= urlResponse.statusCode { - let error = FoundationHTTPStubResponseError( - path: path, - body: data, - request: request, - response: urlResponse - ) - if let mapResponseError = mapResponseError { - try mapResponseError(error) - } else { - throw error - } - } else { - throw FoundationHTTPStubClientError.unexpectedStatusCode(urlResponse.statusCode) - } - } -} - -private func makeDecoder() -> JSONDecoder { - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .millisecondsSince1970 - return decoder -} - -private func makeEncoder() -> JSONEncoder { - let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .millisecondsSince1970 - return encoder -} - -#if canImport(FoundationNetworking) -private class TaskBox: @unchecked Sendable { - var task: URLSessionTask? -} - -extension URLSession { - func data(for request: URLRequest) async throws -> (Data, URLResponse) { - let taskBox = TaskBox() - return try await withTaskCancellationHandler(operation: { - try await withCheckedThrowingContinuation { continuation in - let task = dataTask(with: request) { data, response, error in - guard let data, let response else { - return continuation.resume( - throwing: error ?? URLError(.badServerResponse) - ) - } - return continuation.resume(returning: (data, response)) - } - taskBox.task = task - task.resume() - } - }, onCancel: { - taskBox.task?.cancel() - }) - } -} -#endif -#endif - -"""# - } - - private func processFile(file: Generator.InputFile) throws -> String? { - var stubs: [String] = [] - for stype in file.types.compactMap(ServiceProtocolScanner.scan) { - let stubTypeName = "\(stype.serviceName)ServiceStub" - stubs.append(""" -public struct \(stubTypeName): \(stype.name), Sendable { - private let client: C - public init(client: C) { - self.client = client - } - -\(stype.functions.map { f in - let retVal = f.response.map { " -> \($0.typeName)" } ?? "" - return """ - public func \(f.name)(\(f.request.map { "\($0.argOuterName.map({ "\($0) " }) ?? "")\($0.argName): \($0.typeName)" } ?? "")) async throws\(retVal) { - return try await client.send(path: "\(stype.serviceName)/\(f.name)"\(f.request.map { ", request: \($0.argName)" } ?? "")) - } -""" }.joined(separator: "\n")) -} - -extension StubClientProtocol { - public var \(stype.serviceName.lowercased()): \(stubTypeName) { - \(stubTypeName)(client: self) - } -} - -""") - } - if stubs.isEmpty { return nil } - - let imports: String = Set([definitionModule] + file.imports.map(\.moduleName)) - .sorted() - .map({ "import \($0)" }) - .joined(separator: "\n") - - return """ -\(imports) - -\(stubs.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: "StubClientProtocol.gen.swift", - content: generateStubClient() - )) - try write(file: .init( - name: "FoundationHTTPStubClient.gen.swift", - content: generateFoundationHTTPStubClient() - )) - - 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 f35e132..7ad2ad3 100644 --- a/Sources/CodegenImpl/Runner.swift +++ b/Sources/CodegenImpl/Runner.swift @@ -1,9 +1,8 @@ import Foundation public struct Runner { - public init(definitionDirectory: URL, clientOut: URL? = nil, tsOut: URL? = nil, module: String? = nil, dependencies: [URL] = [], nextjs: Bool = false) { + public init(definitionDirectory: URL, tsOut: URL? = nil, module: String? = nil, dependencies: [URL] = [], nextjs: Bool = false) { self.definitionDirectory = definitionDirectory - self.clientOut = clientOut self.tsOut = tsOut self.module = module self.dependencies = dependencies @@ -11,7 +10,6 @@ public struct Runner { } public var definitionDirectory: URL - public var clientOut: URL? public var tsOut: URL? public var module: String? public var dependencies: [URL] = [] @@ -22,15 +20,6 @@ public struct Runner { throw MessageError("definitionsModuleNameNotFound") } - if let clientOut { - try GenerateSwiftClient( - definitionModule: module, - srcDirectory: definitionDirectory, - dstDirectory: clientOut, - dependencies: dependencies - ).run() - } - if let tsOut { try GenerateTSClient( definitionModule: module, diff --git a/example/Package.swift b/example/Package.swift index 7d88e48..4dee88e 100644 --- a/example/Package.swift +++ b/example/Package.swift @@ -67,11 +67,10 @@ let package = Package( .executableTarget( name: "Client", dependencies: [ + .product(name: "CallableKitURLSessionStub", package: "CallableKit"), "APIDefinition", ], - swiftSettings: [ - .unsafeFlags(["-strict-concurrency=complete"]), - ] + swiftSettings: swiftSettings() ), .plugin( name: "CodegenPlugin", diff --git a/example/Plugins/CodegenPlugin/CodegenPlugin.swift b/example/Plugins/CodegenPlugin/CodegenPlugin.swift index c197f53..d77ac85 100644 --- a/example/Plugins/CodegenPlugin/CodegenPlugin.swift +++ b/example/Plugins/CodegenPlugin/CodegenPlugin.swift @@ -12,7 +12,6 @@ struct CodegenPlugin: CommandPlugin { let arguments: [String] = [ "Sources/APIDefinition", - "--client_out", "Sources/Client/Gen", "--ts_out", "TSClient/src/Gen", "--dependency", "Sources/OtherDependency", ] diff --git a/example/Sources/Client/Gen/Account.gen.swift b/example/Sources/Client/Gen/Account.gen.swift deleted file mode 100644 index cb35a06..0000000 --- a/example/Sources/Client/Gen/Account.gen.swift +++ /dev/null @@ -1,21 +0,0 @@ -import APIDefinition -import CallableKit -import Foundation -import OtherDependency - -public struct AccountServiceStub: AccountServiceProtocol, Sendable { - private let client: C - public init(client: C) { - self.client = client - } - - public func signin(request: AccountSignin.Request) async throws -> CodableResult> { - return try await client.send(path: "Account/signin", request: request) - } -} - -extension StubClientProtocol { - public var account: AccountServiceStub { - AccountServiceStub(client: self) - } -} diff --git a/example/Sources/Client/Gen/Echo.gen.swift b/example/Sources/Client/Gen/Echo.gen.swift deleted file mode 100644 index c96459e..0000000 --- a/example/Sources/Client/Gen/Echo.gen.swift +++ /dev/null @@ -1,47 +0,0 @@ -import APIDefinition -import CallableKit -import Foundation - -public struct EchoServiceStub: EchoServiceProtocol, Sendable { - private let client: C - public init(client: C) { - self.client = client - } - - public func hello(request: EchoHelloRequest) async throws -> EchoHelloResponse { - return try await client.send(path: "Echo/hello", request: request) - } - public func tommorow(from now: Date) async throws -> Date { - return try await client.send(path: "Echo/tommorow", request: now) - } - public func testTypicalEntity(request: User) async throws -> User { - return try await client.send(path: "Echo/testTypicalEntity", request: request) - } - public func testComplexType(request: TestComplexType.Request) async throws -> TestComplexType.Response { - return try await client.send(path: "Echo/testComplexType", request: request) - } - public func emptyRequestAndResponse() async throws { - return try await client.send(path: "Echo/emptyRequestAndResponse") - } - public func testTypeAliasToRawRepr(request: Student) async throws -> Student { - return try await client.send(path: "Echo/testTypeAliasToRawRepr", request: request) - } - public func testRawRepr(request: Student2) async throws -> Student2 { - return try await client.send(path: "Echo/testRawRepr", request: request) - } - public func testRawRepr2(request: Student3) async throws -> Student3 { - return try await client.send(path: "Echo/testRawRepr2", request: request) - } - public func testRawRepr3(request: Student4) async throws -> Student4 { - return try await client.send(path: "Echo/testRawRepr3", request: request) - } - public func testRawRepr4(request: Student5) async throws -> Student5 { - return try await client.send(path: "Echo/testRawRepr4", request: request) - } -} - -extension StubClientProtocol { - public var echo: EchoServiceStub { - EchoServiceStub(client: self) - } -} diff --git a/example/Sources/Client/Gen/StubClientProtocol.gen.swift b/example/Sources/Client/Gen/StubClientProtocol.gen.swift deleted file mode 100644 index e26a668..0000000 --- a/example/Sources/Client/Gen/StubClientProtocol.gen.swift +++ /dev/null @@ -1,49 +0,0 @@ -public protocol StubClientProtocol: Sendable { - func send( - path: String, - request: Req - ) async throws -> Res - - func send( - path: String, - request: Req - ) async throws - - func send( - path: String - ) async throws -> Res - - func send( - path: String - ) async throws -} - -private struct _Empty: Codable {} - -extension StubClientProtocol { - public func send( - path: String, - request: Req - ) async throws -> Res { - try await send(path: path, request: request) - } - - public func send( - path: String, - request: Req - ) async throws { - _ = try await send(path: path, request: request) as _Empty - } - - public func send( - path: String - ) async throws -> Res { - try await send(path: path, request: _Empty()) - } - - public func send( - path: String - ) async throws { - _ = try await send(path: path, request: _Empty()) - } -} diff --git a/example/Sources/Client/Main.swift b/example/Sources/Client/Main.swift index 351dd0a..f628c0d 100644 --- a/example/Sources/Client/Main.swift +++ b/example/Sources/Client/Main.swift @@ -1,5 +1,7 @@ -import Foundation import APIDefinition +import CallableKit +import CallableKitURLSessionStub +import Foundation struct ErrorFrame: Decodable, CustomStringConvertible, LocalizedError { var errorMessage: String @@ -10,7 +12,7 @@ struct ErrorFrame: Decodable, CustomStringConvertible, LocalizedError { @main struct Main { static func main() async throws { - let client: some StubClientProtocol = FoundationHTTPStubClient( + let client: some StubClientProtocol = URLSessionStubClient( baseURL: URL(string: "http://127.0.0.1:8080")!, onWillSendRequest: { request in request.addValue("Bearer xxxxxxxxxxxx", forHTTPHeaderField: "Authorization") @@ -23,24 +25,26 @@ struct ErrorFrame: Decodable, CustomStringConvertible, LocalizedError { } } ) + let echoClient = EchoServiceProtocolStub(client: client) + let accountClient = AccountServiceProtocolStub(client: client) do { - let res = try await client.echo.hello(request: .init(name: "Swift")) + let res = try await echoClient.hello(request: .init(name: "Swift")) print(res.message) } do { - let res = try await client.echo.tommorow(from: Date()) + let res = try await echoClient.tommorow(from: Date()) print(res) } do { - let res = try await client.echo.testTypicalEntity(request: .init(id: .init(rawValue: "id"), name: "name")) + let res = try await echoClient.testTypicalEntity(request: .init(id: .init(rawValue: "id"), name: "name")) dump(res) } do { - let res = try await client.echo.testComplexType(request: .init(a: .some(.init(x: [ + let res = try await echoClient.testComplexType(request: .init(a: .some(.init(x: [ .k(.init(x: .init(x: "hello"))), .i(100), .n, @@ -50,7 +54,7 @@ struct ErrorFrame: Decodable, CustomStringConvertible, LocalizedError { } do { - try await client.echo.emptyRequestAndResponse() + try await echoClient.emptyRequestAndResponse() } do { @@ -58,7 +62,7 @@ struct ErrorFrame: Decodable, CustomStringConvertible, LocalizedError { id: Student.ID(rawValue: "0001"), name: "taro" ) - let res = try await client.echo.testTypeAliasToRawRepr(request: student) + let res = try await echoClient.testTypeAliasToRawRepr(request: student) dump(res) } @@ -67,7 +71,7 @@ struct ErrorFrame: Decodable, CustomStringConvertible, LocalizedError { id: Student2.ID(rawValue: "0002"), name: "taro" ) - let res = try await client.echo.testRawRepr(request: student) + let res = try await echoClient.testRawRepr(request: student) dump(res) } @@ -76,7 +80,7 @@ struct ErrorFrame: Decodable, CustomStringConvertible, LocalizedError { id: Student3.ID(rawValue: .id("0003")), name: "taro" ) - let res = try await client.echo.testRawRepr2(request: student) + let res = try await echoClient.testRawRepr2(request: student) dump(res) } @@ -85,7 +89,7 @@ struct ErrorFrame: Decodable, CustomStringConvertible, LocalizedError { id: Student4.ID(rawValue: .init(rawValue: .id("0004"))), name: "taro" ) - let res = try await client.echo.testRawRepr3(request: student) + let res = try await echoClient.testRawRepr3(request: student) dump(res) } @@ -94,12 +98,12 @@ struct ErrorFrame: Decodable, CustomStringConvertible, LocalizedError { id: Student5.ID(rawValue: "0005"), name: "taro" ) - let res = try await client.echo.testRawRepr4(request: student) + let res = try await echoClient.testRawRepr4(request: student) dump(res) } do { - let res = try await client.account.signin(request: .init( + let res = try await accountClient.signin(request: .init( email: "example@example.com", password: "password" ))