Skip to content

Commit 0c5917b

Browse files
Fix corner cases around code generation and "reserved" keywords (#280)
* Validate component nor handlers start with `restate` or `openapi` * Allow to use send as handler name, by adding a prefix in front of it when generating the client.
1 parent b4e737e commit 0c5917b

File tree

11 files changed

+150
-77
lines changed

11 files changed

+150
-77
lines changed

sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/Component.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,22 @@ public List<Handler> getHandlers() {
128128
return handlers;
129129
}
130130

131-
public Component build() {
131+
public Component validateAndBuild() {
132+
String componentNameLowercase = componentName.toLowerCase();
133+
if (componentNameLowercase.startsWith("restate")
134+
|| componentNameLowercase.startsWith("openapi")) {
135+
throw new IllegalArgumentException(
136+
"A component name cannot start with `restate` or `openapi`");
137+
}
138+
139+
if (componentType.equals(ComponentType.WORKFLOW)) {
140+
if (handlers.stream().filter(m -> m.getHandlerType().equals(HandlerType.WORKFLOW)).count()
141+
!= 1) {
142+
throw new IllegalArgumentException(
143+
"Workflow services must have exactly one method annotated as @Workflow");
144+
}
145+
}
146+
132147
return new Component(
133148
Objects.requireNonNull(targetPkg),
134149
Objects.requireNonNull(targetFqcn),

sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/model/Handler.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,14 @@ public PayloadType getOutputType() {
8787
return outputType;
8888
}
8989

90-
public Handler build() {
90+
public Handler validateAndBuild() {
91+
String handlerNameLowercase = name.toString().toLowerCase();
92+
if (handlerNameLowercase.startsWith("restate")
93+
|| handlerNameLowercase.startsWith("openapi")) {
94+
throw new IllegalArgumentException(
95+
"A component name cannot start with `restate` or `openapi`");
96+
}
97+
9198
return new Handler(
9299
Objects.requireNonNull(name), Objects.requireNonNull(handlerType), inputType, outputType);
93100
}

sdk-api-gen-common/src/main/java/dev/restate/sdk/gen/template/HandlebarsTemplateEngine.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,22 @@
2323
import java.io.Writer;
2424
import java.util.List;
2525
import java.util.Map;
26+
import java.util.Set;
2627
import java.util.stream.Collectors;
2728

2829
public class HandlebarsTemplateEngine {
2930

3031
private final String baseTemplateName;
3132
private final Map<ComponentType, Template> templates;
33+
private final Set<String> handlerNamesToPrefix;
3234

3335
public HandlebarsTemplateEngine(
3436
String baseTemplateName,
3537
TemplateLoader templateLoader,
36-
Map<ComponentType, String> templates) {
38+
Map<ComponentType, String> templates,
39+
Set<String> handlerNamesToPrefix) {
3740
this.baseTemplateName = baseTemplateName;
41+
this.handlerNamesToPrefix = handlerNamesToPrefix;
3842

3943
Handlebars handlebars = new Handlebars(templateLoader);
4044
handlebars.registerHelpers(StringHelpers.class);
@@ -65,7 +69,9 @@ public void generate(ThrowingFunction<String, Writer> createFile, Component comp
6569
this.templates
6670
.get(component.getComponentType())
6771
.apply(
68-
Context.newBuilder(new ComponentTemplateModel(component, this.baseTemplateName))
72+
Context.newBuilder(
73+
new ComponentTemplateModel(
74+
component, this.baseTemplateName, this.handlerNamesToPrefix))
6975
.resolver(FieldValueResolver.INSTANCE)
7076
.build(),
7177
out);
@@ -86,7 +92,8 @@ static class ComponentTemplateModel {
8692
public final boolean isService;
8793
public final List<HandlerTemplateModel> handlers;
8894

89-
private ComponentTemplateModel(Component inner, String baseTemplateName) {
95+
private ComponentTemplateModel(
96+
Component inner, String baseTemplateName, Set<String> handlerNamesToPrefix) {
9097
this.originalClassPkg = inner.getTargetPkg().toString();
9198
this.originalClassFqcn = inner.getTargetFqcn().toString();
9299
this.generatedClassSimpleNamePrefix = inner.getSimpleComponentName();
@@ -99,12 +106,15 @@ private ComponentTemplateModel(Component inner, String baseTemplateName) {
99106
this.isService = inner.getComponentType() == ComponentType.SERVICE;
100107

101108
this.handlers =
102-
inner.getMethods().stream().map(HandlerTemplateModel::new).collect(Collectors.toList());
109+
inner.getMethods().stream()
110+
.map(h -> new HandlerTemplateModel(h, handlerNamesToPrefix))
111+
.collect(Collectors.toList());
103112
}
104113
}
105114

106115
static class HandlerTemplateModel {
107116
public final String name;
117+
public final String methodName;
108118
public final String handlerType;
109119
public final boolean isWorkflow;
110120
public final boolean isShared;
@@ -123,8 +133,9 @@ static class HandlerTemplateModel {
123133
public final String boxedOutputFqcn;
124134
public final String outputSerdeFieldName;
125135

126-
private HandlerTemplateModel(Handler inner) {
136+
private HandlerTemplateModel(Handler inner, Set<String> handlerNamesToPrefix) {
127137
this.name = inner.getName().toString();
138+
this.methodName = (handlerNamesToPrefix.contains(this.name) ? "_" : "") + this.name;
128139
this.handlerType = inner.getHandlerType().toString();
129140
this.isWorkflow = inner.getHandlerType() == HandlerType.WORKFLOW;
130141
this.isShared = inner.getHandlerType() == HandlerType.SHARED;

sdk-api-gen/src/main/java/dev/restate/sdk/gen/ComponentProcessor.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ public class ComponentProcessor extends AbstractProcessor {
3939
private HandlebarsTemplateEngine bindableComponentCodegen;
4040
private HandlebarsTemplateEngine clientCodegen;
4141

42+
private static final Set<String> RESERVED_METHOD_NAMES = Set.of("send");
43+
4244
@Override
4345
public synchronized void init(ProcessingEnvironment processingEnv) {
4446
super.init(processingEnv);
@@ -55,7 +57,8 @@ public synchronized void init(ProcessingEnvironment processingEnv) {
5557
ComponentType.SERVICE,
5658
"templates/BindableComponentFactory.hbs",
5759
ComponentType.VIRTUAL_OBJECT,
58-
"templates/BindableComponentFactory.hbs"));
60+
"templates/BindableComponentFactory.hbs"),
61+
RESERVED_METHOD_NAMES);
5962
this.bindableComponentCodegen =
6063
new HandlebarsTemplateEngine(
6164
"BindableComponent",
@@ -66,7 +69,8 @@ public synchronized void init(ProcessingEnvironment processingEnv) {
6669
ComponentType.SERVICE,
6770
"templates/BindableComponent.hbs",
6871
ComponentType.VIRTUAL_OBJECT,
69-
"templates/BindableComponent.hbs"));
72+
"templates/BindableComponent.hbs"),
73+
RESERVED_METHOD_NAMES);
7074
this.clientCodegen =
7175
new HandlebarsTemplateEngine(
7276
"Client",
@@ -77,7 +81,8 @@ public synchronized void init(ProcessingEnvironment processingEnv) {
7781
ComponentType.SERVICE,
7882
"templates/Client.hbs",
7983
ComponentType.VIRTUAL_OBJECT,
80-
"templates/Client.hbs"));
84+
"templates/Client.hbs"),
85+
RESERVED_METHOD_NAMES);
8186
}
8287

8388
@Override

sdk-api-gen/src/main/java/dev/restate/sdk/gen/ElementConverter.java

Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -107,20 +107,27 @@ public Component fromTypeElement(TypeElement element) {
107107
|| e.getAnnotation(Shared.class) != null)
108108
.map(e -> fromExecutableElement(type, ((ExecutableElement) e)))
109109
.collect(Collectors.toList());
110-
validateHandlers(type, handlers, element);
111110

112111
if (handlers.isEmpty()) {
113112
messager.printMessage(
114113
Diagnostic.Kind.WARNING, "The component " + componentName + " has no handlers", element);
115114
}
116115

117-
return new Component.Builder()
118-
.withTargetPkg(targetPkg)
119-
.withTargetFqcn(targetFqcn)
120-
.withComponentName(componentName)
121-
.withComponentType(type)
122-
.withHandlers(handlers)
123-
.build();
116+
try {
117+
return new Component.Builder()
118+
.withTargetPkg(targetPkg)
119+
.withTargetFqcn(targetFqcn)
120+
.withComponentName(componentName)
121+
.withComponentType(type)
122+
.withHandlers(handlers)
123+
.validateAndBuild();
124+
} catch (Exception e) {
125+
messager.printMessage(
126+
Diagnostic.Kind.ERROR,
127+
"Can't build the component " + componentName + ": " + e.getMessage(),
128+
element);
129+
return null;
130+
}
124131
}
125132

126133
private void validateType(TypeElement element) {
@@ -140,20 +147,6 @@ private void validateType(TypeElement element) {
140147
}
141148
}
142149

143-
private void validateHandlers(
144-
ComponentType componentType, List<Handler> handlers, TypeElement element) {
145-
// Additional validation for Workflow types
146-
if (componentType.equals(ComponentType.WORKFLOW)) {
147-
if (handlers.stream().filter(m -> m.getHandlerType().equals(HandlerType.WORKFLOW)).count()
148-
!= 1) {
149-
messager.printMessage(
150-
Diagnostic.Kind.ERROR,
151-
"Workflow services must have exactly one method annotated as @Workflow",
152-
element);
153-
}
154-
}
155-
}
156-
157150
private Handler fromExecutableElement(ComponentType componentType, ExecutableElement element) {
158151
if (!element.getTypeParameters().isEmpty()) {
159152
messager.printMessage(
@@ -199,18 +192,24 @@ private Handler fromExecutableElement(ComponentType componentType, ExecutableEle
199192

200193
validateMethodSignature(componentType, handlerType, element);
201194

202-
return new Handler.Builder()
203-
.withName(element.getSimpleName())
204-
.withHandlerType(handlerType)
205-
.withInputType(
206-
element.getParameters().size() > 1
207-
? payloadFromType(element.getParameters().get(1).asType())
208-
: EMPTY_PAYLOAD)
209-
.withOutputType(
210-
!element.getReturnType().getKind().equals(TypeKind.VOID)
211-
? payloadFromType(element.getReturnType())
212-
: EMPTY_PAYLOAD)
213-
.build();
195+
try {
196+
return new Handler.Builder()
197+
.withName(element.getSimpleName())
198+
.withHandlerType(handlerType)
199+
.withInputType(
200+
element.getParameters().size() > 1
201+
? payloadFromType(element.getParameters().get(1).asType())
202+
: EMPTY_PAYLOAD)
203+
.withOutputType(
204+
!element.getReturnType().getKind().equals(TypeKind.VOID)
205+
? payloadFromType(element.getReturnType())
206+
: EMPTY_PAYLOAD)
207+
.validateAndBuild();
208+
} catch (Exception e) {
209+
messager.printMessage(
210+
Diagnostic.Kind.ERROR, "Error when building handler: " + e.getMessage(), element);
211+
return null;
212+
}
214213
}
215214

216215
private HandlerType defaultHandlerType(ComponentType componentType, ExecutableElement element) {

sdk-api-gen/src/main/resources/templates/Client.hbs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public class {{generatedClassSimpleName}} {
4040
}
4141

4242
{{#handlers}}
43-
public Awaitable<{{{boxedOutputFqcn}}}> {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) {
43+
public Awaitable<{{{boxedOutputFqcn}}}> {{methodName}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) {
4444
return this.ctx.call(
4545
{{#if isObject}}Target.virtualObject(COMPONENT_NAME, this.key, "{{name}}"){{else}}Target.service(COMPONENT_NAME, "{{name}}"){{/if}},
4646
{{inputSerdeFieldName}},
@@ -65,7 +65,7 @@ public class {{generatedClassSimpleName}} {
6565
}
6666

6767
{{#handlers}}
68-
public void {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) {
68+
public void {{methodName}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) {
6969
ContextClient.this.ctx.send(
7070
{{#if isObject}}Target.virtualObject(COMPONENT_NAME, ContextClient.this.key, "{{name}}"){{else}}Target.service(COMPONENT_NAME, "{{name}}"){{/if}},
7171
{{inputSerdeFieldName}},
@@ -86,13 +86,13 @@ public class {{generatedClassSimpleName}} {
8686
}
8787

8888
{{#handlers}}
89-
public {{#if outputEmpty}}void{{else}}{{{outputFqcn}}}{{/if}} {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) {
90-
{{^outputEmpty}}return {{/outputEmpty}}this.{{name}}(
89+
public {{#if outputEmpty}}void{{else}}{{{outputFqcn}}}{{/if}} {{methodName}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) {
90+
{{^outputEmpty}}return {{/outputEmpty}}this.{{methodName}}(
9191
{{^inputEmpty}}req, {{/inputEmpty}}
9292
dev.restate.sdk.client.RequestOptions.DEFAULT);
9393
}
9494

95-
public {{#if outputEmpty}}void{{else}}{{{outputFqcn}}}{{/if}} {{name}}({{^inputEmpty}}{{{inputFqcn}}} req, {{/inputEmpty}}dev.restate.sdk.client.RequestOptions requestOptions) {
95+
public {{#if outputEmpty}}void{{else}}{{{outputFqcn}}}{{/if}} {{methodName}}({{^inputEmpty}}{{{inputFqcn}}} req, {{/inputEmpty}}dev.restate.sdk.client.RequestOptions requestOptions) {
9696
{{^outputEmpty}}return {{/outputEmpty}}this.ingressClient.call(
9797
{{#if isObject}}Target.virtualObject(COMPONENT_NAME, this.key, "{{name}}"){{else}}Target.service(COMPONENT_NAME, "{{name}}"){{/if}},
9898
{{inputSerdeFieldName}},
@@ -101,13 +101,13 @@ public class {{generatedClassSimpleName}} {
101101
requestOptions);
102102
}
103103

104-
public {{#if outputEmpty}}java.util.concurrent.CompletableFuture<Void>{{else}}java.util.concurrent.CompletableFuture<{{{boxedOutputFqcn}}}>{{/if}} {{name}}Async({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) {
105-
return this.{{name}}Async(
104+
public {{#if outputEmpty}}java.util.concurrent.CompletableFuture<Void>{{else}}java.util.concurrent.CompletableFuture<{{{boxedOutputFqcn}}}>{{/if}} {{methodName}}Async({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) {
105+
return this.{{methodName}}Async(
106106
{{^inputEmpty}}req, {{/inputEmpty}}
107107
dev.restate.sdk.client.RequestOptions.DEFAULT);
108108
}
109109

110-
public {{#if outputEmpty}}java.util.concurrent.CompletableFuture<Void>{{else}}java.util.concurrent.CompletableFuture<{{{boxedOutputFqcn}}}>{{/if}} {{name}}Async({{^inputEmpty}}{{{inputFqcn}}} req, {{/inputEmpty}}dev.restate.sdk.client.RequestOptions requestOptions) {
110+
public {{#if outputEmpty}}java.util.concurrent.CompletableFuture<Void>{{else}}java.util.concurrent.CompletableFuture<{{{boxedOutputFqcn}}}>{{/if}} {{methodName}}Async({{^inputEmpty}}{{{inputFqcn}}} req, {{/inputEmpty}}dev.restate.sdk.client.RequestOptions requestOptions) {
111111
return this.ingressClient.callAsync(
112112
{{#if isObject}}Target.virtualObject(COMPONENT_NAME, this.key, "{{name}}"){{else}}Target.service(COMPONENT_NAME, "{{name}}"){{/if}},
113113
{{inputSerdeFieldName}},
@@ -133,13 +133,13 @@ public class {{generatedClassSimpleName}} {
133133
}
134134

135135
{{#handlers}}
136-
public String {{name}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) {
137-
return this.{{name}}(
136+
public String {{methodName}}({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) {
137+
return this.{{methodName}}(
138138
{{^inputEmpty}}req, {{/inputEmpty}}
139139
dev.restate.sdk.client.RequestOptions.DEFAULT);
140140
}
141141

142-
public String {{name}}({{^inputEmpty}}{{{inputFqcn}}} req, {{/inputEmpty}}dev.restate.sdk.client.RequestOptions requestOptions) {
142+
public String {{methodName}}({{^inputEmpty}}{{{inputFqcn}}} req, {{/inputEmpty}}dev.restate.sdk.client.RequestOptions requestOptions) {
143143
return IngressClient.this.ingressClient.send(
144144
{{#if isObject}}Target.virtualObject(COMPONENT_NAME, IngressClient.this.key, "{{name}}"){{else}}Target.service(COMPONENT_NAME, "{{name}}"){{/if}},
145145
{{inputSerdeFieldName}},
@@ -148,13 +148,13 @@ public class {{generatedClassSimpleName}} {
148148
requestOptions);
149149
}
150150

151-
public java.util.concurrent.CompletableFuture<String> {{name}}Async({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) {
152-
return this.{{name}}Async(
151+
public java.util.concurrent.CompletableFuture<String> {{methodName}}Async({{^inputEmpty}}{{{inputFqcn}}} req{{/inputEmpty}}) {
152+
return this.{{methodName}}Async(
153153
{{^inputEmpty}}req, {{/inputEmpty}}
154154
dev.restate.sdk.client.RequestOptions.DEFAULT);
155155
}
156156

157-
public java.util.concurrent.CompletableFuture<String> {{name}}Async({{^inputEmpty}}{{{inputFqcn}}} req, {{/inputEmpty}}dev.restate.sdk.client.RequestOptions requestOptions) {
157+
public java.util.concurrent.CompletableFuture<String> {{methodName}}Async({{^inputEmpty}}{{{inputFqcn}}} req, {{/inputEmpty}}dev.restate.sdk.client.RequestOptions requestOptions) {
158158
return IngressClient.this.ingressClient.sendAsync(
159159
{{#if isObject}}Target.virtualObject(COMPONENT_NAME, IngressClient.this.key, "{{name}}"){{else}}Target.service(COMPONENT_NAME, "{{name}}"){{/if}},
160160
{{inputSerdeFieldName}},

sdk-api-gen/src/test/java/dev/restate/sdk/CodegenTest.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,15 @@ public void primitiveInput(Context context, int input) {
9393
}
9494
}
9595

96+
@VirtualObject
97+
static class CornerCases {
98+
@Exclusive
99+
public String send(ObjectContext context, String request) {
100+
// Just needs to compile
101+
return CodegenTestCornerCasesClient.fromContext(context, request)._send("my_send").await();
102+
}
103+
}
104+
96105
@Override
97106
public Stream<TestDefinitions.TestDefinition> definitions() {
98107
return Stream.of(

0 commit comments

Comments
 (0)