diff --git a/backend/schema/jsonschema_test.go b/backend/schema/jsonschema_test.go index 1528b00a2a..499c4bd0b3 100644 --- a/backend/schema/jsonschema_test.go +++ b/backend/schema/jsonschema_test.go @@ -18,7 +18,7 @@ var jsonSchemaSample = &Schema{ {Name: "string", Type: &String{}, Comments: []string{"Field comment"}}, {Name: "int", Type: &Int{}}, {Name: "float", Type: &Float{}}, - {Name: "optional", Type: &Optional{&String{}}}, + {Name: "optional", Type: &Optional{Type: &String{}}}, {Name: "bool", Type: &Bool{}}, {Name: "time", Type: &Time{}}, {Name: "array", Type: &Array{Element: &String{}}}, diff --git a/backend/schema/normalise.go b/backend/schema/normalise.go index 8ba858f908..8700f6b3b9 100644 --- a/backend/schema/normalise.go +++ b/backend/schema/normalise.go @@ -1,6 +1,6 @@ package schema -// Normalise a Node. +// Normalise normalises (zeroes) positional information in schema Nodes. func Normalise[T Node](n T) T { var zero Position var ni Node = n diff --git a/backend/schema/protobuf_dec.go b/backend/schema/protobuf_dec.go index fc6f059360..1140ac1674 100644 --- a/backend/schema/protobuf_dec.go +++ b/backend/schema/protobuf_dec.go @@ -32,9 +32,21 @@ func moduleListToSchema(s []*schemapb.Module) ([]*Module, error) { return out, nil } +func PosFromProto(pos *schemapb.Position) Position { + if pos == nil { + return Position{} + } + return Position{ + Line: int(pos.Line), + Column: int(pos.Column), + Filename: pos.Filename, + } +} + // ModuleFromProto converts a protobuf Module to a Module and validates it. func ModuleFromProto(s *schemapb.Module) (*Module, error) { module := &Module{ + Pos: PosFromProto(s.Pos), Name: s.Name, Comments: s.Comments, Decls: declListToSchema(s.Decls), @@ -73,6 +85,7 @@ func declListToSchema(s []*schemapb.Decl) []Decl { func VerbToSchema(s *schemapb.Verb) *Verb { return &Verb{ + Pos: PosFromProto(s.Pos), Name: s.Name, Comments: s.Comments, Request: dataRefToSchema(s.Request), @@ -83,6 +96,7 @@ func VerbToSchema(s *schemapb.Verb) *Verb { func DataToSchema(s *schemapb.Data) *Data { return &Data{ + Pos: PosFromProto(s.Pos), Name: s.Name, Fields: fieldListToSchema(s.Fields), Comments: s.Comments, @@ -99,6 +113,7 @@ func fieldListToSchema(s []*schemapb.Field) []*Field { func fieldToSchema(s *schemapb.Field) *Field { return &Field{ + Pos: PosFromProto(s.Pos), Name: s.Name, Comments: s.Comments, Type: typeToSchema(s.Type), @@ -112,27 +127,28 @@ func typeToSchema(s *schemapb.Type) Type { case *schemapb.Type_DataRef: return dataRefToSchema(s.DataRef) case *schemapb.Type_Int: - return &Int{} + return &Int{Pos: PosFromProto(s.Int.Pos)} case *schemapb.Type_Float: - return &Float{} + return &Float{Pos: PosFromProto(s.Float.Pos)} case *schemapb.Type_String_: - return &String{} + return &String{Pos: PosFromProto(s.String_.Pos)} case *schemapb.Type_Time: - return &Time{} + return &Time{Pos: PosFromProto(s.Time.Pos)} case *schemapb.Type_Bool: - return &Bool{} + return &Bool{Pos: PosFromProto(s.Bool.Pos)} case *schemapb.Type_Array: return arrayToSchema(s.Array) case *schemapb.Type_Map: return mapToSchema(s.Map) case *schemapb.Type_Optional: - return &Optional{Type: typeToSchema(s.Optional.Type)} + return &Optional{Pos: PosFromProto(s.Optional.Pos), Type: typeToSchema(s.Optional.Type)} } panic("unreachable") } func verbRefToSchema(s *schemapb.VerbRef) *VerbRef { return &VerbRef{ + Pos: PosFromProto(s.Pos), Name: s.Name, Module: s.Module, } @@ -140,6 +156,7 @@ func verbRefToSchema(s *schemapb.VerbRef) *VerbRef { func dataRefToSchema(s *schemapb.DataRef) *DataRef { return &DataRef{ + Pos: PosFromProto(s.Pos), Name: s.Name, Module: s.Module, } @@ -147,12 +164,14 @@ func dataRefToSchema(s *schemapb.DataRef) *DataRef { func arrayToSchema(s *schemapb.Array) *Array { return &Array{ + Pos: PosFromProto(s.Pos), Element: typeToSchema(s.Element), } } func mapToSchema(s *schemapb.Map) *Map { return &Map{ + Pos: PosFromProto(s.Pos), Key: typeToSchema(s.Key), Value: typeToSchema(s.Value), } @@ -170,11 +189,13 @@ func metadataToSchema(s *schemapb.Metadata) Metadata { switch s := s.Value.(type) { case *schemapb.Metadata_Calls: return &MetadataCalls{ + Pos: PosFromProto(s.Calls.Pos), Calls: verbRefListToSchema(s.Calls.Calls), } case *schemapb.Metadata_Ingress: return &MetadataIngress{ + Pos: PosFromProto(s.Ingress.Pos), Method: s.Ingress.Method, Path: s.Ingress.Path, } diff --git a/backend/schema/protobuf_test.go b/backend/schema/protobuf_test.go index 7e00ef8efa..7bfaa45ff4 100644 --- a/backend/schema/protobuf_test.go +++ b/backend/schema/protobuf_test.go @@ -3,8 +3,9 @@ package schema import ( "testing" - schemapb "github.com/TBD54566975/ftl/protos/xyz/block/ftl/v1/schema" "github.com/alecthomas/assert/v2" + + schemapb "github.com/TBD54566975/ftl/protos/xyz/block/ftl/v1/schema" ) func TestProtoRoundtrip(t *testing.T) { diff --git a/backend/schema/schema.go b/backend/schema/schema.go index 96ecc4263d..abe1c84658 100644 --- a/backend/schema/schema.go +++ b/backend/schema/schema.go @@ -61,50 +61,52 @@ type Type interface { // Optional represents a Type whose value may be optional. type Optional struct { - Type Type `parser:"@@" protobuf:"1"` + Pos Position `parser:"" protobuf:"1,optional"` + + Type Type `parser:"@@" protobuf:"2,optional"` } type Int struct { - Pos Position `parser:"" protobuf:"-"` + Pos Position `parser:"" protobuf:"1,optional"` Int bool `parser:"@'Int'" protobuf:"-"` } type Float struct { - Pos Position `parser:"" protobuf:"-"` + Pos Position `parser:"" protobuf:"1,optional"` Float bool `parser:"@'Float'" protobuf:"-"` } type String struct { - Pos Position `parser:"" protobuf:"-"` + Pos Position `parser:"" protobuf:"1,optional"` Str bool `parser:"@'String'" protobuf:"-"` } type Bool struct { - Pos Position `parser:"" protobuf:"-"` + Pos Position `parser:"" protobuf:"1,optional"` Bool bool `parser:"@'Bool'" protobuf:"-"` } type Time struct { - Pos Position `parser:"" protobuf:"-"` + Pos Position `parser:"" protobuf:"1,optional"` Time bool `parser:"@'Time'" protobuf:"-"` } type Array struct { - Pos Position `parser:"" protobuf:"-"` + Pos Position `parser:"" protobuf:"1,optional"` - Element Type `parser:"'[' @@ ']'" protobuf:"1"` + Element Type `parser:"'[' @@ ']'" protobuf:"2"` } type Map struct { - Pos Position `parser:"" protobuf:"-"` + Pos Position `parser:"" protobuf:"1,optional"` - Key Type `parser:"'{' @@" protobuf:"1"` - Value Type `parser:"':' @@ '}'" protobuf:"2"` + Key Type `parser:"'{' @@" protobuf:"2"` + Value Type `parser:"':' @@ '}'" protobuf:"3"` } type Field struct { diff --git a/backend/schema/validate.go b/backend/schema/validate.go index b6860acedc..3514aab6c8 100644 --- a/backend/schema/validate.go +++ b/backend/schema/validate.go @@ -14,7 +14,7 @@ var ( } ) -// Validate performs semantic validation of a schema. +// Validate normalises and performs semantic validation of a schema. func Validate(schema *Schema) error { modules := map[string]bool{} verbs := map[string]bool{} diff --git a/frontend/src/protos/xyz/block/ftl/v1/schema/schema_pb.ts b/frontend/src/protos/xyz/block/ftl/v1/schema/schema_pb.ts index 8c15e885fa..6c6f835f22 100644 --- a/frontend/src/protos/xyz/block/ftl/v1/schema/schema_pb.ts +++ b/frontend/src/protos/xyz/block/ftl/v1/schema/schema_pb.ts @@ -14,7 +14,12 @@ import { ModuleRuntime, VerbRuntime } from "./runtime_pb.js"; */ export class Array extends Message { /** - * @generated from field: xyz.block.ftl.v1.schema.Type element = 1; + * @generated from field: optional xyz.block.ftl.v1.schema.Position pos = 1; + */ + pos?: Position; + + /** + * @generated from field: xyz.block.ftl.v1.schema.Type element = 2; */ element?: Type; @@ -26,7 +31,8 @@ export class Array extends Message { static readonly runtime: typeof proto3 = proto3; static readonly typeName = "xyz.block.ftl.v1.schema.Array"; static readonly fields: FieldList = proto3.util.newFieldList(() => [ - { no: 1, name: "element", kind: "message", T: Type }, + { no: 1, name: "pos", kind: "message", T: Position, opt: true }, + { no: 2, name: "element", kind: "message", T: Type }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): Array { @@ -50,6 +56,11 @@ export class Array extends Message { * @generated from message xyz.block.ftl.v1.schema.Bool */ export class Bool extends Message { + /** + * @generated from field: optional xyz.block.ftl.v1.schema.Position pos = 1; + */ + pos?: Position; + constructor(data?: PartialMessage) { super(); proto3.util.initPartial(data, this); @@ -58,6 +69,7 @@ export class Bool extends Message { static readonly runtime: typeof proto3 = proto3; static readonly typeName = "xyz.block.ftl.v1.schema.Bool"; static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "pos", kind: "message", T: Position, opt: true }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): Bool { @@ -296,6 +308,11 @@ export class Field extends Message { * @generated from message xyz.block.ftl.v1.schema.Float */ export class Float extends Message { + /** + * @generated from field: optional xyz.block.ftl.v1.schema.Position pos = 1; + */ + pos?: Position; + constructor(data?: PartialMessage) { super(); proto3.util.initPartial(data, this); @@ -304,6 +321,7 @@ export class Float extends Message { static readonly runtime: typeof proto3 = proto3; static readonly typeName = "xyz.block.ftl.v1.schema.Float"; static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "pos", kind: "message", T: Position, opt: true }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): Float { @@ -327,6 +345,11 @@ export class Float extends Message { * @generated from message xyz.block.ftl.v1.schema.Int */ export class Int extends Message { + /** + * @generated from field: optional xyz.block.ftl.v1.schema.Position pos = 1; + */ + pos?: Position; + constructor(data?: PartialMessage) { super(); proto3.util.initPartial(data, this); @@ -335,6 +358,7 @@ export class Int extends Message { static readonly runtime: typeof proto3 = proto3; static readonly typeName = "xyz.block.ftl.v1.schema.Int"; static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "pos", kind: "message", T: Position, opt: true }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): Int { @@ -359,12 +383,17 @@ export class Int extends Message { */ export class Map extends Message { /** - * @generated from field: xyz.block.ftl.v1.schema.Type key = 1; + * @generated from field: optional xyz.block.ftl.v1.schema.Position pos = 1; + */ + pos?: Position; + + /** + * @generated from field: xyz.block.ftl.v1.schema.Type key = 2; */ key?: Type; /** - * @generated from field: xyz.block.ftl.v1.schema.Type value = 2; + * @generated from field: xyz.block.ftl.v1.schema.Type value = 3; */ value?: Type; @@ -376,8 +405,9 @@ export class Map extends Message { static readonly runtime: typeof proto3 = proto3; static readonly typeName = "xyz.block.ftl.v1.schema.Map"; static readonly fields: FieldList = proto3.util.newFieldList(() => [ - { no: 1, name: "key", kind: "message", T: Type }, - { no: 2, name: "value", kind: "message", T: Type }, + { no: 1, name: "pos", kind: "message", T: Position, opt: true }, + { no: 2, name: "key", kind: "message", T: Type }, + { no: 3, name: "value", kind: "message", T: Type }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): Map { @@ -605,7 +635,12 @@ export class Module extends Message { */ export class Optional extends Message { /** - * @generated from field: xyz.block.ftl.v1.schema.Type type = 1; + * @generated from field: optional xyz.block.ftl.v1.schema.Position pos = 1; + */ + pos?: Position; + + /** + * @generated from field: optional xyz.block.ftl.v1.schema.Type type = 2; */ type?: Type; @@ -617,7 +652,8 @@ export class Optional extends Message { static readonly runtime: typeof proto3 = proto3; static readonly typeName = "xyz.block.ftl.v1.schema.Optional"; static readonly fields: FieldList = proto3.util.newFieldList(() => [ - { no: 1, name: "type", kind: "message", T: Type }, + { no: 1, name: "pos", kind: "message", T: Position, opt: true }, + { no: 2, name: "type", kind: "message", T: Type, opt: true }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): Optional { @@ -733,6 +769,11 @@ export class Schema extends Message { * @generated from message xyz.block.ftl.v1.schema.String */ export class String extends Message { + /** + * @generated from field: optional xyz.block.ftl.v1.schema.Position pos = 1; + */ + pos?: Position; + constructor(data?: PartialMessage) { super(); proto3.util.initPartial(data, this); @@ -741,6 +782,7 @@ export class String extends Message { static readonly runtime: typeof proto3 = proto3; static readonly typeName = "xyz.block.ftl.v1.schema.String"; static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "pos", kind: "message", T: Position, opt: true }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): String { @@ -764,6 +806,11 @@ export class String extends Message { * @generated from message xyz.block.ftl.v1.schema.Time */ export class Time extends Message