Skip to content

Commit

Permalink
feat: support enums in go runtime
Browse files Browse the repository at this point in the history
  • Loading branch information
worstell committed Mar 2, 2024
1 parent 208c3aa commit a9b0029
Show file tree
Hide file tree
Showing 13 changed files with 654 additions and 79 deletions.
89 changes: 89 additions & 0 deletions buildengine/build_go_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package buildengine

import (
"testing"

"github.com/TBD54566975/ftl/backend/schema"
)

func TestGenerateGoModule(t *testing.T) {
sch := &schema.Schema{
Modules: []*schema.Module{
schema.Builtins(),
{Name: "other", Decls: []schema.Decl{
&schema.Enum{
Name: "Color",
Type: &schema.String{},
Variants: []*schema.EnumVariant{
{Name: "Red", Value: &schema.StringValue{Value: "Red"}},
{Name: "Blue", Value: &schema.StringValue{Value: "Blue"}},
{Name: "Green", Value: &schema.StringValue{Value: "Green"}},
},
},
&schema.Enum{
Name: "ColorInt",
Type: &schema.Int{},
Variants: []*schema.EnumVariant{
{Name: "RedInt", Value: &schema.IntValue{Value: 0}},
{Name: "BlueInt", Value: &schema.IntValue{Value: 1}},
{Name: "GreenInt", Value: &schema.IntValue{Value: 2}},
},
},
&schema.Data{Name: "EchoRequest"},
&schema.Data{Name: "EchoResponse"},
&schema.Verb{
Name: "echo",
Request: &schema.DataRef{Name: "EchoRequest"},
Response: &schema.DataRef{Name: "EchoResponse"},
},
}},
{Name: "test"},
},
}
expected := `// Code generated by FTL. DO NOT EDIT.
//ftl:module other
package other
import (
"context"
)
var _ = context.Background
//ftl:enum
type Color string
const (
Red Color = "Red"
Blue Color = "Blue"
Green Color = "Green"
)
//ftl:enum
type ColorInt int
const (
RedInt ColorInt = 0
BlueInt ColorInt = 1
GreenInt ColorInt = 2
)
type EchoRequest struct {
}
type EchoResponse struct {
}
//ftl:verb
func Echo(context.Context, EchoRequest) (EchoResponse, error) {
panic("Verb stubs should not be called directly, instead use github.com/TBD54566975/ftl/runtime-go/ftl.Call()")
}
`
bctx := buildContext{
moduleDir: "testdata/modules/another",
buildDir: "_ftl",
sch: sch,
}
testBuild(t, bctx, []assertion{
assertGeneratedModule("go/modules/other/external_module.go", expected),
})
}
36 changes: 5 additions & 31 deletions buildengine/build_kotlin.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,36 +158,7 @@ var scaffoldFuncs = scaffolder.FuncMap{
"imports": func(m *schema.Module) []string {
imports := sets.NewSet[string]()
_ = schema.Visit(m, func(n schema.Node, next func() error) error {
switch n := n.(type) {
case *schema.DataRef:
decl := m.Resolve(schema.Ref{
Module: n.Module,
Name: n.Name,
})
if decl != nil {
if data, ok := decl.Decl.(*schema.Data); ok {
if len(data.Fields) == 0 {
imports.Add("ftl.builtin.Empty")
break
}
}
}

if n.Module == "" {
break
}

imports.Add("ftl." + n.Module + "." + n.Name)

for _, tp := range n.TypeParameters {
tpRef, err := schema.ParseDataRef(tp.String())
if err != nil {
return err
}
if tpRef.Module != "" && tpRef.Module != m.Name {
imports.Add("ftl." + tpRef.Module + "." + tpRef.Name)
}
}
switch n.(type) {
case *schema.Verb:
imports.Append("xyz.block.ftl.Context", "xyz.block.ftl.Ignore", "xyz.block.ftl.Verb")

Expand All @@ -214,12 +185,15 @@ func genType(module *schema.Module, t schema.Type) string {
if decl != nil {
if data, ok := decl.Decl.(*schema.Data); ok {
if len(data.Fields) == 0 {
return "Empty"
return "ftl.builtin.Empty"
}
}
}

desc := t.Name
if t.Module != "" {
desc = "ftl." + t.Module + "." + desc
}
if len(t.TypeParameters) > 0 {
desc += "<"
for i, tp := range t.TypeParameters {
Expand Down
105 changes: 64 additions & 41 deletions buildengine/build_kotlin_test.go
Original file line number Diff line number Diff line change
@@ -1,30 +1,42 @@
package buildengine

import (
"context"
"os"
"path/filepath"
"testing"

"github.com/TBD54566975/ftl/backend/schema"
"github.com/TBD54566975/ftl/internal/log"
"github.com/alecthomas/assert/v2"
)

func TestGenerateBasicModule(t *testing.T) {
sch := &schema.Schema{
Modules: []*schema.Module{{Name: "test"}},
Modules: []*schema.Module{
schema.Builtins(),
{Name: "test"},
},
}
expected := `// Code generated by FTL. DO NOT EDIT.
package ftl.test
`
assertExpectedSchema(t, sch, "test/Test.kt", expected)
bctx := buildContext{
moduleDir: "testdata/modules/echokotlin",
buildDir: "target",
sch: sch,
}
testBuild(t, bctx, []assertion{
assertGeneratedModule("generated-sources/ftl/test/Test.kt", expected),
})
}

func TestGenerateAllTypes(t *testing.T) {
sch := &schema.Schema{
Modules: []*schema.Module{
schema.Builtins(),
{
Name: "other",
Decls: []schema.Decl{
&schema.Data{Name: "TestRequest", Fields: []*schema.Field{{Name: "field", Type: &schema.Int{}}}},
},
},
{
Name: "test",
Comments: []string{"Module comments"},
Expand All @@ -36,6 +48,7 @@ func TestGenerateAllTypes(t *testing.T) {
{Name: "t", Type: &schema.DataRef{Name: "T"}},
},
},
&schema.Data{Name: "TestRequest", Fields: []*schema.Field{{Name: "field", Type: &schema.Int{}}}},
&schema.Data{
Name: "TestResponse",
Comments: []string{"Response comments"},
Expand Down Expand Up @@ -71,7 +84,7 @@ func TestGenerateAllTypes(t *testing.T) {
{Name: "any", Type: &schema.Any{}},
{Name: "parameterizedDataRef", Type: &schema.DataRef{
Name: "ParamTestData",
TypeParameters: []schema.Type{&schema.DataRef{Name: "T"}},
TypeParameters: []schema.Type{&schema.String{}},
},
},
{Name: "withAlias", Type: &schema.String{}, JSONAlias: "a"},
Expand All @@ -89,14 +102,16 @@ func TestGenerateAllTypes(t *testing.T) {
*/
package ftl.test
import ftl.other.TestRequest
import ftl.test.TestRequest
import java.time.OffsetDateTime
data class ParamTestData<T>(
val t: T,
)
data class TestRequest(
val field: Long,
)
/**
* Response comments
*/
Expand All @@ -110,24 +125,32 @@ data class TestResponse(
val optional: String? = null,
val array: List<String>,
val nestedArray: List<List<String>>,
val dataRefArray: List<TestRequest>,
val dataRefArray: List<ftl.test.TestRequest>,
val map: Map<String, Long>,
val nestedMap: Map<String, Map<String, Long>>,
val dataRef: TestRequest,
val externalDataRef: TestRequest,
val externalDataRef: ftl.other.TestRequest,
val any: Any,
val parameterizedDataRef: ParamTestData<T>,
val parameterizedDataRef: ParamTestData<String>,
val withAlias: String,
val unit: Unit,
)
`
assertExpectedSchema(t, sch, "test/Test.kt", expected)
bctx := buildContext{
moduleDir: "testdata/modules/echokotlin",
buildDir: "target",
sch: sch,
}
testBuild(t, bctx, []assertion{
assertGeneratedModule("generated-sources/ftl/test/Test.kt", expected),
})
}

func TestGenerateAllVerbs(t *testing.T) {
sch := &schema.Schema{
Modules: []*schema.Module{
schema.Builtins(),
{
Name: "test",
Comments: []string{"Module comments"},
Expand Down Expand Up @@ -155,7 +178,6 @@ func TestGenerateAllVerbs(t *testing.T) {
*/
package ftl.test
import ftl.builtin.Empty
import xyz.block.ftl.Context
import xyz.block.ftl.Ignore
import xyz.block.ftl.Verb
Expand All @@ -169,10 +191,17 @@ data class Request(
*/
@Verb
@Ignore
fun testVerb(context: Context, req: Request): Empty = throw
fun testVerb(context: Context, req: Request): ftl.builtin.Empty = throw
NotImplementedError("Verb stubs should not be called directly, instead use context.call(::testVerb, ...)")
`
assertExpectedSchema(t, sch, "test/Test.kt", expected)
bctx := buildContext{
moduleDir: "testdata/modules/echokotlin",
buildDir: "target",
sch: sch,
}
testBuild(t, bctx, []assertion{
assertGeneratedModule("generated-sources/ftl/test/Test.kt", expected),
})
}

func TestGenerateBuiltins(t *testing.T) {
Expand Down Expand Up @@ -211,12 +240,20 @@ data class HttpResponse<Body, Error>(
class Empty
`
assertExpectedSchema(t, sch, "builtin/Builtin.kt", expected)
bctx := buildContext{
moduleDir: "testdata/modules/echokotlin",
buildDir: "target",
sch: sch,
}
testBuild(t, bctx, []assertion{
assertGeneratedModule("generated-sources/ftl/builtin/Builtin.kt", expected),
})
}

func TestGenerateEmptyDataRefs(t *testing.T) {
sch := &schema.Schema{
Modules: []*schema.Module{
schema.Builtins(),
{
Name: "test",
Decls: []schema.Decl{
Expand All @@ -235,35 +272,21 @@ func TestGenerateEmptyDataRefs(t *testing.T) {
expected := `// Code generated by FTL. DO NOT EDIT.
package ftl.test
import ftl.builtin.Empty
import xyz.block.ftl.Context
import xyz.block.ftl.Ignore
import xyz.block.ftl.Verb
@Verb
@Ignore
fun emptyVerb(context: Context, req: Empty): Empty = throw
fun emptyVerb(context: Context, req: ftl.builtin.Empty): ftl.builtin.Empty = throw
NotImplementedError("Verb stubs should not be called directly, instead use context.call(::emptyVerb, ...)")
`
assertExpectedSchema(t, sch, "test/Test.kt", expected)
}

func assertExpectedSchema(t *testing.T, sch *schema.Schema, outputPath string, expectedContent string) {
t.Helper()
ctx := log.ContextWithLogger(context.Background(), log.Configure(os.Stderr, log.Config{}))
module, err := LoadModule(ctx, "testdata/modules/echokotlin")
assert.NoError(t, err)

err = generateExternalModules(ctx, module, sch)
assert.NoError(t, err)

target := filepath.Join("testdata/modules/echokotlin", "target")
output := filepath.Join(target, "generated-sources", "ftl", outputPath)

fileContent, err := os.ReadFile(output)
assert.NoError(t, err)
assert.Equal(t, expectedContent, string(fileContent))

err = os.RemoveAll(target)
assert.NoError(t, err, "Error removing target directory")
bctx := buildContext{
moduleDir: "testdata/modules/echokotlin",
buildDir: "target",
sch: sch,
}
testBuild(t, bctx, []assertion{
assertGeneratedModule("generated-sources/ftl/test/Test.kt", expected),
})
}
Loading

0 comments on commit a9b0029

Please sign in to comment.