diff --git a/.gitignore b/.gitignore index 5a02738e2b..7836f997a4 100644 --- a/.gitignore +++ b/.gitignore @@ -20,7 +20,9 @@ examples/**/go.work examples/**/go.work.sum testdata/**/go.work testdata/**/go.work.sum +**/testdata/**/ftl-module-schema/ go-runtime/schema/testdata/test/test.go +.cache # Leaving old _ftl for now to avoid old stuff getting checked in **/testdata/**/_ftl diff --git a/Justfile b/Justfile index 1d1441cc7a..8bd9e6aa3e 100644 --- a/Justfile +++ b/Justfile @@ -65,8 +65,8 @@ build +tools: build-protos build-zips build-frontend build-backend: just build ftl ftl-controller ftl-runner -build-java: - mvn -f java-runtime/ftl-runtime install +build-java *args: + mvn -f java-runtime/ftl-runtime install {{args}} export DATABASE_URL := "postgres://postgres:secret@localhost:15432/ftl?sslmode=disable" diff --git a/backend/controller/leases/lease_integration_test.go b/backend/controller/leases/lease_integration_test.go index 3af001258c..64f2d7bba3 100644 --- a/backend/controller/leases/lease_integration_test.go +++ b/backend/controller/leases/lease_integration_test.go @@ -19,10 +19,11 @@ import ( func TestLease(t *testing.T) { in.Run(t, + in.WithLanguages("go", "java"), in.CopyModule("leases"), in.Build("leases"), // checks if leases work in a unit test environment - in.ExecModuleTest("leases"), + in.IfLanguage("go", in.ExecModuleTest("leases")), in.Deploy("leases"), // checks if it leases work with a real controller func(t testing.TB, ic in.TestContext) { @@ -34,6 +35,7 @@ func TestLease(t *testing.T) { Verb: &schemapb.Ref{Module: "leases", Name: "acquire"}, Body: []byte("{}"), })) + assert.NoError(t, err) if respErr := resp.Msg.GetError(); respErr != nil { return fmt.Errorf("received error on first call: %v", respErr) } diff --git a/backend/controller/leases/testdata/java/leases/ftl.toml b/backend/controller/leases/testdata/java/leases/ftl.toml new file mode 100644 index 0000000000..970a945305 --- /dev/null +++ b/backend/controller/leases/testdata/java/leases/ftl.toml @@ -0,0 +1,2 @@ +module = "leases" +language = "java" diff --git a/backend/controller/leases/testdata/java/leases/pom.xml b/backend/controller/leases/testdata/java/leases/pom.xml new file mode 100644 index 0000000000..dd938866c6 --- /dev/null +++ b/backend/controller/leases/testdata/java/leases/pom.xml @@ -0,0 +1,141 @@ + + + 4.0.0 + xyz.block.ftl.examples + leases + 1.0-SNAPSHOT + + + 1.0-SNAPSHOT + 3.13.0 + 2.0.0 + 17 + UTF-8 + UTF-8 + quarkus-bom + io.quarkus.platform + 3.12.3 + true + 3.2.5 + + + + + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + + + + + + + xyz.block + ftl-java-runtime + 1.0-SNAPSHOT + + + io.quarkus + quarkus-kotlin + + + io.quarkus + quarkus-jackson + + + io.quarkus + quarkus-rest-jackson + + + io.quarkus + quarkus-junit5 + test + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + io.rest-assured + kotlin-extensions + test + + + + + + + ${quarkus.platform.group-id} + quarkus-maven-plugin + ${quarkus.platform.version} + true + + + + build + generate-code + generate-code-tests + native-image-agent + + + + + + maven-compiler-plugin + ${compiler-plugin.version} + + + -parameters + + + + + maven-surefire-plugin + ${surefire-plugin.version} + + + org.jboss.logmanager.LogManager + ${maven.home} + + + + + maven-failsafe-plugin + ${surefire-plugin.version} + + + + integration-test + verify + + + + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + + + native + + + native + + + + false + true + + + + diff --git a/backend/controller/leases/testdata/java/leases/src/main/java/xyz/block/ftl/java/test/leases/TestLeases.java b/backend/controller/leases/testdata/java/leases/src/main/java/xyz/block/ftl/java/test/leases/TestLeases.java new file mode 100644 index 0000000000..e9fd132a7c --- /dev/null +++ b/backend/controller/leases/testdata/java/leases/src/main/java/xyz/block/ftl/java/test/leases/TestLeases.java @@ -0,0 +1,22 @@ +package xyz.block.ftl.java.test.leases; + +import io.quarkus.logging.Log; +import xyz.block.ftl.Export; +import xyz.block.ftl.LeaseClient; +import xyz.block.ftl.Verb; + +import java.time.Duration; + +public class TestLeases { + + @Export + @Verb + public void acquire(LeaseClient leaseClient) throws Exception { + Log.info("Acquiring lease"); + try (var lease = leaseClient.acquireLease(Duration.ofSeconds(10), "lease")) { + Log.info("Acquired lease"); + Thread.sleep(5000); + } + } + +} diff --git a/examples/kotlin/echo/pom.xml b/examples/kotlin/echo/pom.xml index f4a3a46581..9fee587a75 100644 --- a/examples/kotlin/echo/pom.xml +++ b/examples/kotlin/echo/pom.xml @@ -3,7 +3,7 @@ 4.0.0 xyz.block.ftl.examples echo - 1.0.0-SNAPSHOT + 1.0-SNAPSHOT 1.0-SNAPSHOT @@ -35,7 +35,7 @@ xyz.block ftl-java-runtime - 1.0.0-SNAPSHOT + 1.0-SNAPSHOT io.quarkus diff --git a/examples/kotlin/time/pom.xml b/examples/kotlin/time/pom.xml index 92190cd367..a249eddc10 100644 --- a/examples/kotlin/time/pom.xml +++ b/examples/kotlin/time/pom.xml @@ -3,7 +3,7 @@ 4.0.0 xyz.block.ftl.examples time - 1.0.0-SNAPSHOT + 1.0-SNAPSHOT 1.0-SNAPSHOT @@ -35,7 +35,7 @@ xyz.block ftl-java-runtime - 1.0.0-SNAPSHOT + 1.0-SNAPSHOT io.quarkus diff --git a/integration/actions.go b/integration/actions.go index 7875fd7429..a3b4e76884 100644 --- a/integration/actions.go +++ b/integration/actions.go @@ -12,6 +12,7 @@ import ( "net/url" "os" "path/filepath" + "slices" "strings" "testing" "time" @@ -95,6 +96,17 @@ func Chain(actions ...Action) Action { } } +// SubTests runs a list of individual actions as separate tests +func SubTests(tests ...SubTest) Action { + return func(t testing.TB, ic TestContext) { + for _, test := range tests { + ic.Run(test.Name, func(t *testing.T) { + ic.AssertWithRetry(t, test.Action) + }) + } + } +} + // Repeat an action N times. func Repeat(n int, action Action) Action { return func(t testing.TB, ic TestContext) { @@ -503,6 +515,18 @@ func HttpCall(method string, path string, headers map[string][]string, body []by } } +func IfLanguage(language string, action Action) Action { + return IfLanguages(action, language) +} + +func IfLanguages(action Action, languages ...string) Action { + return func(t testing.TB, ic TestContext) { + if slices.Contains(languages, ic.language) { + action(t, ic) + } + } +} + // Run "go test" in the given module. func ExecModuleTest(module string) Action { return Chdir(module, Exec("go", "test", "./...")) diff --git a/integration/harness.go b/integration/harness.go index b9a192ecc2..29bcf9332a 100644 --- a/integration/harness.go +++ b/integration/harness.go @@ -9,6 +9,7 @@ import ( "fmt" "os" "path/filepath" + "slices" "sync" "syscall" "testing" @@ -78,8 +79,10 @@ func WithEnvar(key, value string) Option { } } -// WithJava is a Run* option that ensures the Java runtime is built. -func WithJava() Option { +// WithJavaBuild is a Run* option that ensures the Java runtime is built. +// If the test languages contain java this is not necessary, as it is implied +// Note that this will not actually add Java as a language under test +func WithJavaBuild() Option { return func(o *options) { o.requireJava = true } @@ -176,13 +179,14 @@ func run(t *testing.T, actionsOrOptions ...ActionOrOption) { Infof("Building ftl") err = ftlexec.Command(ctx, log.Debug, rootDir, "just", "build", "ftl").RunBuffered(ctx) assert.NoError(t, err) - if opts.requireJava { - err = ftlexec.Command(ctx, log.Debug, rootDir, "just", "build-java").RunBuffered(ctx) + if opts.requireJava || slices.Contains(opts.languages, "java") { + err = ftlexec.Command(ctx, log.Debug, rootDir, "just", "build-java", "-DskipTests").RunBuffered(ctx) assert.NoError(t, err) } }) for _, language := range opts.languages { + ctx, done := context.WithCancel(ctx) t.Run(language, func(t *testing.T) { verbs := rpc.Dial(ftlv1connect.NewVerbServiceClient, "http://localhost:8892", log.Debug) @@ -203,6 +207,8 @@ func run(t *testing.T, actionsOrOptions ...ActionOrOption) { workDir: tmpDir, binDir: binDir, Verbs: verbs, + realT: t, + language: language, } if opts.startController { @@ -222,6 +228,7 @@ func run(t *testing.T, actionsOrOptions ...ActionOrOption) { ic.AssertWithRetry(t, action) } }) + done() } } @@ -235,10 +242,18 @@ type TestContext struct { testData string // Path to the "bin" directory. binDir string + // The Language under test + language string Controller ftlv1connect.ControllerServiceClient Console pbconsoleconnect.ConsoleServiceClient Verbs ftlv1connect.VerbServiceClient + + realT *testing.T +} + +func (i TestContext) Run(name string, f func(t *testing.T)) bool { + return i.realT.Run(name, f) } // WorkingDir returns the temporary directory the test is executing in. @@ -283,6 +298,11 @@ func (i TestContext) runAssertionOnce(t testing.TB, assertion Action) (err error type Action func(t testing.TB, ic TestContext) +type SubTest struct { + Name string + Action Action +} + type logWriter struct { mu sync.Mutex logger interface{ Log(...any) } diff --git a/java-runtime/ftl-runtime/deployment/pom.xml b/java-runtime/ftl-runtime/deployment/pom.xml index db056f496e..e0cf812d51 100644 --- a/java-runtime/ftl-runtime/deployment/pom.xml +++ b/java-runtime/ftl-runtime/deployment/pom.xml @@ -5,7 +5,7 @@ xyz.block ftl-java-runtime-parent - 1.0.0-SNAPSHOT + 1.0-SNAPSHOT ftl-java-runtime-deployment Ftl Java Runtime - Deployment diff --git a/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/FTLCodeGenerator.java b/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/FTLCodeGenerator.java index dce8144fed..95c7f1b6b4 100644 --- a/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/FTLCodeGenerator.java +++ b/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/FTLCodeGenerator.java @@ -4,7 +4,7 @@ import java.lang.annotation.Retention; import java.nio.file.Files; import java.nio.file.Path; -import java.time.Instant; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -116,20 +116,20 @@ public boolean trigger(CodeGenContext context) throws CodeGenException { typeBuilder.addSuperinterface(ClassName.get(VerbClientEmpty.class)); } else if (verb.getRequest().hasUnit()) { typeBuilder.addSuperinterface(ParameterizedTypeName.get(ClassName.get(VerbClientSource.class), - toJavaTypeName(verb.getResponse(), typeAliasMap))); + toJavaTypeName(verb.getResponse(), typeAliasMap, true))); typeBuilder.addMethod(MethodSpec.methodBuilder("call") .returns(toAnnotatedJavaTypeName(verb.getResponse(), typeAliasMap)) .addModifiers(Modifier.ABSTRACT).addModifiers(Modifier.PUBLIC).build()); } else if (verb.getResponse().hasUnit()) { typeBuilder.addSuperinterface(ParameterizedTypeName.get(ClassName.get(VerbClientSink.class), - toJavaTypeName(verb.getRequest(), typeAliasMap))); + toJavaTypeName(verb.getRequest(), typeAliasMap, true))); typeBuilder.addMethod(MethodSpec.methodBuilder("call").returns(TypeName.VOID) .addParameter(toAnnotatedJavaTypeName(verb.getRequest(), typeAliasMap), "value") .addModifiers(Modifier.ABSTRACT).addModifiers(Modifier.PUBLIC).build()); } else { typeBuilder.addSuperinterface(ParameterizedTypeName.get(ClassName.get(VerbClient.class), - toJavaTypeName(verb.getRequest(), typeAliasMap), - toJavaTypeName(verb.getResponse(), typeAliasMap))); + toJavaTypeName(verb.getRequest(), typeAliasMap, true), + toJavaTypeName(verb.getResponse(), typeAliasMap, true))); typeBuilder.addMethod(MethodSpec.methodBuilder("call") .returns(toAnnotatedJavaTypeName(verb.getResponse(), typeAliasMap)) .addParameter(toAnnotatedJavaTypeName(verb.getRequest(), typeAliasMap), "value") @@ -274,29 +274,29 @@ private String toJavaName(String name) { } private TypeName toAnnotatedJavaTypeName(Type type, Map typeAliasMap) { - var results = toJavaTypeName(type, typeAliasMap); + var results = toJavaTypeName(type, typeAliasMap, false); if (type.hasRef() || type.hasArray() || type.hasBytes() || type.hasString() || type.hasMap() || type.hasTime()) { return results.annotated(AnnotationSpec.builder(NotNull.class).build()); } return results; } - private TypeName toJavaTypeName(Type type, Map typeAliasMap) { + private TypeName toJavaTypeName(Type type, Map typeAliasMap, boolean boxPrimitives) { if (type.hasArray()) { return ParameterizedTypeName.get(ClassName.get(List.class), - toJavaTypeName(type.getArray().getElement(), typeAliasMap)); + toJavaTypeName(type.getArray().getElement(), typeAliasMap, false)); } else if (type.hasString()) { return ClassName.get(String.class); } else if (type.hasOptional()) { - return toJavaTypeName(type.getOptional().getType(), typeAliasMap); + // Always box for optional, as normal primities can't be null + return toJavaTypeName(type.getOptional().getType(), typeAliasMap, true); } else if (type.hasRef()) { if (type.getRef().getModule().isEmpty()) { return TypeVariableName.get(type.getRef().getName()); } - Key key = new Key(type.getRef().getModule(), type.getRef().getName()); if (typeAliasMap.containsKey(key)) { - return toJavaTypeName(typeAliasMap.get(key), typeAliasMap); + return toJavaTypeName(typeAliasMap.get(key), typeAliasMap, boxPrimitives); } var params = type.getRef().getTypeParametersList(); ClassName className = ClassName.get(PACKAGE_PREFIX + type.getRef().getModule(), type.getRef().getName()); @@ -304,22 +304,23 @@ private TypeName toJavaTypeName(Type type, Map typeAliasMap) { return className; } List javaTypes = params.stream() - .map(s -> s.hasUnit() ? WildcardTypeName.subtypeOf(Object.class) : toJavaTypeName(s, typeAliasMap)) + .map(s -> s.hasUnit() ? WildcardTypeName.subtypeOf(Object.class) : toJavaTypeName(s, typeAliasMap, true)) .toList(); return ParameterizedTypeName.get(className, javaTypes.toArray(new TypeName[javaTypes.size()])); } else if (type.hasMap()) { - return ParameterizedTypeName.get(ClassName.get(Map.class), toJavaTypeName(type.getMap().getKey(), typeAliasMap), - toJavaTypeName(type.getMap().getValue(), typeAliasMap)); + return ParameterizedTypeName.get(ClassName.get(Map.class), + toJavaTypeName(type.getMap().getKey(), typeAliasMap, true), + toJavaTypeName(type.getMap().getValue(), typeAliasMap, true)); } else if (type.hasTime()) { - return ClassName.get(Instant.class); + return ClassName.get(ZonedDateTime.class); } else if (type.hasInt()) { - return TypeName.LONG; + return boxPrimitives ? ClassName.get(Long.class) : TypeName.LONG; } else if (type.hasUnit()) { return TypeName.VOID; } else if (type.hasBool()) { - return TypeName.BOOLEAN; + return boxPrimitives ? ClassName.get(Boolean.class) : TypeName.BOOLEAN; } else if (type.hasFloat()) { - return TypeName.DOUBLE; + return boxPrimitives ? ClassName.get(Double.class) : TypeName.DOUBLE; } else if (type.hasBytes()) { return ArrayTypeName.of(TypeName.BYTE); } else if (type.hasAny()) { diff --git a/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/FtlProcessor.java b/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/FtlProcessor.java index 538ecd739d..2703a30bec 100644 --- a/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/FtlProcessor.java +++ b/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/FtlProcessor.java @@ -6,7 +6,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.PosixFilePermission; +import java.time.Instant; import java.time.OffsetDateTime; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.EnumSet; import java.util.HashMap; @@ -20,10 +22,12 @@ import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.ArrayType; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.ClassType; import org.jboss.jandex.DotName; import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.PrimitiveType; import org.jboss.jandex.VoidType; import org.jboss.logging.Logger; import org.jboss.resteasy.reactive.common.model.MethodParameter; @@ -85,6 +89,7 @@ import xyz.block.ftl.v1.CallRequest; import xyz.block.ftl.v1.schema.Array; import xyz.block.ftl.v1.schema.Bool; +import xyz.block.ftl.v1.schema.Bytes; import xyz.block.ftl.v1.schema.Data; import xyz.block.ftl.v1.schema.Decl; import xyz.block.ftl.v1.schema.Field; @@ -123,6 +128,9 @@ class FtlProcessor { public static final DotName OFFSET_DATE_TIME = DotName.createSimple(OffsetDateTime.class.getName()); public static final DotName GENERATED_REF = DotName.createSimple(GeneratedRef.class); public static final DotName LEASE_CLIENT = DotName.createSimple(LeaseClient.class); + public static final DotName INSTANT = DotName.createSimple(Instant.class); + public static final DotName ZONED_DATE_TIME = DotName.createSimple(ZonedDateTime.class); + public static final DotName NOT_NULL = DotName.createSimple(NotNull.class); @BuildStep ModuleNameBuildItem moduleName(ApplicationInfoBuildItem applicationInfoBuildItem) { @@ -326,9 +334,11 @@ public void registerVerbs(CombinedIndexBuildItem index, output = outputTargetBuildItem.getOutputDirectory().resolve("main"); try (var out = Files.newOutputStream(output)) { - out.write(""" - #!/bin/bash - exec java -jar quarkus-app/quarkus-run.jar""".getBytes(StandardCharsets.UTF_8)); + out.write( + """ + #!/bin/bash + exec java $FTL_JVM_OPTS -jar quarkus-app/quarkus-run.jar""" + .getBytes(StandardCharsets.UTF_8)); } var perms = Files.getPosixFilePermissions(output); EnumSet newPerms = EnumSet.copyOf(perms); @@ -536,9 +546,33 @@ private static Class loadClass(org.jboss.jandex.Type param) throws ClassNotFo default: throw new RuntimeException("Unknown primitive type " + param.asPrimitiveType().primitive()); } - } else { - throw new RuntimeException("Unknown type " + param.kind()); + } else if (param.kind() == org.jboss.jandex.Type.Kind.ARRAY) { + ArrayType array = param.asArrayType(); + if (array.componentType().kind() == org.jboss.jandex.Type.Kind.PRIMITIVE) { + switch (array.componentType().asPrimitiveType().primitive()) { + case BOOLEAN: + return boolean[].class; + case BYTE: + return byte[].class; + case SHORT: + return short[].class; + case INT: + return int[].class; + case LONG: + return long[].class; + case FLOAT: + return float[].class; + case DOUBLE: + return double[].class; + case CHAR: + return char[].class; + default: + throw new RuntimeException("Unknown primitive type " + param.asPrimitiveType().primitive()); + } + } } + throw new RuntimeException("Unknown type " + param.kind()); + } /** @@ -592,13 +626,27 @@ private Type buildType(ExtractionContext context, org.jboss.jandex.Type type) { return Type.newBuilder().setUnit(Unit.newBuilder().build()).build(); } case ARRAY -> { + ArrayType arrayType = type.asArrayType(); + if (arrayType.componentType().kind() == org.jboss.jandex.Type.Kind.PRIMITIVE && arrayType + .componentType().asPrimitiveType().primitive() == PrimitiveType.Primitive.BYTE) { + return Type.newBuilder().setBytes(Bytes.newBuilder().build()).build(); + } return Type.newBuilder() - .setArray(Array.newBuilder().setElement(buildType(context, type.asArrayType().componentType())).build()) + .setArray(Array.newBuilder().setElement(buildType(context, arrayType.componentType())).build()) .build(); } case CLASS -> { var clazz = type.asClassType(); var info = context.index().getComputingIndex().getClassByName(clazz.name()); + + PrimitiveType unboxed = PrimitiveType.unbox(clazz); + if (unboxed != null) { + Type primitive = buildType(context, unboxed); + if (type.hasAnnotation(NOT_NULL)) { + return primitive; + } + return Type.newBuilder().setOptional(Optional.newBuilder().setType(primitive)).build(); + } if (info != null && info.hasDeclaredAnnotation(GENERATED_REF)) { var ref = info.declaredAnnotation(GENERATED_REF); return Type.newBuilder() @@ -612,6 +660,12 @@ private Type buildType(ExtractionContext context, org.jboss.jandex.Type type) { if (clazz.name().equals(OFFSET_DATE_TIME)) { return Type.newBuilder().setTime(Time.newBuilder().build()).build(); } + if (clazz.name().equals(INSTANT)) { + return Type.newBuilder().setTime(Time.newBuilder().build()).build(); + } + if (clazz.name().equals(ZONED_DATE_TIME)) { + return Type.newBuilder().setTime(Time.newBuilder().build()).build(); + } var existing = context.dataElements.get(new TypeKey(clazz.name().toString(), List.of())); if (existing != null) { return Type.newBuilder().setRef(existing).build(); @@ -636,6 +690,7 @@ private Type buildType(ExtractionContext context, org.jboss.jandex.Type type) { .setValue(buildType(context, paramType.arguments().get(0)))) .build(); } else if (paramType.name().equals(DotNames.OPTIONAL)) { + //TODO: optional kinda sucks return Type.newBuilder() .setOptional(Optional.newBuilder().setType(buildType(context, paramType.arguments().get(0)))) .build(); diff --git a/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/TopicsProcessor.java b/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/TopicsProcessor.java index 367393890a..5459411459 100644 --- a/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/TopicsProcessor.java +++ b/java-runtime/ftl-runtime/deployment/src/main/java/xyz/block/ftl/deployment/TopicsProcessor.java @@ -51,7 +51,7 @@ TopicsBuildItem handleTopics(CombinedIndexBuildItem index, BuildProducer> signatures = new LinkedHashSet<>(); + signatures.add(Map.entry(returnType.name().toString(), paramType.name().toString())); + signatures.add(Map.entry(Object.class.getName(), Object.class.getName())); + for (var method : iface.methods()) { + if (method.name().equals("call") && method.parameters().size() == 1) { + signatures.add(Map.entry(method.returnType().name().toString(), + method.parameters().get(0).type().name().toString())); + } + } + for (var sig : signatures) { + + var publish = cc.getMethodCreator("call", sig.getKey(), + sig.getValue()); + var helper = publish.invokeStaticMethod( + MethodDescriptor.ofMethod(VerbClientHelper.class, "instance", VerbClientHelper.class)); + var results = publish.invokeVirtualMethod( + MethodDescriptor.ofMethod(VerbClientHelper.class, "call", Object.class, String.class, + String.class, Object.class, Class.class, boolean.class, boolean.class), + helper, publish.load(name), publish.load(module), publish.getMethodParam(0), + publish.loadClass(returnType.name().toString()), publish.load(false), + publish.load(false)); + publish.returnValue(results); + } + clients.put(iface.name(), new VerbClientBuildItem.DiscoveredClients(name, module, cc.getClassName())); } @@ -113,24 +119,25 @@ VerbClientBuildItem handleTopics(CombinedIndexBuildItem index, BuildProducer signatures = new LinkedHashSet<>(); + signatures.add(paramType.name().toString()); + signatures.add(Object.class.getName()); + for (var method : iface.methods()) { + if (method.name().equals("call") && method.parameters().size() == 1) { + signatures.add(method.parameters().get(0).type().name().toString()); + } + } + for (var sig : signatures) { + var publish = cc.getMethodCreator("call", void.class, sig); + var helper = publish.invokeStaticMethod( + MethodDescriptor.ofMethod(VerbClientHelper.class, "instance", VerbClientHelper.class)); + publish.invokeVirtualMethod( + MethodDescriptor.ofMethod(VerbClientHelper.class, "call", Object.class, String.class, + String.class, Object.class, Class.class, boolean.class, boolean.class), + helper, publish.load(name), publish.load(module), publish.getMethodParam(0), + publish.loadClass(Void.class), publish.load(false), publish.load(false)); + publish.returnVoid(); + } clients.put(iface.name(), new VerbClientBuildItem.DiscoveredClients(name, module, cc.getClassName())); } @@ -150,25 +157,27 @@ VerbClientBuildItem handleTopics(CombinedIndexBuildItem index, BuildProducer signatures = new LinkedHashSet<>(); + signatures.add(returnType.name().toString()); + signatures.add(Object.class.getName()); + for (var method : iface.methods()) { + if (method.name().equals("call") && method.parameters().size() == 0) { + signatures.add(method.returnType().name().toString()); + } + } + for (var sig : signatures) { + var publish = cc.getMethodCreator("call", sig); + var helper = publish.invokeStaticMethod( + MethodDescriptor.ofMethod(VerbClientHelper.class, "instance", VerbClientHelper.class)); + var results = publish.invokeVirtualMethod( + MethodDescriptor.ofMethod(VerbClientHelper.class, "call", Object.class, String.class, + String.class, Object.class, Class.class, boolean.class, boolean.class), + helper, publish.load(name), publish.load(module), publish.loadNull(), + publish.loadClass(returnType.name().toString()), publish.load(false), + publish.load(false)); + publish.returnValue(results); + } - publish = cc.getMethodCreator("call", Object.class); - helper = publish.invokeStaticMethod( - MethodDescriptor.ofMethod(VerbClientHelper.class, "instance", VerbClientHelper.class)); - results = publish.invokeVirtualMethod( - MethodDescriptor.ofMethod(VerbClientHelper.class, "call", Object.class, String.class, - String.class, Object.class, Class.class, boolean.class, boolean.class), - helper, publish.load(name), publish.load(module), publish.loadNull(), - publish.loadClass(returnType.name().toString()), publish.load(false), publish.load(false)); - publish.returnValue(results); clients.put(iface.name(), new VerbClientBuildItem.DiscoveredClients(name, module, cc.getClassName())); } diff --git a/java-runtime/ftl-runtime/integration-tests/pom.xml b/java-runtime/ftl-runtime/integration-tests/pom.xml index 11c116b7c7..823f3a9336 100644 --- a/java-runtime/ftl-runtime/integration-tests/pom.xml +++ b/java-runtime/ftl-runtime/integration-tests/pom.xml @@ -5,7 +5,7 @@ xyz.block ftl-java-runtime-parent - 1.0.0-SNAPSHOT + 1.0-SNAPSHOT ftl-java-runtime-integration-tests Ftl Java Runtime - Integration Tests diff --git a/java-runtime/ftl-runtime/integration-tests/src/main/java/xyz/block/ftl/java/runtime/it/FtlJavaRuntimeResource.java b/java-runtime/ftl-runtime/integration-tests/src/main/java/xyz/block/ftl/java/runtime/it/FtlJavaRuntimeResource.java index cf91946a90..55f9fe3007 100644 --- a/java-runtime/ftl-runtime/integration-tests/src/main/java/xyz/block/ftl/java/runtime/it/FtlJavaRuntimeResource.java +++ b/java-runtime/ftl-runtime/integration-tests/src/main/java/xyz/block/ftl/java/runtime/it/FtlJavaRuntimeResource.java @@ -43,4 +43,9 @@ public String hello(String name, EchoClient echoClient) { public void publish(Person person, MyTopic topic) { topic.publish(person); } + + @Verb + public byte[] bytes(byte[] bytes) { + return bytes; + } } diff --git a/java-runtime/ftl-runtime/integration-tests/src/main/java/xyz/block/ftl/java/runtime/it/MyTopic.java b/java-runtime/ftl-runtime/integration-tests/src/main/java/xyz/block/ftl/java/runtime/it/MyTopic.java index f5b381f0da..0e5ef1e996 100644 --- a/java-runtime/ftl-runtime/integration-tests/src/main/java/xyz/block/ftl/java/runtime/it/MyTopic.java +++ b/java-runtime/ftl-runtime/integration-tests/src/main/java/xyz/block/ftl/java/runtime/it/MyTopic.java @@ -5,6 +5,6 @@ import xyz.block.ftl.TopicDefinition; @Export -@TopicDefinition(name = "testTopic") +@TopicDefinition(value = "testTopic") public interface MyTopic extends Topic { } diff --git a/java-runtime/ftl-runtime/integration-tests/src/test/java/xyz/block/ftl/java/runtime/it/FtlJavaRuntimeResourceTest.java b/java-runtime/ftl-runtime/integration-tests/src/test/java/xyz/block/ftl/java/runtime/it/FtlJavaRuntimeResourceTest.java index ef47c2b6a1..77b17a2df0 100644 --- a/java-runtime/ftl-runtime/integration-tests/src/test/java/xyz/block/ftl/java/runtime/it/FtlJavaRuntimeResourceTest.java +++ b/java-runtime/ftl-runtime/integration-tests/src/test/java/xyz/block/ftl/java/runtime/it/FtlJavaRuntimeResourceTest.java @@ -33,6 +33,10 @@ public class FtlJavaRuntimeResourceTest { @Inject HelloClient helloClient; + @FTLManaged + @Inject + BytesClient bytesClient; + @Test public void testHelloEndpoint() { TestVerbServer.registerFakeVerb("echo", "echo", new Function() { @@ -52,6 +56,11 @@ public void testTopic() { myVerbClient.call(new Person("Stuart", "Douglas")); } + @Test + public void testBytesSerialization() { + Assertions.assertArrayEquals(new byte[] { 1, 2 }, bytesClient.call(new byte[] { 1, 2 })); + } + @VerbClientDefinition(name = "publish") interface PublishVerbClient extends VerbClientSink { } @@ -59,4 +68,8 @@ interface PublishVerbClient extends VerbClientSink { @VerbClientDefinition(name = "hello") interface HelloClient extends VerbClient { } + + @VerbClientDefinition(name = "bytes") + interface BytesClient extends VerbClient { + } } diff --git a/java-runtime/ftl-runtime/pom.xml b/java-runtime/ftl-runtime/pom.xml index 4ff40b3f4f..b4b63c22cc 100644 --- a/java-runtime/ftl-runtime/pom.xml +++ b/java-runtime/ftl-runtime/pom.xml @@ -4,7 +4,7 @@ 4.0.0 xyz.block ftl-java-runtime-parent - 1.0.0-SNAPSHOT + 1.0-SNAPSHOT pom Ftl Java Runtime - Parent diff --git a/java-runtime/ftl-runtime/runtime/pom.xml b/java-runtime/ftl-runtime/runtime/pom.xml index 9ea5ec0c64..63b71e7c15 100644 --- a/java-runtime/ftl-runtime/runtime/pom.xml +++ b/java-runtime/ftl-runtime/runtime/pom.xml @@ -6,7 +6,7 @@ xyz.block ftl-java-runtime-parent - 1.0.0-SNAPSHOT + 1.0-SNAPSHOT ftl-java-runtime Ftl Java Runtime - Runtime diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/LeaseClient.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/LeaseClient.java index 4f5cc51242..759e7785f0 100644 --- a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/LeaseClient.java +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/LeaseClient.java @@ -7,5 +7,13 @@ */ public interface LeaseClient { - void acquireLease(Duration duration, String... keys) throws LeaseFailedException; + /** + * Acquire a lease for the given keys. The lease will be held for the given duration. + * + * @param duration The time to acquire the lease for + * @param keys The lease keys + * @return A handle that can be used to release the lease + * @throws LeaseFailedException + */ + LeaseHandle acquireLease(Duration duration, String... keys) throws LeaseFailedException; } diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/LeaseHandle.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/LeaseHandle.java new file mode 100644 index 0000000000..6d1eff7a7f --- /dev/null +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/LeaseHandle.java @@ -0,0 +1,7 @@ +package xyz.block.ftl; + +public interface LeaseHandle extends AutoCloseable { + + public void close(); + +} diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/Topic.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/Topic.java index 16170fe24e..2b1d2c66ed 100644 --- a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/Topic.java +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/Topic.java @@ -8,5 +8,5 @@ */ public interface Topic { - void publish(T object); + void publish(T object); } diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/TopicDefinition.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/TopicDefinition.java index a6482006d6..7647133cc0 100644 --- a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/TopicDefinition.java +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/TopicDefinition.java @@ -12,6 +12,6 @@ * * @return The name of the topic */ - String name(); + String value(); } diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/FTLController.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/FTLController.java index 25eb62fb65..dbf1d3d7ff 100644 --- a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/FTLController.java +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/FTLController.java @@ -3,11 +3,10 @@ import java.net.URI; import java.time.Duration; import java.util.Arrays; -import java.util.Deque; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import java.util.concurrent.LinkedBlockingDeque; +import jakarta.annotation.PreDestroy; import jakarta.inject.Singleton; import org.eclipse.microprofile.config.inject.ConfigProperty; @@ -20,6 +19,7 @@ import io.quarkus.runtime.Startup; import xyz.block.ftl.LeaseClient; import xyz.block.ftl.LeaseFailedException; +import xyz.block.ftl.LeaseHandle; import xyz.block.ftl.v1.AcquireLeaseRequest; import xyz.block.ftl.v1.AcquireLeaseResponse; import xyz.block.ftl.v1.CallRequest; @@ -36,12 +36,11 @@ public class FTLController implements LeaseClient { private static final Logger log = Logger.getLogger(FTLController.class); final String moduleName; - private StreamObserver leaseClient; - private final Deque> leaseWaiters = new LinkedBlockingDeque<>(); private Throwable currentError; private volatile ModuleContextResponse moduleContextResponse; private boolean waiters = false; + private volatile boolean closed = false; final VerbServiceGrpc.VerbServiceStub verbService; final StreamObserver moduleObserver = new StreamObserver<>() { @@ -68,14 +67,22 @@ public void onError(Throwable throwable) { waiters = false; } } + if (!closed) { + verbService.getModuleContext(ModuleContextRequest.newBuilder().setModule(moduleName).build(), moduleObserver); + } } @Override public void onCompleted() { - verbService.getModuleContext(ModuleContextRequest.newBuilder().setModule(moduleName).build(), moduleObserver); + onError(new RuntimeException("connection closed")); } }; + @PreDestroy + void shutdown() { + + } + public FTLController(@ConfigProperty(name = "ftl.endpoint", defaultValue = "http://localhost:8892") URI uri, @ConfigProperty(name = "ftl.module.name") String moduleName) { this.moduleName = moduleName; @@ -86,29 +93,6 @@ public FTLController(@ConfigProperty(name = "ftl.endpoint", defaultValue = "http var channel = channelBuilder.build(); verbService = VerbServiceGrpc.newStub(channel); verbService.getModuleContext(ModuleContextRequest.newBuilder().setModule(moduleName).build(), moduleObserver); - synchronized (this) { - this.leaseClient = verbService.acquireLease(new StreamObserver() { - @Override - public void onNext(AcquireLeaseResponse value) { - leaseWaiters.pop().complete(null); - } - - @Override - public void onError(Throwable t) { - leaseWaiters.pop().completeExceptionally(t); - } - - @Override - public void onCompleted() { - synchronized (FTLController.this) { - while (!leaseWaiters.isEmpty()) { - leaseWaiters.pop().completeExceptionally(new RuntimeException("connection closed")); - } - leaseClient = verbService.acquireLease(this); - } - } - }); - } } public byte[] getSecret(String secretName) { @@ -187,21 +171,42 @@ public void onCompleted() { } } - public void acquireLease(Duration duration, String... keys) throws LeaseFailedException { + public LeaseHandle acquireLease(Duration duration, String... keys) throws LeaseFailedException { CompletableFuture cf = new CompletableFuture<>(); - synchronized (this) { - leaseWaiters.push(cf); - leaseClient.onNext(AcquireLeaseRequest.newBuilder().setModule(moduleName) - .addAllKey(Arrays.asList(keys)) - .setTtl(com.google.protobuf.Duration.newBuilder() - .setSeconds(duration.toSeconds())) - .build()); - } + var client = verbService.acquireLease(new StreamObserver() { + @Override + public void onNext(AcquireLeaseResponse value) { + cf.complete(null); + } + + @Override + public void onError(Throwable t) { + cf.completeExceptionally(t); + } + + @Override + public void onCompleted() { + if (!cf.isDone()) { + onError(new RuntimeException("stream closed")); + } + } + }); + client.onNext(AcquireLeaseRequest.newBuilder().setModule(moduleName) + .addAllKey(Arrays.asList(keys)) + .setTtl(com.google.protobuf.Duration.newBuilder() + .setSeconds(duration.toSeconds())) + .build()); try { cf.get(); } catch (Exception e) { - throw new LeaseFailedException(e); + throw new LeaseFailedException("lease already held", e); } + return new LeaseHandle() { + @Override + public void close() { + client.onCompleted(); + } + }; } private ModuleContextResponse getModuleContext() { diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/FTLRecorder.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/FTLRecorder.java index 5ee6413076..9b4b03f7b1 100644 --- a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/FTLRecorder.java +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/FTLRecorder.java @@ -26,6 +26,7 @@ public void registerVerb(String module, String verbName, String methodName, List //TODO: this sucks try { var method = verbHandlerClass.getDeclaredMethod(methodName, parameterTypes.toArray(new Class[0])); + method.setAccessible(true); var handlerInstance = Arc.container().instance(verbHandlerClass); Arc.container().instance(VerbRegistry.class).get().register(module, verbName, handlerInstance, method, paramMappers, allowNullReturn); diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/JsonSerializationConfig.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/JsonSerializationConfig.java index b17d9f94fd..5f19e2ca2d 100644 --- a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/JsonSerializationConfig.java +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/JsonSerializationConfig.java @@ -1,9 +1,23 @@ package xyz.block.ftl.runtime; +import java.io.IOException; +import java.util.Base64; + import jakarta.enterprise.event.Observes; +import jakarta.json.stream.JsonGenerator; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; import io.quarkus.runtime.StartupEvent; @@ -14,5 +28,41 @@ public class JsonSerializationConfig { void startup(@Observes StartupEvent event, ObjectMapper mapper) { mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + + SimpleModule module = new SimpleModule("ByteArraySerializer", new Version(1, 0, 0, "")); + module.addSerializer(byte[].class, new ByteArraySerializer()); + module.addDeserializer(byte[].class, new ByteArrayDeserializer()); + mapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE); + mapper.registerModule(module); + } + + public static class ByteArraySerializer extends StdSerializer { + + public ByteArraySerializer() { + super(byte[].class); + } + + @Override + public void serialize(byte[] value, com.fasterxml.jackson.core.JsonGenerator gen, SerializerProvider provider) + throws IOException { + gen.writeString(Base64.getEncoder().encodeToString(value)); + + } } + + public static class ByteArrayDeserializer extends StdDeserializer { + + public ByteArrayDeserializer() { + super(byte[].class); + } + + @Override + public byte[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException { + JsonNode node = p.getCodec().readTree(p); + String base64 = node.asText(); + return Base64.getDecoder().decode(base64); + } + + } + } diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/VerbHandler.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/VerbHandler.java index 714f197a05..8d5103c2ad 100644 --- a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/VerbHandler.java +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/VerbHandler.java @@ -18,9 +18,13 @@ public VerbHandler(VerbRegistry registry) { @Override public void call(CallRequest request, StreamObserver responseObserver) { - var response = registry.invoke(request); - responseObserver.onNext(response); - responseObserver.onCompleted(); + try { + var response = registry.invoke(request); + responseObserver.onNext(response); + responseObserver.onCompleted(); + } catch (Exception e) { + responseObserver.onError(e); + } } @Override diff --git a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/VerbRegistry.java b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/VerbRegistry.java index 42166c0719..2208fe51ea 100644 --- a/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/VerbRegistry.java +++ b/java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/runtime/VerbRegistry.java @@ -1,6 +1,7 @@ package xyz.block.ftl.runtime; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.util.List; @@ -91,9 +92,15 @@ public CallResponse handle(CallRequest in) { var mappedResponse = mapper.writer().writeValueAsBytes(ret); return CallResponse.newBuilder().setBody(ByteString.copyFrom(mappedResponse)).build(); } - } catch (Exception e) { - log.errorf(e, "Failed to invoke verb %s.%s", in.getVerb().getModule(), in.getVerb().getName()); - return CallResponse.newBuilder().setError(CallResponse.Error.newBuilder().setMessage(e.getMessage()).build()) + } catch (Throwable e) { + if (e.getClass() == InvocationTargetException.class) { + e = e.getCause(); + } + var message = String.format("Failed to invoke verb %s.%s", in.getVerb().getModule(), in.getVerb().getName()); + log.error(message, e); + return CallResponse.newBuilder() + .setError(CallResponse.Error.newBuilder().setStack(e.toString()) + .setMessage(message + " " + e.getMessage()).build()) .build(); } } diff --git a/java-runtime/ftl-runtime/test-framework/pom.xml b/java-runtime/ftl-runtime/test-framework/pom.xml index c66028219f..df940f2032 100644 --- a/java-runtime/ftl-runtime/test-framework/pom.xml +++ b/java-runtime/ftl-runtime/test-framework/pom.xml @@ -5,7 +5,7 @@ xyz.block ftl-java-runtime-parent - 1.0.0-SNAPSHOT + 1.0-SNAPSHOT ftl-java-test-framework Ftl Java Runtime - Test Framework diff --git a/java-runtime/java_integration_test.go b/java-runtime/java_integration_test.go index 0680b4989f..f2f3cb899c 100644 --- a/java-runtime/java_integration_test.go +++ b/java-runtime/java_integration_test.go @@ -8,52 +8,159 @@ import ( "github.com/alecthomas/assert/v2" + "github.com/TBD54566975/ftl/go-runtime/ftl" in "github.com/TBD54566975/ftl/integration" "github.com/alecthomas/repr" ) func TestJavaToGoCall(t *testing.T) { - in.Run(t, - in.WithJava(), - in.CopyModule("gomodule"), - in.CopyDir("javamodule", "javamodule"), - in.Deploy("gomodule"), - in.Deploy("javamodule"), - in.Call("javamodule", "timeVerb", in.Obj{}, func(t testing.TB, response in.Obj) { - message, ok := response["time"].(string) - assert.True(t, ok, "time is not a string: %s", repr.String(response)) - result, err := time.Parse(time.RFC3339, message) - assert.NoError(t, err, "time is not a valid RFC3339 time: %s", message) - assert.True(t, result.After(time.Now().Add(-time.Minute)), "time is not recent: %s", message) - }), - // We call both the go and pass through Java versions - // To make sure the response is the same - in.Call("gomodule", "emptyVerb", in.Obj{}, func(t testing.TB, response in.Obj) { - assert.Equal(t, map[string]any{}, response, "expecting empty response, got %s", repr.String(response)) - }), - in.Call("javamodule", "emptyVerb", in.Obj{}, func(t testing.TB, response in.Obj) { - assert.Equal(t, map[string]any{}, response, "expecting empty response, got %s", repr.String(response)) - }), - in.Call("gomodule", "sinkVerb", "ignored", func(t testing.TB, response in.Obj) { + + exampleObject := TestObject{ + IntField: 43, + FloatField: .2, + StringField: "obj", + BytesField: []byte{87, 2, 9}, + BoolField: true, + TimeField: time.Now().UTC(), + ArrayField: []string{"foo", "bar"}, + MapField: map[string]string{"gar": "har"}, + } + exampleOptionalFieldsObject := TestObjectOptionalFields{ + IntField: ftl.Some[int](43), + FloatField: ftl.Some[float64](.2), + StringField: ftl.Some[string]("obj"), + BytesField: ftl.Some[[]byte]([]byte{87, 2, 9}), + BoolField: ftl.Some[bool](true), + TimeField: ftl.Some[time.Time](time.Now().UTC()), + ArrayField: ftl.Some[[]string]([]string{"foo", "bar"}), + MapField: ftl.Some[map[string]string](map[string]string{"gar": "har"}), + } + tests := []in.SubTest{} + tests = append(tests, PairedTest("emptyVerb", func(module string) in.Action { + return in.Call(module, "emptyVerb", in.Obj{}, func(t testing.TB, response in.Obj) { assert.Equal(t, map[string]any{}, response, "expecting empty response, got %s", repr.String(response)) - }), - in.Call("javamodule", "sinkVerb", "ignored", func(t testing.TB, response in.Obj) { + }) + })...) + tests = append(tests, PairedTest("sinkVerb", func(module string) in.Action { + return in.Call(module, "sinkVerb", "ignored", func(t testing.TB, response in.Obj) { assert.Equal(t, map[string]any{}, response, "expecting empty response, got %s", repr.String(response)) - }), - in.Call("gomodule", "sourceVerb", in.Obj{}, func(t testing.TB, response string) { + }) + })...) + tests = append(tests, PairedTest("sourceVerb", func(module string) in.Action { + return in.Call(module, "sourceVerb", in.Obj{}, func(t testing.TB, response string) { assert.Equal(t, "Source Verb", response, "expecting empty response, got %s", response) - }), - in.Call("javamodule", "sourceVerb", in.Obj{}, func(t testing.TB, response string) { - assert.Equal(t, "Source Verb", response, "expecting empty response, got %s", response) - }), - in.Fail( - in.Call("gomodule", "errorEmptyVerb", in.Obj{}, func(t testing.TB, response in.Obj) { - assert.Equal(t, map[string]any{}, response, "expecting empty response, got %s", repr.String(response)) - }), "verb failed"), - in.Fail( - in.Call("gomodule", "errorEmptyVerb", in.Obj{}, func(t testing.TB, response in.Obj) { + }) + })...) + tests = append(tests, PairedTest("errorEmptyVerb", func(module string) in.Action { + return in.Fail( + in.Call(module, "errorEmptyVerb", in.Obj{}, func(t testing.TB, response in.Obj) { assert.Equal(t, map[string]any{}, response, "expecting empty response, got %s", repr.String(response)) - }), "verb failed"), + }), "verb failed") + })...) + tests = append(tests, PairedVerbTest("intVerb", 124)...) + tests = append(tests, PairedVerbTest("floatVerb", 0.123)...) + tests = append(tests, PairedVerbTest("stringVerb", "Hello World")...) + tests = append(tests, PairedVerbTest("bytesVerb", []byte{1, 2, 3, 0, 1})...) + tests = append(tests, PairedVerbTest("boolVerb", true)...) + tests = append(tests, PairedVerbTest("stringArrayVerb", []string{"Hello World"})...) + tests = append(tests, PairedVerbTest("stringMapVerb", map[string]string{"Hello": "World"})...) + tests = append(tests, PairedTest("timeVerb", func(module string) in.Action { + now := time.Now().UTC() + return in.Call(module, "timeVerb", now.Format(time.RFC3339Nano), func(t testing.TB, response string) { + result, err := time.Parse(time.RFC3339Nano, response) + assert.NoError(t, err, "time is not a valid RFC3339 time: %s", response) + assert.Equal(t, now, result, "times not equal %s %s", now, result) + }) + })...) + tests = append(tests, PairedVerbTest("testObjectVerb", exampleObject)...) + tests = append(tests, PairedVerbTest("testObjectOptionalFieldsVerb", exampleOptionalFieldsObject)...) + tests = append(tests, PairedVerbTest("optionalIntVerb", -3)...) + tests = append(tests, PairedVerbTest("optionalFloatVerb", -7.6)...) + tests = append(tests, PairedVerbTest("optionalStringVerb", "foo")...) + tests = append(tests, PairedVerbTest("optionalBytesVerb", []byte{134, 255, 0})...) + tests = append(tests, PairedVerbTest("optionalBoolVerb", false)...) + tests = append(tests, PairedVerbTest("optionalStringArrayVerb", []string{"foo"})...) + tests = append(tests, PairedVerbTest("optionalStringMapVerb", map[string]string{"Hello": "World"})...) + tests = append(tests, PairedTest("optionalTimeVerb", func(module string) in.Action { + now := time.Now().UTC() + return in.Call(module, "optionalTimeVerb", now.Format(time.RFC3339Nano), func(t testing.TB, response string) { + result, err := time.Parse(time.RFC3339Nano, response) + assert.NoError(t, err, "time is not a valid RFC3339 time: %s", response) + assert.Equal(t, now, result, "times not equal %s %s", now, result) + }) + })...) + + tests = append(tests, PairedVerbTest("optionalTestObjectVerb", exampleObject)...) + tests = append(tests, PairedVerbTest("optionalTestObjectOptionalFieldsVerb", exampleOptionalFieldsObject)...) + //tests = append(tests, PairedPrefixVerbTest("nilvalue", "optionalIntVerb", ftl.None[int]())...) + //tests = append(tests, PairedPrefixVerbTest("nilvalue", "optionalFloatVerb", ftl.None[float64]())...) + //tests = append(tests, PairedPrefixVerbTest("nilvalue", "optionalStringVerb", ftl.None[string]())...) + //tests = append(tests, PairedPrefixVerbTest("nilvalue", "optionalBytesVerb", ftl.None[[]byte]())...) + //tests = append(tests, PairedPrefixVerbTest("nilvalue", "optionalBoolVerb", ftl.None[bool]())...) + //tests = append(tests, PairedPrefixVerbTest("nilvalue", "optionalStringArrayVerb", ftl.None[[]string]())...) + //tests = append(tests, PairedPrefixVerbTest("nilvalue", "optionalStringMapVerb", ftl.None[map[string]string]())...) + //tests = append(tests, PairedPrefixVerbTest("nilvalue", "optionalTimeVerb", ftl.None[time.Time]())...) + //tests = append(tests, PairedPrefixVerbTest("nilvalue", "optionalTestObjectVerb", ftl.None[any]())...) + //tests = append(tests, PairedPrefixVerbTest("nilvalue", "optionalTestObjectOptionalFieldsVerb", ftl.None[any]())...) + + in.Run(t, + in.WithLanguages("java"), + in.CopyModule("gomodule"), + in.CopyModule("javamodule"), + in.Deploy("gomodule"), + in.Deploy("javamodule"), + in.SubTests(tests...), ) } + +func PairedTest(name string, testFunc func(module string) in.Action) []in.SubTest { + return []in.SubTest{ + { + Name: name + "-go", + Action: testFunc("gomodule"), + }, + { + Name: name + "-Java", + Action: testFunc("javamodule"), + }, + } +} + +func VerbTest[T any](verb string, value T) func(module string) in.Action { + return func(module string) in.Action { + return in.Call(module, verb, value, func(t testing.TB, response T) { + assert.Equal(t, value, response, "verb call results not equal %s %s", value, response) + }) + } +} + +func PairedVerbTest[T any](verb string, value T) []in.SubTest { + return PairedTest(verb, VerbTest[T](verb, value)) +} + +func PairedPrefixVerbTest[T any](prefex string, verb string, value T) []in.SubTest { + return PairedTest(prefex+"-"+verb, VerbTest[T](verb, value)) +} + +type TestObject struct { + IntField int `json:"intField"` + FloatField float64 `json:"floatField"` + StringField string `json:"stringField"` + BytesField []byte `json:"bytesField"` + BoolField bool `json:"boolField"` + TimeField time.Time `json:"timeField"` + ArrayField []string `json:"arrayField"` + MapField map[string]string `json:"mapField"` +} + +type TestObjectOptionalFields struct { + IntField ftl.Option[int] `json:"intField"` + FloatField ftl.Option[float64] `json:"floatField"` + StringField ftl.Option[string] `json:"stringField"` + BytesField ftl.Option[[]byte] `json:"bytesField"` + BoolField ftl.Option[bool] `json:"boolField"` + TimeField ftl.Option[time.Time] `json:"timeField"` + ArrayField ftl.Option[[]string] `json:"arrayField"` + MapField ftl.Option[map[string]string] `json:"mapField"` +} diff --git a/java-runtime/testdata/go/gomodule/go.mod b/java-runtime/testdata/go/gomodule/go.mod deleted file mode 100644 index 3773c01c5d..0000000000 --- a/java-runtime/testdata/go/gomodule/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module ftl/gomodule - -go 1.22.2 - -replace github.com/TBD54566975/ftl => ./../../../.. diff --git a/java-runtime/testdata/go/gomodule/go.sum b/java-runtime/testdata/go/gomodule/go.sum deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/java-runtime/testdata/go/gomodule/server.go b/java-runtime/testdata/go/gomodule/server.go deleted file mode 100644 index 65d6d4b0df..0000000000 --- a/java-runtime/testdata/go/gomodule/server.go +++ /dev/null @@ -1,38 +0,0 @@ -package gomodule - -import ( - "context" - "fmt" - "time" -) - -type TimeRequest struct { -} -type TimeResponse struct { - Time time.Time -} - -//ftl:verb export -func SourceVerb(ctx context.Context) (string, error) { - return "Source Verb", nil -} - -//ftl:verb export -func SinkVerb(ctx context.Context, req string) error { - return nil -} - -//ftl:verb export -func EmptyVerb(ctx context.Context) error { - return nil -} - -//ftl:verb export -func ErrorEmptyVerb(ctx context.Context) error { - return fmt.Errorf("verb failed") -} - -//ftl:verb export -func Time(ctx context.Context, req TimeRequest) (TimeResponse, error) { - return TimeResponse{Time: time.Now()}, nil -} diff --git a/java-runtime/testdata/go/javamodule/src/main/java/xyz/block/ftl/java/test/TestInvokeGo.java b/java-runtime/testdata/go/javamodule/src/main/java/xyz/block/ftl/java/test/TestInvokeGo.java deleted file mode 100644 index da50c7035f..0000000000 --- a/java-runtime/testdata/go/javamodule/src/main/java/xyz/block/ftl/java/test/TestInvokeGo.java +++ /dev/null @@ -1,45 +0,0 @@ -package xyz.block.ftl.java.test; - -import ftl.gomodule.EmptyVerbClient; -import ftl.gomodule.ErrorEmptyVerbClient; -import ftl.gomodule.SinkVerbClient; -import ftl.gomodule.SourceVerbClient; -import ftl.gomodule.TimeClient; -import ftl.gomodule.TimeRequest; -import ftl.gomodule.TimeResponse; -import org.jetbrains.annotations.NotNull; -import xyz.block.ftl.Export; -import xyz.block.ftl.Verb; - -public class TestInvokeGo { - - @Export - @Verb - public void emptyVerb(EmptyVerbClient emptyVerbClient) { - emptyVerbClient.call(); - } - - @Export - @Verb - public void sinkVerb(String input, SinkVerbClient sinkVerbClient) { - sinkVerbClient.call(input); - } - - @Export - @Verb - public String sourceVerb(SourceVerbClient sourceVerbClient) { - return sourceVerbClient.call(); - } - @Export - @Verb - public void errorEmptyVerb(ErrorEmptyVerbClient client) { - client.call(); - } - - @Export - @Verb - public @NotNull TimeResponse timeVerb(TimeClient client) { - return client.call(new TimeRequest()); - } - -} diff --git a/java-runtime/testdata/go/gomodule/ftl.toml b/java-runtime/testdata/java/gomodule/ftl.toml similarity index 100% rename from java-runtime/testdata/go/gomodule/ftl.toml rename to java-runtime/testdata/java/gomodule/ftl.toml diff --git a/java-runtime/testdata/java/gomodule/go.mod b/java-runtime/testdata/java/gomodule/go.mod new file mode 100644 index 0000000000..7c88a20aad --- /dev/null +++ b/java-runtime/testdata/java/gomodule/go.mod @@ -0,0 +1,48 @@ +module ftl/gomodule + +go 1.22.2 + +replace github.com/TBD54566975/ftl => ./../../../.. + +require github.com/TBD54566975/ftl v0.0.0-00010101000000-000000000000 + +require ( + connectrpc.com/connect v1.16.2 // indirect + connectrpc.com/grpcreflect v1.2.0 // indirect + connectrpc.com/otelconnect v0.7.1 // indirect + github.com/XSAM/otelsql v0.32.0 // indirect + github.com/alecthomas/atomic v0.1.0-alpha2 // indirect + github.com/alecthomas/concurrency v0.0.2 // indirect + github.com/alecthomas/participle/v2 v2.1.1 // indirect + github.com/alecthomas/types v0.16.0 // indirect + github.com/alessio/shellescape v1.4.2 // indirect + github.com/benbjohnson/clock v1.3.5 // indirect + github.com/danieljoos/wincred v1.2.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/hashicorp/cronexpr v1.1.2 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.6.0 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jpillora/backoff v1.0.0 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/multiformats/go-base36 v0.2.0 // indirect + github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect + github.com/swaggest/jsonschema-go v0.3.72 // indirect + github.com/swaggest/refl v1.3.0 // indirect + github.com/zalando/go-keyring v0.2.5 // indirect + go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect +) diff --git a/java-runtime/testdata/java/gomodule/go.sum b/java-runtime/testdata/java/gomodule/go.sum new file mode 100644 index 0000000000..9569a27c55 --- /dev/null +++ b/java-runtime/testdata/java/gomodule/go.sum @@ -0,0 +1,152 @@ +connectrpc.com/connect v1.16.2 h1:ybd6y+ls7GOlb7Bh5C8+ghA6SvCBajHwxssO2CGFjqE= +connectrpc.com/connect v1.16.2/go.mod h1:n2kgwskMHXC+lVqb18wngEpF95ldBHXjZYJussz5FRc= +connectrpc.com/grpcreflect v1.2.0 h1:Q6og1S7HinmtbEuBvARLNwYmTbhEGRpHDhqrPNlmK+U= +connectrpc.com/grpcreflect v1.2.0/go.mod h1:nwSOKmE8nU5u/CidgHtPYk1PFI3U9ignz7iDMxOYkSY= +connectrpc.com/otelconnect v0.7.1 h1:scO5pOb0i4yUE66CnNrHeK1x51yq0bE0ehPg6WvzXJY= +connectrpc.com/otelconnect v0.7.1/go.mod h1:dh3bFgHBTb2bkqGCeVVOtHJreSns7uu9wwL2Tbz17ms= +github.com/TBD54566975/scaffolder v1.0.0 h1:QUFSy2wVzumLDg7IHcKC6AP+IYyqWe9Wxiu72nZn5qU= +github.com/TBD54566975/scaffolder v1.0.0/go.mod h1:auVpczIbOAdIhYDVSruIw41DanxOKB9bSvjf6MEl7Fs= +github.com/XSAM/otelsql v0.32.0 h1:vDRE4nole0iOOlTaC/Bn6ti7VowzgxK39n3Ll1Kt7i0= +github.com/XSAM/otelsql v0.32.0/go.mod h1:Ary0hlyVBbaSwo8atZB8Aoothg9s/LBJj/N/p5qDmLM= +github.com/alecthomas/assert/v2 v2.10.0 h1:jjRCHsj6hBJhkmhznrCzoNpbA3zqy0fYiUcYZP/GkPY= +github.com/alecthomas/assert/v2 v2.10.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= +github.com/alecthomas/atomic v0.1.0-alpha2/go.mod h1:zD6QGEyw49HIq19caJDc2NMXAy8rNi9ROrxtMXATfyI= +github.com/alecthomas/concurrency v0.0.2 h1:Q3kGPtLbleMbH9lHX5OBFvJygfyFw29bXZKBg+IEVuo= +github.com/alecthomas/concurrency v0.0.2/go.mod h1:GmuQb/iHX7mbNtPlC/WDzEFxDMB0HYFer2Qda9QTs7w= +github.com/alecthomas/participle/v2 v2.1.1 h1:hrjKESvSqGHzRb4yW1ciisFJ4p3MGYih6icjJvbsmV8= +github.com/alecthomas/participle/v2 v2.1.1/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/alecthomas/types v0.16.0 h1:o9+JSwCRB6DDaWDeR/Mg7v/zh3R+MlknM6DrnDyY7U0= +github.com/alecthomas/types v0.16.0/go.mod h1:Tswm0qQpjpVq8rn70OquRsUtFxbQKub/8TMyYYGI0+k= +github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0= +github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= +github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= +github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/bool64/dev v0.2.35 h1:M17TLsO/pV2J7PYI/gpe3Ua26ETkzZGb+dC06eoMqlk= +github.com/bool64/dev v0.2.35/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= +github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= +github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs= +github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE= +github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/cronexpr v1.1.2 h1:wG/ZYIKT+RT3QkOdgYc+xsKWVRgnxJ1OJtjjy84fJ9A= +github.com/hashicorp/cronexpr v1.1.2/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= +github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= +github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= +github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= +github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ= +github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU= +github.com/swaggest/jsonschema-go v0.3.72 h1:IHaGlR1bdBUBPfhe4tfacN2TGAPKENEGiNyNzvnVHv4= +github.com/swaggest/jsonschema-go v0.3.72/go.mod h1:OrGyEoVqpfSFJ4Am4V/FQcQ3mlEC1vVeleA+5ggbVW4= +github.com/swaggest/refl v1.3.0 h1:PEUWIku+ZznYfsoyheF97ypSduvMApYyGkYF3nabS0I= +github.com/swaggest/refl v1.3.0/go.mod h1:3Ujvbmh1pfSbDYjC6JGG7nMgPvpG0ehQL4iNonnLNbg= +github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/zalando/go-keyring v0.2.5 h1:Bc2HHpjALryKD62ppdEzaFG6VxL6Bc+5v0LYpN8Lba8= +github.com/zalando/go-keyring v0.2.5/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= +go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08= +go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w= +golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= +modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= +modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U= +modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= +modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= +modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s= +modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/java-runtime/testdata/java/gomodule/server.go b/java-runtime/testdata/java/gomodule/server.go new file mode 100644 index 0000000000..9adc032019 --- /dev/null +++ b/java-runtime/testdata/java/gomodule/server.go @@ -0,0 +1,157 @@ +package gomodule + +import ( + "context" + "fmt" + "time" + + "github.com/TBD54566975/ftl/go-runtime/ftl" +) + +type TestObject struct { + IntField int + FloatField float64 + StringField string + BytesField []byte + BoolField bool + TimeField time.Time + ArrayField []string + MapField map[string]string +} + +type TestObjectOptionalFields struct { + IntField ftl.Option[int] + FloatField ftl.Option[float64] + StringField ftl.Option[string] + BytesField ftl.Option[[]byte] + BoolField ftl.Option[bool] + TimeField ftl.Option[time.Time] + ArrayField ftl.Option[[]string] + MapField ftl.Option[map[string]string] +} + +// Test different signatures + +//ftl:verb export +func SourceVerb(ctx context.Context) (string, error) { + return "Source Verb", nil +} + +//ftl:verb export +func SinkVerb(ctx context.Context, req string) error { + return nil +} + +//ftl:verb export +func EmptyVerb(ctx context.Context) error { + return nil +} + +//ftl:verb export +func ErrorEmptyVerb(ctx context.Context) error { + return fmt.Errorf("verb failed") +} + +// Test different param and return types + +//ftl:verb export +func IntVerb(ctx context.Context, val int) (int, error) { + return val, nil +} + +//ftl:verb export +func FloatVerb(ctx context.Context, val float64) (float64, error) { + return val, nil +} + +//ftl:verb export +func StringVerb(ctx context.Context, val string) (string, error) { + return val, nil +} + +//ftl:verb export +func BytesVerb(ctx context.Context, val []byte) ([]byte, error) { + return val, nil +} + +//ftl:verb export +func BoolVerb(ctx context.Context, val bool) (bool, error) { + return val, nil +} + +//ftl:verb export +func StringArrayVerb(ctx context.Context, val []string) ([]string, error) { + return val, nil +} + +//ftl:verb export +func StringMapVerb(ctx context.Context, val map[string]string) (map[string]string, error) { + return val, nil +} + +//ftl:verb export +func TimeVerb(ctx context.Context, val time.Time) (time.Time, error) { + return val, nil +} + +//ftl:verb export +func TestObjectVerb(ctx context.Context, val TestObject) (TestObject, error) { + return val, nil +} + +//ftl:verb export +func TestObjectOptionalFieldsVerb(ctx context.Context, val TestObjectOptionalFields) (TestObjectOptionalFields, error) { + return val, nil +} + +// Now optional versions of all of the above + +//ftl:verb export +func OptionalIntVerb(ctx context.Context, val ftl.Option[int]) (ftl.Option[int], error) { + return val, nil +} + +//ftl:verb export +func OptionalFloatVerb(ctx context.Context, val ftl.Option[float64]) (ftl.Option[float64], error) { + return val, nil +} + +//ftl:verb export +func OptionalStringVerb(ctx context.Context, val ftl.Option[string]) (ftl.Option[string], error) { + return val, nil +} + +//ftl:verb export +func OptionalBytesVerb(ctx context.Context, val ftl.Option[[]byte]) (ftl.Option[[]byte], error) { + return val, nil +} + +//ftl:verb export +func OptionalBoolVerb(ctx context.Context, val ftl.Option[bool]) (ftl.Option[bool], error) { + return val, nil +} + +//ftl:verb export +func OptionalStringArrayVerb(ctx context.Context, val ftl.Option[[]string]) (ftl.Option[[]string], error) { + return val, nil +} + +//ftl:verb export +func OptionalStringMapVerb(ctx context.Context, val ftl.Option[map[string]string]) (ftl.Option[map[string]string], error) { + return val, nil +} + +//ftl:verb export +func OptionalTimeVerb(ctx context.Context, val ftl.Option[time.Time]) (ftl.Option[time.Time], error) { + return val, nil +} + +//ftl:verb export +func OptionalTestObjectVerb(ctx context.Context, val ftl.Option[TestObject]) (ftl.Option[TestObject], error) { + return val, nil +} + +//ftl:verb export +func OptionalTestObjectOptionalFieldsVerb(ctx context.Context, val ftl.Option[TestObjectOptionalFields]) (ftl.Option[TestObjectOptionalFields], error) { + return val, nil +} diff --git a/java-runtime/testdata/go/javamodule/ftl.toml b/java-runtime/testdata/java/javamodule/ftl.toml similarity index 100% rename from java-runtime/testdata/go/javamodule/ftl.toml rename to java-runtime/testdata/java/javamodule/ftl.toml diff --git a/java-runtime/testdata/go/javamodule/pom.xml b/java-runtime/testdata/java/javamodule/pom.xml similarity index 98% rename from java-runtime/testdata/go/javamodule/pom.xml rename to java-runtime/testdata/java/javamodule/pom.xml index 43d234296e..4027c0007e 100644 --- a/java-runtime/testdata/go/javamodule/pom.xml +++ b/java-runtime/testdata/java/javamodule/pom.xml @@ -3,7 +3,7 @@ 4.0.0 xyz.block.ftl.examples javamodule - 1.0.0-SNAPSHOT + 1.0-SNAPSHOT 1.0-SNAPSHOT @@ -35,7 +35,7 @@ xyz.block ftl-java-runtime - 1.0.0-SNAPSHOT + 1.0-SNAPSHOT io.quarkus diff --git a/java-runtime/testdata/java/javamodule/src/main/java/xyz/block/ftl/java/test/TestInvokeGo.java b/java-runtime/testdata/java/javamodule/src/main/java/xyz/block/ftl/java/test/TestInvokeGo.java new file mode 100644 index 0000000000..2a6e3ad884 --- /dev/null +++ b/java-runtime/testdata/java/javamodule/src/main/java/xyz/block/ftl/java/test/TestInvokeGo.java @@ -0,0 +1,187 @@ +package xyz.block.ftl.java.test; + +import ftl.gomodule.BoolVerbClient; +import ftl.gomodule.BytesVerbClient; +import ftl.gomodule.EmptyVerbClient; +import ftl.gomodule.ErrorEmptyVerbClient; +import ftl.gomodule.FloatVerbClient; +import ftl.gomodule.IntVerbClient; +import ftl.gomodule.OptionalBoolVerbClient; +import ftl.gomodule.OptionalBytesVerbClient; +import ftl.gomodule.OptionalFloatVerbClient; +import ftl.gomodule.OptionalIntVerbClient; +import ftl.gomodule.OptionalStringArrayVerbClient; +import ftl.gomodule.OptionalStringMapVerbClient; +import ftl.gomodule.OptionalStringVerbClient; +import ftl.gomodule.OptionalTestObjectOptionalFieldsVerbClient; +import ftl.gomodule.OptionalTestObjectVerbClient; +import ftl.gomodule.OptionalTimeVerbClient; +import ftl.gomodule.SinkVerbClient; +import ftl.gomodule.SourceVerbClient; +import ftl.gomodule.StringArrayVerbClient; +import ftl.gomodule.StringMapVerbClient; +import ftl.gomodule.StringVerbClient; +import ftl.gomodule.TestObject; +import ftl.gomodule.TestObjectOptionalFields; +import ftl.gomodule.TestObjectOptionalFieldsVerbClient; +import ftl.gomodule.TestObjectVerbClient; +import ftl.gomodule.TimeVerbClient; +import org.jetbrains.annotations.NotNull; +import xyz.block.ftl.Export; +import xyz.block.ftl.Verb; + +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Map; + +public class TestInvokeGo { + + @Export + @Verb + public void emptyVerb(EmptyVerbClient emptyVerbClient) { + emptyVerbClient.call(); + } + + @Export + @Verb + public void sinkVerb(String input, SinkVerbClient sinkVerbClient) { + sinkVerbClient.call(input); + } + + @Export + @Verb + public String sourceVerb(SourceVerbClient sourceVerbClient) { + return sourceVerbClient.call(); + } + + @Export + @Verb + public void errorEmptyVerb(ErrorEmptyVerbClient client) { + client.call(); + } + + @Export + @Verb + public long intVerb(long val, IntVerbClient client) { + return client.call(val); + } + + @Export + @Verb + public double floatVerb(double val, FloatVerbClient client) { + return client.call(val); + } + + @Export + @Verb + public @NotNull String stringVerb(@NotNull String val, StringVerbClient client) { + return client.call(val); + } + + @Export + @Verb + public byte[] bytesVerb(byte[] val, BytesVerbClient client) { + return client.call(val); + } + + @Export + @Verb + public boolean boolVerb(boolean val, BoolVerbClient client) { + return client.call(val); + } + + @Export + @Verb + public @NotNull List stringArrayVerb(@NotNull List val, StringArrayVerbClient client) { + return client.call(val); + } + + @Export + @Verb + public @NotNull Map stringMapVerb(@NotNull Map val, StringMapVerbClient client) { + return client.call(val); + } + + @Export + @Verb + public @NotNull ZonedDateTime timeVerb(@NotNull ZonedDateTime instant, TimeVerbClient client) { + return client.call(instant); + } + + @Export + @Verb + public @NotNull TestObject testObjectVerb(@NotNull TestObject val, TestObjectVerbClient client) { + return client.call(val); + } + + @Export + @Verb + public @NotNull TestObjectOptionalFields testObjectOptionalFieldsVerb(@NotNull TestObjectOptionalFields val, TestObjectOptionalFieldsVerbClient client) { + return client.call(val); + } + + // now the same again but with option return / input types + + + @Export + @Verb + public Long optionalIntVerb(Long val, OptionalIntVerbClient client) { + return client.call(val); + } + + @Export + @Verb + public Double optionalFloatVerb(Double val, OptionalFloatVerbClient client) { + return client.call(val); + } + + @Export + @Verb + public String optionalStringVerb(String val, OptionalStringVerbClient client) { + return client.call(val); + } + + @Export + @Verb + public byte[] optionalBytesVerb(byte[] val, OptionalBytesVerbClient client) { + return client.call(val); + } + + @Export + @Verb + public boolean optionalBoolVerb(boolean val, OptionalBoolVerbClient client) { + return client.call(val); + } + + @Export + @Verb + public List optionalStringArrayVerb(List val, OptionalStringArrayVerbClient client) { + return client.call(val); + } + + @Export + @Verb + public Map optionalStringMapVerb(Map val, OptionalStringMapVerbClient client) { + return client.call(val); + } + + @Export + @Verb + public ZonedDateTime optionalTimeVerb(ZonedDateTime instant, OptionalTimeVerbClient client) { + return client.call(instant); + } + + @Export + @Verb + public TestObject optionalTestObjectVerb(TestObject val, OptionalTestObjectVerbClient client) { + return client.call(val); + } + + @Export + @Verb + public TestObjectOptionalFields optionalTestObjectOptionalFieldsVerb(TestObjectOptionalFields val, OptionalTestObjectOptionalFieldsVerbClient client) { + return client.call(val); + } + + +}