Skip to content

Commit

Permalink
feat: refactor type registry (#1403)
Browse files Browse the repository at this point in the history
- separate go-runtime/typeregistry and internal schema/typeregistry; add
schema type lookups for ingress encoding
- fixes for deriving schema types for sum type variants
  • Loading branch information
worstell authored May 4, 2024
1 parent 5c458d2 commit acece7b
Show file tree
Hide file tree
Showing 13 changed files with 788 additions and 298 deletions.
523 changes: 342 additions & 181 deletions backend/protos/xyz/block/ftl/v1/schema/schema.pb.go

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions backend/protos/xyz/block/ftl/v1/schema/schema.proto
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,10 @@ message StringValue {
string value = 2;
}

message SumTypeVariants {
repeated string value = 1;
}

message Time {
optional Position pos = 1;
}
Expand All @@ -241,6 +245,11 @@ message TypeParameter {
string name = 2;
}

message TypeRegistry {
map<string, Type> schemaTypes = 1;
map<string, SumTypeVariants> sumTypes = 2;
}

message TypeValue {
optional Position pos = 1;
Type value = 2;
Expand Down
5 changes: 3 additions & 2 deletions backend/schema/protobuf.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ var typesWithRuntime = map[string]bool{
// for the FTL schema.
func ProtobufSchema() string {
messages := map[string]string{}
generateMessage(reflect.TypeOf(Schema{}), messages)
generateMessage(reflect.TypeOf(ErrorList{}), messages)
generateMessage(reflect.TypeFor[Schema](), messages)
generateMessage(reflect.TypeFor[ErrorList](), messages)
generateMessage(reflect.TypeFor[TypeRegistry](), messages)
keys := maps.Keys(messages)
slices.Sort(keys)
w := &strings.Builder{}
Expand Down
71 changes: 71 additions & 0 deletions backend/schema/type_registry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package schema

import (
"context"
schemapb "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/schema"
)

type contextKeyTypeRegistry struct{}

// ContextWithTypeRegistry adds a type registry to the given context.
func ContextWithTypeRegistry(ctx context.Context, r *schemapb.TypeRegistry) context.Context {
return context.WithValue(ctx, contextKeyTypeRegistry{}, r)
}

// TypeRegistry is a registry of types that can be resolved to a schema type at runtime.
// It also records sum types and their variants, for use in encoding and decoding.
type TypeRegistry struct {
// SchemaTypes associates a type name with a schema type.
SchemaTypes map[string]Type `protobuf:"1"`
// SumTypes associates a sum type discriminator type name with its variant type names.
SumTypes map[string]SumTypeVariants `protobuf:"2"`
}

// NewTypeRegistry creates a new type registry.
// The type registry is used to instantiate types by their qualified name at runtime.
func NewTypeRegistry() *TypeRegistry {
return &TypeRegistry{
SchemaTypes: make(map[string]Type),
SumTypes: make(map[string]SumTypeVariants),
}
}

// RegisterSumType registers a Go sum type with the type registry. Sum types are represented as enums in the
// FTL schema.
func (t *TypeRegistry) RegisterSumType(discriminator string, variants map[string]Type) {
var values []string
for name, vt := range variants {
values = append(values, name)
t.SchemaTypes[name] = vt
}
t.SumTypes[discriminator] = SumTypeVariants{Value: values}
}

func (t *TypeRegistry) ToProto() *schemapb.TypeRegistry {
typespb := make(map[string]*schemapb.Type, len(t.SchemaTypes))
for k, v := range t.SchemaTypes {
typespb[k] = typeToProto(v)
}

return &schemapb.TypeRegistry{
SumTypes: sumTypeVariantsToProto(t.SumTypes),
SchemaTypes: typespb,
}
}

type SumTypeVariants struct {
// Value is a list of variant names for the sum type.
Value []string `protobuf:"1"`
}

func (s *SumTypeVariants) ToProto() *schemapb.SumTypeVariants {
return &schemapb.SumTypeVariants{Value: s.Value}
}

func sumTypeVariantsToProto(v map[string]SumTypeVariants) map[string]*schemapb.SumTypeVariants {
out := make(map[string]*schemapb.SumTypeVariants, len(v))
for k, v := range v {
out[k] = v.ToProto()
}
return out
}
6 changes: 6 additions & 0 deletions backend/schema/visit.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ func Visit(n Node, visit func(n Node, next func() error) error) error {
// stubbed verbs.
func VisitExcludingMetadataChildren(n Node, visit func(n Node, next func() error) error) error {
return visit(n, func() error {
if d, ok := n.(Decl); ok {
if !d.IsExported() {
// Skip non-exported nodes
return nil
}
}
if _, ok := n.(Metadata); !ok {
for _, child := range n.schemaChildren() {
_, isParentVerb := n.(*Verb)
Expand Down
53 changes: 6 additions & 47 deletions buildengine/build_go_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ package buildengine

import (
"fmt"
"os"
"runtime"
"testing"

"github.com/alecthomas/assert/v2"

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

Expand Down Expand Up @@ -261,58 +264,14 @@ func TestGeneratedTypeRegistry(t *testing.T) {
}},
},
}
expected := `// Code generated by FTL. DO NOT EDIT.
package main
import (
"context"
"reflect"
"github.com/TBD54566975/ftl/common/plugin"
"github.com/TBD54566975/ftl/go-runtime/server"
"github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/ftlv1connect"
"github.com/TBD54566975/ftl/go-runtime/ftl"
"ftl/other"
"ftl/another"
)
func main() {
verbConstructor := server.NewUserVerbServer("other",
server.HandleCall(other.Echo),
)
ctx := context.Background()
typeRegistry := ftl.NewTypeRegistry()
typeRegistry.RegisterSumType(reflect.TypeFor[other.D](), map[string]reflect.Type{
"Bool": reflect.TypeFor[other.Bool](),
"Bytes": reflect.TypeFor[other.Bytes](),
"Float": reflect.TypeFor[other.Float](),
"Int": reflect.TypeFor[other.Int](),
"U": reflect.TypeFor[other.U](),
"List": reflect.TypeFor[other.List](),
"Map": reflect.TypeFor[other.Map](),
"String": reflect.TypeFor[other.String](),
"Struct": reflect.TypeFor[other.Struct](),
})
typeRegistry.RegisterSumType(reflect.TypeFor[other.SecondSumType](), map[string]reflect.Type{
"A": reflect.TypeFor[other.A](),
"B": reflect.TypeFor[other.B](),
})
typeRegistry.RegisterSumType(reflect.TypeFor[another.TypeEnum](), map[string]reflect.Type{
"A": reflect.TypeFor[another.A](),
"B": reflect.TypeFor[another.B](),
})
ctx = context.WithValue(ctx, "typeRegistry", typeRegistry)
plugin.Start(ctx, "other", verbConstructor, ftlv1connect.VerbServiceName, ftlv1connect.NewVerbServiceHandler)
}
`
expected, err := os.ReadFile("testdata/type_registry_main.go")
assert.NoError(t, err)
bctx := buildContext{
moduleDir: "testdata/projects/other",
buildDir: "_ftl",
sch: sch,
}
testBuild(t, bctx, "", []assertion{
assertGeneratedMain(expected),
assertGeneratedMain(string(expected)),
})
}
21 changes: 13 additions & 8 deletions buildengine/testdata/projects/other/other.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ package other
import (
"context"
"fmt"
"time"

"github.com/TBD54566975/ftl/go-runtime/ftl" // Import the FTL SDK.

"ftl/another"
)

//ftl:enum
type D interface {
type TypeEnum interface {
tag()
}

Expand All @@ -30,13 +31,9 @@ type Int int

func (Int) tag() {}

//type Time time.Time
//
//func (Time) tag() {}
type Time time.Time

type U ftl.Unit

func (U) tag() {}
func (Time) tag() {}

type List []string

Expand All @@ -54,8 +51,16 @@ type Struct struct{}

func (Struct) tag() {}

type Option ftl.Option[string]

func (Option) tag() {}

type Unit ftl.Unit

func (Unit) tag() {}

//ftl:enum
type SecondSumType interface {
type SecondTypeEnum interface {
tag2()
}

Expand Down
72 changes: 72 additions & 0 deletions buildengine/testdata/type_registry_main.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit acece7b

Please sign in to comment.