From 37c83e31a06a277e5cf30d6b90103a5f6b43afd7 Mon Sep 17 00:00:00 2001 From: slinkydeveloper Date: Thu, 11 Apr 2024 12:51:59 +0200 Subject: [PATCH 1/2] Validate component nor handlers start with `restate` or `openapi` --- .../dev/restate/sdk/gen/model/Component.java | 17 ++++- .../dev/restate/sdk/gen/model/Handler.java | 9 ++- .../dev/restate/sdk/gen/ElementConverter.java | 67 +++++++++---------- .../sdk/kotlin/gen/ComponentProcessor.kt | 9 ++- .../sdk/kotlin/gen/KElementConverter.kt | 26 ++++--- 5 files changed, 80 insertions(+), 48 deletions(-) diff --git a/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/Component.java b/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/Component.java index 04939368..844584ab 100644 --- a/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/Component.java +++ b/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/Component.java @@ -128,7 +128,22 @@ public List getHandlers() { return handlers; } - public Component build() { + public Component validateAndBuild() { + String componentNameLowercase = componentName.toLowerCase(); + if (componentNameLowercase.startsWith("restate") + || componentNameLowercase.startsWith("openapi")) { + throw new IllegalArgumentException( + "A component name cannot start with `restate` or `openapi`"); + } + + if (componentType.equals(ComponentType.WORKFLOW)) { + if (handlers.stream().filter(m -> m.getHandlerType().equals(HandlerType.WORKFLOW)).count() + != 1) { + throw new IllegalArgumentException( + "Workflow services must have exactly one method annotated as @Workflow"); + } + } + return new Component( Objects.requireNonNull(targetPkg), Objects.requireNonNull(targetFqcn), diff --git a/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/Handler.java b/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/Handler.java index 4093b5af..769b8a93 100644 --- a/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/Handler.java +++ b/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/Handler.java @@ -87,7 +87,14 @@ public PayloadType getOutputType() { return outputType; } - public Handler build() { + public Handler validateAndBuild() { + String handlerNameLowercase = name.toString().toLowerCase(); + if (handlerNameLowercase.startsWith("restate") + || handlerNameLowercase.startsWith("openapi")) { + throw new IllegalArgumentException( + "A component name cannot start with `restate` or `openapi`"); + } + return new Handler( Objects.requireNonNull(name), Objects.requireNonNull(handlerType), inputType, outputType); } diff --git a/sdk-api-gen/src/main/java/dev/restate/sdk/gen/ElementConverter.java b/sdk-api-gen/src/main/java/dev/restate/sdk/gen/ElementConverter.java index e9c7a7ad..43346524 100644 --- a/sdk-api-gen/src/main/java/dev/restate/sdk/gen/ElementConverter.java +++ b/sdk-api-gen/src/main/java/dev/restate/sdk/gen/ElementConverter.java @@ -107,20 +107,27 @@ public Component fromTypeElement(TypeElement element) { || e.getAnnotation(Shared.class) != null) .map(e -> fromExecutableElement(type, ((ExecutableElement) e))) .collect(Collectors.toList()); - validateHandlers(type, handlers, element); if (handlers.isEmpty()) { messager.printMessage( Diagnostic.Kind.WARNING, "The component " + componentName + " has no handlers", element); } - return new Component.Builder() - .withTargetPkg(targetPkg) - .withTargetFqcn(targetFqcn) - .withComponentName(componentName) - .withComponentType(type) - .withHandlers(handlers) - .build(); + try { + return new Component.Builder() + .withTargetPkg(targetPkg) + .withTargetFqcn(targetFqcn) + .withComponentName(componentName) + .withComponentType(type) + .withHandlers(handlers) + .validateAndBuild(); + } catch (Exception e) { + messager.printMessage( + Diagnostic.Kind.ERROR, + "Can't build the component " + componentName + ": " + e.getMessage(), + element); + return null; + } } private void validateType(TypeElement element) { @@ -140,20 +147,6 @@ private void validateType(TypeElement element) { } } - private void validateHandlers( - ComponentType componentType, List handlers, TypeElement element) { - // Additional validation for Workflow types - if (componentType.equals(ComponentType.WORKFLOW)) { - if (handlers.stream().filter(m -> m.getHandlerType().equals(HandlerType.WORKFLOW)).count() - != 1) { - messager.printMessage( - Diagnostic.Kind.ERROR, - "Workflow services must have exactly one method annotated as @Workflow", - element); - } - } - } - private Handler fromExecutableElement(ComponentType componentType, ExecutableElement element) { if (!element.getTypeParameters().isEmpty()) { messager.printMessage( @@ -199,18 +192,24 @@ private Handler fromExecutableElement(ComponentType componentType, ExecutableEle validateMethodSignature(componentType, handlerType, element); - return new Handler.Builder() - .withName(element.getSimpleName()) - .withHandlerType(handlerType) - .withInputType( - element.getParameters().size() > 1 - ? payloadFromType(element.getParameters().get(1).asType()) - : EMPTY_PAYLOAD) - .withOutputType( - !element.getReturnType().getKind().equals(TypeKind.VOID) - ? payloadFromType(element.getReturnType()) - : EMPTY_PAYLOAD) - .build(); + try { + return new Handler.Builder() + .withName(element.getSimpleName()) + .withHandlerType(handlerType) + .withInputType( + element.getParameters().size() > 1 + ? payloadFromType(element.getParameters().get(1).asType()) + : EMPTY_PAYLOAD) + .withOutputType( + !element.getReturnType().getKind().equals(TypeKind.VOID) + ? payloadFromType(element.getReturnType()) + : EMPTY_PAYLOAD) + .validateAndBuild(); + } catch (Exception e) { + messager.printMessage( + Diagnostic.Kind.ERROR, "Error when building handler: " + e.getMessage(), element); + return null; + } } private HandlerType defaultHandlerType(ComponentType componentType, ExecutableElement element) { diff --git a/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/ComponentProcessor.kt b/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/ComponentProcessor.kt index f669e474..069829a2 100644 --- a/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/ComponentProcessor.kt +++ b/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/ComponentProcessor.kt @@ -69,7 +69,14 @@ class ComponentProcessor(private val logger: KSPLogger, private val codeGenerato .map { val componentBuilder = Component.builder() converter.visitAnnotated(it, componentBuilder) - (it to componentBuilder.build()!!) + + var componentModel: Component? = null + try { + componentModel = componentBuilder.validateAndBuild() + } catch (e: Exception) { + logger.error("Unable to build component: $e", it) + } + (it to componentModel!!) } .toList() diff --git a/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/KElementConverter.kt b/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/KElementConverter.kt index 37f56a5c..68b2a6f9 100644 --- a/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/KElementConverter.kt +++ b/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/KElementConverter.kt @@ -156,17 +156,21 @@ class KElementConverter(private val logger: KSPLogger, private val builtIns: KSB validateMethodSignature(data.componentType, handlerType, function) - data.withHandler( - handlerBuilder - .withName(function.simpleName.asString()) - .withHandlerType(handlerType) - .withInputType( - if (function.parameters.size == 2) payloadFromType(function.parameters[1].type) - else EMPTY_PAYLOAD) - .withOutputType( - if (function.returnType != null) payloadFromType(function.returnType!!) - else EMPTY_PAYLOAD) - .build()) + try { + data.withHandler( + handlerBuilder + .withName(function.simpleName.asString()) + .withHandlerType(handlerType) + .withInputType( + if (function.parameters.size == 2) payloadFromType(function.parameters[1].type) + else EMPTY_PAYLOAD) + .withOutputType( + if (function.returnType != null) payloadFromType(function.returnType!!) + else EMPTY_PAYLOAD) + .validateAndBuild()) + } catch (e: Exception) { + logger.error("Error when building handler: $e", function) + } } private fun defaultHandlerType(componentType: ComponentType, node: KSNode): HandlerType { From 7f46b78cb1a7c8dd127291c620548e1d0a5d9e9c Mon Sep 17 00:00:00 2001 From: slinkydeveloper Date: Thu, 11 Apr 2024 13:02:25 +0200 Subject: [PATCH 2/2] Allow to use send as handler name, by adding a prefix in front of it when generating the client. --- .../template/HandlebarsTemplateEngine.java | 21 ++++++++++---- .../restate/sdk/gen/ComponentProcessor.java | 11 ++++++-- .../src/main/resources/templates/Client.hbs | 28 +++++++++---------- .../java/dev/restate/sdk/CodegenTest.java | 9 ++++++ .../sdk/kotlin/gen/ComponentProcessor.kt | 13 +++++++-- .../src/main/resources/templates/Client.hbs | 8 +++--- .../dev/restate/sdk/kotlin/CodegenTest.kt | 9 ++++++ 7 files changed, 70 insertions(+), 29 deletions(-) diff --git a/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/template/HandlebarsTemplateEngine.java b/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/template/HandlebarsTemplateEngine.java index ca17a9eb..32dd376a 100644 --- a/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/template/HandlebarsTemplateEngine.java +++ b/sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/template/HandlebarsTemplateEngine.java @@ -23,18 +23,22 @@ import java.io.Writer; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; public class HandlebarsTemplateEngine { private final String baseTemplateName; private final Map templates; + private final Set handlerNamesToPrefix; public HandlebarsTemplateEngine( String baseTemplateName, TemplateLoader templateLoader, - Map templates) { + Map templates, + Set handlerNamesToPrefix) { this.baseTemplateName = baseTemplateName; + this.handlerNamesToPrefix = handlerNamesToPrefix; Handlebars handlebars = new Handlebars(templateLoader); handlebars.registerHelpers(StringHelpers.class); @@ -65,7 +69,9 @@ public void generate(ThrowingFunction createFile, Component comp this.templates .get(component.getComponentType()) .apply( - Context.newBuilder(new ComponentTemplateModel(component, this.baseTemplateName)) + Context.newBuilder( + new ComponentTemplateModel( + component, this.baseTemplateName, this.handlerNamesToPrefix)) .resolver(FieldValueResolver.INSTANCE) .build(), out); @@ -86,7 +92,8 @@ static class ComponentTemplateModel { public final boolean isService; public final List handlers; - private ComponentTemplateModel(Component inner, String baseTemplateName) { + private ComponentTemplateModel( + Component inner, String baseTemplateName, Set handlerNamesToPrefix) { this.originalClassPkg = inner.getTargetPkg().toString(); this.originalClassFqcn = inner.getTargetFqcn().toString(); this.generatedClassSimpleNamePrefix = inner.getSimpleComponentName(); @@ -99,12 +106,15 @@ private ComponentTemplateModel(Component inner, String baseTemplateName) { this.isService = inner.getComponentType() == ComponentType.SERVICE; this.handlers = - inner.getMethods().stream().map(HandlerTemplateModel::new).collect(Collectors.toList()); + inner.getMethods().stream() + .map(h -> new HandlerTemplateModel(h, handlerNamesToPrefix)) + .collect(Collectors.toList()); } } static class HandlerTemplateModel { public final String name; + public final String methodName; public final String handlerType; public final boolean isWorkflow; public final boolean isShared; @@ -123,8 +133,9 @@ static class HandlerTemplateModel { public final String boxedOutputFqcn; public final String outputSerdeFieldName; - private HandlerTemplateModel(Handler inner) { + private HandlerTemplateModel(Handler inner, Set handlerNamesToPrefix) { this.name = inner.getName().toString(); + this.methodName = (handlerNamesToPrefix.contains(this.name) ? "_" : "") + this.name; this.handlerType = inner.getHandlerType().toString(); this.isWorkflow = inner.getHandlerType() == HandlerType.WORKFLOW; this.isShared = inner.getHandlerType() == HandlerType.SHARED; diff --git a/sdk-api-gen/src/main/java/dev/restate/sdk/gen/ComponentProcessor.java b/sdk-api-gen/src/main/java/dev/restate/sdk/gen/ComponentProcessor.java index 724fb9c4..0571f756 100644 --- a/sdk-api-gen/src/main/java/dev/restate/sdk/gen/ComponentProcessor.java +++ b/sdk-api-gen/src/main/java/dev/restate/sdk/gen/ComponentProcessor.java @@ -39,6 +39,8 @@ public class ComponentProcessor extends AbstractProcessor { private HandlebarsTemplateEngine bindableComponentCodegen; private HandlebarsTemplateEngine clientCodegen; + private static final Set RESERVED_METHOD_NAMES = Set.of("send"); + @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); @@ -55,7 +57,8 @@ public synchronized void init(ProcessingEnvironment processingEnv) { ComponentType.SERVICE, "templates/BindableComponentFactory.hbs", ComponentType.VIRTUAL_OBJECT, - "templates/BindableComponentFactory.hbs")); + "templates/BindableComponentFactory.hbs"), + RESERVED_METHOD_NAMES); this.bindableComponentCodegen = new HandlebarsTemplateEngine( "BindableComponent", @@ -66,7 +69,8 @@ public synchronized void init(ProcessingEnvironment processingEnv) { ComponentType.SERVICE, "templates/BindableComponent.hbs", ComponentType.VIRTUAL_OBJECT, - "templates/BindableComponent.hbs")); + "templates/BindableComponent.hbs"), + RESERVED_METHOD_NAMES); this.clientCodegen = new HandlebarsTemplateEngine( "Client", @@ -77,7 +81,8 @@ public synchronized void init(ProcessingEnvironment processingEnv) { ComponentType.SERVICE, "templates/Client.hbs", ComponentType.VIRTUAL_OBJECT, - "templates/Client.hbs")); + "templates/Client.hbs"), + RESERVED_METHOD_NAMES); } @Override diff --git a/sdk-api-gen/src/main/resources/templates/Client.hbs b/sdk-api-gen/src/main/resources/templates/Client.hbs index 077175c3..7f59b496 100644 --- a/sdk-api-gen/src/main/resources/templates/Client.hbs +++ b/sdk-api-gen/src/main/resources/templates/Client.hbs @@ -40,7 +40,7 @@ public class {{generatedClassSimpleName}} { } {{#handlers}} - public Awaitable<{{{boxedOutputFqcn}}}> {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { + public Awaitable<{{{boxedOutputFqcn}}}> {{methodName}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { return this.ctx.call( {{#if isObject}}Target.virtualObject(COMPONENT_NAME, this.key, "{{name}}"){{else}}Target.service(COMPONENT_NAME, "{{name}}"){{/if}}, {{inputSerdeFieldName}}, @@ -65,7 +65,7 @@ public class {{generatedClassSimpleName}} { } {{#handlers}} - public void {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { + public void {{methodName}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { ContextClient.this.ctx.send( {{#if isObject}}Target.virtualObject(COMPONENT_NAME, ContextClient.this.key, "{{name}}"){{else}}Target.service(COMPONENT_NAME, "{{name}}"){{/if}}, {{inputSerdeFieldName}}, @@ -86,13 +86,13 @@ public class {{generatedClassSimpleName}} { } {{#handlers}} - public {{#if outputEmpty}}void{{else}}{{{outputFqcn}}}{{/if}} {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - {{^outputEmpty}}return {{/outputEmpty}}this.{{name}}( + public {{#if outputEmpty}}void{{else}}{{{outputFqcn}}}{{/if}} {{methodName}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { + {{^outputEmpty}}return {{/outputEmpty}}this.{{methodName}}( {{^inputEmpty}}req, {{/inputEmpty}} dev.restate.sdk.client.RequestOptions.DEFAULT); } - public {{#if outputEmpty}}void{{else}}{{{outputFqcn}}}{{/if}} {{name}}({{^inputEmpty}}{{{inputFqcn}}} req, {{/inputEmpty}}dev.restate.sdk.client.RequestOptions requestOptions) { + public {{#if outputEmpty}}void{{else}}{{{outputFqcn}}}{{/if}} {{methodName}}({{^inputEmpty}}{{{inputFqcn}}} req, {{/inputEmpty}}dev.restate.sdk.client.RequestOptions requestOptions) { {{^outputEmpty}}return {{/outputEmpty}}this.ingressClient.call( {{#if isObject}}Target.virtualObject(COMPONENT_NAME, this.key, "{{name}}"){{else}}Target.service(COMPONENT_NAME, "{{name}}"){{/if}}, {{inputSerdeFieldName}}, @@ -101,13 +101,13 @@ public class {{generatedClassSimpleName}} { requestOptions); } - public {{#if outputEmpty}}java.util.concurrent.CompletableFuture{{else}}java.util.concurrent.CompletableFuture<{{{boxedOutputFqcn}}}>{{/if}} {{name}}Async({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - return this.{{name}}Async( + public {{#if outputEmpty}}java.util.concurrent.CompletableFuture{{else}}java.util.concurrent.CompletableFuture<{{{boxedOutputFqcn}}}>{{/if}} {{methodName}}Async({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { + return this.{{methodName}}Async( {{^inputEmpty}}req, {{/inputEmpty}} dev.restate.sdk.client.RequestOptions.DEFAULT); } - public {{#if outputEmpty}}java.util.concurrent.CompletableFuture{{else}}java.util.concurrent.CompletableFuture<{{{boxedOutputFqcn}}}>{{/if}} {{name}}Async({{^inputEmpty}}{{{inputFqcn}}} req, {{/inputEmpty}}dev.restate.sdk.client.RequestOptions requestOptions) { + public {{#if outputEmpty}}java.util.concurrent.CompletableFuture{{else}}java.util.concurrent.CompletableFuture<{{{boxedOutputFqcn}}}>{{/if}} {{methodName}}Async({{^inputEmpty}}{{{inputFqcn}}} req, {{/inputEmpty}}dev.restate.sdk.client.RequestOptions requestOptions) { return this.ingressClient.callAsync( {{#if isObject}}Target.virtualObject(COMPONENT_NAME, this.key, "{{name}}"){{else}}Target.service(COMPONENT_NAME, "{{name}}"){{/if}}, {{inputSerdeFieldName}}, @@ -133,13 +133,13 @@ public class {{generatedClassSimpleName}} { } {{#handlers}} - public String {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - return this.{{name}}( + public String {{methodName}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { + return this.{{methodName}}( {{^inputEmpty}}req, {{/inputEmpty}} dev.restate.sdk.client.RequestOptions.DEFAULT); } - public String {{name}}({{^inputEmpty}}{{{inputFqcn}}} req, {{/inputEmpty}}dev.restate.sdk.client.RequestOptions requestOptions) { + public String {{methodName}}({{^inputEmpty}}{{{inputFqcn}}} req, {{/inputEmpty}}dev.restate.sdk.client.RequestOptions requestOptions) { return IngressClient.this.ingressClient.send( {{#if isObject}}Target.virtualObject(COMPONENT_NAME, IngressClient.this.key, "{{name}}"){{else}}Target.service(COMPONENT_NAME, "{{name}}"){{/if}}, {{inputSerdeFieldName}}, @@ -148,13 +148,13 @@ public class {{generatedClassSimpleName}} { requestOptions); } - public java.util.concurrent.CompletableFuture {{name}}Async({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { - return this.{{name}}Async( + public java.util.concurrent.CompletableFuture {{methodName}}Async({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) { + return this.{{methodName}}Async( {{^inputEmpty}}req, {{/inputEmpty}} dev.restate.sdk.client.RequestOptions.DEFAULT); } - public java.util.concurrent.CompletableFuture {{name}}Async({{^inputEmpty}}{{{inputFqcn}}} req, {{/inputEmpty}}dev.restate.sdk.client.RequestOptions requestOptions) { + public java.util.concurrent.CompletableFuture {{methodName}}Async({{^inputEmpty}}{{{inputFqcn}}} req, {{/inputEmpty}}dev.restate.sdk.client.RequestOptions requestOptions) { return IngressClient.this.ingressClient.sendAsync( {{#if isObject}}Target.virtualObject(COMPONENT_NAME, IngressClient.this.key, "{{name}}"){{else}}Target.service(COMPONENT_NAME, "{{name}}"){{/if}}, {{inputSerdeFieldName}}, diff --git a/sdk-api-gen/src/test/java/dev/restate/sdk/CodegenTest.java b/sdk-api-gen/src/test/java/dev/restate/sdk/CodegenTest.java index 5d0654bd..72974ffc 100644 --- a/sdk-api-gen/src/test/java/dev/restate/sdk/CodegenTest.java +++ b/sdk-api-gen/src/test/java/dev/restate/sdk/CodegenTest.java @@ -93,6 +93,15 @@ public void primitiveInput(Context context, int input) { } } + @VirtualObject + static class CornerCases { + @Exclusive + public String send(ObjectContext context, String request) { + // Just needs to compile + return CodegenTestCornerCasesClient.fromContext(context, request)._send("my_send").await(); + } + } + @Override public Stream definitions() { return Stream.of( diff --git a/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/ComponentProcessor.kt b/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/ComponentProcessor.kt index 069829a2..24e06981 100644 --- a/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/ComponentProcessor.kt +++ b/sdk-api-kotlin-gen/src/main/kotlin/dev/restate/sdk/kotlin/gen/ComponentProcessor.kt @@ -25,27 +25,34 @@ import java.nio.charset.Charset class ComponentProcessor(private val logger: KSPLogger, private val codeGenerator: CodeGenerator) : SymbolProcessor { + companion object { + private val RESERVED_METHOD_NAMES: Set = setOf("send") + } + private val bindableComponentFactoryCodegen: HandlebarsTemplateEngine = HandlebarsTemplateEngine( "BindableComponentFactory", ClassPathTemplateLoader(), mapOf( ComponentType.SERVICE to "templates/BindableComponentFactory", - ComponentType.VIRTUAL_OBJECT to "templates/BindableComponentFactory")) + ComponentType.VIRTUAL_OBJECT to "templates/BindableComponentFactory"), + RESERVED_METHOD_NAMES) private val bindableComponentCodegen: HandlebarsTemplateEngine = HandlebarsTemplateEngine( "BindableComponent", ClassPathTemplateLoader(), mapOf( ComponentType.SERVICE to "templates/BindableComponent", - ComponentType.VIRTUAL_OBJECT to "templates/BindableComponent")) + ComponentType.VIRTUAL_OBJECT to "templates/BindableComponent"), + RESERVED_METHOD_NAMES) private val clientCodegen: HandlebarsTemplateEngine = HandlebarsTemplateEngine( "Client", ClassPathTemplateLoader(), mapOf( ComponentType.SERVICE to "templates/Client", - ComponentType.VIRTUAL_OBJECT to "templates/Client")) + ComponentType.VIRTUAL_OBJECT to "templates/Client"), + RESERVED_METHOD_NAMES) override fun process(resolver: Resolver): List { val converter = KElementConverter(logger, resolver.builtIns) diff --git a/sdk-api-kotlin-gen/src/main/resources/templates/Client.hbs b/sdk-api-kotlin-gen/src/main/resources/templates/Client.hbs index e0b21d14..10700629 100644 --- a/sdk-api-kotlin-gen/src/main/resources/templates/Client.hbs +++ b/sdk-api-kotlin-gen/src/main/resources/templates/Client.hbs @@ -32,7 +32,7 @@ object {{generatedClassSimpleName}} { class ContextClient(private val ctx: Context{{#isObject}}, private val key: String{{/isObject}}){ {{#handlers}} - suspend fun {{name}}({{^inputEmpty}}req: {{{inputFqcn}}}{{/inputEmpty}}): Awaitable<{{{boxedOutputFqcn}}}> { + suspend fun {{methodName}}({{^inputEmpty}}req: {{{inputFqcn}}}{{/inputEmpty}}): Awaitable<{{{boxedOutputFqcn}}}> { return this.ctx.callAsync( {{#if isObject}}Target.virtualObject(COMPONENT_NAME, this.key, "{{name}}"){{else}}Target.service(COMPONENT_NAME, "{{name}}"){{/if}}, {{inputSerdeFieldName}}, @@ -46,7 +46,7 @@ object {{generatedClassSimpleName}} { inner class Send(private val delay: Duration) { {{#handlers}} - suspend fun {{name}}({{^inputEmpty}}req: {{{inputFqcn}}}{{/inputEmpty}}) { + suspend fun {{methodName}}({{^inputEmpty}}req: {{{inputFqcn}}}{{/inputEmpty}}) { this@ContextClient.ctx.send( {{#if isObject}}Target.virtualObject(COMPONENT_NAME, this@ContextClient.key, "{{name}}"){{else}}Target.service(COMPONENT_NAME, "{{name}}"){{/if}}, {{inputSerdeFieldName}}, @@ -59,7 +59,7 @@ object {{generatedClassSimpleName}} { class IngressClient(private val ingressClient: dev.restate.sdk.client.IngressClient{{#isObject}}, private val key: String{{/isObject}}) { {{#handlers}} - suspend fun {{name}}({{^inputEmpty}}req: {{{inputFqcn}}}, {{/inputEmpty}}requestOptions: dev.restate.sdk.client.RequestOptions = dev.restate.sdk.client.RequestOptions.DEFAULT): {{{boxedOutputFqcn}}} { + suspend fun {{methodName}}({{^inputEmpty}}req: {{{inputFqcn}}}, {{/inputEmpty}}requestOptions: dev.restate.sdk.client.RequestOptions = dev.restate.sdk.client.RequestOptions.DEFAULT): {{{boxedOutputFqcn}}} { return this.ingressClient.callSuspend( {{#if isObject}}Target.virtualObject(COMPONENT_NAME, this.key, "{{name}}"){{else}}Target.service(COMPONENT_NAME, "{{name}}"){{/if}}, {{inputSerdeFieldName}}, @@ -74,7 +74,7 @@ object {{generatedClassSimpleName}} { inner class Send(private val delay: Duration) { {{#handlers}} - suspend fun {{name}}({{^inputEmpty}}req: {{{inputFqcn}}}, {{/inputEmpty}}requestOptions: dev.restate.sdk.client.RequestOptions = dev.restate.sdk.client.RequestOptions.DEFAULT): String { + suspend fun {{methodName}}({{^inputEmpty}}req: {{{inputFqcn}}}, {{/inputEmpty}}requestOptions: dev.restate.sdk.client.RequestOptions = dev.restate.sdk.client.RequestOptions.DEFAULT): String { return this@IngressClient.ingressClient.sendSuspend( {{#if isObject}}Target.virtualObject(COMPONENT_NAME, this@IngressClient.key, "{{name}}"){{else}}Target.service(COMPONENT_NAME, "{{name}}"){{/if}}, {{inputSerdeFieldName}}, diff --git a/sdk-api-kotlin-gen/src/test/kotlin/dev/restate/sdk/kotlin/CodegenTest.kt b/sdk-api-kotlin-gen/src/test/kotlin/dev/restate/sdk/kotlin/CodegenTest.kt index 34bd3031..eb7bb94e 100644 --- a/sdk-api-kotlin-gen/src/test/kotlin/dev/restate/sdk/kotlin/CodegenTest.kt +++ b/sdk-api-kotlin-gen/src/test/kotlin/dev/restate/sdk/kotlin/CodegenTest.kt @@ -85,6 +85,15 @@ class CodegenTest : TestDefinitions.TestSuite { } } + @VirtualObject + class CornerCases { + @Exclusive + suspend fun send(context: ObjectContext, request: String): String { + // Just needs to compile + return CodegenTestCornerCasesClient.fromContext(context, request)._send("my_send").await() + } + } + override fun definitions(): Stream { return Stream.of( testInvocation({ ServiceGreeter() }, "greet")