Skip to content

Commit

Permalink
fix: fail build when schema includes external types (#1082)
Browse files Browse the repository at this point in the history
Fixes #1066

Building a module now fails if an external type is included in the
schema.

Examples:
- `time.Month`
- `github.com/tbd54566975/web5-go/dids/did. PortableDID`
  • Loading branch information
matt2e authored Mar 21, 2024
1 parent 3efd221 commit 6549469
Show file tree
Hide file tree
Showing 11 changed files with 321 additions and 1 deletion.
20 changes: 20 additions & 0 deletions buildengine/build_go_test.go
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down Expand Up @@ -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")
}
27 changes: 27 additions & 0 deletions buildengine/build_kotlin_test.go
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down Expand Up @@ -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

Check failure on line 400 in buildengine/build_kotlin_test.go

View workflow job for this annotation

GitHub Actions / Lint

commentFormatting: put a space between `//` and comment text (gocritic)
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.<module>, but was com.google.type.DayOfWeek")

err = os.RemoveAll(buildDir)
assert.NoError(t, err, "Error removing build directory")
}
37 changes: 37 additions & 0 deletions buildengine/discover_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
19 changes: 19 additions & 0 deletions buildengine/testdata/modules/external/external.go
Original file line number Diff line number Diff line change
@@ -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
}
2 changes: 2 additions & 0 deletions buildengine/testdata/modules/external/ftl.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module = "external"
language = "go"
5 changes: 5 additions & 0 deletions buildengine/testdata/modules/external/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module ftl/external

go 1.22.0

replace github.com/TBD54566975/ftl => ../../../..
Empty file.
2 changes: 2 additions & 0 deletions buildengine/testdata/modules/externalkotlin/ftl.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module = "externalkotlin"
language = "kotlin"
185 changes: 185 additions & 0 deletions buildengine/testdata/modules/externalkotlin/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>ftl</groupId>
<artifactId>external</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<ftl.version>1.0-SNAPSHOT</ftl.version>
<java.version>1.8</java.version>
<kotlin.version>1.9.22</kotlin.version>
<kotlin.compiler.incremental>true</kotlin.compiler.incremental>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>xyz.block</groupId>
<artifactId>ftl-runtime</artifactId>
<version>${ftl.version}</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.2</version>
</dependency>
</dependencies>

<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<sourceDirs>
<sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
</sourceDirs>
</configuration>
</execution>
<execution>
<id>test-compile</id>
<goals>
<goal>test-compile</goal>
</goals>
<configuration>
<sourceDirs>
<sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
</sourceDirs>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.6.1</version>
<executions>
<!-- Copy all dependencies into the target directory -->
<execution>
<id>copy-dependencies</id>
<phase>compile</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/dependency</outputDirectory>
<includeScope>runtime</includeScope>
</configuration>
</execution>
<!-- Collect classpath -->
<execution>
<id>build-classpath</id>
<phase>compile</phase>
<goals>
<goal>build-classpath</goal>
</goals>
<configuration>
<outputFile>${project.build.directory}/classpath.txt</outputFile>
<prefix>dependency</prefix>
</configuration>
</execution>
<execution>
<id>build-classpath-property</id>
<phase>compile</phase>
<goals>
<goal>build-classpath</goal>
</goals>
<configuration>
<outputProperty>generated.classpath</outputProperty>
<prefix>${project.build.directory}/dependency</prefix>
</configuration>
</execution>
</executions>
</plugin>
<!-- Add sources generated by the FTL tooling -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>${project.build.directory}/generated-sources/ftl</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.github.ozsie</groupId>
<artifactId>detekt-maven-plugin</artifactId>
<version>1.23.5</version>
<configuration>
<disableDefaultRuleSets>true</disableDefaultRuleSets>
<classPath>${generated.classpath}</classPath>
<jvmTarget>${java.version}</jvmTarget>
<jdkHome>${java.home}</jdkHome>
<config>${project.build.directory}/detekt.yml</config>
<plugins>
<plugin>
${project.build.directory}/dependency/ftl-runtime-${ftl.version}.jar
</plugin>
</plugins>
<input>${project.basedir}/src/main/kotlin,${project.build.directory}/generated-sources</input>
</configuration>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>check-with-type-resolution</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>xyz.block</groupId>
<artifactId>ftl-runtime</artifactId>
<version>${ftl.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
</plugin>
<!-- Add generated sources to the classpath -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
</plugin>
<!-- Extract schema -->
<plugin>
<groupId>com.github.ozsie</groupId>
<artifactId>detekt-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -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"}!")
}
8 changes: 7 additions & 1 deletion go-runtime/compile/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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)
}

Expand Down

0 comments on commit 6549469

Please sign in to comment.