From 1e68503b7ecc8548ad27cfb163f83b416a82cb15 Mon Sep 17 00:00:00 2001 From: Vadym Yaroshchuk Date: Thu, 17 Oct 2024 01:38:02 +0200 Subject: [PATCH] fix&refactor&chore: fix build errors, server metadata service is renamed to schema service and also reworked, small improvements --- .../main/kotlin/library-convention.gradle.kts | 10 +- client/core/build.gradle.kts | 20 +- .../rrpc/client/ClientRequestHandler.kt | 10 +- ...PServiceClient.kt => RRpcServiceClient.kt} | 6 +- .../rrpc/client/config/RSPClientConfig.kt | 4 +- client/schema/README.md | 2 + client/schema/build.gradle.kts | 43 +++ .../rrpc/client/schema/SchemaClient.kt | 127 ++++++++ .../client/schema/request/BatchedRequest.kt | 22 ++ .../client/schema/request/PagedRequest.kt | 40 +++ common/core/build.gradle.kts | 24 +- .../kotlin/org/timemates/rrpc/DataVariant.kt | 167 +++++++++++ .../kotlin/org/timemates/rrpc/ProtoType.kt | 50 ++++ .../ExperimentalInterceptorsApi.kt | 14 + .../annotations/ExperimentalRSProtoAPI.kt | 4 + .../rrpc/annotations/InternalRSProtoAPI.kt | 4 + .../exceptions/ProcedureNotFoundException.kt | 7 + .../timemates/rrpc/exceptions/RSPException.kt | 3 + .../exceptions/ServiceNotFoundException.kt | 5 + .../rrpc/instances/InstanceContainer.kt | 100 +++++++ .../rrpc/instances/InstancesBuilder.kt | 23 ++ .../rrpc/instances/ProtobufInstance.kt | 41 +++ .../rrpc/instances/ProvidableInstance.kt | 10 + .../rrpc/interceptors/Interceptor.kt | 115 ++++++++ .../rrpc/interceptors/InterceptorContext.kt | 166 +++++++++++ .../timemates/rrpc/metadata/ClientMetadata.kt | 40 +++ .../timemates/rrpc/metadata/ExtraMetadata.kt | 27 ++ .../timemates/rrpc/metadata/RSPMetadata.kt | 15 + .../timemates/rrpc/metadata/ServerMetadata.kt | 29 ++ .../org/timemates/rrpc/options/Option.kt | 39 +++ .../rrpc/options/OptionsWithValue.kt | 35 +++ .../resources/google/protobuf/wrappers.proto | 2 +- .../rsp/common/test/InterceptorsTest.kt | 4 +- .../timemates/rrpc/common/metadata/RMNode.kt | 3 - .../rrpc/common/metadata/RMOptions.kt | 36 --- .../rrpc/common/metadata/RMResolver.kt | 202 ------------- .../rrpc/common/metadata/value/RMTypeUrl.kt | 124 -------- common/{metadata => schema}/build.gradle.kts | 26 +- .../rrpc/common/schema}/Documentable.kt | 2 +- .../timemates/rrpc/common/schema}/Language.kt | 2 +- .../rrpc/common/schema}/RMEnumConstant.kt | 2 +- .../timemates/rrpc/common/schema}/RMExtend.kt | 8 +- .../timemates/rrpc/common/schema}/RMField.kt | 6 +- .../timemates/rrpc/common/schema}/RMFile.kt | 8 +- .../timemates/rrpc/common/schema/RMNode.kt | 3 + .../timemates/rrpc/common/schema}/RMOneOf.kt | 2 +- .../timemates/rrpc/common/schema}/RMOption.kt | 3 +- .../timemates/rrpc/common/schema/RMOptions.kt | 36 +++ .../rrpc/common/schema/RMResolver.kt | 276 ++++++++++++++++++ .../timemates/rrpc/common/schema}/RMRpc.kt | 11 +- .../rrpc/common/schema}/RMService.kt | 6 +- .../timemates/rrpc/common/schema}/RMType.kt | 12 +- .../rrpc/common/schema}/RMTypeMemberUrl.kt | 6 +- .../common/schema}/StreamableRMTypeUrl.kt | 6 +- .../annotations/NonPlatformSpecificAccess.kt | 2 +- .../common/schema/value/RMDeclarationUrl.kt | 124 ++++++++ .../common/schema}/value/RMPackageName.kt | 2 +- generator/core/build.gradle.kts | 12 +- .../timemates/rrpc/codegen/CodeGenerator.kt | 2 +- .../rrpc/codegen/RMDefaultVisitor.kt | 2 +- .../timemates/rrpc/codegen/RMEmptyVisitor.kt | 2 +- .../org/timemates/rrpc/codegen/RMVisitor.kt | 2 +- .../org/timemates/rrpc/codegen/WireExt.kt | 74 ++--- .../rrpc/codegen/adapters/SchemaAdapter.kt | 2 +- .../codegen/exception/GenerationException.kt | 2 +- generator/gradle-plugin/build.gradle.kts | 16 +- .../timemates/rrpc/plugin/RSProtoExtension.kt | 6 +- .../plugin/RSocketProtoGeneratorPlugin.kt | 128 ++++---- generator/kotlin/build.gradle.kts | 13 +- .../rrpc/generator/kotlin/Constant.kt | 2 +- .../generator/kotlin/DescriptorGenerator.kt | 4 +- .../rrpc/generator/kotlin/ExtendGenerator.kt | 10 +- .../rrpc/generator/kotlin/FileGenerator.kt | 6 +- .../kotlin/KotlinMetadataSchemaAdapter.kt | 4 +- .../generator/kotlin/KotlinSchemaAdapter.kt | 8 +- .../rrpc/generator/kotlin/OptionGenerator.kt | 26 +- .../kotlin/client/ClientRpcGenerator.kt | 38 +-- .../kotlin/client/ClientServiceGenerator.kt | 6 +- .../generator/kotlin/ext/KotlinPoetExt.kt | 5 +- .../rrpc/generator/kotlin/ext/RSPExt.kt | 24 +- .../CombinedFilesMetadataGenerator.kt | 6 +- .../metadata/ExtendMetadataGenerator.kt | 7 +- .../kotlin/metadata/FieldMetadataGenerator.kt | 7 +- .../kotlin/metadata/FileMetadataGenerator.kt | 16 +- .../kotlin/metadata/OneOfMetadataGenerator.kt | 9 +- .../metadata/OptionsMetadataGenerator.kt | 11 +- .../kotlin/metadata/RpcMetadataGenerator.kt | 9 +- .../metadata/ServiceMetadataGenerator.kt | 9 +- .../kotlin/metadata/TypeMetadataGenerator.kt | 49 ++-- .../options/ClientOptionsPropertyGenerator.kt | 6 +- .../kotlin/options/OptionValueGenerator.kt | 60 ++-- .../options/RawOptionsCodeGeneration.kt | 12 +- .../kotlin/server/ServerMetadataGenerator.kt | 10 +- .../kotlin/server/ServerRpcGenerator.kt | 8 +- .../kotlin/server/ServerServiceGenerator.kt | 10 +- .../generator/kotlin/typemodel/Annotations.kt | 2 +- .../kotlin/typemodel/ImportRequirement.kt | 2 +- .../kotlin/types/BuiltinsGenerator.kt | 22 +- .../kotlin/types/EnclosingTypeGenerator.kt | 4 +- .../kotlin/types/EnumTypeGenerator.kt | 4 +- .../kotlin/types/TypeDefaultValueGenerator.kt | 34 +-- .../generator/kotlin/types/TypeGenerator.kt | 18 +- .../MessageCompanionObjectGenerator.kt | 4 +- .../message/MessageConstructorGenerator.kt | 8 +- .../message/MessageDSLBuilderGenerator.kt | 2 +- .../message/MessageNestedTypeGenerator.kt | 4 +- .../message/MessageParameterTypeGenerator.kt | 4 +- .../types/message/MessagePropertyGenerator.kt | 2 +- .../types/message/MessageTypeGenerator.kt | 4 +- .../kotlin/types/message/OneOfGenerator.kt | 8 +- gradle/libs.versions.toml | 15 +- gradle/wrapper/gradle-wrapper.properties | 2 +- integration-tests/build.gradle.kts | 39 +++ .../schema/SchemaServiceCommunicationTest.kt | 38 +++ server/core/build.gradle.kts | 20 +- .../module/{RSPModule.kt => RRpcModule.kt} | 3 +- .../rrpc/server/module/RRpcModuleBuilder.kt | 130 +++++++++ ...PModuleHandler.kt => RRpcModuleHandler.kt} | 6 +- .../module/{RSPService.kt => RRpcService.kt} | 0 .../rrpc/server/module/RSPModuleBuilder.kt | 111 ------- .../rrpc/server/metadata/MetadataLookup.kt | 27 -- .../rrpc/server/metadata/MetadataService.kt | 36 --- server/schema/README.md | 5 + server/{metadata => schema}/build.gradle.kts | 18 +- .../rrpc/server/schema/RRpcModuleBuilder.kt | 13 + .../rrpc/server/schema/SchemaLookup.kt | 25 ++ .../rrpc/server/schema/SchemaService.kt | 127 ++++++++ .../server/schema/request/BatchedRequest.kt | 22 ++ .../server/schema/request/PagedRequest.kt | 40 +++ settings.gradle.kts | 20 +- 130 files changed, 2536 insertions(+), 1022 deletions(-) rename client/core/src/commonMain/kotlin/org/timemates/rrpc/client/{RSPServiceClient.kt => RRpcServiceClient.kt} (83%) create mode 100644 client/schema/README.md create mode 100644 client/schema/build.gradle.kts create mode 100644 client/schema/src/commonMain/kotlin/org/timemates/rrpc/client/schema/SchemaClient.kt create mode 100644 client/schema/src/commonMain/kotlin/org/timemates/rrpc/client/schema/request/BatchedRequest.kt create mode 100644 client/schema/src/commonMain/kotlin/org/timemates/rrpc/client/schema/request/PagedRequest.kt create mode 100644 common/core/src/commonMain/kotlin/org/timemates/rrpc/DataVariant.kt create mode 100644 common/core/src/commonMain/kotlin/org/timemates/rrpc/ProtoType.kt create mode 100644 common/core/src/commonMain/kotlin/org/timemates/rrpc/annotations/ExperimentalInterceptorsApi.kt create mode 100644 common/core/src/commonMain/kotlin/org/timemates/rrpc/annotations/ExperimentalRSProtoAPI.kt create mode 100644 common/core/src/commonMain/kotlin/org/timemates/rrpc/annotations/InternalRSProtoAPI.kt create mode 100644 common/core/src/commonMain/kotlin/org/timemates/rrpc/exceptions/ProcedureNotFoundException.kt create mode 100644 common/core/src/commonMain/kotlin/org/timemates/rrpc/exceptions/RSPException.kt create mode 100644 common/core/src/commonMain/kotlin/org/timemates/rrpc/exceptions/ServiceNotFoundException.kt create mode 100644 common/core/src/commonMain/kotlin/org/timemates/rrpc/instances/InstanceContainer.kt create mode 100644 common/core/src/commonMain/kotlin/org/timemates/rrpc/instances/InstancesBuilder.kt create mode 100644 common/core/src/commonMain/kotlin/org/timemates/rrpc/instances/ProtobufInstance.kt create mode 100644 common/core/src/commonMain/kotlin/org/timemates/rrpc/instances/ProvidableInstance.kt create mode 100644 common/core/src/commonMain/kotlin/org/timemates/rrpc/interceptors/Interceptor.kt create mode 100644 common/core/src/commonMain/kotlin/org/timemates/rrpc/interceptors/InterceptorContext.kt create mode 100644 common/core/src/commonMain/kotlin/org/timemates/rrpc/metadata/ClientMetadata.kt create mode 100644 common/core/src/commonMain/kotlin/org/timemates/rrpc/metadata/ExtraMetadata.kt create mode 100644 common/core/src/commonMain/kotlin/org/timemates/rrpc/metadata/RSPMetadata.kt create mode 100644 common/core/src/commonMain/kotlin/org/timemates/rrpc/metadata/ServerMetadata.kt create mode 100644 common/core/src/commonMain/kotlin/org/timemates/rrpc/options/Option.kt create mode 100644 common/core/src/commonMain/kotlin/org/timemates/rrpc/options/OptionsWithValue.kt delete mode 100644 common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMNode.kt delete mode 100644 common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMOptions.kt delete mode 100644 common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMResolver.kt delete mode 100644 common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/value/RMTypeUrl.kt rename common/{metadata => schema}/build.gradle.kts (55%) rename common/{metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata => schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema}/Documentable.kt (62%) rename common/{metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata => schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema}/Language.kt (62%) rename common/{metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata => schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema}/RMEnumConstant.kt (84%) rename common/{metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata => schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema}/RMExtend.kt (52%) rename common/{metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata => schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema}/RMField.kt (71%) rename common/{metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata => schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema}/RMFile.kt (86%) create mode 100644 common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMNode.kt rename common/{metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata => schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema}/RMOneOf.kt (83%) rename common/{metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata => schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema}/RMOption.kt (91%) create mode 100644 common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMOptions.kt create mode 100644 common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMResolver.kt rename common/{metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata => schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema}/RMRpc.kt (80%) rename common/{metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata => schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema}/RMService.kt (74%) rename common/{metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata => schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema}/RMType.kt (88%) rename common/{metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata => schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema}/RMTypeMemberUrl.kt (58%) rename common/{metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata => schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema}/StreamableRMTypeUrl.kt (56%) rename common/{metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata => schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema}/annotations/NonPlatformSpecificAccess.kt (88%) create mode 100644 common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/value/RMDeclarationUrl.kt rename common/{metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata => schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema}/value/RMPackageName.kt (76%) create mode 100644 integration-tests/build.gradle.kts create mode 100644 integration-tests/src/test/kotlin/org/timemates/rrpc/server/schema/SchemaServiceCommunicationTest.kt rename server/core/src/commonMain/kotlin/org/timemates/rrpc/server/module/{RSPModule.kt => RRpcModule.kt} (96%) create mode 100644 server/core/src/commonMain/kotlin/org/timemates/rrpc/server/module/RRpcModuleBuilder.kt rename server/core/src/commonMain/kotlin/org/timemates/rrpc/server/module/{RSPModuleHandler.kt => RRpcModuleHandler.kt} (99%) rename server/core/src/commonMain/kotlin/org/timemates/rrpc/server/module/{RSPService.kt => RRpcService.kt} (100%) delete mode 100644 server/core/src/commonMain/kotlin/org/timemates/rrpc/server/module/RSPModuleBuilder.kt delete mode 100644 server/metadata/src/commonMain/kotlin/org/timemates/rrpc/server/metadata/MetadataLookup.kt delete mode 100644 server/metadata/src/commonMain/kotlin/org/timemates/rrpc/server/metadata/MetadataService.kt create mode 100644 server/schema/README.md rename server/{metadata => schema}/build.gradle.kts (62%) create mode 100644 server/schema/src/commonMain/kotlin/org/timemates/rrpc/server/schema/RRpcModuleBuilder.kt create mode 100644 server/schema/src/commonMain/kotlin/org/timemates/rrpc/server/schema/SchemaLookup.kt create mode 100644 server/schema/src/commonMain/kotlin/org/timemates/rrpc/server/schema/SchemaService.kt create mode 100644 server/schema/src/commonMain/kotlin/org/timemates/rrpc/server/schema/request/BatchedRequest.kt create mode 100644 server/schema/src/commonMain/kotlin/org/timemates/rrpc/server/schema/request/PagedRequest.kt diff --git a/build-conventions/src/main/kotlin/library-convention.gradle.kts b/build-conventions/src/main/kotlin/library-convention.gradle.kts index 5f40f31..d6c7f26 100644 --- a/build-conventions/src/main/kotlin/library-convention.gradle.kts +++ b/build-conventions/src/main/kotlin/library-convention.gradle.kts @@ -4,7 +4,7 @@ plugins { mavenPublishing { pom { - url.set("https://github.com/rrpcroto/rrpc-kotlin") + url.set("https://github.com/RRpc/rrpc-kotlin") inceptionYear.set("2023") licenses { @@ -24,14 +24,14 @@ mavenPublishing { } scm { - url.set("https://github.com/rrpcroto/rrpc-kotlin") - connection.set("scm:git:git://github.com/rrpcroto/rrpc-kotlin.git") - developerConnection.set("scm:git:ssh://git@github.com/rrpcroto/rrpc-kotlin.git") + url.set("https://github.com/RRpc/rrpc-kotlin") + connection.set("scm:git:git://github.com/RRpc/rrpc-kotlin.git") + developerConnection.set("scm:git:ssh://git@github.com/RRpc/rrpc-kotlin.git") } issueManagement { system.set("GitHub Issues") - url.set("https://github.com/rrpcroto/rrpc-kotlin/issues") + url.set("https://github.com/RRpc/rrpc-kotlin/issues") } } } diff --git a/client/core/build.gradle.kts b/client/core/build.gradle.kts index 3316b9e..e289a46 100644 --- a/client/core/build.gradle.kts +++ b/client/core/build.gradle.kts @@ -7,14 +7,22 @@ group = "org.timemates.rrpc" version = System.getenv("LIB_VERSION") ?: "SNAPSHOT" dependencies { - commonMainImplementation(libs.rsocket.client) - commonMainApi(projects.common.core) + dependencies { + // -- Project -- + commonMainImplementation(projects.common.core) + + // -- RSocket -- + commonMainApi(libs.rsocket.client) + + // -- Test -- + jvmTestImplementation(libs.kotlin.test) + jvmTestImplementation(libs.mockk) + } - jvmTestImplementation(libs.kotlin.test) - jvmTestImplementation(libs.mockk) } kotlin { + jvm() // js(IR) { // browser() // nodejs() @@ -32,7 +40,7 @@ mavenPublishing { ) pom { - name.set("RRpcroto Client Core") - description.set("Multiplatform Kotlin core library for RRpcroto clients.") + name.set("RRpc Client Core") + description.set("Multiplatform Kotlin core library for RRpc clients.") } } diff --git a/client/core/src/commonMain/kotlin/org/timemates/rrpc/client/ClientRequestHandler.kt b/client/core/src/commonMain/kotlin/org/timemates/rrpc/client/ClientRequestHandler.kt index fc8c47d..93b6dfb 100644 --- a/client/core/src/commonMain/kotlin/org/timemates/rrpc/client/ClientRequestHandler.kt +++ b/client/core/src/commonMain/kotlin/org/timemates/rrpc/client/ClientRequestHandler.kt @@ -9,7 +9,7 @@ import kotlinx.coroutines.flow.* import kotlinx.serialization.* import org.timemates.rrpc.* import org.timemates.rrpc.annotations.ExperimentalInterceptorsApi -import org.timemates.rrpc.annotations.InternalRRpcrotoAPI +import org.timemates.rrpc.annotations.InternalRRpcAPI import org.timemates.rrpc.client.config.RRpcClientConfig import org.timemates.rrpc.instances.protobuf import org.timemates.rrpc.interceptors.InterceptorContext @@ -23,7 +23,7 @@ import org.timemates.rrpc.options.OptionsWithValue * * @property config The configuration for the RRpc client. */ -@InternalRRpcrotoAPI +@InternalRRpcAPI public class ClientRequestHandler( private val config: RRpcClientConfig, ) { @@ -39,7 +39,7 @@ public class ClientRequestHandler( * @param deserializationStrategy Deserialization strategy for the response data. * @return The response data. */ - @OptIn(InternalRRpcrotoAPI::class) + @OptIn(InternalRRpcAPI::class) public suspend fun requestResponse( metadata: ClientMetadata, data: T, @@ -111,7 +111,7 @@ public class ClientRequestHandler( * @param deserializationStrategy Deserialization strategy for the response data. * @return A flow of the response data. */ - @OptIn(InternalRRpcrotoAPI::class) + @OptIn(InternalRRpcAPI::class) public fun requestStream( metadata: ClientMetadata, data: T, @@ -154,7 +154,7 @@ public class ClientRequestHandler( * @param deserializationStrategy Deserialization strategy for the response data. * @return A flow of the response data. */ - @OptIn(InternalRRpcrotoAPI::class) + @OptIn(InternalRRpcAPI::class) public fun requestChannel( metadata: ClientMetadata, data: Flow, diff --git a/client/core/src/commonMain/kotlin/org/timemates/rrpc/client/RSPServiceClient.kt b/client/core/src/commonMain/kotlin/org/timemates/rrpc/client/RRpcServiceClient.kt similarity index 83% rename from client/core/src/commonMain/kotlin/org/timemates/rrpc/client/RSPServiceClient.kt rename to client/core/src/commonMain/kotlin/org/timemates/rrpc/client/RRpcServiceClient.kt index 43d7215..ec4b708 100644 --- a/client/core/src/commonMain/kotlin/org/timemates/rrpc/client/RSPServiceClient.kt +++ b/client/core/src/commonMain/kotlin/org/timemates/rrpc/client/RRpcServiceClient.kt @@ -1,13 +1,13 @@ -@file:OptIn(InternalRRpcrotoAPI::class) +@file:OptIn(InternalRRpcAPI::class) package org.timemates.rrpc.client -import org.timemates.rrpc.annotations.InternalRRpcrotoAPI +import org.timemates.rrpc.annotations.InternalRRpcAPI import org.timemates.rrpc.client.config.RRpcClientConfig import org.timemates.rrpc.client.options.RPCsOptions /** - * The abstraction for the clients that are generated by `rrpcroto`. + * The abstraction for the clients that are generated by `RRpc`. */ public abstract class RRpcServiceClient( config: RRpcClientConfig, diff --git a/client/core/src/commonMain/kotlin/org/timemates/rrpc/client/config/RSPClientConfig.kt b/client/core/src/commonMain/kotlin/org/timemates/rrpc/client/config/RSPClientConfig.kt index 9b14bad..535b66c 100644 --- a/client/core/src/commonMain/kotlin/org/timemates/rrpc/client/config/RSPClientConfig.kt +++ b/client/core/src/commonMain/kotlin/org/timemates/rrpc/client/config/RSPClientConfig.kt @@ -5,7 +5,7 @@ package org.timemates.rrpc.client.config import io.rsocket.kotlin.RSocket import kotlinx.serialization.ExperimentalSerializationApi import org.timemates.rrpc.annotations.ExperimentalInterceptorsApi -import org.timemates.rrpc.annotations.InternalRRpcrotoAPI +import org.timemates.rrpc.annotations.InternalRRpcAPI import org.timemates.rrpc.instances.* import org.timemates.rrpc.interceptors.Interceptor import org.timemates.rrpc.interceptors.Interceptors @@ -122,7 +122,7 @@ public data class RRpcClientConfig @OptIn(ExperimentalInterceptorsApi::class) co /** * Appends the provided in the [builder] instances to existing ones. */ - @OptIn(InternalRRpcrotoAPI::class) + @OptIn(InternalRRpcAPI::class) public fun instances(builder: InstancesBuilder.() -> Unit): Builder = apply { val instances = InstancesBuilder().apply(builder).build() instances(instances) diff --git a/client/schema/README.md b/client/schema/README.md new file mode 100644 index 0000000..72981a8 --- /dev/null +++ b/client/schema/README.md @@ -0,0 +1,2 @@ +# Server Schema Client +This module is used to communicate with [server-schema](../../server/schema). \ No newline at end of file diff --git a/client/schema/build.gradle.kts b/client/schema/build.gradle.kts new file mode 100644 index 0000000..6b65964 --- /dev/null +++ b/client/schema/build.gradle.kts @@ -0,0 +1,43 @@ +plugins { + id(libs.plugins.conventions.multiplatform.library.get().pluginId) + alias(libs.plugins.kotlinx.serialization) +} + +group = "org.timemates.rrpc" +version = System.getenv("LIB_VERSION") ?: "SNAPSHOT" + +dependencies { + // -- Project -- + commonMainImplementation(projects.common.core) + commonMainImplementation(projects.common.schema) + commonMainImplementation(projects.client.core) + + // -- Test -- + jvmTestImplementation(libs.kotlin.test) + jvmTestImplementation(libs.mockk) +} + + +kotlin { + jvm() +// js(IR) { +// browser() +// nodejs() +// } +// iosArm64() +// iosX64() +// iosSimulatorArm64() +} + +mavenPublishing { + coordinates( + groupId = "org.timemates.rrpc", + artifactId = "server-metadata-client", + version = System.getenv("LIB_VERSION") ?: return@mavenPublishing, + ) + + pom { + name.set("RRpc Server Metadata Client") + description.set("Multiplatform Kotlin Library for working with Server Metadata.") + } +} diff --git a/client/schema/src/commonMain/kotlin/org/timemates/rrpc/client/schema/SchemaClient.kt b/client/schema/src/commonMain/kotlin/org/timemates/rrpc/client/schema/SchemaClient.kt new file mode 100644 index 0000000..f0e6394 --- /dev/null +++ b/client/schema/src/commonMain/kotlin/org/timemates/rrpc/client/schema/SchemaClient.kt @@ -0,0 +1,127 @@ +package org.timemates.rrpc.client.schema + +import org.timemates.rrpc.annotations.InternalRRpcAPI +import org.timemates.rrpc.client.RRpcServiceClient +import org.timemates.rrpc.client.config.RRpcClientConfig +import org.timemates.rrpc.client.options.RPCsOptions +import org.timemates.rrpc.client.schema.request.PagedRequest +import org.timemates.rrpc.common.schema.RMFile +import org.timemates.rrpc.common.schema.RMService +import org.timemates.rrpc.metadata.ClientMetadata +import org.timemates.rrpc.options.OptionsWithValue +import org.timemates.rrpc.client.schema.request.BatchedRequest +import org.timemates.rrpc.common.schema.RMExtend +import org.timemates.rrpc.common.schema.RMType +import io.rsocket.kotlin.RSocketError + +/** + * A client to interact with the `SchemaService` of the server, which provides metadata about + * available services, types, and extensions. This client sends requests to the server and + * receives the corresponding responses using the RSocket-based communication framework. + * + * @param config The configuration for the `RRpcServiceClient`, which defines connection settings. + */ +@OptIn(InternalRRpcAPI::class) +public class SchemaClient( + config: RRpcClientConfig, +) : RRpcServiceClient(config) { + + public companion object { + private const val SERVICE_NAME: String = "timemates.rrpc.server.schema.SchemaService" + } + + /** + * Creates an instance of the client using a configuration builder. + * + * @param creator A lambda to configure and build the `RRpcClientConfig` object. + */ + public constructor( + creator: RRpcClientConfig.Builder.() -> Unit, + ) : this(RRpcClientConfig.create(creator)) + + override val rpcsOptions: RPCsOptions = RPCsOptions.EMPTY + + /** + * Fetches a paged list of available services from the server. + * + * @param request A [PagedRequest] defining pagination settings. + * @return A [PagedRequest.Response] containing a list of [RMService] and the next page token. + * + * @throws RSocketError if the request fails. + */ + public suspend fun getAvailableServices(request: PagedRequest): PagedRequest.Response { + return handler.requestResponse( + metadata = ClientMetadata( + serviceName = SERVICE_NAME, + procedureName = "GetAvailableServices", + ), + data = request, + options = OptionsWithValue.EMPTY, + serializationStrategy = PagedRequest.serializer(), + deserializationStrategy = PagedRequest.Response.serializer(RMService.serializer()), + ) + } + + /** + * Fetches a paged list of available files from the server. + * + * @param request A [PagedRequest] specifying pagination options. + * @return A [PagedRequest.Response] containing a list of [RMFile] and the next page token. + * + * @throws RSocketError if the request fails. + */ + public suspend fun getAvailableFiles(request: PagedRequest): PagedRequest.Response { + return handler.requestResponse( + metadata = ClientMetadata( + serviceName = SERVICE_NAME, + procedureName = "GetAvailableFiles", + ), + data = request, + options = OptionsWithValue.EMPTY, + serializationStrategy = PagedRequest.serializer(), + deserializationStrategy = PagedRequest.Response.serializer(RMFile.serializer()), + ) + } + + /** + * Retrieves detailed information about multiple types in a batched request. + * + * @param request A [BatchedRequest] containing a list of [RMDeclarationUrl]s for the requested types. + * @return A [BatchedRequest.Response] containing a map of each requested [RMDeclarationUrl] to its associated [RMType]. + * + * @throws RSocketError if the request fails. + */ + public suspend fun getTypeDetailsBatch(request: BatchedRequest): BatchedRequest.Response { + return handler.requestResponse( + metadata = ClientMetadata( + serviceName = SERVICE_NAME, + procedureName = "GetTypeDetailsBatch", + ), + data = request, + options = OptionsWithValue.EMPTY, + serializationStrategy = BatchedRequest.serializer(), + deserializationStrategy = BatchedRequest.Response.serializer(RMType.serializer()), + ) + } + + /** + * Retrieves detailed information about multiple extensions in a batched request. + * + * @param request A [BatchedRequest] containing a list of [RMDeclarationUrl]s for the requested extensions. + * @return A [BatchedRequest.Response] containing a map of each requested [RMDeclarationUrl] to its associated [RMExtend]. + * + * @throws RSocketError if the request fails. + */ + public suspend fun getExtendDetailsBatch(request: BatchedRequest): BatchedRequest.Response { + return handler.requestResponse( + metadata = ClientMetadata( + serviceName = SERVICE_NAME, + procedureName = "GetExtendDetailsBatch", + ), + data = request, + options = OptionsWithValue.EMPTY, + serializationStrategy = BatchedRequest.serializer(), + deserializationStrategy = BatchedRequest.Response.serializer(RMExtend.serializer()), + ) + } +} diff --git a/client/schema/src/commonMain/kotlin/org/timemates/rrpc/client/schema/request/BatchedRequest.kt b/client/schema/src/commonMain/kotlin/org/timemates/rrpc/client/schema/request/BatchedRequest.kt new file mode 100644 index 0000000..39fb891 --- /dev/null +++ b/client/schema/src/commonMain/kotlin/org/timemates/rrpc/client/schema/request/BatchedRequest.kt @@ -0,0 +1,22 @@ +package org.timemates.rrpc.client.schema.request + +import kotlinx.serialization.Serializable +import org.timemates.rrpc.common.schema.RMNode +import org.timemates.rrpc.common.schema.value.RMDeclarationUrl + +/** + * Represents a batched request to retrieve multiple metadata entities by their declaration URLs. + * + * @property urls A list of RMDeclarationUrl objects, representing the metadata to retrieve. + */ +@Serializable +public data class BatchedRequest(val urls: List) { + /** + * Response structure for batched requests. + * Contains a map of RMDeclarationUrl to the corresponding resolved metadata (or null if not found). + * + * @param results A map where each RMDeclarationUrl is associated with the corresponding RMNode (or null if not found). + */ + @Serializable + public data class Response(public val services: Map) +} \ No newline at end of file diff --git a/client/schema/src/commonMain/kotlin/org/timemates/rrpc/client/schema/request/PagedRequest.kt b/client/schema/src/commonMain/kotlin/org/timemates/rrpc/client/schema/request/PagedRequest.kt new file mode 100644 index 0000000..8095aef --- /dev/null +++ b/client/schema/src/commonMain/kotlin/org/timemates/rrpc/client/schema/request/PagedRequest.kt @@ -0,0 +1,40 @@ +package org.timemates.rrpc.client.schema.request + +import kotlinx.serialization.Serializable +import kotlin.io.encoding.Base64 +import kotlin.io.encoding.ExperimentalEncodingApi + +/** + * Represents a paginated request to retrieve metadata entities. + * + * @property cursor A token used for retrieving the next page of results. + * @property size The maximum number of results to retrieve per page. + */ +@Serializable +public data class PagedRequest( + public val cursor: String? = null, + public val size: Int? = null, +) { + public companion object { + @OptIn(ExperimentalEncodingApi::class) + internal fun encoded(string: String): String { + return Base64.encode(string.toByteArray()) + } + } + + /** + * Response structure for paginated requests. + * Contains the list of metadata nodes retrieved and a token for the next page. + * + * @param list A list of RMNode objects representing the metadata retrieved. + * @param nextCursor A token to retrieve the next page of results, or null if no more results. + */ + @Serializable + public data class Response( + public val list: List, + public val nextCursor: String?, + ) +} + +@OptIn(ExperimentalEncodingApi::class) +internal fun PagedRequest.decoded(): String? = this@decoded.cursor?.let { String(Base64.decode(it)) } \ No newline at end of file diff --git a/common/core/build.gradle.kts b/common/core/build.gradle.kts index cfb617b..51b2752 100644 --- a/common/core/build.gradle.kts +++ b/common/core/build.gradle.kts @@ -4,21 +4,23 @@ plugins { } dependencies { - commonMainApi(libs.kotlinx.serialization.proto) - commonMainApi(libs.rsocket.core) + // -- Serialization -- + commonMainApi(libs.kotlinx.serialization.proto) + // -- Test -- jvmTestImplementation(libs.kotlin.test) jvmTestImplementation(libs.mockk) } + kotlin { - js(IR) { - browser() - nodejs() - } - iosArm64() - iosX64() - iosSimulatorArm64() +// js(IR) { +// browser() +// nodejs() +// } +// iosArm64() +// iosX64() +// iosSimulatorArm64() } mavenPublishing { @@ -29,7 +31,7 @@ mavenPublishing { ) pom { - name.set("RRpcroto Common Core") - description.set("Multiplatform Kotlin core library for RRpcroto servers and clients.") + name.set("RRpc Common Core") + description.set("Multiplatform Kotlin core library for RRpc servers and clients.") } } \ No newline at end of file diff --git a/common/core/src/commonMain/kotlin/org/timemates/rrpc/DataVariant.kt b/common/core/src/commonMain/kotlin/org/timemates/rrpc/DataVariant.kt new file mode 100644 index 0000000..269b90d --- /dev/null +++ b/common/core/src/commonMain/kotlin/org/timemates/rrpc/DataVariant.kt @@ -0,0 +1,167 @@ +package org.timemates.rrpc + +import kotlinx.coroutines.flow.Flow +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract +import kotlin.jvm.JvmSynthetic + +/** + * Represents a value that can be in one of three states: a single value, a streaming value, or a failure. + * + * @param T The type of the value. + */ +public sealed interface DataVariant { + + /** + * Checks if the other [DataVariant] is of the same type as this one. + * + * @param other The other [DataVariant] to compare with. + * @return `true` if the other [DataVariant] is of the same type, `false` otherwise. + */ + public fun isSameType(other: DataVariant<*>): Boolean +} + +/** + * Represents a single value. + * + * @param T The type of the value. + * @property value The single value. + */ +public data class Single(public val value: T) : DataVariant { + public companion object { + /** + * Denotes that single has no value inside. Usually, it's applicable only + * to the Metadata Push requests. + */ + public val EMPTY: Single = Single(Unit) + } + + override fun isSameType(other: DataVariant<*>): Boolean { + return other is Single + } + + override fun toString(): String { + return "Single(value=$value)" + } +} + +/** + * Represents a failure state, usually relevant only for clients as the server can return an error without a body. + * In addition, it's applicable only if original expected type is [Single]. For [Streaming] use flow's + * operators to handle failures. + * + * @param T The type of the value that was expected. + * @property exception The exception representing the failure. + */ +public data class Failure(public val exception: Exception) : DataVariant { + override fun isSameType(other: DataVariant<*>): Boolean { + return other is Failure + } +} + +/** + * Represents a streaming value. + * + * @param T The type of the values in the stream. + * @property flow The flow of values. + */ +public class Streaming( + @get:JvmSynthetic + public val flow: Flow, +) : DataVariant { + override fun isSameType(other: DataVariant<*>): Boolean { + return other is Streaming + } + + override fun toString(): String { + return "Streaming(flow=$flow)" + } +} + +/** + * Checks if the [DataVariant] is a single value. + * + * @return `true` if the variant is a [Single], `false` otherwise. + */ +@OptIn(ExperimentalContracts::class) +public inline fun DataVariant.isSingle(): Boolean { + contract { + returns(true) implies (this@isSingle is Single) + returns(false) implies (this@isSingle !is Single) + } + + return this is Single +} + +/** + * Checks if the [DataVariant] is a streaming value. + * + * @return `true` if the variant is a [Streaming], `false` otherwise. + */ +@OptIn(ExperimentalContracts::class) +public inline fun DataVariant.isStreaming(): Boolean { + contract { + returns(true) implies (this@isStreaming is Streaming) + returns(false) implies (this@isStreaming !is Streaming) + } + + return this is Streaming +} + +/** + * Checks if the [DataVariant] represents a failure. + * + * @return `true` if the variant is a [Failure], `false` otherwise. + */ +@OptIn(ExperimentalContracts::class) +public inline fun DataVariant.isFailure(): Boolean { + contract { + returns(true) implies (this@isFailure is Failure) + returns(false) implies (this@isFailure !is Failure) + } + + return this is Failure +} + +/** + * Requires that the [DataVariant] is a single value and returns it. + * + * @return The single value. + * @throws IllegalStateException if the variant is not a [Single]. + */ +@OptIn(ExperimentalContracts::class) +public inline fun DataVariant.requireSingle(): T { + contract { + returns() implies (this@requireSingle is Single) + } + return if (isSingle()) this.value else error("Expected a single value, but got: $this.") +} + +/** + * Requires that the [DataVariant] is a streaming value and returns it. + * + * @return The streaming value. + * @throws IllegalStateException if the variant is not a [Streaming]. + */ +@OptIn(ExperimentalContracts::class) +public inline fun DataVariant.requireStreaming(): Flow { + contract { + returns() implies (this@requireStreaming is Streaming) + } + return if (isStreaming()) this.flow else error("Expected a streaming value, but was not.") +} + +/** + * Requires that the [DataVariant] is a failure and returns it. + * + * @return [Failure] + * @throws IllegalStateException if the variant is not a [Single]. + */ +@OptIn(ExperimentalContracts::class) +public inline fun DataVariant.requireFailure(): Exception { + contract { + returns() implies (this@requireFailure is Failure) + } + return if (isFailure()) this.exception else error("Expected a single value, but got: $this.") +} + diff --git a/common/core/src/commonMain/kotlin/org/timemates/rrpc/ProtoType.kt b/common/core/src/commonMain/kotlin/org/timemates/rrpc/ProtoType.kt new file mode 100644 index 0000000..c5f7a18 --- /dev/null +++ b/common/core/src/commonMain/kotlin/org/timemates/rrpc/ProtoType.kt @@ -0,0 +1,50 @@ +package org.timemates.rrpc + +/** + * Interface representing a type that can be serialized and deserialized using ProtoBuf. + * + * Implementing this interface allows a class to integrate with the ProtoBuf serialization framework. + * It provides access to the type's definition, which includes metadata and default instances. + */ +public interface ProtoType { + + /** + * Provides the definition of the ProtoBuf type. + * + * The definition includes metadata about the type, such as its unique identifier and default instance. + * + * @return The definition of the ProtoBuf type. + */ + public val definition: Definition<*> + + /** + * Interface representing the definition of a ProtoBuf type. + * + * This interface contains metadata and a default instance for the ProtoBuf type. It helps in identifying + * the type and provides a way to access a default instance, which is useful for serialization and deserialization. + * + * @param T The type of the ProtoBuf type being defined. Must extend [ProtoType]. + */ + public interface Definition { + + /** + * The unique identifier for the ProtoBuf type. + * + * This URL is used to identify the type within the ProtoBuf framework, particularly when working + * with `Any` types that encapsulate different messages. + * + * @return The type URL as a string. + */ + public val typeUrl: String + + /** + * The default instance of the ProtoBuf type. + * + * This instance represents the default or zero-value state of the type. It is often used as a starting point + * or as a placeholder when no specific value is provided. + * + * @return A default instance of the ProtoBuf type. + */ + public val Default: T + } +} diff --git a/common/core/src/commonMain/kotlin/org/timemates/rrpc/annotations/ExperimentalInterceptorsApi.kt b/common/core/src/commonMain/kotlin/org/timemates/rrpc/annotations/ExperimentalInterceptorsApi.kt new file mode 100644 index 0000000..bf1166e --- /dev/null +++ b/common/core/src/commonMain/kotlin/org/timemates/rrpc/annotations/ExperimentalInterceptorsApi.kt @@ -0,0 +1,14 @@ +package org.timemates.rrpc.annotations + +@RequiresOptIn(message = "This API has subject to change.", level = RequiresOptIn.Level.WARNING) +@Target( + allowedTargets = [ + AnnotationTarget.CLASS, + AnnotationTarget.CONSTRUCTOR, + AnnotationTarget.FUNCTION, + AnnotationTarget.TYPEALIAS, + AnnotationTarget.VALUE_PARAMETER, + AnnotationTarget.PROPERTY + ], +) +public annotation class ExperimentalInterceptorsApi \ No newline at end of file diff --git a/common/core/src/commonMain/kotlin/org/timemates/rrpc/annotations/ExperimentalRSProtoAPI.kt b/common/core/src/commonMain/kotlin/org/timemates/rrpc/annotations/ExperimentalRSProtoAPI.kt new file mode 100644 index 0000000..b078ba0 --- /dev/null +++ b/common/core/src/commonMain/kotlin/org/timemates/rrpc/annotations/ExperimentalRSProtoAPI.kt @@ -0,0 +1,4 @@ +package org.timemates.rrpc.annotations + +@RequiresOptIn(message = "This API is considered as experimental.", level = RequiresOptIn.Level.WARNING) +public annotation class ExperimentalRRpcAPI \ No newline at end of file diff --git a/common/core/src/commonMain/kotlin/org/timemates/rrpc/annotations/InternalRSProtoAPI.kt b/common/core/src/commonMain/kotlin/org/timemates/rrpc/annotations/InternalRSProtoAPI.kt new file mode 100644 index 0000000..643aaf4 --- /dev/null +++ b/common/core/src/commonMain/kotlin/org/timemates/rrpc/annotations/InternalRSProtoAPI.kt @@ -0,0 +1,4 @@ +package org.timemates.rrpc.annotations + +@RequiresOptIn(message = "This API is considered as internal.", level = RequiresOptIn.Level.ERROR) +public annotation class InternalRRpcAPI \ No newline at end of file diff --git a/common/core/src/commonMain/kotlin/org/timemates/rrpc/exceptions/ProcedureNotFoundException.kt b/common/core/src/commonMain/kotlin/org/timemates/rrpc/exceptions/ProcedureNotFoundException.kt new file mode 100644 index 0000000..d5a831d --- /dev/null +++ b/common/core/src/commonMain/kotlin/org/timemates/rrpc/exceptions/ProcedureNotFoundException.kt @@ -0,0 +1,7 @@ +package org.timemates.rrpc.exceptions + +import org.timemates.rrpc.metadata.ClientMetadata + +public class ProcedureNotFoundException( + metadata: ClientMetadata +) : Exception("Procedure '${metadata.procedureName}' is not found in service named '${metadata.serviceName}'") \ No newline at end of file diff --git a/common/core/src/commonMain/kotlin/org/timemates/rrpc/exceptions/RSPException.kt b/common/core/src/commonMain/kotlin/org/timemates/rrpc/exceptions/RSPException.kt new file mode 100644 index 0000000..0b9186c --- /dev/null +++ b/common/core/src/commonMain/kotlin/org/timemates/rrpc/exceptions/RSPException.kt @@ -0,0 +1,3 @@ +package org.timemates.rrpc.exceptions + +public abstract class RRpcException(message: String) : RuntimeException(message) \ No newline at end of file diff --git a/common/core/src/commonMain/kotlin/org/timemates/rrpc/exceptions/ServiceNotFoundException.kt b/common/core/src/commonMain/kotlin/org/timemates/rrpc/exceptions/ServiceNotFoundException.kt new file mode 100644 index 0000000..6ccd75e --- /dev/null +++ b/common/core/src/commonMain/kotlin/org/timemates/rrpc/exceptions/ServiceNotFoundException.kt @@ -0,0 +1,5 @@ +package org.timemates.rrpc.exceptions + +public class ServiceNotFoundException( + serviceName: String, +) : RRpcException("Service '$serviceName' is not found.") \ No newline at end of file diff --git a/common/core/src/commonMain/kotlin/org/timemates/rrpc/instances/InstanceContainer.kt b/common/core/src/commonMain/kotlin/org/timemates/rrpc/instances/InstanceContainer.kt new file mode 100644 index 0000000..edf86d4 --- /dev/null +++ b/common/core/src/commonMain/kotlin/org/timemates/rrpc/instances/InstanceContainer.kt @@ -0,0 +1,100 @@ +package org.timemates.rrpc.instances + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.protobuf.ProtoBuf +import org.timemates.rrpc.annotations.InternalRRpcAPI +import kotlin.coroutines.CoroutineContext + +/** + * Creates an [InstanceContainer] with the provided map of instances. + * + * @param instances A map where the key is a [ProvidableInstance.Key] and the value is a [ProvidableInstance]. + * @return A new [InstanceContainer] containing the provided instances. + */ +public fun InstanceContainer(instances: Map, ProvidableInstance>): InstanceContainer = + InstanceContainerImpl(instances) + +/** + * Container for holding and retrieving instances of [ProvidableInstance]. + * + * This interface allows retrieving instances using a key and adding new instances to the container. + */ +public interface InstanceContainer { + /** + * Retrieves an instance of type [T] associated with the given [key]. + * + * @param key The key associated with the instance to be retrieved. + * @return The instance associated with the provided key. + */ + public fun getInstance(key: ProvidableInstance.Key): T? + + /** + * Creates a new [InstanceContainer] that contains all instances from this container plus the provided [instance]. + * + * @param instance The instance to be added to the container. + * @return A new [InstanceContainer] containing the existing instances plus the provided instance. + */ + public operator fun plus(instance: ProvidableInstance): InstanceContainer + + /** + * Creates a new [InstanceContainer] that contains all instances from this container plus the provided [instances]. + * + * @param instances The list of instances to be added to the container. + * @return A new [InstanceContainer] containing the existing instances plus the provided instances. + */ + public operator fun plus(instances: List): InstanceContainer + + /** + * Creates a new [InstanceContainer] that contains all instances from this container plus the provided [instances]. + * + * @param container another container with instances to be appended. + * @return A new [InstanceContainer] containing the existing instances plus the provided instances. + */ + public operator fun plus(container: InstanceContainer): InstanceContainer + + /** + * Retrieves instances as map. + */ + public fun asMap(): Map, ProvidableInstance> +} + +internal class InstanceContainerImpl( + private val instances: Map, ProvidableInstance> +) : InstanceContainer { + @Suppress("UNCHECKED_CAST") + override fun getInstance(key: ProvidableInstance.Key): T? = + instances[key] as? T + + + override operator fun plus(instance: ProvidableInstance): InstanceContainer { + return InstanceContainerImpl(instances + (instance.key to instance)) + } + + + override operator fun plus(instances: List): InstanceContainer { + return InstanceContainerImpl( + this.instances.plus(instances.associateBy(ProvidableInstance::key)) + ) + } + + override fun plus(container: InstanceContainer): InstanceContainer { + return InstanceContainer(instances + container.asMap()) + } + + override fun asMap(): Map, ProvidableInstance> { + return instances + } +} + +@OptIn(ExperimentalSerializationApi::class) +public val InstanceContainer.protobuf: ProtoBuf? + get() = getInstance(ProtobufInstance)?.protobuf + +@InternalRRpcAPI +public class CoroutineContextInstanceContainer( + public val container: InstanceContainer, +) : CoroutineContext.Element { + override val key: CoroutineContext.Key<*> = Key + + public companion object Key : CoroutineContext.Key +} \ No newline at end of file diff --git a/common/core/src/commonMain/kotlin/org/timemates/rrpc/instances/InstancesBuilder.kt b/common/core/src/commonMain/kotlin/org/timemates/rrpc/instances/InstancesBuilder.kt new file mode 100644 index 0000000..ca5f668 --- /dev/null +++ b/common/core/src/commonMain/kotlin/org/timemates/rrpc/instances/InstancesBuilder.kt @@ -0,0 +1,23 @@ +package org.timemates.rrpc.instances + +import org.timemates.rrpc.annotations.InternalRRpcAPI + +public class InstancesBuilder @InternalRRpcAPI constructor() { + private val instances: MutableSet = mutableSetOf() + + public fun register(instance: ProvidableInstance) { + if (instances.contains(instance)) + instances.remove(instance) + instances += instance + } + + @InternalRRpcAPI + public fun build(): List { + return instances.toList() + } +} + +public fun instances(block: InstancesBuilder.() -> Unit): List { + @OptIn(InternalRRpcAPI::class) + return InstancesBuilder().also(block).build() +} \ No newline at end of file diff --git a/common/core/src/commonMain/kotlin/org/timemates/rrpc/instances/ProtobufInstance.kt b/common/core/src/commonMain/kotlin/org/timemates/rrpc/instances/ProtobufInstance.kt new file mode 100644 index 0000000..aa69f13 --- /dev/null +++ b/common/core/src/commonMain/kotlin/org/timemates/rrpc/instances/ProtobufInstance.kt @@ -0,0 +1,41 @@ +package org.timemates.rrpc.instances + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.protobuf.ProtoBuf +import kotlinx.serialization.protobuf.ProtoBufBuilder +import kotlin.jvm.JvmInline + +@OptIn(ExperimentalSerializationApi::class) +@JvmInline +public value class ProtobufInstance(public val protobuf: ProtoBuf) : ProvidableInstance { + + public companion object Key : ProvidableInstance.Key + + override val key: ProvidableInstance.Key + get() = Key +} + +/** + * Register the protobuf instance for the [InstanceContainer]. + * + * @param protobuf The ProtoBuf instance to register. + */ +@OptIn(ExperimentalSerializationApi::class) +public fun InstancesBuilder.protobuf( + protobuf: ProtoBuf = ProtoBuf +) { + register(ProtobufInstance(protobuf)) +} + +/** + * Registers a Protobuf instance for the [InstanceContainer]. + * This allows the client/server to handle remote method calls using Protobuf encoding. + * + * @param builder A lambda function that configures the Protobuf instance using the [ProtoBufBuilder]. + */ +@OptIn(ExperimentalSerializationApi::class) +public fun InstancesBuilder.protobuf( + builder: ProtoBufBuilder.() -> Unit +) { + register(ProtobufInstance(ProtoBuf(builderAction = builder))) +} \ No newline at end of file diff --git a/common/core/src/commonMain/kotlin/org/timemates/rrpc/instances/ProvidableInstance.kt b/common/core/src/commonMain/kotlin/org/timemates/rrpc/instances/ProvidableInstance.kt new file mode 100644 index 0000000..4dbe182 --- /dev/null +++ b/common/core/src/commonMain/kotlin/org/timemates/rrpc/instances/ProvidableInstance.kt @@ -0,0 +1,10 @@ +package org.timemates.rrpc.instances + +/** + * A contract for classes that can provide instances based on a specified key. + */ +public interface ProvidableInstance { + public val key: Key<*> + + public interface Key +} \ No newline at end of file diff --git a/common/core/src/commonMain/kotlin/org/timemates/rrpc/interceptors/Interceptor.kt b/common/core/src/commonMain/kotlin/org/timemates/rrpc/interceptors/Interceptor.kt new file mode 100644 index 0000000..afbd885 --- /dev/null +++ b/common/core/src/commonMain/kotlin/org/timemates/rrpc/interceptors/Interceptor.kt @@ -0,0 +1,115 @@ +@file:OptIn(InternalRRpcAPI::class) + +package org.timemates.rrpc.interceptors + +import org.timemates.rrpc.DataVariant +import org.timemates.rrpc.Failure +import org.timemates.rrpc.annotations.ExperimentalInterceptorsApi +import org.timemates.rrpc.annotations.InternalRRpcAPI +import org.timemates.rrpc.instances.InstanceContainer +import org.timemates.rrpc.metadata.ClientMetadata +import org.timemates.rrpc.metadata.RRpcMetadata +import org.timemates.rrpc.metadata.ServerMetadata +import org.timemates.rrpc.options.OptionsWithValue + +/** + * Represents an interceptor that processes metadata of type [TMetadata]. + * + * @param TMetadata The type of metadata that the interceptor processes. + */ +@ExperimentalInterceptorsApi +public interface Interceptor { + /** + * Intercepts and processes the given [context]. + * + * **ANY consumption of request input / output that is Flow should rely + * on the functions like [kotlinx.coroutines.flow.onEach]. Otherwise, it will lead to data loss or performance problems.** + * + * @param context The context containing metadata to be processed by the interceptor. + * @return The processed [InterceptorContext] with potentially modified metadata. + */ + public suspend fun intercept( + context: InterceptorContext, + ): InterceptorContext +} + +/** + * A type alias for an interceptor that processes client metadata. + */ +@ExperimentalInterceptorsApi +public typealias RequestInterceptor = Interceptor + +/** + * A type alias for an interceptor that processes server metadata. + */ +@ExperimentalInterceptorsApi +public typealias ResponseInterceptor = Interceptor + +@ExperimentalInterceptorsApi +public data class Interceptors( + val request: List>, + val response: List>, +) { + /** + * Runs input interceptors and returns the result of the provided block. + * + * @param data The input data. + * @param clientMetadata The client metadata. + * @param options The options for the procedure. + * @param instanceContainer The instance container. + * @param block The block of code to execute after running the interceptors. + * @return The result of the block execution. + */ + @InternalRRpcAPI + public suspend inline fun runInputInterceptors( + data: DataVariant<*>, + clientMetadata: ClientMetadata, + options: OptionsWithValue, + instanceContainer: InstanceContainer, + ): InterceptorContext? { + if (request.isNotEmpty()) { + val initialContext = InterceptorContext(data, clientMetadata, options, instanceContainer) + return request.fold(initialContext) { acc, interceptor -> + try { + interceptor.intercept(acc) + } catch (e: Exception) { + interceptor.intercept(acc.copy(data = Failure(e))) + } + } + } + + return null + } + + /** + * Runs output interceptors on the provided data. + * + * **API Note**: The API is considered internal, to backward-compatibility is + * guaranteed. + * + * @param data The output data. + * @param serverMetadata The server metadata. + * @param options The options for the procedure. + * @param instanceContainer The instance container. + * + * @return [InterceptorContext] or null if no interceptors were involved. + */ + @InternalRRpcAPI + public suspend fun runOutputInterceptors( + data: DataVariant<*>, + serverMetadata: ServerMetadata, + options: OptionsWithValue, + instanceContainer: InstanceContainer + ): InterceptorContext? { + return if (response.isNotEmpty()) { + val initialContext = InterceptorContext(data, serverMetadata, options, instanceContainer) + response.fold(initialContext) { acc, interceptor -> + try { + interceptor.intercept(acc) + } catch (e: Exception) { + interceptor.intercept(acc.copy(data = Failure(e))) + } + } + } else null + } +} \ No newline at end of file diff --git a/common/core/src/commonMain/kotlin/org/timemates/rrpc/interceptors/InterceptorContext.kt b/common/core/src/commonMain/kotlin/org/timemates/rrpc/interceptors/InterceptorContext.kt new file mode 100644 index 0000000..c12e54e --- /dev/null +++ b/common/core/src/commonMain/kotlin/org/timemates/rrpc/interceptors/InterceptorContext.kt @@ -0,0 +1,166 @@ +@file:Suppress("MemberVisibilityCanBePrivate") + +package org.timemates.rrpc.interceptors + +import org.timemates.rrpc.DataVariant +import org.timemates.rrpc.annotations.InternalRRpcAPI +import org.timemates.rrpc.instances.InstanceContainer +import org.timemates.rrpc.instances.ProvidableInstance +import org.timemates.rrpc.metadata.ExtraMetadata +import org.timemates.rrpc.metadata.RRpcMetadata +import org.timemates.rrpc.options.OptionsWithValue +import kotlin.jvm.JvmSynthetic + +/** + * Represents the context for interceptors to access and modify request-related data, + * metadata, and options during request processing. + * + * @property data Request/response data linked to the request, which can be either + * single-value or streaming data. + * @property metadata Request metadata attached to the request. + * @property options Options linked to the request. + * @property instances Container for instances available within the request pipeline. + */ +@OptIn(InternalRRpcAPI::class) +public class InterceptorContext @InternalRRpcAPI constructor( + public val data: DataVariant<*>, + public val metadata: TMetadata, + public val options: OptionsWithValue, + public val instances: InstanceContainer, +) { + + /** + * Provides a builder to modify the context immutably, preserving the current state of the context. + */ + public fun builder(): Builder { + return Builder( + instances, metadata, data, options, + ) + } + + + /** + * Copies current [InterceptorContext] with the ability to change some of its fields. + * + * It's better to use [builder] as it ensures that no data was lost during the chain of interceptors. + * Make sure that [instances] are properly handled and nothing lost. + * + * Marked as [JvmSynthetic], because named arguments are not supported from Java side. + */ + @JvmSynthetic + public fun copy( + data: DataVariant<*> = this.data, + extra: ExtraMetadata = this.metadata.extra, + options: OptionsWithValue = this.options, + instances: InstanceContainer = this.instances, + ): InterceptorContext { + @Suppress("UNCHECKED_CAST") + return InterceptorContext( + data, metadata.extra(extra) as TMetadata, options, instances, + ) + } + + /** + * Builder class for modifying an immutable [InterceptorContext]. + */ + public class Builder( + private var instances: InstanceContainer, + private var metadata: TMetadata, + private var data: DataVariant<*>, + private var options: OptionsWithValue, + ) { + /** + * Adds local instances to the given request pipeline. These instances do not affect + * the global [InstanceContainer]. + * + * @param instance The instance to add to the local request pipeline. + * @return This builder instance for chaining method calls. + */ + public fun addLocalInstance(instance: ProvidableInstance): Builder { + instances += instance + return this + } + + /** + * Adds multiple local instances to the given request pipeline. These instances do not affect + * the global [InstanceContainer]. + * + * @param list The list of instances to add to the local request pipeline. + * @return This builder instance for chaining method calls. + */ + public fun addLocalInstances(list: List): Builder { + instances += list + return this + } + + /** + * Adds extra metadata to the builder, modifying the current metadata instance. + * + * @param extra The extra metadata to add. + * @return This builder instance for chaining method calls. + */ + public fun extras(extra: ExtraMetadata): Builder { + @Suppress("UNCHECKED_CAST") + metadata = metadata.extra(extra) as TMetadata + return this + } + + /** + * Sets the request/response data in the builder. Ensures that the incoming data type matches + * the current data type to prevent confusion and potential type errors. + * + * @param value The new value for request/response data. + * @return This builder instance for chaining method calls. + * @throws IllegalArgumentException if the data type of [value] does not match the current data type. + */ + public fun data(value: DataVariant<*>): Builder { + data = value + return this + } + + /** + * Sets the options in the builder. + * + * @param options The new options for the request. + */ + public fun options(options: OptionsWithValue): Builder { + this.options = options + return this + } + + /** + * Constructs an immutable [InterceptorContext] instance based on the current builder state. + * + * @return An immutable [InterceptorContext] instance. + */ + public fun build(): InterceptorContext { + return InterceptorContext( + data, metadata, options, instances + ) + } + } +} + +/** + * Adds multiple local instances to the [InterceptorContext.Builder] using vararg parameter syntax. + * + * @param instances The vararg list of instances to add to the builder. + * @return The [InterceptorContext.Builder] instance with added local instances. + */ +public fun InterceptorContext.Builder.addLocalInstances( + vararg instances: ProvidableInstance +): InterceptorContext.Builder { + return addLocalInstances(instances.toList()) +} + +/** + * Modifies the current [InterceptorContext] immutably using a builder pattern. + * + * @param block The configuration block to modify the [InterceptorContext.Builder]. + * @return The modified [InterceptorContext] instance. + */ +public inline fun InterceptorContext.modify( + block: InterceptorContext.Builder.() -> Unit +): InterceptorContext { + return builder().apply(block).build() +} \ No newline at end of file diff --git a/common/core/src/commonMain/kotlin/org/timemates/rrpc/metadata/ClientMetadata.kt b/common/core/src/commonMain/kotlin/org/timemates/rrpc/metadata/ClientMetadata.kt new file mode 100644 index 0000000..962fe5e --- /dev/null +++ b/common/core/src/commonMain/kotlin/org/timemates/rrpc/metadata/ClientMetadata.kt @@ -0,0 +1,40 @@ +@file:OptIn(ExperimentalSerializationApi::class) + +package org.timemates.rrpc.metadata + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.Serializable +import kotlinx.serialization.protobuf.ProtoNumber +import kotlin.jvm.JvmField +import kotlin.jvm.JvmOverloads +import kotlin.jvm.JvmStatic + +/** + * Represents metadata information. + * + * @property serviceName The name of the service. + * @property procedureName The name of the procedure. + * @property extra Additional key-value pairs of metadata. + */ +@Serializable +public data class ClientMetadata @JvmOverloads constructor( + @ProtoNumber(1) + public val schemaVersion: Int = RRpcMetadata.CURRENT_SCHEMA_VERSION, + @ProtoNumber(2) + public val serviceName: String = "", + @ProtoNumber(3) + public val procedureName: String = "", + @ProtoNumber(4) + public override val extra: ExtraMetadata = ExtraMetadata.EMPTY, +) : RRpcMetadata { + public companion object { + @JvmField + public val EMPTY: ClientMetadata = ClientMetadata() + } + + override fun extra(extra: ExtraMetadata): ClientMetadata { + return if (extra == this.extra) + this + else copy(extra = extra) + } +} \ No newline at end of file diff --git a/common/core/src/commonMain/kotlin/org/timemates/rrpc/metadata/ExtraMetadata.kt b/common/core/src/commonMain/kotlin/org/timemates/rrpc/metadata/ExtraMetadata.kt new file mode 100644 index 0000000..ff8955b --- /dev/null +++ b/common/core/src/commonMain/kotlin/org/timemates/rrpc/metadata/ExtraMetadata.kt @@ -0,0 +1,27 @@ +package org.timemates.rrpc.metadata + +import kotlinx.serialization.Serializable +import org.timemates.rrpc.instances.InstanceContainer +import org.timemates.rrpc.instances.ProvidableInstance +import kotlin.jvm.JvmInline + +/** + * Represents extra metadata within incoming request associated with a coroutine context. + * + * @property extra The map containing the extra metadata. + */ +@JvmInline +@Serializable +public value class ExtraMetadata(public val extra: Map) : ProvidableInstance { + public companion object : ProvidableInstance.Key { + public val EMPTY: ExtraMetadata = ExtraMetadata(emptyMap()) + } + + override val key: ProvidableInstance.Key<*> + get() = Companion + + public operator fun get(key: String): ByteArray? = extra[key] +} + +public val InstanceContainer.extraMetadata: ExtraMetadata + get() = getInstance(ExtraMetadata) ?: error("Called from the wrong context") \ No newline at end of file diff --git a/common/core/src/commonMain/kotlin/org/timemates/rrpc/metadata/RSPMetadata.kt b/common/core/src/commonMain/kotlin/org/timemates/rrpc/metadata/RSPMetadata.kt new file mode 100644 index 0000000..56ee4cf --- /dev/null +++ b/common/core/src/commonMain/kotlin/org/timemates/rrpc/metadata/RSPMetadata.kt @@ -0,0 +1,15 @@ +package org.timemates.rrpc.metadata + +public sealed interface RRpcMetadata { + public companion object { + /** + * This property implies the version of communication. It has + * different versioning from RRpc as such, only for global changes to the scheme. + */ + public const val CURRENT_SCHEMA_VERSION: Int = 1 + } + + public val extra: ExtraMetadata + + public fun extra(extra: ExtraMetadata): RRpcMetadata +} \ No newline at end of file diff --git a/common/core/src/commonMain/kotlin/org/timemates/rrpc/metadata/ServerMetadata.kt b/common/core/src/commonMain/kotlin/org/timemates/rrpc/metadata/ServerMetadata.kt new file mode 100644 index 0000000..e6f5c47 --- /dev/null +++ b/common/core/src/commonMain/kotlin/org/timemates/rrpc/metadata/ServerMetadata.kt @@ -0,0 +1,29 @@ +@file:OptIn(ExperimentalSerializationApi::class) + +package org.timemates.rrpc.metadata + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.Serializable +import kotlinx.serialization.protobuf.ProtoNumber +import kotlin.jvm.JvmField + +/** + * Represents metadata information that sent from Server to Client. + * @property extra Additional key-value pairs of metadata. + */ +@Serializable +public data class ServerMetadata( + @ProtoNumber(1) + val schemaVersion: Int = RRpcMetadata.CURRENT_SCHEMA_VERSION, + @ProtoNumber(2) + public override val extra: ExtraMetadata = ExtraMetadata.EMPTY, +) : RRpcMetadata { + public companion object { + @JvmField + public val EMPTY: ServerMetadata = ServerMetadata() + } + + override fun extra(extra: ExtraMetadata): RRpcMetadata { + return copy(extra = extra) + } +} \ No newline at end of file diff --git a/common/core/src/commonMain/kotlin/org/timemates/rrpc/options/Option.kt b/common/core/src/commonMain/kotlin/org/timemates/rrpc/options/Option.kt new file mode 100644 index 0000000..5f6beab --- /dev/null +++ b/common/core/src/commonMain/kotlin/org/timemates/rrpc/options/Option.kt @@ -0,0 +1,39 @@ +package org.timemates.rrpc.options + +/** + * This interface represents [protobuf options](https://protobuf.dev/programming-guides/proto3/#options). + * + * **For now, RRpc supports only service and rpc options.** + * + * @property name the name assigned to the given option. It's used mostly for debugging + * and better understanding of the code. It has no actual connection with resolving + * options within requests or responses. + * @property tag unique identifier assigned to the given option. + */ +@Suppress("unused") +public interface Option { + public val name: String + public val tag: Int +} + +public data class ServiceOption( + override val name: String, + override val tag: Int, +) : Option { + public companion object +} + +public data class RPCOption( + override val name: String, + override val tag: Int, +) : Option { + public companion object +} + +public data class FileOption( + override val name: String, + override val tag: Int, +) : Option { + public companion object +} + diff --git a/common/core/src/commonMain/kotlin/org/timemates/rrpc/options/OptionsWithValue.kt b/common/core/src/commonMain/kotlin/org/timemates/rrpc/options/OptionsWithValue.kt new file mode 100644 index 0000000..d81c658 --- /dev/null +++ b/common/core/src/commonMain/kotlin/org/timemates/rrpc/options/OptionsWithValue.kt @@ -0,0 +1,35 @@ +package org.timemates.rrpc.options + +import kotlin.jvm.JvmInline +import kotlin.jvm.JvmStatic + +@JvmInline +public value class OptionsWithValue(private val map: Map, Any>) { + public constructor(vararg pairs: Pair, Any>) : this(mapOf(*pairs)) + + public companion object { + @JvmStatic + public val EMPTY: OptionsWithValue = OptionsWithValue(emptyMap()) + } + + public operator fun get(option: Option): T? { + @Suppress("UNCHECKED_CAST") + return map[option] as? T + } + + public operator fun plus(other: OptionsWithValue): OptionsWithValue { + return OptionsWithValue(this.map + other.map) + } + + public operator fun plus(other: Pair, T>): OptionsWithValue { + return OptionsWithValue(this.map + (other.first to other.second)) + } + + public operator fun minus(option: Option<*>): OptionsWithValue { + return OptionsWithValue(map - option) + } + + public fun asMap(): Map, Any> = map +} + +public fun OptionsWithValue.hasOption(option: Option<*>): Boolean = get(option) != null \ No newline at end of file diff --git a/common/core/src/commonMain/resources/google/protobuf/wrappers.proto b/common/core/src/commonMain/resources/google/protobuf/wrappers.proto index db01dc1..452a316 100644 --- a/common/core/src/commonMain/resources/google/protobuf/wrappers.proto +++ b/common/core/src/commonMain/resources/google/protobuf/wrappers.proto @@ -45,7 +45,7 @@ package google.protobuf; option cc_enable_arenas = true; option go_package = "google.golang.org/protobuf/types/known/wrapperrpcb"; option java_package = "com.google.protobuf"; -option java_outer_classname = "WrappeRRpcroto"; +option java_outer_classname = "WrappeRRpc"; option java_multiple_files = true; option objc_class_prefix = "GPB"; option csharp_namespace = "Google.Protobuf.WellKnownTypes"; diff --git a/common/core/src/jvmTest/kotlin/org/timemates/rsp/common/test/InterceptorsTest.kt b/common/core/src/jvmTest/kotlin/org/timemates/rsp/common/test/InterceptorsTest.kt index d18a24f..a80a313 100644 --- a/common/core/src/jvmTest/kotlin/org/timemates/rsp/common/test/InterceptorsTest.kt +++ b/common/core/src/jvmTest/kotlin/org/timemates/rsp/common/test/InterceptorsTest.kt @@ -1,4 +1,4 @@ -@file:OptIn(ExperimentalInterceptorsApi::class, InternalRRpcrotoAPI::class) +@file:OptIn(ExperimentalInterceptorsApi::class, InternalRRpcAPI::class) package org.timemates.rrpc.common.test @@ -7,7 +7,7 @@ import io.mockk.mockk import kotlinx.coroutines.runBlocking import org.timemates.rrpc.Single import org.timemates.rrpc.annotations.ExperimentalInterceptorsApi -import org.timemates.rrpc.annotations.InternalRRpcrotoAPI +import org.timemates.rrpc.annotations.InternalRRpcAPI import org.timemates.rrpc.instances.InstanceContainer import org.timemates.rrpc.interceptors.Interceptor import org.timemates.rrpc.interceptors.InterceptorContext diff --git a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMNode.kt b/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMNode.kt deleted file mode 100644 index 600723b..0000000 --- a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMNode.kt +++ /dev/null @@ -1,3 +0,0 @@ -package org.timemates.rrpc.common.metadata - -public sealed interface RMNode \ No newline at end of file diff --git a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMOptions.kt b/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMOptions.kt deleted file mode 100644 index 005dd86..0000000 --- a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMOptions.kt +++ /dev/null @@ -1,36 +0,0 @@ -package org.timemates.rrpc.common.metadata - -import kotlinx.serialization.Serializable -import org.timemates.rrpc.common.metadata.value.RMTypeUrl -import kotlin.jvm.JvmInline - - -@Serializable -@JvmInline -public value class RMOptions( - public val list: List -) { - public companion object { - public val EMPTY: RMOptions = RMOptions(emptyList()) - - public val FILE_OPTIONS: RMTypeUrl = RMTypeUrl("google.protobuf.FileOptions") - public val MESSAGE_OPTIONS: RMTypeUrl = RMTypeUrl("google.protobuf.MessageOptions") - public val SERVICE_OPTIONS: RMTypeUrl = RMTypeUrl("google.protobuf.ServiceOptions") - public val FIELD_OPTIONS: RMTypeUrl = RMTypeUrl("google.protobuf.FieldOptions") - public val ONEOF_OPTIONS: RMTypeUrl = RMTypeUrl("google.protobuf.OneofOptions") - public val ENUM_OPTIONS: RMTypeUrl = RMTypeUrl("google.protobuf.EnumOptions") - public val ENUM_VALUE_OPTIONS: RMTypeUrl = RMTypeUrl("google.protobuf.EnumValueOptions") - public val METHOD_OPTIONS: RMTypeUrl = RMTypeUrl("google.protobuf.MethodOptions") - public val EXTENSION_RANGE_OPTIONS: RMTypeUrl = RMTypeUrl("google.protobuf.ExtensionRangeOptions") - } - - public operator fun get(fieldUrl: RMTypeMemberUrl): RMOption? { - return list.firstOrNull { it.fieldUrl == fieldUrl } - } - - public operator fun contains(fieldUrl: RMTypeMemberUrl): Boolean { - return get(fieldUrl) != null - } -} - -public val RMOptions.isDeprecated: Boolean get() = TODO() \ No newline at end of file diff --git a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMResolver.kt b/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMResolver.kt deleted file mode 100644 index a67d683..0000000 --- a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMResolver.kt +++ /dev/null @@ -1,202 +0,0 @@ -package org.timemates.rrpc.common.metadata - -import org.timemates.rrpc.common.metadata.value.RMPackageName -import org.timemates.rrpc.common.metadata.value.RMTypeUrl - -public fun RMResolver( - project: List, -): RMResolver = InMemoryRMResolver(project) - -public fun RMResolver( - vararg resolvers: RMResolver, -): RMResolver = CompoundRMResolver(*resolvers) - -/** - * Interface for resolving various components (fields, types, files, services) in the RPC metadata model. - * This provides a lookup mechanism for retrieving data based on unique identifiers like type URLs or package names. - */ -public interface RMResolver { - /** - * Resolves a field within a type by the given [typeMemberUrl]. - */ - public fun resolveField(typeMemberUrl: RMTypeMemberUrl): RMField? - - /** - * Resolves a type by the given [typeUrl]. - * - * @param typeUrl The unique identifier for the type, typically used in protobuf definitions. - * @return The corresponding [RMType] if found, or `null` if no matching type is found. - */ - public fun resolveType(typeUrl: RMTypeUrl): RMType? - - /** - * Resolves a file by the given [packageName] and [name]. - * - * @param packageName The package name where the file is defined. - * @param name The name of the file to be resolved. - * @return The corresponding [RMFile] if found, or `null` if no matching file is found. - */ - public fun resolveFileOf(packageName: RMPackageName, name: String): RMFile? - - /** - * Resolves file where a type is present. - * - * @param typeUrl reference to the type. - * @return The corresponding [RMFile] if type is found in the one of the files, or - * `null` if no matching file is found. - */ - public fun resolveFileOf(typeUrl: RMTypeUrl): RMFile? - - /** - * Resolves a service by the given [typeUrl]. - * - * @param typeUrl The unique identifier for the service, typically used in protobuf definitions. - * @return The corresponding [RMService] if found, or `null` if no matching service is found. - */ - public fun resolveService(typeUrl: RMTypeUrl): RMService? - - /** - * Resolves all files available in this particular [RMResolver] - */ - public fun resolveAvailableFiles(): Sequence - - public fun resolveAllServices(): Sequence - public fun resolveAllTypes(): Sequence - - public fun resolveNodesWithOption(optionFieldUrl: RMTypeMemberUrl): Sequence - public fun resolveServicesWithOption(optionFieldUrl: RMTypeMemberUrl): Sequence - public fun resolveTypesWithOption(optionFieldUrl: RMTypeMemberUrl): Sequence - public fun resolveRpcsWithOption(optionFieldUrl: RMTypeMemberUrl): Sequence - public fun resolveFieldsWithOption(optionFieldUrl: RMTypeMemberUrl): Sequence - public fun resolveConstantsWithOption(optionFieldUrl: RMTypeMemberUrl): Sequence -} - -private class InMemoryRMResolver( - private val files: List, -) : RMResolver { - override fun resolveField(typeMemberUrl: RMTypeMemberUrl): RMField? { - TODO() - } - - override fun resolveType(typeUrl: RMTypeUrl): RMType? { - TODO() - } - - override fun resolveFileOf(packageName: RMPackageName, name: String): RMFile? { - TODO() - } - - override fun resolveService(typeUrl: RMTypeUrl): RMService? { - TODO() - } - - override fun resolveAvailableFiles(): Sequence { - return files.asSequence() - } - - override fun resolveAllServices(): Sequence { - return resolveAvailableFiles().flatMap { it.services } - } - - override fun resolveAllTypes(): Sequence { - return resolveAvailableFiles().flatMap { it.types } - } - - override fun resolveNodesWithOption(optionFieldUrl: RMTypeMemberUrl): Sequence { - TODO("Not yet implemented") - } - - override fun resolveServicesWithOption(optionFieldUrl: RMTypeMemberUrl): Sequence { - TODO("Not yet implemented") - } - - override fun resolveRpcsWithOption(optionFieldUrl: RMTypeUrl): Sequence { - TODO("Not yet implemented") - } - - override fun resolveFieldsWithOption(optionFieldUrl: RMTypeUrl): Sequence { - TODO("Not yet implemented") - } - - override fun resolveConstantsWithOption(optionFieldUrl: RMTypeUrl): Sequence { - TODO("Not yet implemented") - } -} - -private class CompoundRMResolver( - private vararg val resolvers: RMResolver, -) : RMResolver { - override fun resolveField(typeMemberUrl: RMTypeMemberUrl): RMField? { - return resolvers.firstNotNullOfOrNull { it.resolveField(typeMemberUrl) } - } - - override fun resolveType(typeUrl: RMTypeUrl): RMType? { - return resolvers.firstNotNullOfOrNull { it.resolveType(typeUrl) } - } - - override fun resolveFileOf(packageName: RMPackageName, name: String): RMFile? { - return resolvers.firstNotNullOfOrNull { it.resolveFileOf(packageName, name) } - } - - override fun resolveService(typeUrl: RMTypeUrl): RMService? { - return resolvers.firstNotNullOfOrNull { it.resolveService(typeUrl) } - } - - override fun resolveAvailableFiles(): Sequence { - return sequence { - resolvers.forEach { - yieldAll(it.resolveAvailableFiles()) - } - } - } - - override fun resolveAllServices(): Sequence { - return sequence { - resolvers.forEach { - yieldAll(it.resolveAllServices()) - } - } - } - - override fun resolveAllTypes(): Sequence { - return sequence { - resolvers.forEach { - yieldAll(it.resolveAllTypes()) - } - } - } - - override fun resolveNodesWithOption(optionFieldUrl: RMTypeMemberUrl): Sequence { - return sequenceOf(resolveServicesWithOption(optionFieldUrl), resolveFieldsWithOption(optionFieldUrl), resolve - } - - override fun resolveServicesWithOption(optionFieldUrl: RMTypeMemberUrl): Sequence { - return resolvers.asSequence().flatMap { resolver -> - resolver.resolveAllServices().filter { optionFieldUrl in it.options } - } - } - - override fun resolveRpcsWithOption(optionFieldUrl: RMTypeMemberUrl): Sequence { - return resolvers.asSequence().flatMap { resolver -> - resolver.resolveAllServices().flatMap { it.rpcs }.filter { rpc -> - optionFieldUrl in rpc.options - } - } - } - - override fun resolveFieldsWithOption(optionFieldUrl: RMTypeMemberUrl): Sequence { - return resolvers.asSequence().flatMap { resolver -> - resolver.resolveAllTypes().filterIsInstance().flatMap { it.fields }.filter { field -> - optionFieldUrl in field.options - } - } - } - - override fun resolveConstantsWithOption(optionFieldUrl: RMTypeMemberUrl): Sequence { - return resolvers.asSequence().flatMap { resolver -> - resolver.resolveAllTypes().filterIsInstance().flatMap { it.constants }.filter { constant -> - optionFieldUrl in constant.options - } - } - } -} \ No newline at end of file diff --git a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/value/RMTypeUrl.kt b/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/value/RMTypeUrl.kt deleted file mode 100644 index 5ec7efc..0000000 --- a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/value/RMTypeUrl.kt +++ /dev/null @@ -1,124 +0,0 @@ -package org.timemates.rrpc.common.metadata.value - -import kotlinx.serialization.Serializable -import kotlin.jvm.JvmInline - -@Serializable -@JvmInline -public value class RMTypeUrl(public val value: String) { - public val isScalar: Boolean get() = this in SCALAR_TYPES - public val isWrapper: Boolean get() = this in WRAPPER_TYPES - public val isGoogleBuiltin: Boolean get() = this in GOOGLE_BUILTIN_TYPES - - public val isMap: Boolean get() = value.startsWith("map<") - public val firstTypeArgument: RMTypeUrl? get() = - if (isMap) - RMTypeUrl(value.substringAfter('<').substringBefore(',')) - else null - - public val secondTypeArgument: RMTypeUrl? get() = - if (isMap) - RMTypeUrl(value.substringAfter(',').substringBefore('>').trim()) - else null - - public val simpleName: String get() = value.substringAfterLast('.') - public val enclosingTypeOrPackage: String? get() { - val string = value.substringAfterLast('/') - val dot = string.lastIndexOf('.') - return if (dot == -1) null else string.substring(0, dot) - } - - public companion object { - public val UNKNOWN: RMTypeUrl = RMTypeUrl("unknown") - - public val INT32: RMTypeUrl = RMTypeUrl("int32") - public val INT64: RMTypeUrl = RMTypeUrl("int32") - - public val STRING: RMTypeUrl = RMTypeUrl("string") - - public val SINT32: RMTypeUrl = RMTypeUrl("int32") - public val SINT64: RMTypeUrl = RMTypeUrl("int32") - - public val BOOL: RMTypeUrl = RMTypeUrl("bool") - - public val UINT32: RMTypeUrl = RMTypeUrl("uint32") - public val UINT64: RMTypeUrl = RMTypeUrl("uint64") - - public val SFIXED32: RMTypeUrl = RMTypeUrl("sfixed32") - public val SFIXED64: RMTypeUrl = RMTypeUrl("sfixed64") - - public val FIXED32: RMTypeUrl = RMTypeUrl("fixed32") - public val FIXED64: RMTypeUrl = RMTypeUrl("fixed64") - - public val FLOAT: RMTypeUrl = RMTypeUrl("float") - public val DOUBLE: RMTypeUrl = RMTypeUrl("double") - - public val BYTES: RMTypeUrl = RMTypeUrl("bytes") - - public fun ofMap(first: RMTypeUrl, second: RMTypeUrl): RMTypeUrl { - return RMTypeUrl("map<${first.value}, ${second.value}>") - } - - public val SCALAR_TYPES: List = listOf( - INT32, - INT64, - STRING, - SINT32, - SINT64, - BOOL, - UINT32, - UINT64, - STRING, - FIXED32, - FIXED64, - FLOAT, - DOUBLE, - BYTES, - ) - - public val ANY: RMTypeUrl = RMTypeUrl("type.googleapis.com/google.protobuf.Any") - public val TIMESTAMP: RMTypeUrl = RMTypeUrl("type.googleapis.com/google.protobuf.Timestamp") - public val DURATION: RMTypeUrl = RMTypeUrl("type.googleapis.com/google.protobuf.Duration") - public val EMPTY: RMTypeUrl = RMTypeUrl("type.googleapis.com/google.protobuf.Empty") - public val STRUCT: RMTypeUrl = RMTypeUrl("type.googleapis.com/google.protobuf.Struct") - public val STRUCT_MAP: RMTypeUrl = RMTypeUrl("type.googleapis.com/google.protobuf.StructMap") - public val STRUCT_VALUE: RMTypeUrl = RMTypeUrl("type.googleapis.com/google.protobuf.Value") - public val STRUCT_NULL: RMTypeUrl = RMTypeUrl("type.googleapis.com/google.protobuf.NullValue") - public val STRUCT_LIST: RMTypeUrl = RMTypeUrl("type.googleapis.com/google.protobuf.ListValue") - - public val GOOGLE_BUILTIN_TYPES: List = listOf( - ANY, - TIMESTAMP, - DURATION, - EMPTY, - STRUCT, - STRUCT_MAP, - STRUCT_VALUE, - STRUCT_NULL, - STRUCT_LIST, - ) - - public val DOUBLE_VALUE: RMTypeUrl = RMTypeUrl("type.googleapis.com/google.protobuf.DoubleValue") - public val FLOAT_VALUE: RMTypeUrl = RMTypeUrl("type.googleapis.com/google.protobuf.FloatValue") - public val INT32_VALUE: RMTypeUrl = RMTypeUrl("type.googleapis.com/google.protobuf.Int32Value") - public val INT64_VALUE: RMTypeUrl = RMTypeUrl("type.googleapis.com/google.protobuf.Int64Value") - public val UINT32_VALUE: RMTypeUrl = RMTypeUrl("type.googleapis.com/google.protobuf.UInt32Value") - public val UINT64_VALUE: RMTypeUrl = RMTypeUrl("type.googleapis.com/google.protobuf.UINT64Value") - public val STRING_VALUE: RMTypeUrl = RMTypeUrl("type.googleapis.com/google.protobuf.StringValue") - public val BYTES_VALUE: RMTypeUrl = RMTypeUrl("type.googleapis.com/google.protobuf.BytesValue") - public val BOOL_VALUE: RMTypeUrl = RMTypeUrl("type.googleapis.com/google.protobuf.BoolValue") - - public val ACK: RMTypeUrl = RMTypeUrl("type.googleapis.com/timemates.rrpc.Ack") - - public val WRAPPER_TYPES: List = listOf( - DOUBLE_VALUE, - FLOAT_VALUE, - INT32_VALUE, - INT64_VALUE, - UINT32_VALUE, - UINT64_VALUE, - STRING_VALUE, - BYTES_VALUE, - ) - } -} \ No newline at end of file diff --git a/common/metadata/build.gradle.kts b/common/schema/build.gradle.kts similarity index 55% rename from common/metadata/build.gradle.kts rename to common/schema/build.gradle.kts index 2ad33b7..bc56249 100644 --- a/common/metadata/build.gradle.kts +++ b/common/schema/build.gradle.kts @@ -4,18 +4,22 @@ plugins { } dependencies { - commonMainApi(libs.kotlinx.serialization.proto) - commonMainApi(projects.common.core) + // -- Project -- + commonMainImplementation(projects.common.core) + + // -- Serialization -- + commonMainImplementation(libs.kotlinx.serialization.proto) } + kotlin { - js(IR) { - browser() - nodejs() - } - iosArm64() - iosX64() - iosSimulatorArm64() +// js(IR) { +// browser() +// nodejs() +// } +// iosArm64() +// iosX64() +// iosSimulatorArm64() } mavenPublishing { @@ -26,7 +30,7 @@ mavenPublishing { ) pom { - name.set("RRpcroto Common Metadata") - description.set("Multiplatform Kotlin Mutadata library for RRpcroto servers and clients.") + name.set("RRpc Common Metadata") + description.set("Multiplatform Kotlin Mutadata library for RRpc servers and clients.") } } \ No newline at end of file diff --git a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/Documentable.kt b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/Documentable.kt similarity index 62% rename from common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/Documentable.kt rename to common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/Documentable.kt index 47ba87e..f18b615 100644 --- a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/Documentable.kt +++ b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/Documentable.kt @@ -1,4 +1,4 @@ -package org.timemates.rrpc.common.metadata +package org.timemates.rrpc.common.schema public interface Documentable { public val documentation: String? diff --git a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/Language.kt b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/Language.kt similarity index 62% rename from common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/Language.kt rename to common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/Language.kt index e113987..8ed5480 100644 --- a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/Language.kt +++ b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/Language.kt @@ -1,4 +1,4 @@ -package org.timemates.rrpc.common.metadata +package org.timemates.rrpc.common.schema public enum class Language { JAVA, KOTLIN, PHP, C_SHARP, PYTHON, diff --git a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMEnumConstant.kt b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMEnumConstant.kt similarity index 84% rename from common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMEnumConstant.kt rename to common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMEnumConstant.kt index 109681b..5abcbaf 100644 --- a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMEnumConstant.kt +++ b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMEnumConstant.kt @@ -1,4 +1,4 @@ -package org.timemates.rrpc.common.metadata +package org.timemates.rrpc.common.schema import kotlinx.serialization.Serializable diff --git a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMExtend.kt b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMExtend.kt similarity index 52% rename from common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMExtend.kt rename to common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMExtend.kt index 0151211..e32d0f0 100644 --- a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMExtend.kt +++ b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMExtend.kt @@ -1,12 +1,12 @@ -package org.timemates.rrpc.common.metadata +package org.timemates.rrpc.common.schema import kotlinx.serialization.Serializable -import org.timemates.rrpc.common.metadata.value.RMTypeUrl +import org.timemates.rrpc.common.schema.value.RMDeclarationUrl @Serializable public class RMExtend( - public val typeUrl: RMTypeUrl, + public val typeUrl: RMDeclarationUrl, public val name: String, public val fields: List, override val documentation: String?, -) : Documentable \ No newline at end of file +) : Documentable, RMNode \ No newline at end of file diff --git a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMField.kt b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMField.kt similarity index 71% rename from common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMField.kt rename to common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMField.kt index 08dbafd..f4d9dcb 100644 --- a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMField.kt +++ b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMField.kt @@ -1,7 +1,7 @@ -package org.timemates.rrpc.common.metadata +package org.timemates.rrpc.common.schema import kotlinx.serialization.Serializable -import org.timemates.rrpc.common.metadata.value.RMTypeUrl +import org.timemates.rrpc.common.schema.value.RMDeclarationUrl @Serializable public class RMField( @@ -9,7 +9,7 @@ public class RMField( public val name: String, public val options: RMOptions, override val documentation: String?, - public val typeUrl: RMTypeUrl, + public val typeUrl: RMDeclarationUrl, public val isRepeated: Boolean, public val isInOneOf: Boolean = false, public val isExtension: Boolean = false, diff --git a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMFile.kt b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMFile.kt similarity index 86% rename from common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMFile.kt rename to common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMFile.kt index e50f7be..8f1b9ff 100644 --- a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMFile.kt +++ b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMFile.kt @@ -1,8 +1,8 @@ -package org.timemates.rrpc.common.metadata +package org.timemates.rrpc.common.schema import kotlinx.serialization.Serializable -import org.timemates.rrpc.common.metadata.annotations.NonPlatformSpecificAccess -import org.timemates.rrpc.common.metadata.value.RMPackageName +import org.timemates.rrpc.common.schema.annotations.NonPlatformSpecificAccess +import org.timemates.rrpc.common.schema.value.RMPackageName import org.timemates.rrpc.options.FileOption @Serializable @@ -50,7 +50,7 @@ public class RMFile( @OptIn(NonPlatformSpecificAccess::class) public fun platformPackageName(language: Language): RMPackageName { return when (language) { - Language.JAVA -> options[JAVA_PACKAGE] + Language.JAVA, Language.KOTLIN -> TODO() Language.PHP -> TODO() Language.C_SHARP -> TODO() Language.PYTHON -> TODO() diff --git a/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMNode.kt b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMNode.kt new file mode 100644 index 0000000..d45fbc0 --- /dev/null +++ b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMNode.kt @@ -0,0 +1,3 @@ +package org.timemates.rrpc.common.schema + +public sealed interface RMNode \ No newline at end of file diff --git a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMOneOf.kt b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMOneOf.kt similarity index 83% rename from common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMOneOf.kt rename to common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMOneOf.kt index ff3ce3a..a0dea03 100644 --- a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMOneOf.kt +++ b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMOneOf.kt @@ -1,4 +1,4 @@ -package org.timemates.rrpc.common.metadata +package org.timemates.rrpc.common.schema import kotlinx.serialization.SerialName diff --git a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMOption.kt b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMOption.kt similarity index 91% rename from common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMOption.kt rename to common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMOption.kt index 25fb5a3..cbd5d81 100644 --- a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMOption.kt +++ b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMOption.kt @@ -1,4 +1,4 @@ -package org.timemates.rrpc.common.metadata +package org.timemates.rrpc.common.schema import kotlinx.serialization.Serializable import kotlin.jvm.JvmInline @@ -7,7 +7,6 @@ import kotlin.jvm.JvmInline @Serializable public class RMOption( public val name: String, - public val tag: Int, public val fieldUrl: RMTypeMemberUrl, /** * Value is present only if RPSOption is present in position where a specific value is diff --git a/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMOptions.kt b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMOptions.kt new file mode 100644 index 0000000..b555a15 --- /dev/null +++ b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMOptions.kt @@ -0,0 +1,36 @@ +package org.timemates.rrpc.common.schema + +import kotlinx.serialization.Serializable +import org.timemates.rrpc.common.schema.value.RMDeclarationUrl +import kotlin.jvm.JvmInline + + +@Serializable +@JvmInline +public value class RMOptions( + public val list: List +) { + public companion object { + public val EMPTY: RMOptions = RMOptions(emptyList()) + + public val FILE_OPTIONS: RMDeclarationUrl = RMDeclarationUrl("google.protobuf.FileOptions") + public val MESSAGE_OPTIONS: RMDeclarationUrl = RMDeclarationUrl("google.protobuf.MessageOptions") + public val SERVICE_OPTIONS: RMDeclarationUrl = RMDeclarationUrl("google.protobuf.ServiceOptions") + public val FIELD_OPTIONS: RMDeclarationUrl = RMDeclarationUrl("google.protobuf.FieldOptions") + public val ONEOF_OPTIONS: RMDeclarationUrl = RMDeclarationUrl("google.protobuf.OneofOptions") + public val ENUM_OPTIONS: RMDeclarationUrl = RMDeclarationUrl("google.protobuf.EnumOptions") + public val ENUM_VALUE_OPTIONS: RMDeclarationUrl = RMDeclarationUrl("google.protobuf.EnumValueOptions") + public val METHOD_OPTIONS: RMDeclarationUrl = RMDeclarationUrl("google.protobuf.MethodOptions") + public val EXTENSION_RANGE_OPTIONS: RMDeclarationUrl = RMDeclarationUrl("google.protobuf.ExtensionRangeOptions") + } + + public operator fun get(fieldUrl: RMTypeMemberUrl): RMOption? { + return list.firstOrNull { it.fieldUrl == fieldUrl } + } + + public operator fun contains(fieldUrl: RMTypeMemberUrl): Boolean { + return get(fieldUrl) != null + } +} + +public val RMOptions.isDeprecated: Boolean get() = TODO() \ No newline at end of file diff --git a/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMResolver.kt b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMResolver.kt new file mode 100644 index 0000000..db2defd --- /dev/null +++ b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMResolver.kt @@ -0,0 +1,276 @@ +package org.timemates.rrpc.common.schema + +import org.timemates.rrpc.common.schema.value.RMPackageName +import org.timemates.rrpc.common.schema.value.RMDeclarationUrl + +public fun RMResolver( + project: List, +): RMResolver = TODO() + +public fun RMResolver( + vararg resolvers: RMResolver, +): RMResolver = TODO() + +/** + * Interface for resolving various components (fields, types, files, services) in the RPC metadata model. + * This provides a lookup mechanism for retrieving metadata elements such as types, services, fields, + * extensions, and files based on unique identifiers like type URLs or package names. + */ +public interface RMResolver { + + /** + * Resolves a field within a type by the given [typeMemberUrl]. + * + * @param typeMemberUrl The URL that identifies the specific field within the type. + * @return The corresponding [RMField] if found, or `null` if no matching field is found. + */ + public fun resolveField(typeMemberUrl: RMTypeMemberUrl): RMField? + + /** + * Resolves a type by the given [url]. + * + * @param url The unique identifier for the type, typically used in protobuf definitions. + * @return The corresponding [RMType] if found, or `null` if no matching type is found. + */ + public fun resolveType(url: RMDeclarationUrl): RMType? + + /** + * Resolves an extension by the given [url]. + * + * @param url The unique identifier for the extension. + * @return The corresponding [RMExtend] if found, or `null` if no matching extension is found. + */ + public fun resolveExtend(url: RMDeclarationUrl): RMExtend? + + /** + * Resolves a file by the given [packageName] and [name]. + * + * @param packageName The package where the file is defined. + * @param name The name of the file. + * @return The corresponding [RMFile] if found, or `null` if no matching file is found. + */ + public fun resolveFileOf(packageName: RMPackageName, name: String): RMFile? + + /** + * Resolves the file where a type is present. + * + * @param url The reference to the type. + * @return The corresponding [RMFile] where the type is found, or `null` if no matching file is found. + */ + public fun resolveFileOf(url: RMDeclarationUrl): RMFile? + + /** + * Resolves a service by the given [url]. + * + * @param url The unique identifier for the service, typically used in protobuf definitions. + * @return The corresponding [RMService] if found, or `null` if no matching service is found. + */ + public fun resolveService(url: RMDeclarationUrl): RMService? + + /** + * Resolves all available files in the current [RMResolver]. + * + * @return A sequence of all [RMFile]s available within this resolver. + */ + public fun resolveAvailableFiles(): Sequence + + /** + * Resolves all available services in the current [RMResolver]. + * + * @return A sequence of all [RMService]s available within this resolver. + */ + public fun resolveAllServices(): Sequence + + /** + * Resolves all available types in the current [RMResolver]. + * + * @return A sequence of all [RMType]s available within this resolver. + */ + public fun resolveAllTypes(): Sequence + + /** + * Resolves nodes with a specific option defined by the given [optionFieldUrl]. + * + * @param optionFieldUrl The URL of the option field to search for within the nodes. + * @return A sequence of [RMNode]s that contain the specified option. + */ + public fun resolveNodesWithOption(optionFieldUrl: RMTypeMemberUrl): Sequence + + /** + * Resolves services with a specific option defined by the given [optionFieldUrl]. + * + * @param optionFieldUrl The URL of the option field to search for within the services. + * @return A sequence of [RMService]s that contain the specified option. + */ + public fun resolveServicesWithOption(optionFieldUrl: RMTypeMemberUrl): Sequence + + /** + * Resolves types with a specific option defined by the given [optionFieldUrl]. + * + * @param optionFieldUrl The URL of the option field to search for within the types. + * @return A sequence of [RMType]s that contain the specified option. + */ + public fun resolveTypesWithOption(optionFieldUrl: RMTypeMemberUrl): Sequence + + /** + * Resolves RPCs (Remote Procedure Calls) with a specific option defined by the given [optionFieldUrl]. + * + * @param optionFieldUrl The URL of the option field to search for within the RPCs. + * @return A sequence of [RMRpc]s that contain the specified option. + */ + public fun resolveRpcsWithOption(optionFieldUrl: RMTypeMemberUrl): Sequence + + /** + * Resolves fields with a specific option defined by the given [optionFieldUrl]. + * + * @param optionFieldUrl The URL of the option field to search for within the fields. + * @return A sequence of [RMField]s that contain the specified option. + */ + public fun resolveFieldsWithOption(optionFieldUrl: RMTypeMemberUrl): Sequence + + /** + * Resolves constants (enum values) with a specific option defined by the given [optionFieldUrl]. + * + * @param optionFieldUrl The URL of the option field to search for within the constants. + * @return A sequence of [RMEnumConstant]s that contain the specified option. + */ + public fun resolveConstantsWithOption(optionFieldUrl: RMTypeMemberUrl): Sequence + + /** + * Filters nodes based on a provided condition. + * + * @param condition A lambda that represents the filtering logic for [RMNode]s. + * @return A new [RMResolver] that only includes the nodes satisfying the provided condition. + */ + public fun filterNodes(condition: (RMNode) -> Boolean): RMResolver +} + +//private class InMemoryRMResolver( +// private val files: List, +//) : RMResolver { +// override fun resolveField(typeMemberUrl: RMTypeMemberUrl): RMField? { +// TODO() +// } +// +// override fun resolveType(url: RMDeclarationUrl): RMType? { +// TODO() +// } +// +// override fun resolveFileOf(packageName: RMPackageName, name: String): RMFile? { +// TODO() +// } +// +// override fun resolveService(url: RMDeclarationUrl): RMService? { +// TODO() +// } +// +// override fun resolveAvailableFiles(): Sequence { +// return files.asSequence() +// } +// +// override fun resolveAllServices(): Sequence { +// return resolveAvailableFiles().flatMap { it.services } +// } +// +// override fun resolveAllTypes(): Sequence { +// return resolveAvailableFiles().flatMap { it.types } +// } +// +// override fun resolveNodesWithOption(optionFieldUrl: RMTypeMemberUrl): Sequence { +// TODO("Not yet implemented") +// } +// +// override fun resolveServicesWithOption(optionFieldUrl: RMTypeMemberUrl): Sequence { +// TODO("Not yet implemented") +// } +// +// override fun resolveRpcsWithOption(optionFieldUrl: RMDeclarationUrl): Sequence { +// TODO("Not yet implemented") +// } +// +// override fun resolveFieldsWithOption(optionFieldUrl: RMDeclarationUrl): Sequence { +// TODO("Not yet implemented") +// } +// +// override fun resolveConstantsWithOption(optionFieldUrl: RMDeclarationUrl): Sequence { +// TODO("Not yet implemented") +// } +//} + +//private class CompoundRMResolver( +// private vararg val resolvers: RMResolver, +//) : RMResolver { +// override fun resolveField(typeMemberUrl: RMTypeMemberUrl): RMField? { +// return resolvers.firstNotNullOfOrNull { it.resolveField(typeMemberUrl) } +// } +// +// override fun resolveType(url: RMDeclarationUrl): RMType? { +// return resolvers.firstNotNullOfOrNull { it.resolveType(url) } +// } +// +// override fun resolveFileOf(packageName: RMPackageName, name: String): RMFile? { +// return resolvers.firstNotNullOfOrNull { it.resolveFileOf(packageName, name) } +// } +// +// override fun resolveService(url: RMDeclarationUrl): RMService? { +// return resolvers.firstNotNullOfOrNull { it.resolveService(url) } +// } +// +// override fun resolveAvailableFiles(): Sequence { +// return sequence { +// resolvers.forEach { +// yieldAll(it.resolveAvailableFiles()) +// } +// } +// } +// +// override fun resolveAllServices(): Sequence { +// return sequence { +// resolvers.forEach { +// yieldAll(it.resolveAllServices()) +// } +// } +// } +// +// override fun resolveAllTypes(): Sequence { +// return sequence { +// resolvers.forEach { +// yieldAll(it.resolveAllTypes()) +// } +// } +// } +// +// override fun resolveNodesWithOption(optionFieldUrl: RMTypeMemberUrl): Sequence { +// return sequenceOf(resolveServicesWithOption(optionFieldUrl), resolveFieldsWithOption(optionFieldUrl), resolve +// } +// +// override fun resolveServicesWithOption(optionFieldUrl: RMTypeMemberUrl): Sequence { +// return resolvers.asSequence().flatMap { resolver -> +// resolver.resolveAllServices().filter { optionFieldUrl in it.options } +// } +// } +// +// override fun resolveRpcsWithOption(optionFieldUrl: RMTypeMemberUrl): Sequence { +// return resolvers.asSequence().flatMap { resolver -> +// resolver.resolveAllServices().flatMap { it.rpcs }.filter { rpc -> +// optionFieldUrl in rpc.options +// } +// } +// } +// +// override fun resolveFieldsWithOption(optionFieldUrl: RMTypeMemberUrl): Sequence { +// return resolvers.asSequence().flatMap { resolver -> +// resolver.resolveAllTypes().filterIsInstance().flatMap { it.fields }.filter { field -> +// optionFieldUrl in field.options +// } +// } +// } +// +// override fun resolveConstantsWithOption(optionFieldUrl: RMTypeMemberUrl): Sequence { +// return resolvers.asSequence().flatMap { resolver -> +// resolver.resolveAllTypes().filterIsInstance().flatMap { it.constants }.filter { constant -> +// optionFieldUrl in constant.options +// } +// } +// } +//} \ No newline at end of file diff --git a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMRpc.kt b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMRpc.kt similarity index 80% rename from common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMRpc.kt rename to common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMRpc.kt index cfa4e9e..aa1adb7 100644 --- a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMRpc.kt +++ b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMRpc.kt @@ -1,7 +1,8 @@ -package org.timemates.rrpc.common.metadata +package org.timemates.rrpc.common.schema import kotlinx.serialization.Serializable -import org.timemates.rrpc.common.metadata.annotations.NonPlatformSpecificAccess +import org.timemates.rrpc.common.schema.annotations.NonPlatformSpecificAccess +import org.timemates.rrpc.common.schema.value.RMDeclarationUrl @Serializable public class RMRpc( @@ -53,4 +54,8 @@ public fun RMRpc.kotlinName(): String = languageSpecificName(Language.KOTLIN) public val RMRpc.isRequestResponse: Boolean get() = !requestType.isStreaming && !responseType.isStreaming public val RMRpc.isRequestStream: Boolean get() = !requestType.isStreaming && responseType.isStreaming -public val RMRpc.isRequestChannel: Boolean get() = requestType.isStreaming && responseType.isStreaming \ No newline at end of file +public val RMRpc.isRequestChannel: Boolean get() = requestType.isStreaming && responseType.isStreaming +public val RMRpc.isFireAndForget: Boolean + get() = requestType.type != RMDeclarationUrl.ACK && responseType.type == RMDeclarationUrl.ACK +public val RMRpc.isMetadataPush: Boolean + get() = requestType.type == RMDeclarationUrl.ACK && responseType.type == RMDeclarationUrl.ACK \ No newline at end of file diff --git a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMService.kt b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMService.kt similarity index 74% rename from common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMService.kt rename to common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMService.kt index bde0dbb..8ba5f46 100644 --- a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMService.kt +++ b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMService.kt @@ -1,7 +1,7 @@ -package org.timemates.rrpc.common.metadata +package org.timemates.rrpc.common.schema import kotlinx.serialization.Serializable -import org.timemates.rrpc.common.metadata.value.RMTypeUrl +import org.timemates.rrpc.common.schema.value.RMDeclarationUrl @Serializable @@ -24,5 +24,5 @@ public class RMService( /** * String reference representation. */ - public val typeUrl: RMTypeUrl, + public val typeUrl: RMDeclarationUrl, ) : RMNode diff --git a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMType.kt b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMType.kt similarity index 88% rename from common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMType.kt rename to common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMType.kt index af6c03d..dbe72d4 100644 --- a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMType.kt +++ b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMType.kt @@ -1,13 +1,13 @@ -package org.timemates.rrpc.common.metadata +package org.timemates.rrpc.common.schema import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import org.timemates.rrpc.common.metadata.value.RMTypeUrl +import org.timemates.rrpc.common.schema.value.RMDeclarationUrl @Serializable public sealed interface RMType : RMNode, Documentable { public val name: String - public val typeUrl: RMTypeUrl + public val typeUrl: RMDeclarationUrl public val nestedTypes: List public val nestedExtends: List @@ -18,7 +18,7 @@ public sealed interface RMType : RMNode, Documentable { override val documentation: String?, public val options: RMOptions, override val nestedTypes: List, - override val typeUrl: RMTypeUrl, + override val typeUrl: RMDeclarationUrl, override val nestedExtends: List, ) : RMType @@ -29,7 +29,7 @@ public sealed interface RMType : RMNode, Documentable { public val fields: List, public val oneOfs: List, public val options: RMOptions, - override val typeUrl: RMTypeUrl, + override val typeUrl: RMDeclarationUrl, override val nestedTypes: List, override val nestedExtends: List, ) : RMType { @@ -70,7 +70,7 @@ public sealed interface RMType : RMNode, Documentable { public class Enclosing( override val name: String, override val documentation: String?, - override val typeUrl: RMTypeUrl, + override val typeUrl: RMDeclarationUrl, override val nestedTypes: List, override val nestedExtends: List, ) : RMType diff --git a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMTypeMemberUrl.kt b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMTypeMemberUrl.kt similarity index 58% rename from common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMTypeMemberUrl.kt rename to common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMTypeMemberUrl.kt index c770ddf..579fee7 100644 --- a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/RMTypeMemberUrl.kt +++ b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/RMTypeMemberUrl.kt @@ -1,11 +1,11 @@ -package org.timemates.rrpc.common.metadata +package org.timemates.rrpc.common.schema import kotlinx.serialization.Serializable -import org.timemates.rrpc.common.metadata.value.RMTypeUrl +import org.timemates.rrpc.common.schema.value.RMDeclarationUrl @Serializable public class RMTypeMemberUrl( - public val typeUrl: RMTypeUrl, + public val typeUrl: RMDeclarationUrl, public val memberName: String, ) { override fun toString(): String { diff --git a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/StreamableRMTypeUrl.kt b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/StreamableRMTypeUrl.kt similarity index 56% rename from common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/StreamableRMTypeUrl.kt rename to common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/StreamableRMTypeUrl.kt index 4a0b3b9..d87cc68 100644 --- a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/StreamableRMTypeUrl.kt +++ b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/StreamableRMTypeUrl.kt @@ -1,10 +1,10 @@ -package org.timemates.rrpc.common.metadata +package org.timemates.rrpc.common.schema import kotlinx.serialization.Serializable -import org.timemates.rrpc.common.metadata.value.RMTypeUrl +import org.timemates.rrpc.common.schema.value.RMDeclarationUrl /** * Denotes that type might be of streaming type. */ @Serializable -public class StreamableRMTypeUrl(public val isStreaming: Boolean, public val type: RMTypeUrl) \ No newline at end of file +public class StreamableRMTypeUrl(public val isStreaming: Boolean, public val type: RMDeclarationUrl) \ No newline at end of file diff --git a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/annotations/NonPlatformSpecificAccess.kt b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/annotations/NonPlatformSpecificAccess.kt similarity index 88% rename from common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/annotations/NonPlatformSpecificAccess.kt rename to common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/annotations/NonPlatformSpecificAccess.kt index 8900c5c..9ac8a1f 100644 --- a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/annotations/NonPlatformSpecificAccess.kt +++ b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/annotations/NonPlatformSpecificAccess.kt @@ -1,4 +1,4 @@ -package org.timemates.rrpc.common.metadata.annotations +package org.timemates.rrpc.common.schema.annotations @Target( AnnotationTarget.CLASS, diff --git a/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/value/RMDeclarationUrl.kt b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/value/RMDeclarationUrl.kt new file mode 100644 index 0000000..2d63658 --- /dev/null +++ b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/value/RMDeclarationUrl.kt @@ -0,0 +1,124 @@ +package org.timemates.rrpc.common.schema.value + +import kotlinx.serialization.Serializable +import kotlin.jvm.JvmInline + +@Serializable +@JvmInline +public value class RMDeclarationUrl(public val value: String) { + public val isScalar: Boolean get() = this in SCALAR_TYPES + public val isWrapper: Boolean get() = this in WRAPPER_TYPES + public val isGoogleBuiltin: Boolean get() = this in GOOGLE_BUILTIN_TYPES + + public val isMap: Boolean get() = value.startsWith("map<") + public val firstTypeArgument: RMDeclarationUrl? get() = + if (isMap) + RMDeclarationUrl(value.substringAfter('<').substringBefore(',')) + else null + + public val secondTypeArgument: RMDeclarationUrl? get() = + if (isMap) + RMDeclarationUrl(value.substringAfter(',').substringBefore('>').trim()) + else null + + public val simpleName: String get() = value.substringAfterLast('.') + public val enclosingTypeOrPackage: String? get() { + val string = value.substringAfterLast('/') + val dot = string.lastIndexOf('.') + return if (dot == -1) null else string.substring(0, dot) + } + + public companion object { + public val UNKNOWN: RMDeclarationUrl = RMDeclarationUrl("unknown") + + public val INT32: RMDeclarationUrl = RMDeclarationUrl("int32") + public val INT64: RMDeclarationUrl = RMDeclarationUrl("int32") + + public val STRING: RMDeclarationUrl = RMDeclarationUrl("string") + + public val SINT32: RMDeclarationUrl = RMDeclarationUrl("int32") + public val SINT64: RMDeclarationUrl = RMDeclarationUrl("int32") + + public val BOOL: RMDeclarationUrl = RMDeclarationUrl("bool") + + public val UINT32: RMDeclarationUrl = RMDeclarationUrl("uint32") + public val UINT64: RMDeclarationUrl = RMDeclarationUrl("uint64") + + public val SFIXED32: RMDeclarationUrl = RMDeclarationUrl("sfixed32") + public val SFIXED64: RMDeclarationUrl = RMDeclarationUrl("sfixed64") + + public val FIXED32: RMDeclarationUrl = RMDeclarationUrl("fixed32") + public val FIXED64: RMDeclarationUrl = RMDeclarationUrl("fixed64") + + public val FLOAT: RMDeclarationUrl = RMDeclarationUrl("float") + public val DOUBLE: RMDeclarationUrl = RMDeclarationUrl("double") + + public val BYTES: RMDeclarationUrl = RMDeclarationUrl("bytes") + + public fun ofMap(first: RMDeclarationUrl, second: RMDeclarationUrl): RMDeclarationUrl { + return RMDeclarationUrl("map<${first.value}, ${second.value}>") + } + + public val SCALAR_TYPES: List = listOf( + INT32, + INT64, + STRING, + SINT32, + SINT64, + BOOL, + UINT32, + UINT64, + STRING, + FIXED32, + FIXED64, + FLOAT, + DOUBLE, + BYTES, + ) + + public val ANY: RMDeclarationUrl = RMDeclarationUrl("type.googleapis.com/google.protobuf.Any") + public val TIMESTAMP: RMDeclarationUrl = RMDeclarationUrl("type.googleapis.com/google.protobuf.Timestamp") + public val DURATION: RMDeclarationUrl = RMDeclarationUrl("type.googleapis.com/google.protobuf.Duration") + public val EMPTY: RMDeclarationUrl = RMDeclarationUrl("type.googleapis.com/google.protobuf.Empty") + public val STRUCT: RMDeclarationUrl = RMDeclarationUrl("type.googleapis.com/google.protobuf.Struct") + public val STRUCT_MAP: RMDeclarationUrl = RMDeclarationUrl("type.googleapis.com/google.protobuf.StructMap") + public val STRUCT_VALUE: RMDeclarationUrl = RMDeclarationUrl("type.googleapis.com/google.protobuf.Value") + public val STRUCT_NULL: RMDeclarationUrl = RMDeclarationUrl("type.googleapis.com/google.protobuf.NullValue") + public val STRUCT_LIST: RMDeclarationUrl = RMDeclarationUrl("type.googleapis.com/google.protobuf.ListValue") + + public val GOOGLE_BUILTIN_TYPES: List = listOf( + ANY, + TIMESTAMP, + DURATION, + EMPTY, + STRUCT, + STRUCT_MAP, + STRUCT_VALUE, + STRUCT_NULL, + STRUCT_LIST, + ) + + public val DOUBLE_VALUE: RMDeclarationUrl = RMDeclarationUrl("type.googleapis.com/google.protobuf.DoubleValue") + public val FLOAT_VALUE: RMDeclarationUrl = RMDeclarationUrl("type.googleapis.com/google.protobuf.FloatValue") + public val INT32_VALUE: RMDeclarationUrl = RMDeclarationUrl("type.googleapis.com/google.protobuf.Int32Value") + public val INT64_VALUE: RMDeclarationUrl = RMDeclarationUrl("type.googleapis.com/google.protobuf.Int64Value") + public val UINT32_VALUE: RMDeclarationUrl = RMDeclarationUrl("type.googleapis.com/google.protobuf.UInt32Value") + public val UINT64_VALUE: RMDeclarationUrl = RMDeclarationUrl("type.googleapis.com/google.protobuf.UINT64Value") + public val STRING_VALUE: RMDeclarationUrl = RMDeclarationUrl("type.googleapis.com/google.protobuf.StringValue") + public val BYTES_VALUE: RMDeclarationUrl = RMDeclarationUrl("type.googleapis.com/google.protobuf.BytesValue") + public val BOOL_VALUE: RMDeclarationUrl = RMDeclarationUrl("type.googleapis.com/google.protobuf.BoolValue") + + public val ACK: RMDeclarationUrl = RMDeclarationUrl("type.googleapis.com/timemates.rrpc.Ack") + + public val WRAPPER_TYPES: List = listOf( + DOUBLE_VALUE, + FLOAT_VALUE, + INT32_VALUE, + INT64_VALUE, + UINT32_VALUE, + UINT64_VALUE, + STRING_VALUE, + BYTES_VALUE, + ) + } +} \ No newline at end of file diff --git a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/value/RMPackageName.kt b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/value/RMPackageName.kt similarity index 76% rename from common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/value/RMPackageName.kt rename to common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/value/RMPackageName.kt index 8368b3b..a6ad4e2 100644 --- a/common/metadata/src/commonMain/kotlin/org/timemates/rrpc/common/metadata/value/RMPackageName.kt +++ b/common/schema/src/commonMain/kotlin/org/timemates/rrpc/common/schema/value/RMPackageName.kt @@ -1,4 +1,4 @@ -package org.timemates.rrpc.common.metadata.value +package org.timemates.rrpc.common.schema.value import kotlinx.serialization.Serializable import kotlin.jvm.JvmInline diff --git a/generator/core/build.gradle.kts b/generator/core/build.gradle.kts index bd3c1ac..e4ed339 100644 --- a/generator/core/build.gradle.kts +++ b/generator/core/build.gradle.kts @@ -10,16 +10,20 @@ group = "org.timemates.rrpc" version = System.getenv("LIB_VERSION") ?: "SNAPSHOT" dependencies { + // -- Project -- + commonMainApi(projects.common.schema) + + // -- SquareUp -- commonMainImplementation(libs.squareup.wire.schema) commonMainImplementation(libs.squareup.kotlinpoet) commonMainImplementation(libs.squareup.okio) + // -- Test -- commonTestImplementation(libs.kotlin.test) commonTestImplementation(libs.squareup.okio.fakeFs) - commonMainImplementation(projects.common.core) - commonMainApi(projects.common.metadata) } + mavenPublishing { coordinates( groupId = "org.timemates.rrpc", @@ -28,7 +32,7 @@ mavenPublishing { ) pom { - name.set("RRpcroto Code Generator Core") - description.set("Code-generation library for RRpcroto servers and clients.") + name.set("RRpc Code Generator Core") + description.set("Code-generation library for RRpc servers and clients.") } } \ No newline at end of file diff --git a/generator/core/src/commonMain/kotlin/org/timemates/rrpc/codegen/CodeGenerator.kt b/generator/core/src/commonMain/kotlin/org/timemates/rrpc/codegen/CodeGenerator.kt index 7a83af0..1766f22 100644 --- a/generator/core/src/commonMain/kotlin/org/timemates/rrpc/codegen/CodeGenerator.kt +++ b/generator/core/src/commonMain/kotlin/org/timemates/rrpc/codegen/CodeGenerator.kt @@ -4,7 +4,7 @@ import com.squareup.wire.schema.Location import com.squareup.wire.schema.SchemaLoader import org.timemates.rrpc.codegen.adapters.SchemaAdapter import org.timemates.rrpc.codegen.configuration.RMGlobalConfiguration -import org.timemates.rrpc.common.metadata.RMResolver +import org.timemates.rrpc.common.schema.RMResolver import kotlin.io.path.absolutePathString public object CodeGenerator { diff --git a/generator/core/src/commonMain/kotlin/org/timemates/rrpc/codegen/RMDefaultVisitor.kt b/generator/core/src/commonMain/kotlin/org/timemates/rrpc/codegen/RMDefaultVisitor.kt index 9c43af8..3e3ee69 100644 --- a/generator/core/src/commonMain/kotlin/org/timemates/rrpc/codegen/RMDefaultVisitor.kt +++ b/generator/core/src/commonMain/kotlin/org/timemates/rrpc/codegen/RMDefaultVisitor.kt @@ -1,6 +1,6 @@ package org.timemates.rrpc.codegen -import org.timemates.rrpc.common.metadata.* +import org.timemates.rrpc.common.schema.* public abstract class RMDefaultVisitor : RMEmptyVisitor() { override fun visitFile(file: RMFile, data: D): R { diff --git a/generator/core/src/commonMain/kotlin/org/timemates/rrpc/codegen/RMEmptyVisitor.kt b/generator/core/src/commonMain/kotlin/org/timemates/rrpc/codegen/RMEmptyVisitor.kt index 08ba17f..1ab5328 100644 --- a/generator/core/src/commonMain/kotlin/org/timemates/rrpc/codegen/RMEmptyVisitor.kt +++ b/generator/core/src/commonMain/kotlin/org/timemates/rrpc/codegen/RMEmptyVisitor.kt @@ -1,6 +1,6 @@ package org.timemates.rrpc.codegen -import org.timemates.rrpc.common.metadata.* +import org.timemates.rrpc.common.schema.* public abstract class RMEmptyVisitor : RMVisitor { public abstract fun defaultHandler(node: RMNode, data: D): R diff --git a/generator/core/src/commonMain/kotlin/org/timemates/rrpc/codegen/RMVisitor.kt b/generator/core/src/commonMain/kotlin/org/timemates/rrpc/codegen/RMVisitor.kt index e9ec603..86e620b 100644 --- a/generator/core/src/commonMain/kotlin/org/timemates/rrpc/codegen/RMVisitor.kt +++ b/generator/core/src/commonMain/kotlin/org/timemates/rrpc/codegen/RMVisitor.kt @@ -1,6 +1,6 @@ package org.timemates.rrpc.codegen -import org.timemates.rrpc.common.metadata.* +import org.timemates.rrpc.common.schema.* /** * Visitor interface for traversing RRpc schema objects. diff --git a/generator/core/src/commonMain/kotlin/org/timemates/rrpc/codegen/WireExt.kt b/generator/core/src/commonMain/kotlin/org/timemates/rrpc/codegen/WireExt.kt index 06207a1..1d2441b 100644 --- a/generator/core/src/commonMain/kotlin/org/timemates/rrpc/codegen/WireExt.kt +++ b/generator/core/src/commonMain/kotlin/org/timemates/rrpc/codegen/WireExt.kt @@ -2,49 +2,49 @@ package org.timemates.rrpc.codegen import com.squareup.wire.schema.* import org.timemates.rrpc.codegen.exception.GenerationException -import org.timemates.rrpc.common.metadata.* -import org.timemates.rrpc.common.metadata.value.RMPackageName -import org.timemates.rrpc.common.metadata.value.RMTypeUrl +import org.timemates.rrpc.common.schema.* +import org.timemates.rrpc.common.schema.value.RMPackageName +import org.timemates.rrpc.common.schema.value.RMDeclarationUrl -internal fun ProtoType.asRMTypeUrl(): RMTypeUrl { +internal fun ProtoType.asRMTypeUrl(): RMDeclarationUrl { return when { - isMap -> RMTypeUrl("map<${keyType!!.asRMTypeUrl()}, ${valueType!!.asRMTypeUrl()}>") + isMap -> RMDeclarationUrl("map<${keyType!!.asRMTypeUrl()}, ${valueType!!.asRMTypeUrl()}>") isScalar -> when (this) { - ProtoType.BOOL -> RMTypeUrl.BOOL - ProtoType.BYTES -> RMTypeUrl.BYTES - ProtoType.DOUBLE -> RMTypeUrl.DOUBLE - ProtoType.FLOAT -> RMTypeUrl.FLOAT - ProtoType.FIXED32 -> RMTypeUrl.FIXED32 - ProtoType.FIXED64 -> RMTypeUrl.FIXED64 - ProtoType.INT32 -> RMTypeUrl.INT32 - ProtoType.INT64 -> RMTypeUrl.INT64 - ProtoType.SFIXED32 -> RMTypeUrl.SFIXED32 - ProtoType.SFIXED64 -> RMTypeUrl.SFIXED64 - ProtoType.SINT32 -> RMTypeUrl.SINT32 - ProtoType.SINT64 -> RMTypeUrl.SINT64 - ProtoType.STRING -> RMTypeUrl.STRING - ProtoType.UINT32 -> RMTypeUrl.UINT32 - ProtoType.UINT64 -> RMTypeUrl.UINT64 - ProtoType.ANY -> RMTypeUrl.ANY - ProtoType.DURATION -> RMTypeUrl.DURATION - ProtoType.TIMESTAMP -> RMTypeUrl.TIMESTAMP - ProtoType.EMPTY -> RMTypeUrl.EMPTY - ProtoType.STRUCT_MAP -> RMTypeUrl.STRUCT_MAP - ProtoType.STRUCT_LIST -> RMTypeUrl.STRUCT_LIST - ProtoType.STRUCT_VALUE -> RMTypeUrl.STRUCT_VALUE - ProtoType.STRUCT_NULL -> RMTypeUrl.STRUCT_NULL - ProtoType.DOUBLE_VALUE -> RMTypeUrl.DOUBLE_VALUE - ProtoType.FLOAT_VALUE -> RMTypeUrl.FLOAT_VALUE - ProtoType.INT32_VALUE -> RMTypeUrl.INT32_VALUE - ProtoType.INT64_VALUE -> RMTypeUrl.INT64_VALUE - ProtoType.UINT32_VALUE -> RMTypeUrl.UINT32_VALUE - ProtoType.UINT64_VALUE -> RMTypeUrl.UINT64_VALUE - ProtoType.BOOL_VALUE -> RMTypeUrl.BOOL_VALUE - ProtoType.BYTES_VALUE -> RMTypeUrl.BYTES_VALUE + ProtoType.BOOL -> RMDeclarationUrl.BOOL + ProtoType.BYTES -> RMDeclarationUrl.BYTES + ProtoType.DOUBLE -> RMDeclarationUrl.DOUBLE + ProtoType.FLOAT -> RMDeclarationUrl.FLOAT + ProtoType.FIXED32 -> RMDeclarationUrl.FIXED32 + ProtoType.FIXED64 -> RMDeclarationUrl.FIXED64 + ProtoType.INT32 -> RMDeclarationUrl.INT32 + ProtoType.INT64 -> RMDeclarationUrl.INT64 + ProtoType.SFIXED32 -> RMDeclarationUrl.SFIXED32 + ProtoType.SFIXED64 -> RMDeclarationUrl.SFIXED64 + ProtoType.SINT32 -> RMDeclarationUrl.SINT32 + ProtoType.SINT64 -> RMDeclarationUrl.SINT64 + ProtoType.STRING -> RMDeclarationUrl.STRING + ProtoType.UINT32 -> RMDeclarationUrl.UINT32 + ProtoType.UINT64 -> RMDeclarationUrl.UINT64 + ProtoType.ANY -> RMDeclarationUrl.ANY + ProtoType.DURATION -> RMDeclarationUrl.DURATION + ProtoType.TIMESTAMP -> RMDeclarationUrl.TIMESTAMP + ProtoType.EMPTY -> RMDeclarationUrl.EMPTY + ProtoType.STRUCT_MAP -> RMDeclarationUrl.STRUCT_MAP + ProtoType.STRUCT_LIST -> RMDeclarationUrl.STRUCT_LIST + ProtoType.STRUCT_VALUE -> RMDeclarationUrl.STRUCT_VALUE + ProtoType.STRUCT_NULL -> RMDeclarationUrl.STRUCT_NULL + ProtoType.DOUBLE_VALUE -> RMDeclarationUrl.DOUBLE_VALUE + ProtoType.FLOAT_VALUE -> RMDeclarationUrl.FLOAT_VALUE + ProtoType.INT32_VALUE -> RMDeclarationUrl.INT32_VALUE + ProtoType.INT64_VALUE -> RMDeclarationUrl.INT64_VALUE + ProtoType.UINT32_VALUE -> RMDeclarationUrl.UINT32_VALUE + ProtoType.UINT64_VALUE -> RMDeclarationUrl.UINT64_VALUE + ProtoType.BOOL_VALUE -> RMDeclarationUrl.BOOL_VALUE + ProtoType.BYTES_VALUE -> RMDeclarationUrl.BYTES_VALUE else -> throw GenerationException("Unable to convert scalar type '$this'.") } - else -> RMTypeUrl(typeUrl!!) + else -> RMDeclarationUrl(typeUrl!!) } } diff --git a/generator/core/src/commonMain/kotlin/org/timemates/rrpc/codegen/adapters/SchemaAdapter.kt b/generator/core/src/commonMain/kotlin/org/timemates/rrpc/codegen/adapters/SchemaAdapter.kt index 27cbe40..42f754e 100644 --- a/generator/core/src/commonMain/kotlin/org/timemates/rrpc/codegen/adapters/SchemaAdapter.kt +++ b/generator/core/src/commonMain/kotlin/org/timemates/rrpc/codegen/adapters/SchemaAdapter.kt @@ -2,7 +2,7 @@ package org.timemates.rrpc.codegen.adapters import okio.FileSystem import okio.Path -import org.timemates.rrpc.common.metadata.RMResolver +import org.timemates.rrpc.common.schema.RMResolver public interface SchemaAdapter { public data class Config( diff --git a/generator/core/src/commonMain/kotlin/org/timemates/rrpc/codegen/exception/GenerationException.kt b/generator/core/src/commonMain/kotlin/org/timemates/rrpc/codegen/exception/GenerationException.kt index 19f725c..796410a 100644 --- a/generator/core/src/commonMain/kotlin/org/timemates/rrpc/codegen/exception/GenerationException.kt +++ b/generator/core/src/commonMain/kotlin/org/timemates/rrpc/codegen/exception/GenerationException.kt @@ -1,6 +1,6 @@ package org.timemates.rrpc.codegen.exception -class GenerationException( +public class GenerationException( message: String, cause: Throwable? = null ) : RuntimeException(message, cause) \ No newline at end of file diff --git a/generator/gradle-plugin/build.gradle.kts b/generator/gradle-plugin/build.gradle.kts index 4486522..bb41f68 100644 --- a/generator/gradle-plugin/build.gradle.kts +++ b/generator/gradle-plugin/build.gradle.kts @@ -14,21 +14,25 @@ dependencies { constraints { api("org.timemates.rrpc.generator:kotlin:$version") } - api(projects.generator.core) - api(projects.generator.kotlin) + // -- Project -- + implementation(projects.generator.core) + implementation(projects.generator.kotlin) + + // -- Libraries -- implementation(libs.kotlin.plugin) implementation(libs.squareup.okio) } + gradlePlugin { - website = "https://github.com/rrpcroto" - vcsUrl = "https://github.com/rrpcroto" + website = "https://github.com/RRpc" + vcsUrl = "https://github.com/RRpc" plugins { - create("rrpcroto-plugin") { + create("RRpc-plugin") { id = "org.timemates.rrpc" - displayName = "RRpcroto Code Generator" + displayName = "RRpc Code Generator" description = "Code Generator from .proto files to Kotlin code." tags = listOf("kotlin", "rsocket", "protobuf", "proto") diff --git a/generator/gradle-plugin/src/main/kotlin/org/timemates/rrpc/plugin/RSProtoExtension.kt b/generator/gradle-plugin/src/main/kotlin/org/timemates/rrpc/plugin/RSProtoExtension.kt index 8b074a5..d528c6e 100644 --- a/generator/gradle-plugin/src/main/kotlin/org/timemates/rrpc/plugin/RSProtoExtension.kt +++ b/generator/gradle-plugin/src/main/kotlin/org/timemates/rrpc/plugin/RSProtoExtension.kt @@ -151,8 +151,8 @@ public open class RRpcExtension(objects: ObjectFactory) { /** * Contains the path to the folder where the generated code will be saved. */ - public val generationOutput: Property = - objects.property.convention(null) - // objects.property().convention("generated/rrpc/src/commonMain") +// public val generationOutput: Property = +// objects.property.convention(null) +// // objects.property().convention("generated/rrpc/src/commonMain") } } \ No newline at end of file diff --git a/generator/gradle-plugin/src/main/kotlin/org/timemates/rrpc/plugin/RSocketProtoGeneratorPlugin.kt b/generator/gradle-plugin/src/main/kotlin/org/timemates/rrpc/plugin/RSocketProtoGeneratorPlugin.kt index c429d2e..0f8ea42 100644 --- a/generator/gradle-plugin/src/main/kotlin/org/timemates/rrpc/plugin/RSocketProtoGeneratorPlugin.kt +++ b/generator/gradle-plugin/src/main/kotlin/org/timemates/rrpc/plugin/RSocketProtoGeneratorPlugin.kt @@ -14,67 +14,67 @@ import org.timemates.rrpc.codegen.CodeGenerator import org.timemates.rrpc.codegen.configuration.RMGlobalConfiguration import java.io.File -public class RSocketProtoGeneratorPlugin : Plugin { - override fun apply(target: Project) { - val extension = target.extensions.create("rrpc", target.objects) - val generationOutputPath = target.layout.buildDirectory.file(extension.paths.generationOutput) - - val generateProto = target.tasks.create("generateCode") { - group = "rrpc" - - inputs.dir(extension.paths.protoSources) - outputs.dir(generationOutputPath) - - doLast { - val codeGenerator = CodeGenerator.generate( - FileSystem.SYSTEM, - adapters = mapOf() - ) - - try { - generationOutputPath.get() - .asFile - .listFiles() - ?.forEach(File::deleteRecursively) - } catch (e: Exception) { - if(logger.isDebugEnabled) - logger.error(e.stackTraceToString()) - } - - codeGenerator.generate( - configuration = RMGlobalConfiguration( - inputs = target.file(extension.paths.protoSources.get()).toOkioPath(), - output = target.file(generationOutputPath).toOkioPath(), - clientGeneration = extension.profiles.client.get(), - serverGeneration = extension.profiles.server.get(), - builderTypes = extension.options.builderTypes.get(), - permitPackageCycles = extension.options.permitPackageCycles.get(), - ) - ) - } - } - - target.afterEvaluate { - val allSourceSets = target.extensions.findByType()?.sourceSets - ?: target.extensions.findByType()?.sourceSets - ?: target.extensions.findByType()?.sourceSets - ?: error("Does Kotlin plugin apply to the buildscript?") - - val commonSourceSet = allSourceSets - .findByName(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) - val mainSourceSet = allSourceSets.findByName("main") - - val sourceSet = if (extension.targetSourceSet.getOrNull() != null) - allSourceSets.getByName(extension.targetSourceSet.get()) - else commonSourceSet ?: mainSourceSet - - sourceSet - ?.kotlin - ?.srcDirs(generateProto.outputs) - ?: error(SOURCE_SET_NOT_FOUND) - } - } -} - -private const val SOURCE_SET_NOT_FOUND = - "Unable to obtain source set: you should have commonMain/main or custom one that is set up in the [rrpc.targetSourceSet]" \ No newline at end of file +//public class RSocketProtoGeneratorPlugin : Plugin { +// override fun apply(target: Project) { +// val extension = target.extensions.create("rrpc", target.objects) +// val generationOutputPath = target.layout.buildDirectory.file(extension.paths.generationOutput) +// +// val generateProto = target.tasks.create("generateCode") { +// group = "rrpc" +// +// inputs.dir(extension.paths.protoSources) +// outputs.dir(generationOutputPath) +// +// doLast { +// val codeGenerator = CodeGenerator.generate( +// FileSystem.SYSTEM, +// adapters = mapOf() +// ) +// +// try { +// generationOutputPath.get() +// .asFile +// .listFiles() +// ?.forEach(File::deleteRecursively) +// } catch (e: Exception) { +// if(logger.isDebugEnabled) +// logger.error(e.stackTraceToString()) +// } +// +// codeGenerator.generate( +// configuration = RMGlobalConfiguration( +// inputs = target.file(extension.paths.protoSources.get()).toOkioPath(), +// output = target.file(generationOutputPath).toOkioPath(), +// clientGeneration = extension.profiles.client.get(), +// serverGeneration = extension.profiles.server.get(), +// builderTypes = extension.options.builderTypes.get(), +// permitPackageCycles = extension.options.permitPackageCycles.get(), +// ) +// ) +// } +// } +// +// target.afterEvaluate { +// val allSourceSets = target.extensions.findByType()?.sourceSets +// ?: target.extensions.findByType()?.sourceSets +// ?: target.extensions.findByType()?.sourceSets +// ?: error("Does Kotlin plugin apply to the buildscript?") +// +// val commonSourceSet = allSourceSets +// .findByName(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) +// val mainSourceSet = allSourceSets.findByName("main") +// +// val sourceSet = if (extension.targetSourceSet.getOrNull() != null) +// allSourceSets.getByName(extension.targetSourceSet.get()) +// else commonSourceSet ?: mainSourceSet +// +// sourceSet +// ?.kotlin +// ?.srcDirs(generateProto.outputs) +// ?: error(SOURCE_SET_NOT_FOUND) +// } +// } +//} +// +//private const val SOURCE_SET_NOT_FOUND = +// "Unable to obtain source set: you should have commonMain/main or custom one that is set up in the [rrpc.targetSourceSet]" \ No newline at end of file diff --git a/generator/kotlin/build.gradle.kts b/generator/kotlin/build.gradle.kts index 6cb58f5..aba3926 100644 --- a/generator/kotlin/build.gradle.kts +++ b/generator/kotlin/build.gradle.kts @@ -10,13 +10,16 @@ group = "org.timemates.rrpc" version = System.getenv("LIB_VERSION") ?: "SNAPSHOT" dependencies { - commonMainImplementation(libs.squareup.kotlinpoet) - commonMainImplementation(libs.squareup.okio) - + // -- Project -- commonMainImplementation(projects.common.core) commonMainImplementation(projects.generator.core) + + // -- SquareUp -- + commonMainImplementation(libs.squareup.kotlinpoet) + commonMainImplementation(libs.squareup.okio) } + mavenPublishing { coordinates( groupId = "org.timemates.rrpc", @@ -25,7 +28,7 @@ mavenPublishing { ) pom { - name.set("RRpcroto Kotlin Code Generator") - description.set("Code-generation library for RRpcroto servers and clients.") + name.set("RRpc Kotlin Code Generator") + description.set("Code-generation library for RRpc servers and clients.") } } \ No newline at end of file diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/Constant.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/Constant.kt index 9384995..88da356 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/Constant.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/Constant.kt @@ -2,7 +2,7 @@ package org.timemates.rrpc.generator.kotlin internal object Constant { val GENERATED_COMMENT = """ - This file is generated by the `rrpcroto/rrpc-kotlin` library. + This file is generated by the `RRpc/rrpc-kotlin` library. WARNING: DO NOT MODIFY THIS FILE MANUALLY! diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/DescriptorGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/DescriptorGenerator.kt index fdbe69a..a432d10 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/DescriptorGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/DescriptorGenerator.kt @@ -2,8 +2,8 @@ package org.timemates.rrpc.generator.kotlin import com.squareup.kotlinpoet.* import org.timemates.rrpc.codegen.typemodel.Types -import org.timemates.rrpc.common.metadata.* -import org.timemates.rrpc.common.metadata.annotations.NonPlatformSpecificAccess +import org.timemates.rrpc.common.schema.* +import org.timemates.rrpc.common.schema.annotations.NonPlatformSpecificAccess import org.timemates.rrpc.generator.kotlin.ext.addAllSeparated import org.timemates.rrpc.generator.kotlin.ext.asClassName import org.timemates.rrpc.generator.kotlin.ext.qualifiedName diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/ExtendGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/ExtendGenerator.kt index ceb0716..fa55abd 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/ExtendGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/ExtendGenerator.kt @@ -4,10 +4,10 @@ import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.PropertySpec import org.timemates.rrpc.codegen.exception.GenerationException import org.timemates.rrpc.codegen.typemodel.Types -import org.timemates.rrpc.common.metadata.RMExtend -import org.timemates.rrpc.common.metadata.RMOptions -import org.timemates.rrpc.common.metadata.RMResolver -import org.timemates.rrpc.common.metadata.value.RMTypeUrl +import org.timemates.rrpc.common.schema.RMExtend +import org.timemates.rrpc.common.schema.RMOptions +import org.timemates.rrpc.common.schema.RMResolver +import org.timemates.rrpc.common.schema.value.RMDeclarationUrl public object ExtendGenerator { public fun generateExtend(extend: RMExtend, resolver: RMResolver): List { @@ -35,7 +35,7 @@ public object ExtendGenerator { } } - private fun getClassNameFromExtendType(type: RMTypeUrl): ClassName { + private fun getClassNameFromExtendType(type: RMDeclarationUrl): ClassName { return when (type) { RMOptions.METHOD_OPTIONS -> Types.Option.RPC RMOptions.SERVICE_OPTIONS -> Types.Option.Service diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/FileGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/FileGenerator.kt index d9f3790..3287aac 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/FileGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/FileGenerator.kt @@ -4,9 +4,9 @@ import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.FileSpec import org.timemates.rrpc.codegen.typemodel.Annotations import org.timemates.rrpc.codegen.typemodel.Types -import org.timemates.rrpc.common.metadata.RMFile -import org.timemates.rrpc.common.metadata.RMResolver -import org.timemates.rrpc.common.metadata.kotlinPackage +import org.timemates.rrpc.common.schema.RMFile +import org.timemates.rrpc.common.schema.RMResolver +import org.timemates.rrpc.common.schema.kotlinPackage import org.timemates.rrpc.generator.kotlin.client.ClientServiceGenerator import org.timemates.rrpc.generator.kotlin.ext.addImports import org.timemates.rrpc.generator.kotlin.server.ServerServiceGenerator diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/KotlinMetadataSchemaAdapter.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/KotlinMetadataSchemaAdapter.kt index ec09fdb..d68f199 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/KotlinMetadataSchemaAdapter.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/KotlinMetadataSchemaAdapter.kt @@ -2,8 +2,8 @@ package org.timemates.rrpc.generator.kotlin import org.timemates.rrpc.codegen.adapters.SchemaAdapter import org.timemates.rrpc.codegen.exception.GenerationException -import org.timemates.rrpc.common.metadata.RMResolver -import org.timemates.rrpc.common.metadata.annotations.NonPlatformSpecificAccess +import org.timemates.rrpc.common.schema.RMResolver +import org.timemates.rrpc.common.schema.annotations.NonPlatformSpecificAccess import org.timemates.rrpc.generator.kotlin.metadata.CombinedFilesMetadataGenerator public object KotlinMetadataSchemaAdapter : SchemaAdapter { diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/KotlinSchemaAdapter.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/KotlinSchemaAdapter.kt index a3a8968..efa4ad3 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/KotlinSchemaAdapter.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/KotlinSchemaAdapter.kt @@ -1,8 +1,8 @@ package org.timemates.rrpc.generator.kotlin import org.timemates.rrpc.codegen.adapters.SchemaAdapter -import org.timemates.rrpc.common.metadata.RMResolver -import org.timemates.rrpc.common.metadata.annotations.NonPlatformSpecificAccess +import org.timemates.rrpc.common.schema.RMResolver +import org.timemates.rrpc.common.schema.annotations.NonPlatformSpecificAccess public object KotlinSchemaAdapter : SchemaAdapter { @OptIn(NonPlatformSpecificAccess::class) @@ -23,8 +23,8 @@ public object KotlinSchemaAdapter : SchemaAdapter { }.forEach { spec -> config.output.forEach { output -> when (output) { - is SchemaAdapter.Config.Custom -> {} - is SchemaAdapter.Config.FS -> { + is SchemaAdapter.Config.Output.Custom -> {} + is SchemaAdapter.Config.Output.FS -> { spec.writeTo(output.path.toNioPath()) } } diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/OptionGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/OptionGenerator.kt index 894e710..06ad53c 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/OptionGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/OptionGenerator.kt @@ -2,9 +2,9 @@ package org.timemates.rrpc.generator.kotlin import com.squareup.kotlinpoet.* import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy -import org.timemates.rrpc.common.metadata.RMField -import org.timemates.rrpc.common.metadata.RMResolver -import org.timemates.rrpc.common.metadata.value.RMTypeUrl +import org.timemates.rrpc.common.schema.RMField +import org.timemates.rrpc.common.schema.RMResolver +import org.timemates.rrpc.common.schema.value.RMDeclarationUrl import org.timemates.rrpc.generator.kotlin.ext.asClassName public object OptionGenerator { @@ -15,17 +15,17 @@ public object OptionGenerator { .delegate("lazy·{·%T(%S, %L)·}", type, field.name, field.tag) .build() - private fun toKotlinTypeName(type: RMTypeUrl, resolver: RMResolver): TypeName { + private fun toKotlinTypeName(type: RMDeclarationUrl, resolver: RMResolver): TypeName { return when (type) { - RMTypeUrl.STRING, RMTypeUrl.STRING_VALUE -> return STRING - RMTypeUrl.DOUBLE, RMTypeUrl.DOUBLE_VALUE -> return DOUBLE - RMTypeUrl.BYTES, RMTypeUrl.BYTES_VALUE -> return BYTE_ARRAY - RMTypeUrl.BOOL, RMTypeUrl.BOOL_VALUE -> return BOOLEAN - RMTypeUrl.UINT64, RMTypeUrl.UINT64_VALUE -> return U_LONG - RMTypeUrl.UINT32, RMTypeUrl.UINT32_VALUE -> return U_INT - RMTypeUrl.INT32, RMTypeUrl.INT32_VALUE, RMTypeUrl.SFIXED32, RMTypeUrl.FIXED32 -> return INT - RMTypeUrl.INT64, RMTypeUrl.INT64_VALUE, RMTypeUrl.SFIXED64, RMTypeUrl.FIXED64 -> LONG - RMTypeUrl.FLOAT, RMTypeUrl.FLOAT_VALUE -> return FLOAT + RMDeclarationUrl.STRING, RMDeclarationUrl.STRING_VALUE -> return STRING + RMDeclarationUrl.DOUBLE, RMDeclarationUrl.DOUBLE_VALUE -> return DOUBLE + RMDeclarationUrl.BYTES, RMDeclarationUrl.BYTES_VALUE -> return BYTE_ARRAY + RMDeclarationUrl.BOOL, RMDeclarationUrl.BOOL_VALUE -> return BOOLEAN + RMDeclarationUrl.UINT64, RMDeclarationUrl.UINT64_VALUE -> return U_LONG + RMDeclarationUrl.UINT32, RMDeclarationUrl.UINT32_VALUE -> return U_INT + RMDeclarationUrl.INT32, RMDeclarationUrl.INT32_VALUE, RMDeclarationUrl.SFIXED32, RMDeclarationUrl.FIXED32 -> return INT + RMDeclarationUrl.INT64, RMDeclarationUrl.INT64_VALUE, RMDeclarationUrl.SFIXED64, RMDeclarationUrl.FIXED64 -> LONG + RMDeclarationUrl.FLOAT, RMDeclarationUrl.FLOAT_VALUE -> return FLOAT else -> type.asClassName(resolver) } } diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/client/ClientRpcGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/client/ClientRpcGenerator.kt index ba0cb3a..88bac03 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/client/ClientRpcGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/client/ClientRpcGenerator.kt @@ -2,26 +2,28 @@ package org.timemates.rrpc.generator.kotlin.client import com.squareup.kotlinpoet.* import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy -import com.squareup.wire.schema.ProtoType -import com.squareup.wire.schema.Rpc -import org.timemates.rrpc.common.metadata.RMResolver -import org.timemates.rrpc.codegen.ext.* import org.timemates.rrpc.codegen.typemodel.Types +import org.timemates.rrpc.common.schema.* +import org.timemates.rrpc.common.schema.value.RMDeclarationUrl +import org.timemates.rrpc.generator.kotlin.ext.asClassName +import org.timemates.rrpc.generator.kotlin.ext.deprecated public object ClientRpcGenerator { - public fun generateRpc(serviceName: String, rpc: Rpc, schema: RMResolver): FunSpec { + public fun generateRpc(serviceName: String, rpc: RMRpc, schema: RMResolver): FunSpec { when { - rpc.requestType == ProtoType.ACK && rpc.requestStreaming -> + rpc.requestType.type == RMDeclarationUrl.ACK && rpc.requestType.isStreaming -> error("Ack type cannot be used as streaming type.") - rpc.responseType == ProtoType.ACK && rpc.responseStreaming -> + + rpc.responseType.type == RMDeclarationUrl.ACK && rpc.responseType.isStreaming -> error("Ack type cannot be used as streaming type.") - rpc.requestStreaming && !rpc.responseStreaming -> + + rpc.requestType.isStreaming && !rpc.responseType.isStreaming -> error("Client-only streaming is not supported.") } - val rpcName = rpc.name.decapitalized() - val rpcRequestType = rpc.requestType!!.asClassName(schema) - val rpcReturnType = rpc.responseType!!.asClassName(schema) + val rpcName = rpc.kotlinName() + val rpcRequestType = rpc.requestType.type.asClassName(schema) + val rpcReturnType = rpc.responseType.type.asClassName(schema) val code = when { rpc.isFireAndForget -> CodeBlock.of( @@ -33,6 +35,7 @@ public object ClientRpcGenerator { rpcRequestType, Types.Options, ) + rpc.isMetadataPush -> CodeBlock.of( METADATA_PUSH_CODE, Types.ClientMetadata, @@ -41,6 +44,7 @@ public object ClientRpcGenerator { Types.ExtraMetadata, Types.Options, ) + else -> CodeBlock.of( format = BASIC_REQUEST_CODE, args = arrayOf( @@ -54,7 +58,7 @@ public object ClientRpcGenerator { serviceName, rpcName, Types.ExtraMetadata, - if (rpc.requestStreaming) "messages" else "message", + if (rpc.requestType.isStreaming) "messages" else "message", rpcRequestType, rpcReturnType, Types.Options, @@ -62,10 +66,10 @@ public object ClientRpcGenerator { ) } - return FunSpec.builder(rpc.name.decapitalized()) + return FunSpec.builder(rpc.kotlinName()) .deprecated(rpc.options.isDeprecated) .apply { - if (rpc.requestStreaming) { + if (rpc.requestType.isStreaming) { addParameter( name = "messages", type = Types.Flow(rpcRequestType) @@ -78,17 +82,17 @@ public object ClientRpcGenerator { } } .addParameter( - ParameteRRpcec.builder("extra", MAP.parameterizedBy(STRING, BYTE_ARRAY)) + ParameterSpec.builder("extra", MAP.parameterizedBy(STRING, BYTE_ARRAY)) .defaultValue("emptyMap()") .build() ) .apply { - if (!rpc.requestStreaming && !rpc.responseStreaming) { + if (rpc.isRequestResponse || rpc.isFireAndForget || rpc.isMetadataPush) { addModifiers(KModifier.SUSPEND) } } .addCode(code) - .returns(rpcReturnType.let { if (rpc.responseStreaming) Types.Flow(it) else it }) + .returns(rpcReturnType.let { if (rpc.responseType.isStreaming) Types.Flow(it) else it }) .build() } } diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/client/ClientServiceGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/client/ClientServiceGenerator.kt index 4ef769d..68ed184 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/client/ClientServiceGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/client/ClientServiceGenerator.kt @@ -5,8 +5,8 @@ import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.TypeSpec import org.timemates.rrpc.codegen.typemodel.Annotations import org.timemates.rrpc.codegen.typemodel.Types -import org.timemates.rrpc.common.metadata.RMResolver -import org.timemates.rrpc.common.metadata.RMService +import org.timemates.rrpc.common.schema.RMResolver +import org.timemates.rrpc.common.schema.RMService import org.timemates.rrpc.generator.kotlin.options.ClientOptionsPropertyGenerator import org.timemates.rrpc.generator.kotlin.typemodel.ImportRequirement @@ -32,7 +32,7 @@ public object ClientServiceGenerator { return Result( typeSpec = TypeSpec.classBuilder(className) .addAnnotation( - Annotations.OptIn(Annotations.InternalRRpcrotoAPI) + Annotations.OptIn(Annotations.InternalRRpcAPI) ) .primaryConstructor( FunSpec.constructorBuilder() diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/ext/KotlinPoetExt.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/ext/KotlinPoetExt.kt index 456dd71..47af934 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/ext/KotlinPoetExt.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/ext/KotlinPoetExt.kt @@ -2,6 +2,7 @@ package org.timemates.rrpc.generator.kotlin.ext import com.squareup.kotlinpoet.* import org.timemates.rrpc.codegen.typemodel.Annotations +import org.timemates.rrpc.common.schema.value.RMPackageName import org.timemates.rrpc.generator.kotlin.typemodel.ImportRequirement /** @@ -18,7 +19,7 @@ internal fun FileSpec.Builder.addTypes(types: List): FileSpec.Builder internal fun FileSpec.Builder.addImports(imports: List): FileSpec.Builder = apply { imports.forEach { - addImport(it.packageName, it.simpleNames) + addImport(it.packageName.value, it.simpleNames) } } @@ -46,7 +47,7 @@ internal fun FunSpec.Builder.deprecated(deprecated: Boolean = true): FunSpec.Bui } internal fun ClassName.asImportRequirement(): ImportRequirement = ImportRequirement( - packageName, + RMPackageName(packageName), simpleNames, ) diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/ext/RSPExt.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/ext/RSPExt.kt index 3c76027..be81409 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/ext/RSPExt.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/ext/RSPExt.kt @@ -3,20 +3,20 @@ package org.timemates.rrpc.generator.kotlin.ext import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.TypeName import org.timemates.rrpc.codegen.typemodel.Types -import org.timemates.rrpc.common.metadata.Language -import org.timemates.rrpc.common.metadata.RMResolver -import org.timemates.rrpc.common.metadata.StreamableRMTypeUrl -import org.timemates.rrpc.common.metadata.annotations.NonPlatformSpecificAccess -import org.timemates.rrpc.common.metadata.value.RMTypeUrl +import org.timemates.rrpc.common.schema.Language +import org.timemates.rrpc.common.schema.RMResolver +import org.timemates.rrpc.common.schema.StreamableRMTypeUrl +import org.timemates.rrpc.common.schema.annotations.NonPlatformSpecificAccess +import org.timemates.rrpc.common.schema.value.RMDeclarationUrl @OptIn(NonPlatformSpecificAccess::class) -internal fun RMTypeUrl.asClassName(resolver: RMResolver): ClassName { +internal fun RMDeclarationUrl.asClassName(resolver: RMResolver): ClassName { return when (this) { - RMTypeUrl.ANY -> ClassName("com.google.protobuf", "ProtoAny") - RMTypeUrl.TIMESTAMP -> ClassName("com.google.protobuf", "ProtoTimestamp") - RMTypeUrl.DURATION -> ClassName("com.google.protobuf", "ProtoDuration") - RMTypeUrl.STRUCT_MAP -> ClassName("com.google.protobuf", "ProtoStruct") - RMTypeUrl.EMPTY -> ClassName("com.google.protobuf", "ProtoEmpty") + RMDeclarationUrl.ANY -> ClassName("com.google.protobuf", "ProtoAny") + RMDeclarationUrl.TIMESTAMP -> ClassName("com.google.protobuf", "ProtoTimestamp") + RMDeclarationUrl.DURATION -> ClassName("com.google.protobuf", "ProtoDuration") + RMDeclarationUrl.STRUCT_MAP -> ClassName("com.google.protobuf", "ProtoStruct") + RMDeclarationUrl.EMPTY -> ClassName("com.google.protobuf", "ProtoEmpty") else -> { val file = resolver.resolveFileOf(this) ?: return ClassName(enclosingTypeOrPackage ?: "", simpleName) @@ -37,7 +37,7 @@ internal fun StreamableRMTypeUrl.asClassName(resolver: RMResolver): TypeName { } @OptIn(NonPlatformSpecificAccess::class) -internal fun RMTypeUrl.qualifiedName(resolver: RMResolver): String { +internal fun RMDeclarationUrl.qualifiedName(resolver: RMResolver): String { val packageName = resolver.resolveFileOf(this)?.packageName?.value?.plus(".") ?: "" return packageName + simpleName diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/CombinedFilesMetadataGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/CombinedFilesMetadataGenerator.kt index ab35181..0c2c067 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/CombinedFilesMetadataGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/CombinedFilesMetadataGenerator.kt @@ -7,10 +7,8 @@ import com.squareup.kotlinpoet.TypeSpec import com.squareup.kotlinpoet.buildCodeBlock import com.squareup.kotlinpoet.withIndent import org.timemates.rrpc.codegen.typemodel.Types -import org.timemates.rrpc.common.metadata.RMResolver +import org.timemates.rrpc.common.schema.RMResolver import org.timemates.rrpc.generator.kotlin.ext.newline -import org.timemates.rrpc.generator.kotlin.metadata.FileMetadataGenerator -import java.util.UUID import kotlin.random.Random public object CombinedFilesMetadataGenerator { @@ -33,7 +31,7 @@ public object CombinedFilesMetadataGenerator { withIndent { resolver.resolveAvailableFiles().forEach { file -> newline() - add(FileMetadataGenerator.generate(file)) + add(FileMetadataGenerator.generate(file, resolver)) add(",") } } diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/ExtendMetadataGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/ExtendMetadataGenerator.kt index f78e4ed..08f75d2 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/ExtendMetadataGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/ExtendMetadataGenerator.kt @@ -4,11 +4,12 @@ import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.buildCodeBlock import com.squareup.kotlinpoet.withIndent import org.timemates.rrpc.codegen.typemodel.Types -import org.timemates.rrpc.common.metadata.RMExtend +import org.timemates.rrpc.common.schema.RMExtend +import org.timemates.rrpc.common.schema.RMResolver import org.timemates.rrpc.generator.kotlin.ext.newline internal object ExtendMetadataGenerator { - fun generate(extend: RMExtend): CodeBlock { + fun generate(extend: RMExtend, resolver: RMResolver): CodeBlock { return buildCodeBlock { addStatement("%T(", Types.RM.Extend) withIndent { @@ -22,7 +23,7 @@ internal object ExtendMetadataGenerator { withIndent { extend.fields.forEach { field -> newline() - add(FieldMetadataGenerator.generate(field)) + add(FieldMetadataGenerator.generate(field, resolver)) add(",") } } diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/FieldMetadataGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/FieldMetadataGenerator.kt index ab88ce5..a0d1436 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/FieldMetadataGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/FieldMetadataGenerator.kt @@ -4,10 +4,11 @@ import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.buildCodeBlock import com.squareup.kotlinpoet.withIndent import org.timemates.rrpc.codegen.typemodel.Types -import org.timemates.rrpc.common.metadata.RMField +import org.timemates.rrpc.common.schema.RMField +import org.timemates.rrpc.common.schema.RMResolver internal object FieldMetadataGenerator { - fun generate(field: RMField): CodeBlock { + fun generate(field: RMField, resolver: RMResolver): CodeBlock { return buildCodeBlock { addStatement("%T(") withIndent { @@ -15,7 +16,7 @@ internal object FieldMetadataGenerator { addStatement("name = %S,", field.name) addStatement( format = "options = %P,", - OptionsMetadataGenerator.generate(field.options) + OptionsMetadataGenerator.generate(field.options, resolver) ) addStatement( "documentation = %L,", diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/FileMetadataGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/FileMetadataGenerator.kt index fce8e04..8134081 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/FileMetadataGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/FileMetadataGenerator.kt @@ -3,27 +3,25 @@ package org.timemates.rrpc.generator.kotlin.metadata import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.withIndent import org.timemates.rrpc.codegen.typemodel.Types -import org.timemates.rrpc.common.metadata.RMFile -import org.timemates.rrpc.common.metadata.annotations.NonPlatformSpecificAccess +import org.timemates.rrpc.common.schema.RMFile +import org.timemates.rrpc.common.schema.RMResolver +import org.timemates.rrpc.common.schema.annotations.NonPlatformSpecificAccess import org.timemates.rrpc.generator.kotlin.ext.newline -import org.timemates.rrpc.generator.kotlin.metadata.ExtendMetadataGenerator -import org.timemates.rrpc.generator.kotlin.metadata.OptionsMetadataGenerator -import org.timemates.rrpc.generator.kotlin.metadata.ServiceMetadataGenerator internal object FileMetadataGenerator { - fun generate(file: RMFile): CodeBlock { + fun generate(file: RMFile, resolver: RMResolver): CodeBlock { return CodeBlock.builder().apply { addStatement("%T(", Types.RM.File) indent() addStatement("name = %S,", file.name) @OptIn(NonPlatformSpecificAccess::class) addStatement("packageName = %T(%S),", Types.RM.Value.PackageName, file.packageName) - addStatement("options = %P,", OptionsMetadataGenerator.generate(file.options)) + addStatement("options = %P,", OptionsMetadataGenerator.generate(file.options, resolver)) addStatement("services = listOf(") withIndent { file.services.forEach { service -> newline() - add(ServiceMetadataGenerator.generate(service)) + add(ServiceMetadataGenerator.generate(service, resolver)) } } addStatement("),") @@ -31,7 +29,7 @@ internal object FileMetadataGenerator { withIndent { file.extends.forEach { extend -> newline() - add(ExtendMetadataGenerator.generate(extend)) + add(ExtendMetadataGenerator.generate(extend, resolver)) add(",") } } diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/OneOfMetadataGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/OneOfMetadataGenerator.kt index e632f45..08486d1 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/OneOfMetadataGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/OneOfMetadataGenerator.kt @@ -4,11 +4,12 @@ import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.buildCodeBlock import com.squareup.kotlinpoet.withIndent import org.timemates.rrpc.codegen.typemodel.Types -import org.timemates.rrpc.common.metadata.RMOneOf +import org.timemates.rrpc.common.schema.RMOneOf +import org.timemates.rrpc.common.schema.RMResolver import org.timemates.rrpc.generator.kotlin.ext.newline internal object OneOfMetadataGenerator { - fun generate(oneOf: RMOneOf): CodeBlock { + fun generate(oneOf: RMOneOf, resolver: RMResolver): CodeBlock { return buildCodeBlock { add("%T(", Types.RM.OneOf) withIndent { @@ -18,12 +19,12 @@ internal object OneOfMetadataGenerator { withIndent { oneOf.fields.forEach { field -> newline() - add(FieldMetadataGenerator.generate(field)) + add(FieldMetadataGenerator.generate(field, resolver)) add(",") } } addStatement("),") - addStatement("options = %P", OptionsMetadataGenerator.generate(oneOf.options)) + addStatement("options = %P", OptionsMetadataGenerator.generate(oneOf.options, resolver)) } addStatement(")") } diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/OptionsMetadataGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/OptionsMetadataGenerator.kt index fd491a3..7c67b66 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/OptionsMetadataGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/OptionsMetadataGenerator.kt @@ -4,18 +4,21 @@ import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.buildCodeBlock import com.squareup.kotlinpoet.withIndent import org.timemates.rrpc.codegen.typemodel.Types -import org.timemates.rrpc.common.metadata.RMOption -import org.timemates.rrpc.common.metadata.RMOptions +import org.timemates.rrpc.common.schema.RMOption +import org.timemates.rrpc.common.schema.RMOptions +import org.timemates.rrpc.common.schema.RMResolver import org.timemates.rrpc.generator.kotlin.ext.newline internal object OptionsMetadataGenerator { - fun generate(options: RMOptions): CodeBlock { + fun generate(options: RMOptions, resolver: RMResolver): CodeBlock { return buildCodeBlock { add("%T(", Types.RM.Options) newline() indent() add("listOf(") options.list.forEach { option -> + val field = resolver.resolveField(option.fieldUrl)!! + withIndent { newline() add("%T(", Types.RM.Option) @@ -23,7 +26,7 @@ internal object OptionsMetadataGenerator { withIndent { add("name = %S,", option.name) newline() - add("tag = %L,", option.tag) + add("tag = %L,", field.tag) newline() add("fieldUrl = %1T(", Types.RM.TypeMemberUrl, option.fieldUrl.typeUrl) newline() diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/RpcMetadataGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/RpcMetadataGenerator.kt index d34b0a6..7cd4bce 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/RpcMetadataGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/RpcMetadataGenerator.kt @@ -4,12 +4,13 @@ import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.buildCodeBlock import com.squareup.kotlinpoet.withIndent import org.timemates.rrpc.codegen.typemodel.Types -import org.timemates.rrpc.common.metadata.RMRpc -import org.timemates.rrpc.common.metadata.annotations.NonPlatformSpecificAccess +import org.timemates.rrpc.common.schema.RMResolver +import org.timemates.rrpc.common.schema.RMRpc +import org.timemates.rrpc.common.schema.annotations.NonPlatformSpecificAccess import org.timemates.rrpc.generator.kotlin.ext.newline internal object RpcMetadataGenerator { - fun generate(rpc: RMRpc): CodeBlock { + fun generate(rpc: RMRpc, resolver: RMResolver): CodeBlock { return buildCodeBlock { add("%T(", Types.RM.Rpc) withIndent { @@ -33,7 +34,7 @@ internal object RpcMetadataGenerator { rpc.responseType.type.value ) newline() - add("options = %P,", OptionsMetadataGenerator.generate(rpc.options)) + add("options = %P,", OptionsMetadataGenerator.generate(rpc.options, resolver)) add( "documentation = %L,", if (rpc.documentation == null) "null" else "\"${rpc.documentation}\"" diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/ServiceMetadataGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/ServiceMetadataGenerator.kt index e01543a..e48a1b6 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/ServiceMetadataGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/ServiceMetadataGenerator.kt @@ -4,11 +4,12 @@ import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.buildCodeBlock import com.squareup.kotlinpoet.withIndent import org.timemates.rrpc.codegen.typemodel.Types -import org.timemates.rrpc.common.metadata.RMService +import org.timemates.rrpc.common.schema.RMResolver +import org.timemates.rrpc.common.schema.RMService import org.timemates.rrpc.generator.kotlin.ext.newline internal object ServiceMetadataGenerator { - fun generate(service: RMService): CodeBlock { + fun generate(service: RMService, resolver: RMResolver): CodeBlock { return buildCodeBlock { addStatement("%T(", Types.RM.Service) withIndent { @@ -16,12 +17,12 @@ internal object ServiceMetadataGenerator { addStatement("rpcs = listOf(").withIndent { service.rpcs.forEach { rpc -> newline() - add(RpcMetadataGenerator.generate(rpc)) + add(RpcMetadataGenerator.generate(rpc, resolver)) newline(before = ",") } } addStatement("),") - add("options = %P,", OptionsMetadataGenerator.generate(service.options)) + add("options = %P,", OptionsMetadataGenerator.generate(service.options, resolver)) newline() addStatement( format = "typeUrl = %T(%S),", diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/TypeMetadataGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/TypeMetadataGenerator.kt index 576ec01..08e6c39 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/TypeMetadataGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/metadata/TypeMetadataGenerator.kt @@ -4,20 +4,21 @@ import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.buildCodeBlock import com.squareup.kotlinpoet.withIndent import org.timemates.rrpc.codegen.typemodel.Types -import org.timemates.rrpc.common.metadata.RMExtend -import org.timemates.rrpc.common.metadata.RMType +import org.timemates.rrpc.common.schema.RMExtend +import org.timemates.rrpc.common.schema.RMResolver +import org.timemates.rrpc.common.schema.RMType import org.timemates.rrpc.generator.kotlin.ext.newline internal object TypeMetadataGenerator { - fun generate(type: RMType): CodeBlock { + fun generate(type: RMType, resolver: RMResolver): CodeBlock { return when (type) { - is RMType.Enclosing -> generateEnclosing(type) - is RMType.Enum -> generateEnum(type) - is RMType.Message -> generateMessage(type) + is RMType.Enclosing -> generateEnclosing(type, resolver) + is RMType.Enum -> generateEnum(type, resolver) + is RMType.Message -> generateMessage(type, resolver) } } - private fun generateMessage(message: RMType.Message): CodeBlock { + private fun generateMessage(message: RMType.Message, resolver: RMResolver): CodeBlock { return buildCodeBlock { add("%T(", Types.RM.Enum) withIndent { @@ -27,7 +28,7 @@ internal object TypeMetadataGenerator { withIndent { message.fields.forEach { field -> newline() - add(FieldMetadataGenerator.generate(field)) + add(FieldMetadataGenerator.generate(field, resolver)) add(",") } } @@ -36,34 +37,34 @@ internal object TypeMetadataGenerator { withIndent { message.oneOfs.forEach { oneOf -> newline() - add(OneOfMetadataGenerator.generate(oneOf)) + add(OneOfMetadataGenerator.generate(oneOf, resolver)) } } addStatement("),") - addStatement("options = %P", OptionsMetadataGenerator.generate(message.options)) + addStatement("options = %P", OptionsMetadataGenerator.generate(message.options, resolver)) addStatement("typeUrl = %T(%S)", Types.RM.Value.TypeUrl, message.typeUrl) - addNestedTypes(message.nestedTypes) - addNestedExtends(message.nestedExtends) + addNestedTypes(message.nestedTypes, resolver) + addNestedExtends(message.nestedExtends, resolver) } addStatement(")") } } - private fun generateEnclosing(enclosing: RMType.Enclosing): CodeBlock { + private fun generateEnclosing(enclosing: RMType.Enclosing, resolver: RMResolver): CodeBlock { return buildCodeBlock { add("%T(", Types.RM.Enum) withIndent { addStatement("name = %S,", enclosing.name) addDocumentation(enclosing.documentation) addStatement("typeUrl = %T(%S),", Types.RM.Value.TypeUrl, enclosing.typeUrl) - addNestedTypes(enclosing.nestedTypes) - addNestedExtends(enclosing.nestedExtends) + addNestedTypes(enclosing.nestedTypes, resolver) + addNestedExtends(enclosing.nestedExtends, resolver) } add(")") } } - private fun generateEnum(enum: RMType.Enum): CodeBlock { + private fun generateEnum(enum: RMType.Enum, resolver: RMResolver): CodeBlock { return buildCodeBlock { add("%T(", Types.RM.Enum) withIndent { @@ -76,7 +77,7 @@ internal object TypeMetadataGenerator { withIndent { addStatement("name = %S,", constant.name) addStatement("tag = %L,", constant.tag) - addStatement("options = %P,", OptionsMetadataGenerator.generate(constant.options)) + addStatement("options = %P,", OptionsMetadataGenerator.generate(constant.options, resolver)) addStatement( format = "documentation = %L,", if (constant.documentation == null) "null" else "\"${constant.documentation}\"" @@ -87,9 +88,9 @@ internal object TypeMetadataGenerator { } add("),") addDocumentation(enum.documentation) - addStatement("options = %P,", OptionsMetadataGenerator.generate(enum.options)) - addNestedTypes(enum.nestedTypes) - addNestedExtends(enum.nestedExtends) + addStatement("options = %P,", OptionsMetadataGenerator.generate(enum.options, resolver)) + addNestedTypes(enum.nestedTypes, resolver) + addNestedExtends(enum.nestedExtends, resolver) addStatement( format = "typeUrl = %T(%S),", Types.RM.Value.TypeUrl, @@ -100,24 +101,24 @@ internal object TypeMetadataGenerator { } } - private fun CodeBlock.Builder.addNestedTypes(nestedTypes: List) { + private fun CodeBlock.Builder.addNestedTypes(nestedTypes: List, resolver: RMResolver) { addStatement("nestedTypes = listOf(") withIndent { nestedTypes.forEach { nestedType -> newline() - add(generate(nestedType)) + add(generate(nestedType, resolver)) add(",") } } add("),") } - private fun CodeBlock.Builder.addNestedExtends(nestedExtends: List) { + private fun CodeBlock.Builder.addNestedExtends(nestedExtends: List, resolver: RMResolver) { addStatement("nestedExtends = listOf(") withIndent { nestedExtends.forEach { nested -> newline() - add(ExtendMetadataGenerator.generate(nested)) + add(ExtendMetadataGenerator.generate(nested, resolver)) add(",") } } diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/options/ClientOptionsPropertyGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/options/ClientOptionsPropertyGenerator.kt index 683d043..8f1acad 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/options/ClientOptionsPropertyGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/options/ClientOptionsPropertyGenerator.kt @@ -5,9 +5,9 @@ import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.buildCodeBlock import org.timemates.rrpc.codegen.typemodel.Types -import org.timemates.rrpc.common.metadata.RMOptions -import org.timemates.rrpc.common.metadata.RMResolver -import org.timemates.rrpc.common.metadata.kotlinPackage +import org.timemates.rrpc.common.schema.RMOptions +import org.timemates.rrpc.common.schema.RMResolver +import org.timemates.rrpc.common.schema.kotlinPackage import org.timemates.rrpc.generator.kotlin.ext.newline import org.timemates.rrpc.generator.kotlin.typemodel.ImportRequirement diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/options/OptionValueGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/options/OptionValueGenerator.kt index 052f635..24a397e 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/options/OptionValueGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/options/OptionValueGenerator.kt @@ -1,23 +1,23 @@ package org.timemates.rrpc.generator.kotlin.options import com.squareup.kotlinpoet.CodeBlock -import org.timemates.rrpc.common.metadata.RMOption -import org.timemates.rrpc.common.metadata.RMResolver -import org.timemates.rrpc.common.metadata.RMType -import org.timemates.rrpc.common.metadata.RMTypeMemberUrl -import org.timemates.rrpc.common.metadata.value.RMTypeUrl +import org.timemates.rrpc.common.schema.RMOption +import org.timemates.rrpc.common.schema.RMResolver +import org.timemates.rrpc.common.schema.RMType +import org.timemates.rrpc.common.schema.RMTypeMemberUrl +import org.timemates.rrpc.common.schema.value.RMDeclarationUrl import org.timemates.rrpc.generator.kotlin.ext.asClassName import org.timemates.rrpc.generator.kotlin.ext.protoByteStringToByteArray public object OptionValueGenerator { public fun generate( - typeUrl: RMTypeUrl, + typeUrl: RMDeclarationUrl, value: RMOption.Value?, resolver: RMResolver, ): String { @Suppress("UNCHECKED_CAST") return when { - typeUrl.isScalar || typeUrl == RMTypeUrl.STRING || typeUrl == RMTypeUrl.STRING_VALUE -> generateScalarOrString( + typeUrl.isScalar || typeUrl == RMDeclarationUrl.STRING || typeUrl == RMDeclarationUrl.STRING_VALUE -> generateScalarOrString( typeUrl, value ) @@ -29,51 +29,51 @@ public object OptionValueGenerator { } private fun generateScalarOrString( - protoType: RMTypeUrl, + protoType: RMDeclarationUrl, value: Any?, ): String { return when (protoType) { - RMTypeUrl.STRING, RMTypeUrl.STRING_VALUE -> "\"${value}\"" - RMTypeUrl.BOOL -> "$value" + RMDeclarationUrl.STRING, RMDeclarationUrl.STRING_VALUE -> "\"${value}\"" + RMDeclarationUrl.BOOL -> "$value" // We don't support special types such as fixed32, sfixed32 and sint32, // because it makes no sense if not used in serialization. // All they are represented as regular Int. - RMTypeUrl.INT32, RMTypeUrl.FIXED32, RMTypeUrl.SFIXED32, RMTypeUrl.SINT32 -> "$value" + RMDeclarationUrl.INT32, RMDeclarationUrl.FIXED32, RMDeclarationUrl.SFIXED32, RMDeclarationUrl.SINT32 -> "$value" // We don't support special types such as fixed64, sfixed64 and sint64, // because it makes no sense if not used in serialization. // All they are represented as regular Long. - RMTypeUrl.INT64, RMTypeUrl.FIXED64, RMTypeUrl.SFIXED64, RMTypeUrl.SINT64 -> "${value}L" - RMTypeUrl.FLOAT -> "${value}f" - RMTypeUrl.DOUBLE -> "${value}.toDouble()" - RMTypeUrl.UINT32 -> "${value}.toUInt()" - RMTypeUrl.UINT64 -> "${value}uL" - RMTypeUrl.BYTES -> byteArrayToSourceCode((value as String).protoByteStringToByteArray()) + RMDeclarationUrl.INT64, RMDeclarationUrl.FIXED64, RMDeclarationUrl.SFIXED64, RMDeclarationUrl.SINT64 -> "${value}L" + RMDeclarationUrl.FLOAT -> "${value}f" + RMDeclarationUrl.DOUBLE -> "${value}.toDouble()" + RMDeclarationUrl.UINT32 -> "${value}.toUInt()" + RMDeclarationUrl.UINT64 -> "${value}uL" + RMDeclarationUrl.BYTES -> byteArrayToSourceCode((value as String).protoByteStringToByteArray()) else -> error("Unsupported type") } } private fun generateWrapperValue( - protoType: RMTypeUrl, + protoType: RMDeclarationUrl, value: Map, ): String { val element = value.values.first().toString() return when (protoType) { - RMTypeUrl.STRING_VALUE -> "\"${element}\"" - RMTypeUrl.BOOL_VALUE -> element - RMTypeUrl.INT32_VALUE -> element - RMTypeUrl.INT64_VALUE -> "${element}L" - RMTypeUrl.FLOAT_VALUE -> "${element}f" - RMTypeUrl.DOUBLE -> "${element}.toDouble()" - RMTypeUrl.UINT32_VALUE -> "${element}u" - RMTypeUrl.UINT64_VALUE -> "${element}uL" + RMDeclarationUrl.STRING_VALUE -> "\"${element}\"" + RMDeclarationUrl.BOOL_VALUE -> element + RMDeclarationUrl.INT32_VALUE -> element + RMDeclarationUrl.INT64_VALUE -> "${element}L" + RMDeclarationUrl.FLOAT_VALUE -> "${element}f" + RMDeclarationUrl.DOUBLE -> "${element}.toDouble()" + RMDeclarationUrl.UINT32_VALUE -> "${element}u" + RMDeclarationUrl.UINT64_VALUE -> "${element}uL" else -> TODO("Unsupported for now: $protoType") } } private fun generateMap( - typeUrl: RMTypeUrl, - value: Map, + typeUrl: RMDeclarationUrl, + value: Map, schema: RMResolver, ): String { requireNotNull(typeUrl.firstTypeArgument) @@ -97,11 +97,11 @@ public object OptionValueGenerator { } private fun generateCustomType( - typeUrl: RMTypeUrl, + typeUrl: RMDeclarationUrl, value: RMOption.Value?, schema: RMResolver, ): String { - require(typeUrl != RMTypeUrl.ANY) { "google.protobuf.Any type is not supported." } + require(typeUrl != RMDeclarationUrl.ANY) { "google.protobuf.Any type is not supported." } val type = schema.resolveType(typeUrl) val className = typeUrl.asClassName(schema) diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/options/RawOptionsCodeGeneration.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/options/RawOptionsCodeGeneration.kt index 405d8dd..985e78f 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/options/RawOptionsCodeGeneration.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/options/RawOptionsCodeGeneration.kt @@ -3,11 +3,11 @@ package org.timemates.rrpc.generator.kotlin.options import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.buildCodeBlock import org.timemates.rrpc.codegen.typemodel.Types -import org.timemates.rrpc.common.metadata.RMField -import org.timemates.rrpc.common.metadata.RMOptions -import org.timemates.rrpc.common.metadata.RMResolver -import org.timemates.rrpc.common.metadata.kotlinPackage -import org.timemates.rrpc.common.metadata.value.RMTypeUrl +import org.timemates.rrpc.common.schema.RMField +import org.timemates.rrpc.common.schema.RMOptions +import org.timemates.rrpc.common.schema.RMResolver +import org.timemates.rrpc.common.schema.kotlinPackage +import org.timemates.rrpc.common.schema.value.RMDeclarationUrl import org.timemates.rrpc.generator.kotlin.ext.newline import org.timemates.rrpc.generator.kotlin.typemodel.ImportRequirement @@ -15,7 +15,7 @@ internal object RawOptionsCodeGeneration { fun generate( options: RMOptions, resolver: RMResolver, - optionsType: RMTypeUrl, + optionsType: RMDeclarationUrl, ): CodeBlock { val imports = mutableListOf() diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/server/ServerMetadataGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/server/ServerMetadataGenerator.kt index 6a61aa4..6fd9fe7 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/server/ServerMetadataGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/server/ServerMetadataGenerator.kt @@ -3,11 +3,9 @@ package org.timemates.rrpc.generator.kotlin.server import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.buildCodeBlock -import com.squareup.wire.schema.Options -import org.timemates.rrpc.codegen.ext.* import org.timemates.rrpc.codegen.typemodel.Types -import org.timemates.rrpc.common.metadata.* -import org.timemates.rrpc.common.metadata.annotations.NonPlatformSpecificAccess +import org.timemates.rrpc.common.schema.* +import org.timemates.rrpc.common.schema.annotations.NonPlatformSpecificAccess import org.timemates.rrpc.generator.kotlin.ext.asClassName import org.timemates.rrpc.generator.kotlin.ext.newline import org.timemates.rrpc.generator.kotlin.options.RawOptionsCodeGeneration @@ -55,7 +53,7 @@ public object ServerMetadataGenerator { newline(",") add( "options = %L", - RawOptionsCodeGeneration.generate(rpc.options, resolver, Options.METHOD_OPTIONS) + RawOptionsCodeGeneration.generate(rpc.options, resolver, RMOptions.METHOD_OPTIONS) ) newline(before = ",") unindent() @@ -67,7 +65,7 @@ public object ServerMetadataGenerator { newline() add( "options = %L", - RawOptionsCodeGeneration.generate(service.options, resolver, Options.SERVICE_OPTIONS) + RawOptionsCodeGeneration.generate(service.options, resolver, RMOptions.SERVICE_OPTIONS) ) newline() unindent() diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/server/ServerRpcGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/server/ServerRpcGenerator.kt index 502ba0c..44916ba 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/server/ServerRpcGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/server/ServerRpcGenerator.kt @@ -4,8 +4,8 @@ import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.TypeName import org.timemates.rrpc.codegen.typemodel.Types -import org.timemates.rrpc.common.metadata.* -import org.timemates.rrpc.common.metadata.value.RMTypeUrl +import org.timemates.rrpc.common.schema.* +import org.timemates.rrpc.common.schema.value.RMDeclarationUrl import org.timemates.rrpc.generator.kotlin.ext.asClassName import org.timemates.rrpc.generator.kotlin.ext.deprecated @@ -25,13 +25,13 @@ public object ServerRpcGenerator { if (rpc.isRequestResponse) addModifiers(KModifier.SUSPEND) - if (rpc.requestType.type != RMTypeUrl.ACK) + if (rpc.requestType.type != RMDeclarationUrl.ACK) addParameter( "request", requestType, ) - if (rpc.responseType.type != RMTypeUrl.ACK) + if (rpc.responseType.type != RMDeclarationUrl.ACK) returns(returnType) } .build() diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/server/ServerServiceGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/server/ServerServiceGenerator.kt index 58996e1..0560481 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/server/ServerServiceGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/server/ServerServiceGenerator.kt @@ -2,19 +2,14 @@ package org.timemates.rrpc.generator.kotlin.server import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.TypeSpec -import org.timemates.rrpc.common.metadata.RMResolver import org.timemates.rrpc.codegen.typemodel.Types -import org.timemates.rrpc.common.metadata.RMService -import org.timemates.rrpc.generator.kotlin.annotation.ExperimentalSpecModifierApi -import org.timemates.rrpc.generator.kotlin.modifier.ModifiersRegistry -import org.timemates.rrpc.generator.kotlin.modifier.ServerServiceModifier +import org.timemates.rrpc.common.schema.RMResolver +import org.timemates.rrpc.common.schema.RMService public object ServerServiceGenerator { - @OptIn(ExperimentalSpecModifierApi::class) public fun generateService( service: RMService, resolver: RMResolver, - modifiersRegistry: ModifiersRegistry, ): TypeSpec { return TypeSpec.classBuilder(service.name) .addModifiers(KModifier.ABSTRACT) @@ -22,6 +17,5 @@ public object ServerServiceGenerator { .addProperty(ServerMetadataGenerator.generateMetadata(service, resolver)) .addFunctions(service.rpcs.map { ServerRpcGenerator.generateRpc(it, resolver) }) .build() - .let { spec -> modifiersRegistry.modified(ServerServiceModifier, spec, service, resolver) } } } \ No newline at end of file diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/typemodel/Annotations.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/typemodel/Annotations.kt index 9fcb494..46a3314 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/typemodel/Annotations.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/typemodel/Annotations.kt @@ -31,5 +31,5 @@ internal object Annotations { val ProtoPacked = AnnotationSpec.builder(ClassName("kotlinx.serialization.protobuf", "ProtoPacked")).build() val ProtoOneOf = AnnotationSpec.builder(ClassName("kotlinx.serialization.protobuf", "ProtoOneOf")).build() - val InternalRRpcrotoAPI = ClassName("org.timemates.rrpc.annotations", "InternalRRpcrotoAPI") + val InternalRRpcAPI = ClassName("org.timemates.rrpc.annotations", "InternalRRpcAPI") } \ No newline at end of file diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/typemodel/ImportRequirement.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/typemodel/ImportRequirement.kt index d0fc006..6c82739 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/typemodel/ImportRequirement.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/typemodel/ImportRequirement.kt @@ -1,6 +1,6 @@ package org.timemates.rrpc.generator.kotlin.typemodel -import org.timemates.rrpc.common.metadata.value.RMPackageName +import org.timemates.rrpc.common.schema.value.RMPackageName public data class ImportRequirement( public val packageName: RMPackageName, diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/BuiltinsGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/BuiltinsGenerator.kt index 62c5a4b..eb0002e 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/BuiltinsGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/BuiltinsGenerator.kt @@ -1,20 +1,20 @@ package org.timemates.rrpc.generator.kotlin.types import com.squareup.kotlinpoet.* -import org.timemates.rrpc.common.metadata.value.RMTypeUrl +import org.timemates.rrpc.common.schema.value.RMDeclarationUrl internal object BuiltinsGenerator { - fun generateBuiltin(incoming: RMTypeUrl): TypeName { + fun generateBuiltin(incoming: RMDeclarationUrl): TypeName { return when (incoming) { - RMTypeUrl.BOOL -> BOOLEAN - RMTypeUrl.INT32, RMTypeUrl.SINT32, RMTypeUrl.FIXED32, RMTypeUrl.SFIXED32, RMTypeUrl.INT32_VALUE -> INT - RMTypeUrl.INT64, RMTypeUrl.SINT64, RMTypeUrl.FIXED64, RMTypeUrl.SFIXED64, RMTypeUrl.INT64_VALUE -> LONG - RMTypeUrl.BYTES, RMTypeUrl.BYTES_VALUE -> BYTE_ARRAY - RMTypeUrl.FLOAT, RMTypeUrl.FLOAT_VALUE -> FLOAT - RMTypeUrl.UINT32, RMTypeUrl.UINT32_VALUE -> U_INT - RMTypeUrl.UINT64, RMTypeUrl.UINT64_VALUE -> U_LONG - RMTypeUrl.DOUBLE, RMTypeUrl.DOUBLE_VALUE -> DOUBLE - RMTypeUrl.STRING -> STRING + RMDeclarationUrl.BOOL -> BOOLEAN + RMDeclarationUrl.INT32, RMDeclarationUrl.SINT32, RMDeclarationUrl.FIXED32, RMDeclarationUrl.SFIXED32, RMDeclarationUrl.INT32_VALUE -> INT + RMDeclarationUrl.INT64, RMDeclarationUrl.SINT64, RMDeclarationUrl.FIXED64, RMDeclarationUrl.SFIXED64, RMDeclarationUrl.INT64_VALUE -> LONG + RMDeclarationUrl.BYTES, RMDeclarationUrl.BYTES_VALUE -> BYTE_ARRAY + RMDeclarationUrl.FLOAT, RMDeclarationUrl.FLOAT_VALUE -> FLOAT + RMDeclarationUrl.UINT32, RMDeclarationUrl.UINT32_VALUE -> U_INT + RMDeclarationUrl.UINT64, RMDeclarationUrl.UINT64_VALUE -> U_LONG + RMDeclarationUrl.DOUBLE, RMDeclarationUrl.DOUBLE_VALUE -> DOUBLE + RMDeclarationUrl.STRING -> STRING else -> error("Unsupported protobuf type $incoming") } } diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/EnclosingTypeGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/EnclosingTypeGenerator.kt index 1cf88f9..168961c 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/EnclosingTypeGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/EnclosingTypeGenerator.kt @@ -3,8 +3,8 @@ package org.timemates.rrpc.generator.kotlin.types import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.TypeSpec -import org.timemates.rrpc.common.metadata.RMResolver -import org.timemates.rrpc.common.metadata.RMType +import org.timemates.rrpc.common.schema.RMResolver +import org.timemates.rrpc.common.schema.RMType internal object EnclosingTypeGenerator { diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/EnumTypeGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/EnumTypeGenerator.kt index 0e38ffa..17b901d 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/EnumTypeGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/EnumTypeGenerator.kt @@ -3,8 +3,8 @@ package org.timemates.rrpc.generator.kotlin.types import com.squareup.kotlinpoet.* import org.timemates.rrpc.codegen.typemodel.Annotations import org.timemates.rrpc.codegen.typemodel.Types -import org.timemates.rrpc.common.metadata.RMResolver -import org.timemates.rrpc.common.metadata.RMType +import org.timemates.rrpc.common.schema.RMResolver +import org.timemates.rrpc.common.schema.RMType internal object EnumTypeGenerator { fun generateEnum(incoming: RMType.Enum, schema: RMResolver): TypeSpec { diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/TypeDefaultValueGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/TypeDefaultValueGenerator.kt index 2b829cd..a5ccaf1 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/TypeDefaultValueGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/TypeDefaultValueGenerator.kt @@ -1,7 +1,7 @@ package org.timemates.rrpc.generator.kotlin.types -import org.timemates.rrpc.common.metadata.RMField -import org.timemates.rrpc.common.metadata.value.RMTypeUrl +import org.timemates.rrpc.common.schema.RMField +import org.timemates.rrpc.common.schema.value.RMDeclarationUrl internal object TypeDefaultValueGenerator { fun generateTypeDefault(field: RMField): String { @@ -9,23 +9,23 @@ internal object TypeDefaultValueGenerator { return "emptyList()" return when (field.typeUrl) { - RMTypeUrl.INT32, - RMTypeUrl.INT64, - RMTypeUrl.DURATION, - RMTypeUrl.FIXED32, - RMTypeUrl.FIXED64, - RMTypeUrl.SFIXED32, - RMTypeUrl.SFIXED64, - RMTypeUrl.SINT32, - RMTypeUrl.SINT64, + RMDeclarationUrl.INT32, + RMDeclarationUrl.INT64, + RMDeclarationUrl.DURATION, + RMDeclarationUrl.FIXED32, + RMDeclarationUrl.FIXED64, + RMDeclarationUrl.SFIXED32, + RMDeclarationUrl.SFIXED64, + RMDeclarationUrl.SINT32, + RMDeclarationUrl.SINT64, -> "0" - RMTypeUrl.UINT32, RMTypeUrl.UINT64 -> "0u" - RMTypeUrl.STRING -> "\"\"" - RMTypeUrl.BOOL -> "false" - RMTypeUrl.BYTES -> "byteArrayOf()" - RMTypeUrl.DOUBLE -> "0.0" - RMTypeUrl.FLOAT -> "0.0f" + RMDeclarationUrl.UINT32, RMDeclarationUrl.UINT64 -> "0u" + RMDeclarationUrl.STRING -> "\"\"" + RMDeclarationUrl.BOOL -> "false" + RMDeclarationUrl.BYTES -> "byteArrayOf()" + RMDeclarationUrl.DOUBLE -> "0.0" + RMDeclarationUrl.FLOAT -> "0.0f" else -> "null" } } diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/TypeGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/TypeGenerator.kt index 0ad42e1..de9d3b5 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/TypeGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/TypeGenerator.kt @@ -2,34 +2,28 @@ package org.timemates.rrpc.generator.kotlin.types import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.TypeSpec -import org.timemates.rrpc.common.metadata.RMResolver -import org.timemates.rrpc.common.metadata.RMType -import org.timemates.rrpc.common.metadata.value.RMTypeUrl -import org.timemates.rrpc.generator.kotlin.annotation.ExperimentalSpecModifierApi -import org.timemates.rrpc.generator.kotlin.modifier.ModifiersRegistry -import org.timemates.rrpc.generator.kotlin.modifier.TypeModifier +import org.timemates.rrpc.common.schema.RMResolver +import org.timemates.rrpc.common.schema.RMType +import org.timemates.rrpc.common.schema.value.RMDeclarationUrl import org.timemates.rrpc.generator.kotlin.types.message.MessageTypeGenerator internal object TypeGenerator { data class Result(val typeSpec: TypeSpec, val constructorFun: FunSpec?) - @OptIn(ExperimentalSpecModifierApi::class) fun generateType( incoming: RMType, resolver: RMResolver, - modifiersRegistry: ModifiersRegistry, ): Result? { - if (incoming.typeUrl == RMTypeUrl.ACK) - // ignore timemates.rrpc.Ack – it's type-marker + if (incoming.typeUrl == RMDeclarationUrl.ACK) + // ignore timemates.rrpc.Ack – it's type-marker return null return when (incoming) { is RMType.Message -> MessageTypeGenerator.generateMessage(incoming, resolver) .let { Result(it.type, it.constructorFun) } + is RMType.Enum -> Result(EnumTypeGenerator.generateEnum(incoming, resolver), null) is RMType.Enclosing -> Result(EnclosingTypeGenerator.generatorEnclosingType(incoming, resolver), null) - }.let { - it.copy(typeSpec = modifiersRegistry.modified(TypeModifier, it.typeSpec, incoming, resolver)) } } diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/message/MessageCompanionObjectGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/message/MessageCompanionObjectGenerator.kt index 3d5b82c..6203da7 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/message/MessageCompanionObjectGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/message/MessageCompanionObjectGenerator.kt @@ -2,7 +2,7 @@ package org.timemates.rrpc.generator.kotlin.types.message import com.squareup.kotlinpoet.* import org.timemates.rrpc.codegen.typemodel.Types -import org.timemates.rrpc.common.metadata.value.RMTypeUrl +import org.timemates.rrpc.common.schema.value.RMDeclarationUrl import org.timemates.rrpc.generator.kotlin.types.TypeGenerator internal object MessageCompanionObjectGenerator { @@ -10,7 +10,7 @@ internal object MessageCompanionObjectGenerator { className: ClassName, nested: List, generateCreateFun: Boolean, - typeUrl: RMTypeUrl, + typeUrl: RMDeclarationUrl, ): TypeSpec { return TypeSpec.companionObjectBuilder() .addSuperinterface(Types.ProtoTypeDefinition(className)) diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/message/MessageConstructorGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/message/MessageConstructorGenerator.kt index 6a80665..24d92b0 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/message/MessageConstructorGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/message/MessageConstructorGenerator.kt @@ -2,9 +2,9 @@ package org.timemates.rrpc.generator.kotlin.types.message import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.KModifier -import com.squareup.kotlinpoet.ParameteRRpcec +import com.squareup.kotlinpoet.ParameterSpec import com.squareup.kotlinpoet.TypeName -import org.timemates.rrpc.common.metadata.RMType +import org.timemates.rrpc.common.schema.RMType import org.timemates.rrpc.generator.kotlin.types.TypeDefaultValueGenerator internal object MessageConstructorGenerator { @@ -17,7 +17,7 @@ internal object MessageConstructorGenerator { .addModifiers(KModifier.PRIVATE) .addParameters(incoming.fields.mapIndexed { index, field -> val type = parameterTypes[index] - ParameteRRpcec.builder(field.name, type) + ParameterSpec.builder(field.name, type) .defaultValue( if (type.isNullable) "null" else TypeDefaultValueGenerator.generateTypeDefault( field @@ -26,7 +26,7 @@ internal object MessageConstructorGenerator { .build() }) .addParameters(oneOfs.map { - ParameteRRpcec.builder(it.property.name, it.property.type).defaultValue("null").build() + ParameterSpec.builder(it.property.name, it.property.type).defaultValue("null").build() }) .build() } diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/message/MessageDSLBuilderGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/message/MessageDSLBuilderGenerator.kt index 407fb8a..209287c 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/message/MessageDSLBuilderGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/message/MessageDSLBuilderGenerator.kt @@ -4,7 +4,7 @@ import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.TypeSpec -import org.timemates.rrpc.common.metadata.RMField +import org.timemates.rrpc.common.schema.RMField import org.timemates.rrpc.generator.kotlin.types.TypeDefaultValueGenerator internal object MessageDSLBuilderGenerator { diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/message/MessageNestedTypeGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/message/MessageNestedTypeGenerator.kt index 043e4a9..99eee16 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/message/MessageNestedTypeGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/message/MessageNestedTypeGenerator.kt @@ -1,7 +1,7 @@ package org.timemates.rrpc.generator.kotlin.types.message -import org.timemates.rrpc.common.metadata.RMResolver -import org.timemates.rrpc.common.metadata.RMType +import org.timemates.rrpc.common.schema.RMResolver +import org.timemates.rrpc.common.schema.RMType import org.timemates.rrpc.generator.kotlin.types.TypeGenerator internal object MessageNestedTypeGenerator { diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/message/MessageParameterTypeGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/message/MessageParameterTypeGenerator.kt index f60b20d..eeabe26 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/message/MessageParameterTypeGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/message/MessageParameterTypeGenerator.kt @@ -3,8 +3,8 @@ package org.timemates.rrpc.generator.kotlin.types.message import com.squareup.kotlinpoet.LIST import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy import com.squareup.kotlinpoet.TypeName -import org.timemates.rrpc.common.metadata.RMResolver -import org.timemates.rrpc.common.metadata.RMType +import org.timemates.rrpc.common.schema.RMResolver +import org.timemates.rrpc.common.schema.RMType import org.timemates.rrpc.generator.kotlin.ext.asClassName import org.timemates.rrpc.generator.kotlin.types.BuiltinsGenerator diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/message/MessagePropertyGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/message/MessagePropertyGenerator.kt index 066bcf9..5044353 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/message/MessagePropertyGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/message/MessagePropertyGenerator.kt @@ -3,7 +3,7 @@ package org.timemates.rrpc.generator.kotlin.types.message import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.TypeName import org.timemates.rrpc.codegen.typemodel.Annotations -import org.timemates.rrpc.common.metadata.RMType +import org.timemates.rrpc.common.schema.RMType internal object MessagePropertyGenerator { fun generateProperties(incoming: RMType.Message, parameterTypes: List): List { diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/message/MessageTypeGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/message/MessageTypeGenerator.kt index af7d16b..686562a 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/message/MessageTypeGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/message/MessageTypeGenerator.kt @@ -3,8 +3,8 @@ package org.timemates.rrpc.generator.kotlin.types.message import com.squareup.kotlinpoet.* import org.timemates.rrpc.codegen.typemodel.Annotations import org.timemates.rrpc.codegen.typemodel.Types -import org.timemates.rrpc.common.metadata.RMResolver -import org.timemates.rrpc.common.metadata.RMType +import org.timemates.rrpc.common.schema.RMResolver +import org.timemates.rrpc.common.schema.RMType import org.timemates.rrpc.generator.kotlin.ext.asClassName import org.timemates.rrpc.generator.kotlin.types.TypeGenerator diff --git a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/message/OneOfGenerator.kt b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/message/OneOfGenerator.kt index b957520..1351e57 100644 --- a/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/message/OneOfGenerator.kt +++ b/generator/kotlin/src/commonMain/kotlin/org/timemates/rrpc/generator/kotlin/types/message/OneOfGenerator.kt @@ -2,9 +2,9 @@ package org.timemates.rrpc.generator.kotlin.types.message import com.squareup.kotlinpoet.* import org.timemates.rrpc.codegen.typemodel.Annotations -import org.timemates.rrpc.common.metadata.RMOneOf -import org.timemates.rrpc.common.metadata.RMResolver -import org.timemates.rrpc.common.metadata.RMType +import org.timemates.rrpc.common.schema.RMOneOf +import org.timemates.rrpc.common.schema.RMResolver +import org.timemates.rrpc.common.schema.RMType import org.timemates.rrpc.generator.kotlin.ext.asClassName import org.timemates.rrpc.generator.kotlin.ext.capitalized import org.timemates.rrpc.generator.kotlin.types.TypeDefaultValueGenerator @@ -36,7 +36,7 @@ internal object OneOfGenerator { .primaryConstructor( FunSpec.constructorBuilder() .addParameter( - ParameteRRpcec.builder("value", typeName) + ParameterSpec.builder("value", typeName) .defaultValue( defaultValue.takeUnless { it == "null" } ?: "%T.Default", typeName, ) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4ab2b29..4940a3e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,26 +1,27 @@ [versions] -kotlin = "2.0.0" -kotlinx-coroutines = "1.7.3" -kotlinx-serialization = "1.7.1" +kotlin = "2.0.21" +kotlinx-coroutines = "1.9.0" +kotlinx-serialization = "1.7.3" ktor = "2.3.12" jupiter = "5.4.0" exposed = "0.41.1" android-gradle-plugin = "7.3.1" -okio = "3.6.0" +okio = "3.9.1" rsocket = "0.16.0" [libraries] # kotlinx libraries kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } +kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" } kotlinx-serialization-proto = { module = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", version.ref = "kotlinx-serialization" } # Ktor libraries ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" } ktor-server-netty = { module = "io.ktor:ktor-server-netty", version.ref = "ktor" } -ktor-server-cors = { module = "io.ktor:ktor-server-cors", version.ref = "ktor" } ktor-server-call-logging = { module = "io.ktor:ktor-server-call-logging", version.ref = "ktor" } ktor-server-websockets = { module = "io.ktor:ktor-server-websockets", version.ref = "ktor" } -ktor-server-statusPages = { module = "io.ktor:ktor-server-status-pages", version.ref = "ktor" } +ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" } +ktor-client-websockets = { module = "io.ktor:ktor-client-websockets", version.ref = "ktor" } ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" } ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } @@ -63,7 +64,7 @@ mockk = { group = "io.mockk", name = "mockk", version.require = "1.13.12" } # Build Conventions conventions-multiplatform = { id = "multiplatform-convention", version.require = "SNAPSHOT" } conventions-multiplatform-library = { id = "multiplatform-library-convention", version.require = "SNAPSHOT" } -conventions-jvm = { id = "jvm-convention", version.require = "SNAPSHOT" } +conventions-jvm-core = { id = "jvm-convention", version.require = "SNAPSHOT" } conventions-jvm-library = { id = "jvm-library-convention", version.require = "SNAPSHOT" } # Compiler plugins diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0d18421..1e2fbf0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/integration-tests/build.gradle.kts b/integration-tests/build.gradle.kts new file mode 100644 index 0000000..100ff8d --- /dev/null +++ b/integration-tests/build.gradle.kts @@ -0,0 +1,39 @@ +plugins { + id(libs.plugins.conventions.jvm.core.get().pluginId) + alias(libs.plugins.kotlinx.serialization) +} + +group = "org.timemates.rrpc" +version = System.getenv("LIB_VERSION") ?: "SNAPSHOT" + +dependencies { + // -- Project -- + implementation(projects.server.core) + implementation(projects.server.schema) + implementation(projects.client.core) + implementation(projects.client.schema) + + // -- Serialization -- + implementation(libs.kotlinx.serialization.proto) + + // -- RSocket -- + implementation(libs.rsocket.client) + implementation(libs.rsocket.server) + + // -- Ktor -- + implementation(libs.ktor.server.core) + implementation(libs.ktor.server.netty) + implementation(libs.ktor.client.cio) + implementation(libs.ktor.client.websockets) + + // -- JUnit -- + implementation(libs.junit.jupiter) + + // -- MockK -- + implementation(libs.mockk) + + // -- Coroutines + implementation(libs.kotlinx.coroutines.test) +} + + diff --git a/integration-tests/src/test/kotlin/org/timemates/rrpc/server/schema/SchemaServiceCommunicationTest.kt b/integration-tests/src/test/kotlin/org/timemates/rrpc/server/schema/SchemaServiceCommunicationTest.kt new file mode 100644 index 0000000..f65e4aa --- /dev/null +++ b/integration-tests/src/test/kotlin/org/timemates/rrpc/server/schema/SchemaServiceCommunicationTest.kt @@ -0,0 +1,38 @@ +package org.timemates.rrpc.server.schema + +//object SchemaServiceCommunicationTest { +// private val testScope = TestScope() +// +// private val port = Random.nextInt(1000, 9999) +// +// private val module = RRpcModule { +// services { +// schemaService() +// } +// } +// +// @JvmStatic +// private val server = embeddedServer(Netty, port = port) { +// routing { +// rrpcEndpoint(module = module) +// } +// }.start(false) +// +// @JvmStatic +// private var schemaClient: SchemaClient by Delegates.notNull() +// +// @JvmStatic +// @BeforeAll +// fun setup(): Unit = runTest { +// val client = HttpClient { +// install(WebSockets) +// install(RSocketSupport) +// } +// val rsocket = client.rSocket(urlString = "localhost:${port}/rrpc") { +// +// } +// schemaClient = SchemaService { +// rsocket(rsocket) +// } +// } +//} \ No newline at end of file diff --git a/server/core/build.gradle.kts b/server/core/build.gradle.kts index 8da71b5..dae8d33 100644 --- a/server/core/build.gradle.kts +++ b/server/core/build.gradle.kts @@ -7,15 +7,21 @@ group = "org.timemates.rrpc" version = System.getenv("LIB_VERSION") ?: "SNAPSHOT" dependencies { - commonMainImplementation(libs.rsocket.server) - commonMainImplementation(libs.kotlinx.serialization.proto) - commonMainApi(projects.common.core) + // -- Project -- + commonMainImplementation(projects.common.core) + + // -- Ktor -- + commonMainImplementation(libs.ktor.server.core) + commonMainImplementation(libs.ktor.server.websockets) - commonMainImplementation(libs.ktor.server.websockets) + // -- RSocket -- + commonMainImplementation(libs.rsocket.server) - commonMainImplementation(libs.ktor.server.core) + // -- Serialization -- + commonMainImplementation(libs.kotlinx.serialization.proto) } + mavenPublishing { coordinates( groupId = "org.timemates.rrpc", @@ -24,7 +30,7 @@ mavenPublishing { ) pom { - name.set("RRpcroto Server Core") - description.set("Multiplatform Kotlin core library for RRpcroto servers.") + name.set("RRpc Server Core") + description.set("Multiplatform Kotlin core library for RRpc servers.") } } diff --git a/server/core/src/commonMain/kotlin/org/timemates/rrpc/server/module/RSPModule.kt b/server/core/src/commonMain/kotlin/org/timemates/rrpc/server/module/RRpcModule.kt similarity index 96% rename from server/core/src/commonMain/kotlin/org/timemates/rrpc/server/module/RSPModule.kt rename to server/core/src/commonMain/kotlin/org/timemates/rrpc/server/module/RRpcModule.kt index ea9ab0d..8051a9e 100644 --- a/server/core/src/commonMain/kotlin/org/timemates/rrpc/server/module/RSPModule.kt +++ b/server/core/src/commonMain/kotlin/org/timemates/rrpc/server/module/RRpcModule.kt @@ -5,6 +5,7 @@ import org.timemates.rrpc.annotations.ExperimentalInterceptorsApi import org.timemates.rrpc.instances.InstanceContainer import org.timemates.rrpc.instances.ProvidableInstance import org.timemates.rrpc.interceptors.Interceptors +import org.timemates.rrpc.server.OptionsContainer import org.timemates.rrpc.server.module.descriptors.ProcedureDescriptor import org.timemates.rrpc.server.module.descriptors.ServiceDescriptor @@ -43,7 +44,7 @@ public interface RRpcModule : InstanceContainer, ServicesContainer { * @see ProcedureDescriptor * @see RRpcModule */ -public val RRpcModule.knownProcedures: List> +public val RRpcModule.knownProcedures: List get() = services.flatMap { it.procedures } /** diff --git a/server/core/src/commonMain/kotlin/org/timemates/rrpc/server/module/RRpcModuleBuilder.kt b/server/core/src/commonMain/kotlin/org/timemates/rrpc/server/module/RRpcModuleBuilder.kt new file mode 100644 index 0000000..5a84c20 --- /dev/null +++ b/server/core/src/commonMain/kotlin/org/timemates/rrpc/server/module/RRpcModuleBuilder.kt @@ -0,0 +1,130 @@ +package org.timemates.rrpc.server.module + +import kotlinx.serialization.ExperimentalSerializationApi +import org.timemates.rrpc.annotations.ExperimentalInterceptorsApi +import org.timemates.rrpc.annotations.InternalRRpcAPI +import org.timemates.rrpc.instances.InstanceContainer +import org.timemates.rrpc.instances.InstancesBuilder +import org.timemates.rrpc.instances.protobuf +import org.timemates.rrpc.interceptors.Interceptors +import org.timemates.rrpc.interceptors.RequestInterceptor +import org.timemates.rrpc.interceptors.ResponseInterceptor + +/** + * Creates an [RRpcModule] using the provided [block] to configure its builder. + * + * @param block A lambda with receiver of type [RRpcModuleBuilder] used to configure the module. + * @return A configured [RRpcModule]. + */ +@OptIn(ExperimentalSerializationApi::class) +public fun RRpcModule(block: RRpcModuleBuilder.() -> Unit): RRpcModule { + return RRpcModuleBuilder().apply { + instances { + protobuf { + encodeDefaults = true + } + } + }.apply(block).build() +} + +/** + * Builder class for constructing an [RRpcModule]. + */ +@OptIn(InternalRRpcAPI::class) +public class RRpcModuleBuilder internal constructor() { + private val instances: InstancesBuilder = InstancesBuilder() + private val services: ServicesBuilder = ServicesBuilder() + @OptIn(ExperimentalInterceptorsApi::class) + private val interceptors: InterceptorsBuilder = InterceptorsBuilder() + + public fun services(builder: ServicesBuilder.() -> Unit) { + services.apply(builder).build() + } + + @ExperimentalInterceptorsApi + public fun interceptors(builder: InterceptorsBuilder.() -> Unit) { + interceptors.builder() + } + + /** + * Configures instances for the module using the provided [block]. + * + * @param block A lambda with receiver of type [InstancesBuilder] used to configure instances. + */ + public fun instances(block: InstancesBuilder.() -> Unit) { + instances.apply(block).build() + } + + /** + * Builds the [RRpcModule] with the configured services, interceptors, and instances. + * + * @return A configured [RRpcModule]. + */ + @OptIn(ExperimentalInterceptorsApi::class) + public fun build(): RRpcModule { + return RRpcModuleImpl( + services = services.build().map { it.descriptor }, + interceptors = interceptors.build(), + instanceContainer = InstanceContainer(instances.build().associateBy { it.key }), + ) + } + + public class ServicesBuilder { + private val services: MutableList = mutableListOf() + + public fun register(service: RRpcService) { + services += service + } + + public fun build(): List = services.toList() + } + + @ExperimentalInterceptorsApi + public class InterceptorsBuilder { + private val requestInterceptors: MutableList = mutableListOf() + private val responseInterceptors: MutableList = mutableListOf() + + /** + * Adds a request interceptor to the module. + * + * @param interceptor The [RequestInterceptor] to add. + */ + @ExperimentalInterceptorsApi + public fun request(interceptor: RequestInterceptor) { + requestInterceptors += interceptor + } + + /** + * Adds a response interceptor to the module. + * + * @param interceptor The [ResponseInterceptor] to add. + */ + @ExperimentalInterceptorsApi + public fun response(interceptor: ResponseInterceptor) { + responseInterceptors += interceptor + } + + @ExperimentalInterceptorsApi + public fun build(): Interceptors = Interceptors(requestInterceptors.toList(), responseInterceptors.toList()) + } +} + +/** + * Adds multiple request interceptors to the module. + * + * @param interceptors Vararg of [RequestInterceptor] to add. + */ +@ExperimentalInterceptorsApi +public fun RRpcModuleBuilder.InterceptorsBuilder.request(vararg interceptors: RequestInterceptor) { + interceptors.forEach { request(it) } +} + +/** + * Adds multiple response interceptors to the module. + * + * @param interceptors Vararg of [ResponseInterceptor] to add. + */ +@ExperimentalInterceptorsApi +public fun RRpcModuleBuilder.InterceptorsBuilder.response(vararg interceptors: ResponseInterceptor) { + interceptors.forEach { response(interceptor = it) } +} diff --git a/server/core/src/commonMain/kotlin/org/timemates/rrpc/server/module/RSPModuleHandler.kt b/server/core/src/commonMain/kotlin/org/timemates/rrpc/server/module/RRpcModuleHandler.kt similarity index 99% rename from server/core/src/commonMain/kotlin/org/timemates/rrpc/server/module/RSPModuleHandler.kt rename to server/core/src/commonMain/kotlin/org/timemates/rrpc/server/module/RRpcModuleHandler.kt index 9ffaa3e..7db1c43 100644 --- a/server/core/src/commonMain/kotlin/org/timemates/rrpc/server/module/RSPModuleHandler.kt +++ b/server/core/src/commonMain/kotlin/org/timemates/rrpc/server/module/RRpcModuleHandler.kt @@ -1,4 +1,4 @@ -@file:OptIn(ExperimentalSerializationApi::class, ExperimentalInterceptorsApi::class, InternalRRpcrotoAPI::class) +@file:OptIn(ExperimentalSerializationApi::class, ExperimentalInterceptorsApi::class, InternalRRpcAPI::class) package org.timemates.rrpc.server.module @@ -10,7 +10,7 @@ import kotlinx.coroutines.flow.* import kotlinx.serialization.* import org.timemates.rrpc.* import org.timemates.rrpc.annotations.ExperimentalInterceptorsApi -import org.timemates.rrpc.annotations.InternalRRpcrotoAPI +import org.timemates.rrpc.annotations.InternalRRpcAPI import org.timemates.rrpc.exceptions.ProcedureNotFoundException import org.timemates.rrpc.exceptions.ServiceNotFoundException import org.timemates.rrpc.instances.ProtobufInstance @@ -296,7 +296,7 @@ public class RRpcModuleHandler(private val module: RRpcModule) { if (finalContext?.data is Failure) throw (finalContext.data as Failure).exception } - @OptIn(InternalRRpcrotoAPI::class) + @OptIn(InternalRRpcAPI::class) private suspend fun handleException( exception: Exception, method: ProcedureDescriptor?, diff --git a/server/core/src/commonMain/kotlin/org/timemates/rrpc/server/module/RSPService.kt b/server/core/src/commonMain/kotlin/org/timemates/rrpc/server/module/RRpcService.kt similarity index 100% rename from server/core/src/commonMain/kotlin/org/timemates/rrpc/server/module/RSPService.kt rename to server/core/src/commonMain/kotlin/org/timemates/rrpc/server/module/RRpcService.kt diff --git a/server/core/src/commonMain/kotlin/org/timemates/rrpc/server/module/RSPModuleBuilder.kt b/server/core/src/commonMain/kotlin/org/timemates/rrpc/server/module/RSPModuleBuilder.kt deleted file mode 100644 index 252ad97..0000000 --- a/server/core/src/commonMain/kotlin/org/timemates/rrpc/server/module/RSPModuleBuilder.kt +++ /dev/null @@ -1,111 +0,0 @@ -package org.timemates.rrpc.server.module - -import kotlinx.serialization.ExperimentalSerializationApi -import org.timemates.rrpc.annotations.ExperimentalInterceptorsApi -import org.timemates.rrpc.annotations.InternalRRpcrotoAPI -import org.timemates.rrpc.instances.InstanceContainer -import org.timemates.rrpc.instances.InstancesBuilder -import org.timemates.rrpc.instances.ProvidableInstance -import org.timemates.rrpc.instances.protobuf -import org.timemates.rrpc.interceptors.Interceptors -import org.timemates.rrpc.interceptors.RequestInterceptor -import org.timemates.rrpc.interceptors.ResponseInterceptor - -/** - * Creates an [RRpcModule] using the provided [block] to configure its builder. - * - * @param block A lambda with receiver of type [RRpcModuleBuilder] used to configure the module. - * @return A configured [RRpcModule]. - */ -@OptIn(ExperimentalSerializationApi::class) -public fun RRpcModule(block: RRpcModuleBuilder.() -> Unit): RRpcModule { - return RRpcModuleBuilder().apply { - instances { - protobuf { - encodeDefaults = true - } - } - }.apply(block).build() -} - -/** - * Builder class for constructing an [RRpcModule]. - */ -@OptIn(InternalRRpcrotoAPI::class) -public class RRpcModuleBuilder internal constructor() { - private val instances: MutableList = mutableListOf() - private val services: MutableList = mutableListOf() - private val requestInterceptors: MutableList = mutableListOf() - private val responseInterceptors: MutableList = mutableListOf() - - /** - * Adds a service to the module. - * - * @param service The [RRpcService] to add. - */ - public fun service(service: RRpcService) { - services += service - } - - /** - * Adds a request interceptor to the module. - * - * @param interceptor The [RequestInterceptor] to add. - */ - @ExperimentalInterceptorsApi - public fun requestInterceptor(interceptor: RequestInterceptor) { - requestInterceptors += interceptor - } - - /** - * Adds a response interceptor to the module. - * - * @param interceptor The [ResponseInterceptor] to add. - */ - @ExperimentalInterceptorsApi - public fun responseInterceptor(interceptor: ResponseInterceptor) { - responseInterceptors += interceptor - } - - /** - * Configures instances for the module using the provided [block]. - * - * @param block A lambda with receiver of type [InstancesBuilder] used to configure instances. - */ - public fun instances(block: InstancesBuilder.() -> Unit) { - instances += InstancesBuilder().apply(block).build() - } - - /** - * Builds the [RRpcModule] with the configured services, interceptors, and instances. - * - * @return A configured [RRpcModule]. - */ - public fun build(): RRpcModule { - return RRpcModuleImpl( - services = services.map { it.descriptor }, - interceptors = Interceptors(requestInterceptors, responseInterceptors), - instanceContainer = InstanceContainer(instances.associateBy { it.key }), - ) - } -} - -/** - * Adds multiple request interceptors to the module. - * - * @param interceptors Vararg of [RequestInterceptor] to add. - */ -@ExperimentalInterceptorsApi -public fun RRpcModuleBuilder.requestInterceptors(vararg interceptors: RequestInterceptor) { - interceptors.forEach { requestInterceptor(it) } -} - -/** - * Adds multiple response interceptors to the module. - * - * @param interceptors Vararg of [ResponseInterceptor] to add. - */ -@ExperimentalInterceptorsApi -public fun RRpcModuleBuilder.responseInterceptors(vararg interceptors: ResponseInterceptor) { - interceptors.forEach { responseInterceptor(it) } -} diff --git a/server/metadata/src/commonMain/kotlin/org/timemates/rrpc/server/metadata/MetadataLookup.kt b/server/metadata/src/commonMain/kotlin/org/timemates/rrpc/server/metadata/MetadataLookup.kt deleted file mode 100644 index c263aab..0000000 --- a/server/metadata/src/commonMain/kotlin/org/timemates/rrpc/server/metadata/MetadataLookup.kt +++ /dev/null @@ -1,27 +0,0 @@ -package org.timemates.rrpc.server.metadata - -import org.timemates.rrpc.common.metadata.RMResolver - -public fun MetadataLookup(resolver: RMResolver): MetadataLookup = DelegatedMetadataLookupGroup(resolver) - -public interface MetadataLookup : RMResolver { - public companion object Global : MetadataLookup by _resolver { - /** - * Register a [MetadataLookup] to the global scope that is used by default. - */ - public fun register(group: MetadataLookup) { - _resolver = DelegatedMetadataLookupGroup(RMResolver(this, group)) - } - - /** - * Register a [RMResolver] to the global scope that is used by default. - */ - public fun register(resolver: RMResolver) { - register(DelegatedMetadataLookupGroup(resolver)) - } - } -} - -internal class DelegatedMetadataLookupGroup(resolver: RMResolver) : MetadataLookup, RMResolver by _resolver - -private var _resolver: MetadataLookup = DelegatedMetadataLookupGroup(RMResolver(emptyList())) diff --git a/server/metadata/src/commonMain/kotlin/org/timemates/rrpc/server/metadata/MetadataService.kt b/server/metadata/src/commonMain/kotlin/org/timemates/rrpc/server/metadata/MetadataService.kt deleted file mode 100644 index f299226..0000000 --- a/server/metadata/src/commonMain/kotlin/org/timemates/rrpc/server/metadata/MetadataService.kt +++ /dev/null @@ -1,36 +0,0 @@ -package org.timemates.rrpc.server.metadata - -import com.google.protobuf.ProtoEmpty -import kotlinx.serialization.builtins.ListSerializer -import org.timemates.rrpc.common.metadata.RMService -import org.timemates.rrpc.common.metadata.RMType -import org.timemates.rrpc.options.OptionsWithValue -import org.timemates.rrpc.server.module.RRpcService -import org.timemates.rrpc.server.module.descriptors.ProcedureDescriptor -import org.timemates.rrpc.server.module.descriptors.ServiceDescriptor - -public class MetadataService( - private val group: MetadataLookupGroup, -) : RRpcService { - override val descriptor: ServiceDescriptor = ServiceDescriptor( - name = "org.timemates.rrpc.server.metadata", - procedures = listOf( - ProcedureDescriptor.RequestResponse( - name = "GetAvailableServices", - inputSerializer = ProtoEmpty.serializer(), - outputSerializer = ListSerializer(RMService.serializer()), - procedure = { _, _ -> getAvailableServices() }, - options = OptionsWithValue.EMPTY, - ) - ), - options = OptionsWithValue.EMPTY, - ) - - public fun getAvailableServices(): List { - return group.resolveAllServices().toList() - } - - public fun getAvailableTypes(): List { - return group.resolveAllTypes().toList() - } -} \ No newline at end of file diff --git a/server/schema/README.md b/server/schema/README.md new file mode 100644 index 0000000..9a17ec5 --- /dev/null +++ b/server/schema/README.md @@ -0,0 +1,5 @@ +# Server Schema Client +This module is used to provide rich information about Services, Types and so on that +is running on the server. + +To communicate from the client side, use [client-schema](../../client/schema). \ No newline at end of file diff --git a/server/metadata/build.gradle.kts b/server/schema/build.gradle.kts similarity index 62% rename from server/metadata/build.gradle.kts rename to server/schema/build.gradle.kts index 956bf1b..3e1eee7 100644 --- a/server/metadata/build.gradle.kts +++ b/server/schema/build.gradle.kts @@ -7,14 +7,16 @@ group = "org.timemates.rrpc" version = System.getenv("LIB_VERSION") ?: "SNAPSHOT" dependencies { - commonMainImplementation(libs.kotlinx.serialization.proto) - commonMainApi(projects.server.core) - - commonMainImplementation(projects.common.metadata) + // -- Project -- + commonMainImplementation(projects.server.core) + commonMainImplementation(projects.common.schema) - commonMainImplementation(libs.ktor.server.websockets) + // -- Ktor -- + commonMainImplementation(libs.ktor.server.core) + commonMainImplementation(libs.ktor.server.websockets) - commonMainImplementation(libs.ktor.server.core) + // -- Serialization -- + commonMainImplementation(libs.kotlinx.serialization.proto) } mavenPublishing { @@ -25,7 +27,7 @@ mavenPublishing { ) pom { - name.set("RRpcroto Server Metadata") - description.set("Multiplatform Kotlin metadata library for RRpcroto servers.") + name.set("RRpc Server Metadata") + description.set("Multiplatform Kotlin metadata library for RRpc servers.") } } diff --git a/server/schema/src/commonMain/kotlin/org/timemates/rrpc/server/schema/RRpcModuleBuilder.kt b/server/schema/src/commonMain/kotlin/org/timemates/rrpc/server/schema/RRpcModuleBuilder.kt new file mode 100644 index 0000000..e365c55 --- /dev/null +++ b/server/schema/src/commonMain/kotlin/org/timemates/rrpc/server/schema/RRpcModuleBuilder.kt @@ -0,0 +1,13 @@ +package org.timemates.rrpc.server.schema + +import org.timemates.rrpc.server.module.RRpcModuleBuilder + +/** + * Registers a [SchemaService] for given module. By default, it will use + * global instance of [SchemaLookup]. + */ +public fun RRpcModuleBuilder.ServicesBuilder.schemaService( + lookup: SchemaLookup = SchemaLookup.Global, +) { + register(SchemaService(lookup)) +} \ No newline at end of file diff --git a/server/schema/src/commonMain/kotlin/org/timemates/rrpc/server/schema/SchemaLookup.kt b/server/schema/src/commonMain/kotlin/org/timemates/rrpc/server/schema/SchemaLookup.kt new file mode 100644 index 0000000..83c5c3a --- /dev/null +++ b/server/schema/src/commonMain/kotlin/org/timemates/rrpc/server/schema/SchemaLookup.kt @@ -0,0 +1,25 @@ +package org.timemates.rrpc.server.schema + +import org.timemates.rrpc.common.schema.RMResolver + +public fun MetadataLookup(resolver: RMResolver): SchemaLookup = DelegatedSchemaLookupGroup(resolver) + +public interface SchemaLookup : RMResolver { + public companion object Global : SchemaLookup by _resolver { + /** + * Register a [SchemaLookup] to the global scope that is used by default. + */ + public fun register(group: SchemaLookup) { + _resolver = DelegatedSchemaLookupGroup(RMResolver(this, group)) + } + + /** + * Register a [RMResolver] to the global scope that is used by default. + */ + public fun register(resolver: RMResolver): Unit = register(MetadataLookup(resolver)) + } +} + +internal class DelegatedSchemaLookupGroup(resolver: RMResolver) : SchemaLookup, RMResolver by _resolver + +private var _resolver: SchemaLookup = DelegatedSchemaLookupGroup(RMResolver(emptyList())) diff --git a/server/schema/src/commonMain/kotlin/org/timemates/rrpc/server/schema/SchemaService.kt b/server/schema/src/commonMain/kotlin/org/timemates/rrpc/server/schema/SchemaService.kt new file mode 100644 index 0000000..f5896ca --- /dev/null +++ b/server/schema/src/commonMain/kotlin/org/timemates/rrpc/server/schema/SchemaService.kt @@ -0,0 +1,127 @@ +package org.timemates.rrpc.server.schema + +import io.rsocket.kotlin.RSocketError +import org.timemates.rrpc.common.schema.RMExtend +import org.timemates.rrpc.common.schema.RMFile +import org.timemates.rrpc.common.schema.RMService +import org.timemates.rrpc.common.schema.RMType +import org.timemates.rrpc.options.OptionsWithValue +import org.timemates.rrpc.server.schema.request.BatchedRequest +import org.timemates.rrpc.server.schema.request.PagedRequest +import org.timemates.rrpc.server.schema.request.decoded +import org.timemates.rrpc.server.module.RRpcService +import org.timemates.rrpc.server.module.descriptors.ProcedureDescriptor +import org.timemates.rrpc.server.module.descriptors.ServiceDescriptor + +/** + * Handles metadata-related operations for the RSocket-based reflection service. + * This service exposes APIs that allow clients to query information about + * available services, types, and extensions in the system. + * + * It supports both paged requests for larger datasets and batched requests + * for retrieving multiple types of metadata in one go. + * + * @param group The metadata lookup group used for resolving services, types, and extensions. + */ +public class SchemaService( + private val group: SchemaLookup = SchemaLookup.Global, +) : RRpcService { + override val descriptor: ServiceDescriptor = ServiceDescriptor( + name = "timemates.rrpc.server.schema.SchemaService", + procedures = listOf( + ProcedureDescriptor.RequestResponse( + name = "GetAvailableServices", + inputSerializer = PagedRequest.serializer(), + outputSerializer = PagedRequest.Response.serializer(RMService.serializer()), + procedure = { _, request -> getAvailableServices(request) }, + options = OptionsWithValue.EMPTY, + ), + ProcedureDescriptor.RequestResponse( + name = "GetAvailableFiles", + inputSerializer = PagedRequest.serializer(), + outputSerializer = PagedRequest.Response.serializer(RMFile.serializer()), + procedure = { _, request -> getAvailableFiles(request) }, + options = OptionsWithValue.EMPTY, + ), + ProcedureDescriptor.RequestResponse( + name = "GetTypeDetailsBatch", + inputSerializer = BatchedRequest.serializer(), + outputSerializer = BatchedRequest.Response.serializer(RMType.serializer()), + procedure = { _, request -> getTypeDetailsBatch(request) }, + options = OptionsWithValue.EMPTY, + ), + ProcedureDescriptor.RequestResponse( + name = "GetExtendDetailsBatch", + inputSerializer = BatchedRequest.serializer(), + outputSerializer = BatchedRequest.Response.serializer(RMExtend.serializer()), + procedure = { _, request -> getExtendDetailsBatch(request) }, + options = OptionsWithValue.EMPTY, + ), + ), + options = OptionsWithValue.EMPTY, + ) + + /** + * Retrieves a paginated list of available services from the metadata. + * + * @param request The paged request containing the pagination token and size. + * @return A paginated response containing a list of RMService objects. + */ + public fun getAvailableServices(request: PagedRequest): PagedRequest.Response { + val decoded = request.decoded()?.split(":") + val index = decoded?.getOrElse(1) { invalidPageToken() }?.toInt() ?: -1 + + val result = group.resolveAllServices().drop(index + 1).take(request.size ?: 20).toList() + + return PagedRequest.Response( + list = result, + nextCursor = if (result.size == request.size) "c:${index + result.size}" else null + ) + } + + /** + * Retrieves available files that are loaded into [SchemaLookup]. + * + * @param request The paged request containing pagination information. + * @return A paged response containing a list of available RMFiles. + */ + public fun getAvailableFiles(request: PagedRequest): PagedRequest.Response { + val decoded = request.decoded()?.split(":") + val index = decoded?.getOrElse(1) { invalidPageToken() }?.toInt() ?: -1 + + val result = group.resolveAvailableFiles().drop(index + 1).take(30).toList() + + return PagedRequest.Response( + list = result, + nextCursor = "c:${index + result.size}", + ) + } + + /** + * Retrieves type details based on a batch of requested URLs. + * + * Each RMDeclarationUrl is mapped to either the found RMType or null if no type was found. + * + * @param request The batched request containing a list of RMDeclarationUrl objects. + * @return A response containing a map of RMDeclarationUrl to RMType (or null if not found). + */ + public fun getTypeDetailsBatch(request: BatchedRequest): BatchedRequest.Response { + val types = request.urls.associateWith { group.resolveType(it) } + return BatchedRequest.Response(types) + } + + /** + * Retrieves extension details based on a batch of requested URLs. + * + * Each RMDeclarationUrl is mapped to either the found RMExtend or null if no extension was found. + * + * @param request The batched request containing a list of RMDeclarationUrl objects. + * @return A response containing a map of RMDeclarationUrl to RMExtend (or null if not found). + */ + public fun getExtendDetailsBatch(request: BatchedRequest): BatchedRequest.Response { + val extensions = request.urls.associateWith { group.resolveExtend(it) } + return BatchedRequest.Response(extensions) + } +} + +private fun invalidPageToken(): Nothing = throw RSocketError.Invalid("Invalid page token") \ No newline at end of file diff --git a/server/schema/src/commonMain/kotlin/org/timemates/rrpc/server/schema/request/BatchedRequest.kt b/server/schema/src/commonMain/kotlin/org/timemates/rrpc/server/schema/request/BatchedRequest.kt new file mode 100644 index 0000000..1383c63 --- /dev/null +++ b/server/schema/src/commonMain/kotlin/org/timemates/rrpc/server/schema/request/BatchedRequest.kt @@ -0,0 +1,22 @@ +package org.timemates.rrpc.server.schema.request + +import kotlinx.serialization.Serializable +import org.timemates.rrpc.common.schema.RMNode +import org.timemates.rrpc.common.schema.value.RMDeclarationUrl + +/** + * Represents a batched request to retrieve multiple metadata entities by their declaration URLs. + * + * @property urls A list of RMDeclarationUrl objects, representing the metadata to retrieve. + */ +@Serializable +public data class BatchedRequest(val urls: List) { + /** + * Response structure for batched requests. + * Contains a map of RMDeclarationUrl to the corresponding resolved metadata (or null if not found). + * + * @param results A map where each RMDeclarationUrl is associated with the corresponding RMNode (or null if not found). + */ + @Serializable + public data class Response(public val services: Map) +} \ No newline at end of file diff --git a/server/schema/src/commonMain/kotlin/org/timemates/rrpc/server/schema/request/PagedRequest.kt b/server/schema/src/commonMain/kotlin/org/timemates/rrpc/server/schema/request/PagedRequest.kt new file mode 100644 index 0000000..09a1a4f --- /dev/null +++ b/server/schema/src/commonMain/kotlin/org/timemates/rrpc/server/schema/request/PagedRequest.kt @@ -0,0 +1,40 @@ +package org.timemates.rrpc.server.schema.request + +import kotlinx.serialization.Serializable +import kotlin.io.encoding.Base64 +import kotlin.io.encoding.ExperimentalEncodingApi + +/** + * Represents a paginated request to retrieve metadata entities. + * + * @property cursor A token used for retrieving the next page of results. + * @property size The maximum number of results to retrieve per page. + */ +@Serializable +public data class PagedRequest( + public val cursor: String? = null, + public val size: Int? = null, +) { + public companion object { + @OptIn(ExperimentalEncodingApi::class) + internal fun encoded(string: String): String { + return Base64.encode(string.toByteArray()) + } + } + + /** + * Response structure for paginated requests. + * Contains the list of metadata nodes retrieved and a token for the next page. + * + * @param list A list of RMNode objects representing the metadata retrieved. + * @param nextCursor A token to retrieve the next page of results, or null if no more results. + */ + @Serializable + public data class Response( + public val list: List, + public val nextCursor: String?, + ) +} + +@OptIn(ExperimentalEncodingApi::class) +internal fun PagedRequest.decoded(): String? = this@decoded.cursor?.let { String(Base64.decode(it)) } \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 07d693d..970f6bd 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -25,11 +25,23 @@ includeBuild("build-conventions") include( ":common:core", - ":common:metadata", + ":common:schema", ) -include(":server:core", ":server:metadata") +include( + ":server:core", + ":server:schema", +) -include(":client:core") +include( + ":client:core", + ":client:schema", +) + +include( + ":generator:core", + ":generator:kotlin", + ":generator:gradle-plugin", +) -include(":generator:core", ":generator:kotlin", ":generator:gradle-plugin") +include(":integration-tests")