Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: schema changes to support SumType #1322

Merged
merged 15 commits into from
Apr 24, 2024
2 changes: 1 addition & 1 deletion backend/controller/console.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func (c *ConsoleService) GetModules(ctx context.Context, req *connect.Request[pb
Config: c,
})

case *schema.Database, *schema.Enum:
case *schema.Database, *schema.Enum, *schema.SumType:
}
}

Expand Down
974 changes: 546 additions & 428 deletions backend/protos/xyz/block/ftl/v1/schema/schema.pb.go

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions backend/protos/xyz/block/ftl/v1/schema/schema.proto
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ message Decl {
Enum enum = 4;
Config config = 5;
Secret secret = 6;
SumType sumType = 7;
}
}

Expand Down Expand Up @@ -212,6 +213,13 @@ message StringValue {
string value = 2;
}

message SumType {
optional Position pos = 1;
repeated string comments = 2;
string name = 3;
repeated Type variants = 4;
}

message Time {
optional Position pos = 1;
}
Expand Down
2 changes: 1 addition & 1 deletion backend/schema/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ func (d *Data) Monomorphise(ref *Ref) (*Data, error) {
IngressPathComponent, *IngressPathLiteral, *IngressPathParameter, *Int,
Metadata, *MetadataCalls, *MetadataDatabases, *MetadataIngress, *MetadataCronJob,
*MetadataAlias, *Module, *Schema, *String, *Time, Type, *TypeParameter,
*Unit, *Verb, *Enum, *EnumVariant,
*Unit, *Verb, *Enum, *EnumVariant, *SumType,
Value, *IntValue, *StringValue, Symbol, Named:
}
return next()
Expand Down
9 changes: 9 additions & 0 deletions backend/schema/jsonschema.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ func DataToJSONSchema(sch *Schema, ref Ref) (*jsonschema.Schema, error) {
}
case *Enum:
root.Definitions[r.String()] = jsonschema.SchemaOrBool{TypeObject: nodeToJSSchema(n, refs)}
case *SumType:
root.Definitions[r.String()] = jsonschema.SchemaOrBool{TypeObject: nodeToJSSchema(n, refs)}
case *Config, *Database, *Secret, *Verb:
return nil, fmt.Errorf("reference to unsupported node type %T", decl)
}
Expand Down Expand Up @@ -161,6 +163,13 @@ func nodeToJSSchema(node Node, refs map[RefKey]*Ref) *jsonschema.Schema {
case *TypeParameter:
return &jsonschema.Schema{}

case *SumType:
variants := make([]jsonschema.SchemaOrBool, len(node.Variants))
for i, v := range node.Variants {
variants[i] = jsonschema.SchemaOrBool{TypeObject: nodeToJSSchema(v, refs)}
}
return &jsonschema.Schema{OneOf: variants}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

Though, this should be using {"kind": xyz, "value": ...}, or is that in a followup?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fyi - from feedback during the design sync we actually lifted the original struct out of value, so it'll just be an additional field named @_kind (prefixed with @_ to avoid conflicts) at the top level now

Copy link
Collaborator

@alecthomas alecthomas Apr 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmm, @_kind can still conflict though if the sum type variant is a map:

sumtype SumType = {String: String} | Int

var value SumType = {"@_kind": "foo"}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah good point, i misunderstood your prior comment from the design sync. we can go with the kind/value approach then


case Decl, *Field, Metadata, *MetadataCalls, *MetadataDatabases, *MetadataIngress,
*MetadataAlias, IngressPathComponent, *IngressPathLiteral, *IngressPathParameter, *Module,
*Schema, Type, *Database, *Verb, *EnumVariant, *MetadataCronJob,
Expand Down
6 changes: 6 additions & 0 deletions backend/schema/normalise.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,12 @@ func Normalise[T Node](n T) T {
c.Pos = zero
c.Type = Normalise(c.Type)

case *SumType:
c.Pos = zero
for i, v := range c.Variants {
c.Variants[i] = Normalise(v)
}

case Named, Symbol, Decl, Metadata, IngressPathComponent, Type, Value: // Can never occur in reality, but here to satisfy the sum-type check.
panic("??")
}
Expand Down
3 changes: 2 additions & 1 deletion backend/schema/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
)

var (
declUnion = []Decl{&Data{}, &Verb{}, &Database{}, &Enum{}, &Config{}, &Secret{}}
declUnion = []Decl{&Data{}, &Verb{}, &Database{}, &Enum{}, &Config{}, &Secret{}, &SumType{}}
nonOptionalTypeUnion = []Type{
&Int{}, &Float{}, &String{}, &Bytes{}, &Bool{}, &Time{}, &Array{},
&Map{}, &Any{}, &Unit{},
Expand Down Expand Up @@ -41,6 +41,7 @@ var (
{Name: "String", Pattern: `"(?:\\.|[^"])*"`},
{Name: "Number", Pattern: `[0-9]+(?:\.[0-9]+)?`},
{Name: "Punct", Pattern: `[%/\-\_:[\]{}<>()*+?.,\\^$|#~!\'@]`},
{Name: "Equals", Pattern: `=`},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just add this to Punct instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done!

})

commonParserOptions = []participle.Option{
Expand Down
2 changes: 2 additions & 0 deletions backend/schema/protobuf_dec.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ func declListToSchema(s []*schemapb.Decl) []Decl {
out = append(out, ConfigFromProto(n.Config))
case *schemapb.Decl_Secret:
out = append(out, SecretFromProto(n.Secret))
case *schemapb.Decl_SumType:
out = append(out, SumTypeFromProto(n.SumType))
}
}
return out
Expand Down
3 changes: 3 additions & 0 deletions backend/schema/protobuf_enc.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ func declListToProto(nodes []Decl) []*schemapb.Decl {

case *Secret:
v = &schemapb.Decl_Secret{Secret: n.ToProto().(*schemapb.Secret)}

case *SumType:
v = &schemapb.Decl_SumType{SumType: n.ToProto().(*schemapb.SumType)}
}
out[i] = &schemapb.Decl{Value: v}
}
Expand Down
14 changes: 14 additions & 0 deletions backend/schema/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ module todo {

database testdb

// SumType comment
sumtype IntOrBool = Int | Bool

data CreateRequest {
name {String: String}? +alias json "rqn"
}
Expand Down Expand Up @@ -102,6 +105,9 @@ Module
Secret
String
Database
SumType
Int
Bool
Data
Field
Optional
Expand Down Expand Up @@ -378,6 +384,9 @@ module todo {
secret secretValue String
database testdb

// SumType comment
sumtype IntOrBool = Int | Bool

data CreateRequest {
name {String: String}? +alias json "rqn"
}
Expand Down Expand Up @@ -435,6 +444,11 @@ var testSchema = MustValidate(&Schema{
Name: "todo",
Comments: []string{"A comment"},
Decls: []Decl{
&SumType{
Comments: []string{"SumType comment"},
Name: "IntOrBool",
Variants: []Type{&Int{}, &Bool{}},
},
&Secret{
Name: "secretValue",
Type: &String{},
Expand Down
70 changes: 70 additions & 0 deletions backend/schema/sumtype.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package schema

import (
"fmt"
"strings"

schemapb "github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1/schema"
"google.golang.org/protobuf/proto"
)

type SumType struct {
Pos Position `parser:"" protobuf:"1,optional"`

Comments []string `parser:"@Comment*" protobuf:"2"`
Name string `parser:"'sumtype' @Ident Equals" protobuf:"3"`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be just '=' by adding = to Punct.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oo lala, thank you!

Variants []Type `parser:"@@ ('|' @@)*" protobuf:"4"`
}

var _ Decl = (*SumType)(nil)
var _ Symbol = (*SumType)(nil)

func (s *SumType) Position() Position { return s.Pos }

func (s *SumType) String() string {
w := &strings.Builder{}
fmt.Fprint(w, encodeComments(s.Comments))
typeNames := make([]string, len(s.Variants))
for i, v := range s.Variants {
typeNames[i] = v.String()
}
fmt.Fprintf(w, "sumtype %s = %s", s.Name, strings.Join(typeNames, " | "))
return w.String()
}

func (s *SumType) schemaDecl() {}
func (*SumType) schemaSymbol() {}
func (s *SumType) schemaChildren() []Node {
children := make([]Node, len(s.Variants))
for i, v := range s.Variants {
children[i] = v
}
return children
}
func (s *SumType) ToProto() proto.Message {
variants := make([]*schemapb.Type, len(s.Variants))
for i, v := range s.Variants {
variants[i] = typeToProto(v)
}
return &schemapb.SumType{
Pos: posToProto(s.Pos),
Comments: s.Comments,
Name: s.Name,
Variants: variants,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have a helper for this, so you don't have to construct your own list!

Suggested change
Variants: variants,
Variants: nodeListToProto[*schemapb.Type](s.Variants),

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, actually:

Suggested change
Variants: variants,
Variants: slices.Map(s.Variants, typeToProto),

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done :D

}
}

func (s *SumType) GetName() string { return s.Name }

func SumTypeFromProto(s *schemapb.SumType) *SumType {
variants := make([]Type, len(s.Variants))
for i, v := range s.Variants {
variants[i] = typeToSchema(v)
}
return &SumType{
Pos: posFromProto(s.Pos),
Name: s.Name,
Comments: s.Comments,
Variants: variants,
}
}
10 changes: 6 additions & 4 deletions backend/schema/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ func ValidateModuleInSchema(schema *Schema, m optional.Option[*Module]) (*Schema
*Int, *Map, Metadata, *MetadataCalls, *MetadataDatabases, *MetadataCronJob,
*MetadataIngress, *MetadataAlias, *Module, *Optional, *Schema,
*String, *Time, Type, *Unit, *Any, *TypeParameter, *EnumVariant,
Value, *IntValue, *StringValue, *Config, *Secret, Symbol, Named:
Value, *IntValue, *StringValue, *Config, *Secret, *SumType, Symbol, Named:
}
return next()
})
Expand Down Expand Up @@ -306,7 +306,7 @@ func ValidateModule(module *Module) error {
*Time, *Map, *Module, *Schema, *String, *Bytes,
*MetadataCalls, *MetadataDatabases, *MetadataIngress, *MetadataCronJob, *MetadataAlias,
IngressPathComponent, *IngressPathLiteral, *IngressPathParameter, *Optional,
*Unit, *Any, *TypeParameter, *Enum, *EnumVariant, *IntValue, *StringValue:
*Unit, *Any, *TypeParameter, *Enum, *EnumVariant, *IntValue, *StringValue, *SumType:

case Named, Symbol, Type, Metadata, Decl, Value: // Union types.
}
Expand Down Expand Up @@ -339,10 +339,12 @@ func getDeclSortingPriority(decl Decl) int {
priority = 3
case *Enum:
priority = 4
case *Data:
case *SumType:
priority = 5
case *Verb:
case *Data:
priority = 6
case *Verb:
priority = 7
}
return priority
}
Expand Down
62 changes: 62 additions & 0 deletions frontend/src/protos/xyz/block/ftl/v1/schema/schema_pb.ts

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

Loading