From df046cd0705a78eae308aa41bce15d16720fe371 Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Thu, 15 Aug 2024 09:57:59 +1000 Subject: [PATCH] fix: java type handling (#2354) Improves the tests and fixes problems with the handling of some types, there is still work to go (as seen by the commented out tests), but this significantly improves the situation. --- .gitignore | 2 + Justfile | 4 +- .../leases/lease_integration_test.go | 4 +- .../leases/testdata/java/leases/ftl.toml | 2 + .../leases/testdata/java/leases/pom.xml | 141 +++++++++++++ .../ftl/java/test/leases/TestLeases.java | 22 +++ examples/kotlin/echo/pom.xml | 4 +- examples/kotlin/time/pom.xml | 4 +- integration/actions.go | 24 +++ integration/harness.go | 28 ++- java-runtime/ftl-runtime/deployment/pom.xml | 2 +- .../ftl/deployment/FTLCodeGenerator.java | 37 ++-- .../block/ftl/deployment/FtlProcessor.java | 67 ++++++- .../block/ftl/deployment/TopicsProcessor.java | 2 +- .../ftl/deployment/VerbClientsProcessor.java | 119 +++++------ .../ftl-runtime/integration-tests/pom.xml | 2 +- .../runtime/it/FtlJavaRuntimeResource.java | 5 + .../block/ftl/java/runtime/it/MyTopic.java | 2 +- .../it/FtlJavaRuntimeResourceTest.java | 13 ++ java-runtime/ftl-runtime/pom.xml | 2 +- java-runtime/ftl-runtime/runtime/pom.xml | 2 +- .../main/java/xyz/block/ftl/LeaseClient.java | 10 +- .../main/java/xyz/block/ftl/LeaseHandle.java | 7 + .../src/main/java/xyz/block/ftl/Topic.java | 2 +- .../java/xyz/block/ftl/TopicDefinition.java | 2 +- .../xyz/block/ftl/runtime/FTLController.java | 81 ++++---- .../xyz/block/ftl/runtime/FTLRecorder.java | 1 + .../ftl/runtime/JsonSerializationConfig.java | 50 +++++ .../xyz/block/ftl/runtime/VerbHandler.java | 10 +- .../xyz/block/ftl/runtime/VerbRegistry.java | 13 +- .../ftl-runtime/test-framework/pom.xml | 2 +- java-runtime/java_integration_test.go | 181 +++++++++++++---- java-runtime/testdata/go/gomodule/go.mod | 5 - java-runtime/testdata/go/gomodule/go.sum | 0 java-runtime/testdata/go/gomodule/server.go | 38 ---- .../xyz/block/ftl/java/test/TestInvokeGo.java | 45 ----- .../testdata/{go => java}/gomodule/ftl.toml | 0 java-runtime/testdata/java/gomodule/go.mod | 48 +++++ java-runtime/testdata/java/gomodule/go.sum | 152 ++++++++++++++ java-runtime/testdata/java/gomodule/server.go | 157 +++++++++++++++ .../testdata/{go => java}/javamodule/ftl.toml | 0 .../testdata/{go => java}/javamodule/pom.xml | 4 +- .../xyz/block/ftl/java/test/TestInvokeGo.java | 187 ++++++++++++++++++ 43 files changed, 1212 insertions(+), 271 deletions(-) create mode 100644 backend/controller/leases/testdata/java/leases/ftl.toml create mode 100644 backend/controller/leases/testdata/java/leases/pom.xml create mode 100644 backend/controller/leases/testdata/java/leases/src/main/java/xyz/block/ftl/java/test/leases/TestLeases.java create mode 100644 java-runtime/ftl-runtime/runtime/src/main/java/xyz/block/ftl/LeaseHandle.java delete mode 100644 java-runtime/testdata/go/gomodule/go.mod delete mode 100644 java-runtime/testdata/go/gomodule/go.sum delete mode 100644 java-runtime/testdata/go/gomodule/server.go delete mode 100644 java-runtime/testdata/go/javamodule/src/main/java/xyz/block/ftl/java/test/TestInvokeGo.java rename java-runtime/testdata/{go => java}/gomodule/ftl.toml (100%) create mode 100644 java-runtime/testdata/java/gomodule/go.mod create mode 100644 java-runtime/testdata/java/gomodule/go.sum create mode 100644 java-runtime/testdata/java/gomodule/server.go rename java-runtime/testdata/{go => java}/javamodule/ftl.toml (100%) rename java-runtime/testdata/{go => java}/javamodule/pom.xml (98%) create mode 100644 java-runtime/testdata/java/javamodule/src/main/java/xyz/block/ftl/java/test/TestInvokeGo.java 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); + } + + +}