Skip to main content
Module

x/pbkit/codegen/swift/services.ts

Protobuf toolkit for modern web development
Go to Latest
File
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669
import { StringReader } from "https://deno.land/std@0.122.0/io/readers.ts";import { Rpc, Schema, Service } from "../../core/schema/model.ts";import { CodeEntry } from "../index.ts";import { join } from "../path.ts";import { CustomTypeMapping, GenMessagesConfig, GenServicesConfig, getSwiftFullName, ServiceType, toSwiftName,} from "./index.ts";import { toCamelCase } from "./swift-protobuf/name.ts";
export interface GenConfig { customTypeMapping: CustomTypeMapping; messages: GenMessagesConfig; services: GenServicesConfig;}
export default function* gen( schema: Schema, config: GenConfig,) { const { customTypeMapping, messages, services } = config; for (const [typePath, type] of Object.entries(schema.services)) { yield* genService({ typePath, type, customTypeMapping, schema, messages, services, }); }}
export function getFilePath( typePath: string, services: GenServicesConfig, genType: ServiceType, ext = "swift",): string { return join( services.outDir, [typePath.replace(/^\./, ""), genType, ext].join("."), );}
interface GenServiceConfig { typePath: string; type: Service; schema: Schema; customTypeMapping: CustomTypeMapping; messages: GenMessagesConfig; services: GenServicesConfig;}function* genService({ typePath, type, customTypeMapping, schema, messages, services,}: GenServiceConfig): Generator<CodeEntry> { const serviceName = typePath.split(".").slice(1).join("."); const fragments = typePath.split(".").slice(1); const serviceSwiftName = fragments.map((fragment, index) => fragments.length === index + 1 ? fragment.charAt(0).toUpperCase() + fragment.slice(1) : toCamelCase(fragment, true) ).join("_"); const getCodeConfig: GetCodeConfig = { type, schema, serviceName, swiftName: serviceSwiftName, }; for (const genType of services.genTypes) { const filePath = getFilePath(typePath, services, genType); const importCode = getImportCode(genType); switch (genType) { case "grpc": { const protocolCode = getProtocolCode(getCodeConfig); const extensionCode = getProtocolExtensionCode(getCodeConfig); const clientInterceptorFactoryProtocolCode = getClientInterceptorFactoryProtocolCode(getCodeConfig); const clientCode = getClientCode(getCodeConfig); const providerCode = getProviderCode(getCodeConfig); const providerExtensionCode = getProviderExtensionCode(getCodeConfig); const factoryProtocolCode = getFactoryProtocolCode(getCodeConfig); yield [ filePath, new StringReader([ importCode, protocolCode, extensionCode, clientInterceptorFactoryProtocolCode, clientCode, providerCode, providerExtensionCode, factoryProtocolCode, ].join("\n")), ]; return; } case "wrp": { const providerCode = getWrpProviderCode(getCodeConfig); const providerExtensionCode = getWrpProviderExtensionCode( getCodeConfig, ); const clientCode = getWrpClientCode(getCodeConfig); const clientProtocolCode = getWrpClientProtocolCode(getCodeConfig); const clientProtocolExtensionCode = getWrpClientProtocolExtensionCode( getCodeConfig, ); yield [ filePath, new StringReader([ importCode, clientCode, clientProtocolCode, clientProtocolExtensionCode, providerCode, providerExtensionCode, ].join("\n")), ]; return; } } }}
function getImportCode(type: ServiceType) { switch (type) { case "grpc": return getSwiftImportCode(["GRPC", "SwiftProtobuf", "NIO"]); case "wrp": return getSwiftImportCode(["GRPC", "SwiftProtobuf", "Wrp"]); } function getSwiftImportCode(deps: string[]) { return deps.map((dep) => `import ${dep}\n`).join(""); }}
interface GetCodeConfig { schema: Schema; type: Service; serviceName: string; swiftName: string;}type GetCodeFn = (config: GetCodeConfig) => string;const getProtocolCode: GetCodeFn = ({ type, schema, swiftName,}) => { return [ `internal protocol ${swiftName}ClientProtocol: GRPCClient {\n`, ` var serviceName: String { get }\n`, ` var interceptors: ${swiftName}ClientInterceptorFactoryProtocol? { get }\n`, Object.entries(type.rpcs).map(([rpcName, rpc]) => { const reqType = getSwiftFullName({ schema, typePath: rpc.reqType.typePath, }); const resType = getSwiftFullName({ schema, typePath: rpc.resType.typePath, }); const rpcCallType = getSwiftRpcCallType(rpc); return [ "\n", ` func ${toSimpleCamelCase(rpcName)}(\n`, rpcCallType === "UnaryCall" || rpcCallType === "ServerStreamingCall" ? ` _ request: ${reqType},\n` : "", ` callOptions: CallOptions?`, rpcCallType === "ServerStreamingCall" || rpcCallType === "BidirectionalStreamingCall" ? `,\n handler: @escaping (${resType}) -> Void` : "", `\n ) -> ${rpcCallType}<${reqType}, ${resType}>\n`, ].join(""); }).join(""), `}\n`, ].join("");};
const getWrpClientProtocolCode: GetCodeFn = ({ type, schema, swiftName,}) => { return [ `internal protocol ${swiftName}WrpClientProtocol {\n`, ` var client: WrpClient { get }\n`, Object.entries(type.rpcs).map(([rpcName, rpc]) => { const reqType = getSwiftFullName({ schema, typePath: rpc.reqType.typePath, }); const resType = getSwiftFullName({ schema, typePath: rpc.resType.typePath, }); const requestType = rpc.reqType.stream ? `AsyncStream<${reqType}>` : reqType; const responseType = rpc.resType.stream ? `WrpStreamingResponse<${resType}>` : `WrpUnaryResponse<${resType}>`; return [ "\n", ` func ${toSimpleCamelCase(rpcName)}(\n`, ` _ request: ${requestType},\n`, ` callOptions: WrpClientCallOptions?`, `\n ) throws -> ${responseType}\n`, ].join(""); }).join(""), `}\n`, ].join("");};
const getProtocolExtensionCode: GetCodeFn = ({ schema, type, swiftName, serviceName,}) => { return [ `extension ${swiftName}WrpClientProtocol {\n`, ` public var serviceName: String {\n`, ` return "${serviceName}"\n`, ` }\n`, Object.entries(type.rpcs).map(([rpcName, rpc]) => { const reqType = getSwiftFullName({ schema, typePath: rpc.reqType.typePath, }); const resType = getSwiftFullName({ schema, typePath: rpc.resType.typePath, }); const rpcCallType = getSwiftRpcCallType(rpc); return [ "\n", ` func ${toSimpleCamelCase(rpcName)}(\n`, (() => { switch (rpcCallType) { case "UnaryCall": return [ ` _ request: ${reqType},\n`, ` callOptions: CallOptions? = nil\n`, ` ) -> ${rpcCallType}<${reqType}, ${resType}> {\n`, ` return self.make${rpcCallType}(\n`, ` path: "/${serviceName}/${rpcName}",\n`, ` request: request,\n`, ` callOptions: callOptions ?? self.defaultCallOptions,\n`, ` interceptors: self.interceptors?.make${rpcName}Interceptors() ?? []\n`, ].join(""); case "ServerStreamingCall": return [ ` _ request: ${reqType},\n`, ` callOptions: CallOptions? = nil,\n`, ` handler: @escaping (${resType}) -> Void\n`, ` ) -> ${rpcCallType}<${reqType}, ${resType}> {\n`, ` return self.make${rpcCallType}(\n`, ` path: "/${serviceName}/${rpcName}",\n`, ` request: request,\n`, ` callOptions: callOptions ?? self.defaultCallOptions,\n`, ` interceptors: self.interceptors?.make${rpcName}Interceptors() ?? [],\n`, ` handler: handler\n`, ].join(""); case "ClientStreamingCall": return [ ` callOptions: CallOptions? = nil\n`, ` ) -> ${rpcCallType}<${reqType}, ${resType}> {\n`, ` return self.make${rpcCallType}(\n`, ` path: "/${serviceName}/${rpcName}",\n`, ` callOptions: callOptions ?? self.defaultCallOptions,\n`, ` interceptors: self.interceptors?.make${rpcName}Interceptors() ?? []\n`, ].join(""); case "BidirectionalStreamingCall": return [ ` callOptions: CallOptions? = nil,\n`, ` handler: @escaping (${resType}) -> Void\n`, ` ) -> ${rpcCallType}<${reqType}, ${resType}> {\n`, ` return self.make${rpcCallType}(\n`, ` path: "/${serviceName}/${rpcName}",\n`, ` callOptions: callOptions ?? self.defaultCallOptions,\n`, ` interceptors: self.interceptors?.make${rpcName}Interceptors() ?? [],\n`, ` handler: handler\n`, ].join(""); default: return ""; } })(), ` )\n`, ` }\n`, ].join(""); }).join(""), `}\n`, ].join("");};
const getWrpClientProtocolExtensionCode: GetCodeFn = ({ schema, type, swiftName, serviceName,}) => { return [ `extension ${swiftName}WrpClientProtocol {`, Object.entries(type.rpcs).map(([rpcName, rpc]) => { const reqType = getSwiftFullName({ schema, typePath: rpc.reqType.typePath, }); const resType = getSwiftFullName({ schema, typePath: rpc.resType.typePath, }); const rpcCallType = getSwiftRpcCallType(rpc); const requestType = rpc.reqType.stream ? `AsyncStream<${reqType}>` : reqType; const responseType = rpc.resType.stream ? "WrpStreamingResponse" : "WrpUnaryResponse"; return [ `\n func ${toSimpleCamelCase(rpcName)}(\n`, (() => { switch (rpcCallType) { case "UnaryCall": return [ ` _ request: ${requestType},\n`, ` callOptions: WrpClientCallOptions? = nil\n`, ` ) throws -> ${responseType}<${resType}> {\n`, ` return try self.client.make${rpcCallType}(\n`, ` path: "/${serviceName}/${rpcName}",\n`, ` request: request\n`, ].join(""); case "ServerStreamingCall": return [ ` _ request: ${requestType},\n`, ` callOptions: WrpClientCallOptions? = nil\n`, ` ) throws -> ${responseType}<${resType}> {\n`, ` return try self.client.make${rpcCallType}(\n`, ` path: "/${serviceName}/${rpcName}",\n`, ` request: request\n`, ].join(""); case "ClientStreamingCall": return [ ` _ request: ${requestType},\n`, ` callOptions: WrpClientCallOptions? = nil\n`, ` ) throws -> ${responseType}<${resType}> {\n`, ` return try self.client.make${rpcCallType}(\n`, ` path: "/${serviceName}/${rpcName}",\n`, ` request: request\n`, ].join(""); case "BidirectionalStreamingCall": return [ ` _ request: ${requestType},\n`, ` callOptions: WrpClientCallOptions? = nil\n`, ` ) throws -> ${responseType}<${resType}> {\n`, ` return try self.client.make${rpcCallType}(\n`, ` path: "/${serviceName}/${rpcName}",\n`, ` request: request\n`, ].join(""); default: return ""; } })(), ` )\n`, ` }\n`, ].join(""); }).join(""), `}\n`, ].join("");};
const getClientInterceptorFactoryProtocolCode: GetCodeFn = ({ schema, type, swiftName,}) => { return [ `public protocol ${swiftName}ClientInterceptorFactoryProtocol {`, Object.entries(type.rpcs).map(([rpcName, rpc]) => { const reqType = getSwiftFullName({ schema, typePath: rpc.reqType.typePath, }); const resType = getSwiftFullName({ schema, typePath: rpc.resType.typePath, }); return [ "\n", ` func make${rpcName}Interceptors() -> [ClientInterceptor<${reqType}, ${resType}>]\n`, ].join(""); }).join(""), `}\n`, ].join("");};
const getClientCode: GetCodeFn = ({ swiftName }) => { return [ `public final class ${swiftName}Client: ${swiftName}ClientProtocol {\n`, ` public let channel: GRPCChannel\n`, ` public var defaultCallOptions: CallOptions\n`, ` public var interceptors: ${swiftName}ClientInterceptorFactoryProtocol?\n`, "\n", " public init(\n", " channel: GRPCChannel,\n", " defaultCallOptions: CallOptions = CallOptions(),\n", ` interceptors: ${swiftName}ClientInterceptorFactoryProtocol? = nil\n`, " ) {\n", " self.channel = channel\n", " self.defaultCallOptions = defaultCallOptions\n", " self.interceptors = interceptors\n", " }\n", "}\n", ].join("");};
const getWrpClientCode: GetCodeFn = ({ swiftName }) => { return [ `public final class ${swiftName}WrpClient: ${swiftName}WrpClientProtocol {\n`, ` public let client: WrpClient\n`, "\n", " public init(\n", " client: WrpClient\n", " ) {\n", " self.client = client\n", " }\n", "}\n", ].join("");};
const getProviderCode: GetCodeFn = ({ schema, type, swiftName }) => { return [ `public protocol ${swiftName}Provider: CallHandlerProvider {\n`, ` var interceptors: ${swiftName}ServerInterceptorFactoryProtocol? { get }\n`, Object.entries(type.rpcs).map( ([rpcName, rpc]) => { const reqType = getSwiftFullName({ schema, typePath: rpc.reqType.typePath, }); const resType = getSwiftFullName({ schema, typePath: rpc.resType.typePath, }); const rpcCallType = getRpcCallType(rpc); switch (rpcCallType) { case "unary": return `\n func ${ toSimpleCamelCase(rpcName) }(request: ${reqType}, context: StatusOnlyCallContext) -> EventLoopFuture<${resType}>\n`; case "client": return `\n func ${ toSimpleCamelCase(rpcName) }(context: UnaryResponseCallContext<${resType}>) -> EventLoopFuture<(StreamEvent<${reqType}>) -> Void>\n`; case "server": return `\n func ${ toSimpleCamelCase(rpcName) }(request: ${reqType}, context: StreamingResponseCallContext<${resType}>) -> EventLoopFuture<GRPCStatus>\n`; case "bidi": return `\n func ${ toSimpleCamelCase(rpcName) }(context: StreamingResponseCallContext<${resType}>) -> EventLoopFuture<(StreamEvent<${reqType}>) -> Void>\n`; } }, ).join(""), `}\n`, ].join("");};
const getWrpProviderCode: GetCodeFn = ({ schema, type, swiftName }) => { return [ `public protocol ${swiftName}WrpProvider: WrpServiceProvider {`, Object.entries(type.rpcs).map( ([rpcName, rpc]) => { const reqType = getSwiftFullName({ schema, typePath: rpc.reqType.typePath, }); const resType = getSwiftFullName({ schema, typePath: rpc.resType.typePath, }); return `\n func ${ toSimpleCamelCase(rpcName) }(request: AsyncStream<${reqType}>, context: WrpRequestContext<${resType}>) async\n`; }, ).join(""), `}\n`, ].join("");};
const getProviderExtensionCode: GetCodeFn = ( { schema, type, swiftName, serviceName },) => { return [ `extension ${swiftName}Provider {\n`, ` public var serviceName: Substring { return "${serviceName}" }\n`, "\n", " public func handle(\n", " method name: Substring,\n", " context: CallHandlerContext\n", " ) -> GRPCServerHandlerProtocol? {\n", " switch name {\n", Object.entries(type.rpcs).map(([rpcName, rpc]) => { const reqType = getSwiftFullName({ schema, typePath: rpc.reqType.typePath, }); const resType = getSwiftFullName({ schema, typePath: rpc.resType.typePath, }); const rpcHandlerType = getSwiftRpcHandlerType(rpc); return [ ` case "${rpcName}":\n`, ` return ${rpcHandlerType}(\n`, " context: context,\n", ` requestDeserializer: ProtobufDeserializer<${reqType}>(),\n`, ` responseSerializer: ProtobufSerializer<${resType}>(),\n`, ` interceptors: self.interceptors?.make${ toSimpleUpperCase(rpcName) }Interceptors() ?? [],\n`, rpcHandlerType === "ClientStreamingServerHandler" || rpcHandlerType === "BidirectionalStreamingServerHandler" ? ` observerFactory: self.${ toSimpleCamelCase(rpcName) }(context:)\n` : ` userFunction: self.${ toSimpleCamelCase(rpcName) }(request:context:)\n`, " )\n\n", ].join(""); }).join(""), ` default:\n`, ` return nil\n`, ` }\n`, " }\n", "}\n", ].join("");};
const getWrpProviderExtensionCode: GetCodeFn = ( { schema, type, swiftName, serviceName },) => { return [ `public extension ${swiftName}WrpProvider {\n`, ` var serviceName: Substring { return "${serviceName}" }\n\n`, ` var methodNames: [Substring] { return [${ Object.entries(type.rpcs).map(([rpcName]) => `"${rpcName}"`).join(", ") }] }\n\n`, " func handle(\n", " method name: Substring,\n", " context: WrpRequestHandlerContext\n", " ) -> WrpRequestHandlerProtocol? {\n", " switch name {\n", Object.entries(type.rpcs).map(([rpcName, rpc]) => { const reqType = getSwiftFullName({ schema, typePath: rpc.reqType.typePath, }); const resType = getSwiftFullName({ schema, typePath: rpc.resType.typePath, }); return [ ` case "${rpcName}":\n`, ` return WrpRequestHandler(\n`, " context: context,\n", ` requestDeserializer: ProtobufDeserializer<${reqType}>(),\n`, ` responseSerializer: ProtobufSerializer<${resType}>(),\n`, ` userFunction: self.${ toSimpleCamelCase(rpcName) }(request:context:)\n`, " )\n\n", ].join(""); }).join(""), ` default:\n`, ` return nil\n`, ` }\n`, " }\n", "}\n", ].join("");};
type RpcCallType = "unary" | "server" | "client" | "bidi";function getRpcCallType({ reqType, resType }: Rpc): RpcCallType { if (reqType.stream) { if (resType.stream) return "bidi"; return "client"; } if (resType.stream) return "server"; return "unary";}type SwiftRpcCallType = | "UnaryCall" | "ServerStreamingCall" | "ClientStreamingCall" | "BidirectionalStreamingCall";function getSwiftRpcCallType(rpc: Rpc): SwiftRpcCallType { switch (getRpcCallType(rpc)) { case "unary": return "UnaryCall"; case "client": return "ClientStreamingCall"; case "server": return "ServerStreamingCall"; case "bidi": return "BidirectionalStreamingCall"; }}
type SwiftRpcHandlerType = | "UnaryServerHandler" | "ServerStreamingServerHandler" | "ClientStreamingServerHandler" | "BidirectionalStreamingServerHandler";function getSwiftRpcHandlerType(rpc: Rpc): SwiftRpcHandlerType { switch (getRpcCallType(rpc)) { case "unary": return "UnaryServerHandler"; case "client": return "ClientStreamingServerHandler"; case "server": return "ServerStreamingServerHandler"; case "bidi": return "BidirectionalStreamingServerHandler"; }}
const getFactoryProtocolCode: GetCodeFn = ({ schema, type, swiftName }) => { return [ `public protocol ${swiftName}ServerInterceptorFactoryProtocol {`, Object.entries(type.rpcs).map(([rpcName, rpc]) => { const reqType = getSwiftFullName({ schema, typePath: rpc.reqType.typePath, }); const resType = getSwiftFullName({ schema, typePath: rpc.resType.typePath, }); return `\n func make${ toSimpleUpperCase(rpcName) }Interceptors() -> [ServerInterceptor<${reqType}, ${resType}>]\n`; }).join(""), `}\n`, ].join("");};
function toSimpleUpperCase(str: string) { return str[0].toUpperCase() + str.slice(1);}
function toSimpleCamelCase(str: string) { return str[0].toLowerCase() + str.slice(1);}