diff --git a/buildengine/build_go_test.go b/buildengine/build_go_test.go index c0ebf26e26..a04bcac1c5 100644 --- a/buildengine/build_go_test.go +++ b/buildengine/build_go_test.go @@ -1,9 +1,13 @@ package buildengine import ( + "context" + "os" "testing" "github.com/TBD54566975/ftl/backend/schema" + "github.com/TBD54566975/ftl/internal/log" + "github.com/alecthomas/assert/v2" ) func TestGenerateGoModule(t *testing.T) { @@ -176,3 +180,19 @@ func Call(context.Context, Req) (Resp, error) { assertGeneratedModule("go/modules/test/external_module.go", expected), }) } + +func TestExternalType(t *testing.T) { + moduleDir := "testdata/modules/external" + buildDir := "_ftl" + + ctx := log.ContextWithLogger(context.Background(), log.Configure(os.Stderr, log.Config{})) + module, err := LoadModule(ctx, moduleDir) + assert.NoError(t, err) + + sch := &schema.Schema{} + err = Build(ctx, sch, module) + assert.Contains(t, err.Error(), "field Month: unsupported external type time.Month") + + err = os.RemoveAll(buildDir) + assert.NoError(t, err, "Error removing build directory") +} diff --git a/buildengine/build_kotlin_test.go b/buildengine/build_kotlin_test.go index 290dc71711..a42d9328e8 100644 --- a/buildengine/build_kotlin_test.go +++ b/buildengine/build_kotlin_test.go @@ -1,9 +1,14 @@ package buildengine import ( + "bytes" + "context" + "os" "testing" "github.com/TBD54566975/ftl/backend/schema" + "github.com/TBD54566975/ftl/internal/log" + "github.com/alecthomas/assert/v2" ) func TestGenerateBasicModule(t *testing.T) { @@ -383,3 +388,25 @@ fun nothing(context: Context): Unit = throw assertGeneratedModule("generated-sources/ftl/test/Test.kt", expected), }) } + +func TestKotlinExternalType(t *testing.T) { + moduleDir := "testdata/modules/externalkotlin" + buildDir := "_ftl" + + ctx := log.ContextWithLogger(context.Background(), log.Configure(os.Stderr, log.Config{})) + module, err := LoadModule(ctx, moduleDir) + assert.NoError(t, err) + + //create a logger that writes to a buffer.Bytes + logBuffer := bytes.Buffer{} + logger := log.Configure(&logBuffer, log.Config{}) + ctx = log.ContextWithLogger(ctx, logger) + + sch := &schema.Schema{} + err = Build(ctx, sch, module) + assert.Error(t, err) + assert.Contains(t, logBuffer.String(), "Expected module name to be in the form ftl., but was com.google.type.DayOfWeek") + + err = os.RemoveAll(buildDir) + assert.NoError(t, err, "Error removing build directory") +} diff --git a/buildengine/discover_test.go b/buildengine/discover_test.go index 77af400c35..0e558772cf 100644 --- a/buildengine/discover_test.go +++ b/buildengine/discover_test.go @@ -55,6 +55,43 @@ func TestDiscoverModules(t *testing.T) { "target/generated-sources", }, }, + { + Dir: "testdata/modules/external", + Language: "go", + Realm: "home", + Module: "external", + Build: "", + Deploy: []string{ + "main", + }, + DeployDir: "_ftl", + Schema: "schema.pb", + Watch: []string{ + "**/*.go", + "go.mod", + "go.sum", + }, + }, + { + Dir: "testdata/modules/externalkotlin", + Language: "kotlin", + Realm: "home", + Module: "externalkotlin", + Build: "mvn -B compile", + Deploy: []string{ + "main", + "classes", + "dependency", + "classpath.txt", + }, + DeployDir: "target", + Schema: "schema.pb", + Watch: []string{ + "pom.xml", + "src/**", + "target/generated-sources", + }, + }, { Dir: "testdata/modules/other", Language: "go", diff --git a/buildengine/testdata/modules/external/external.go b/buildengine/testdata/modules/external/external.go new file mode 100644 index 0000000000..93feca28d1 --- /dev/null +++ b/buildengine/testdata/modules/external/external.go @@ -0,0 +1,19 @@ +//ftl:module external +package external + +import ( + "context" + "time" +) + +type ExternalRequest struct{} +type ExternalResponse struct { + Month time.Month // external type should not be allowed +} + +// External returns the current month as an external type. +// +//ftl:verb +func Time(ctx context.Context, req ExternalRequest) (ExternalResponse, error) { + return ExternalResponse{Month: time.Now().Month()}, nil +} diff --git a/buildengine/testdata/modules/external/ftl.toml b/buildengine/testdata/modules/external/ftl.toml new file mode 100644 index 0000000000..8dacc44933 --- /dev/null +++ b/buildengine/testdata/modules/external/ftl.toml @@ -0,0 +1,2 @@ +module = "external" +language = "go" diff --git a/buildengine/testdata/modules/external/go.mod b/buildengine/testdata/modules/external/go.mod new file mode 100644 index 0000000000..73c4cb29fd --- /dev/null +++ b/buildengine/testdata/modules/external/go.mod @@ -0,0 +1,5 @@ +module ftl/external + +go 1.22.0 + +replace github.com/TBD54566975/ftl => ../../../.. diff --git a/buildengine/testdata/modules/external/go.sum b/buildengine/testdata/modules/external/go.sum new file mode 100644 index 0000000000..e69de29bb2 diff --git a/buildengine/testdata/modules/externalkotlin/ftl.toml b/buildengine/testdata/modules/externalkotlin/ftl.toml new file mode 100644 index 0000000000..219c07845f --- /dev/null +++ b/buildengine/testdata/modules/externalkotlin/ftl.toml @@ -0,0 +1,2 @@ +module = "externalkotlin" +language = "kotlin" diff --git a/buildengine/testdata/modules/externalkotlin/pom.xml b/buildengine/testdata/modules/externalkotlin/pom.xml new file mode 100644 index 0000000000..f0981625aa --- /dev/null +++ b/buildengine/testdata/modules/externalkotlin/pom.xml @@ -0,0 +1,185 @@ + + + 4.0.0 + + ftl + external + 1.0-SNAPSHOT + + + 1.0-SNAPSHOT + 1.8 + 1.9.22 + true + ${java.version} + ${java.version} + + + + + org.jetbrains.kotlin + kotlin-stdlib + ${kotlin.version} + + + xyz.block + ftl-runtime + ${ftl.version} + + + org.postgresql + postgresql + 42.7.2 + + + + + + + + kotlin-maven-plugin + org.jetbrains.kotlin + ${kotlin.version} + + + compile + + compile + + + + ${project.basedir}/src/main/kotlin + + + + + test-compile + + test-compile + + + + ${project.basedir}/src/test/kotlin + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.6.1 + + + + copy-dependencies + compile + + copy-dependencies + + + ${project.build.directory}/dependency + runtime + + + + + build-classpath + compile + + build-classpath + + + ${project.build.directory}/classpath.txt + dependency + + + + build-classpath-property + compile + + build-classpath + + + generated.classpath + ${project.build.directory}/dependency + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.5.0 + + + generate-sources + + add-source + + + + ${project.build.directory}/generated-sources/ftl + + + + + + + com.github.ozsie + detekt-maven-plugin + 1.23.5 + + true + ${generated.classpath} + ${java.version} + ${java.home} + ${project.build.directory}/detekt.yml + + + ${project.build.directory}/dependency/ftl-runtime-${ftl.version}.jar + + + ${project.basedir}/src/main/kotlin,${project.build.directory}/generated-sources + + + + compile + + check-with-type-resolution + + + + + + xyz.block + ftl-runtime + ${ftl.version} + + + + + + + + kotlin-maven-plugin + org.jetbrains.kotlin + + + org.apache.maven.plugins + maven-dependency-plugin + + + + org.codehaus.mojo + build-helper-maven-plugin + + + + com.github.ozsie + detekt-maven-plugin + + + + \ No newline at end of file diff --git a/buildengine/testdata/modules/externalkotlin/src/main/kotlin/ftl/externalkotlin/ExternalKotlin.kt b/buildengine/testdata/modules/externalkotlin/src/main/kotlin/ftl/externalkotlin/ExternalKotlin.kt new file mode 100644 index 0000000000..7bd35258e2 --- /dev/null +++ b/buildengine/testdata/modules/externalkotlin/src/main/kotlin/ftl/externalkotlin/ExternalKotlin.kt @@ -0,0 +1,17 @@ +package ftl.externalkotlin + +import com.google.type.DayOfWeek +import xyz.block.ftl.Context +import xyz.block.ftl.Verb +import xyz.block.ftl.v1.schema.Optional + +class InvalidInput(val field: String) : Exception() + +data class ExternalRequest(val name: String?, val dayOfWeek: DayOfWeek) +data class ExternalResponse(val message: String) + +@Throws(InvalidInput::class) +@Verb +fun external(context: Context, req: ExternalRequest): ExternalResponse { + return ExternalResponse(message = "Hello, ${req.name ?: "anonymous"}!") +} diff --git a/go-runtime/compile/schema.go b/go-runtime/compile/schema.go index 418a6529c7..40f58183f7 100644 --- a/go-runtime/compile/schema.go +++ b/go-runtime/compile/schema.go @@ -670,6 +670,9 @@ func visitType(pctx *parseContext, pos token.Pos, tnode types.Type) (schema.Type } else if !strings.HasPrefix(nodePath, pctx.pkg.PkgPath) { // If this type is named and declared in another module, it's a reference. // The only basic-typed references supported are enums. + if !strings.HasPrefix(named.Obj().Pkg().Path(), "ftl/") { + return nil, fmt.Errorf("unsupported external type %s", named.Obj().Pkg().Path()+"."+named.Obj().Name()) + } base := path.Dir(pctx.pkg.PkgPath) destModule := path.Base(strings.TrimPrefix(nodePath, base+"/")) enumRef := &schema.Ref{ @@ -718,8 +721,11 @@ func visitType(pctx *parseContext, pos token.Pos, tnode types.Type) (schema.Type return nil, err } return &schema.Optional{Type: underlying}, nil - default: + nodePath := named.Obj().Pkg().Path() + if !strings.HasPrefix(nodePath, pctx.pkg.PkgPath) && !strings.HasPrefix(nodePath, "ftl/") { + return nil, fmt.Errorf("unsupported external type %s", nodePath+"."+named.Obj().Name()) + } return visitStruct(pctx, pos, tnode) }