From 29a7150e8e36b3b05e0f3efe016db01feb0d997d Mon Sep 17 00:00:00 2001 From: Wes Date: Wed, 1 May 2024 11:03:34 -0700 Subject: [PATCH] feat: add export to verb, data, and enum (#1366) Fixes #1354 --- backend/controller/controller.go | 18 +- backend/controller/ingress/handler_test.go | 8 +- backend/controller/ingress/ingress.go | 10 +- backend/controller/ingress/ingress_test.go | 2 +- backend/controller/ingress/request_test.go | 8 +- .../xyz/block/ftl/v1/schema/schema.pb.go | 239 ++++++++++-------- .../xyz/block/ftl/v1/schema/schema.proto | 27 +- backend/schema/builtin.go | 6 +- backend/schema/config.go | 1 + backend/schema/data.go | 25 +- backend/schema/database.go | 3 +- backend/schema/enum.go | 15 +- backend/schema/module.go | 3 +- backend/schema/parser.go | 2 + backend/schema/schema_test.go | 51 ++-- backend/schema/secret.go | 1 + backend/schema/validate.go | 33 +-- backend/schema/validate_test.go | 112 ++++++-- backend/schema/verb.go | 19 +- .../xyz/block/ftl/v1/schema/schema_pb.ts | 76 +++--- go-runtime/compile/parser.go | 13 + go-runtime/compile/schema.go | 57 +++-- go-runtime/compile/schema_test.go | 15 +- go-runtime/compile/testdata/one/one.go | 15 +- integration/testdata/go/time/time.go | 2 +- 25 files changed, 483 insertions(+), 278 deletions(-) diff --git a/backend/controller/controller.go b/backend/controller/controller.go index 1a1386568e..1cf7c16b9c 100644 --- a/backend/controller/controller.go +++ b/backend/controller/controller.go @@ -657,14 +657,20 @@ func (s *Service) callWithRequest(ctx context.Context, req *connect.Request[ftlv if req.Msg.Body == nil { return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("body is required")) } - verbRef := schema.RefFromProto(req.Msg.Verb) sch, err := s.getActiveSchema(ctx) if err != nil { return nil, err } - err = ingress.ValidateCallBody(req.Msg.Body, verbRef, sch) + verbRef := schema.RefFromProto(req.Msg.Verb) + verb := &schema.Verb{} + err = sch.ResolveRefToType(verbRef, verb) + if err != nil { + return nil, err + } + + err = ingress.ValidateCallBody(req.Msg.Body, verb, sch) if err != nil { return nil, err } @@ -684,6 +690,14 @@ func (s *Service) callWithRequest(ctx context.Context, req *connect.Request[ftlv return nil, err } + if !verb.IsExported() { + for _, caller := range callers { + if caller.Module != module { + return nil, connect.NewError(connect.CodePermissionDenied, fmt.Errorf("verb %q is not exported", verbRef)) + } + } + } + var requestKey model.RequestKey isNewRequestKey := false if k, ok := key.Get(); ok { diff --git a/backend/controller/ingress/handler_test.go b/backend/controller/ingress/handler_test.go index ea10c38edb..da4844fadb 100644 --- a/backend/controller/ingress/handler_test.go +++ b/backend/controller/ingress/handler_test.go @@ -44,16 +44,16 @@ func TestIngress(t *testing.T) { foo String } - verb getAlias(HttpRequest) HttpResponse + export verb getAlias(HttpRequest) HttpResponse +ingress http GET /getAlias - verb getPath(HttpRequest) HttpResponse + export verb getPath(HttpRequest) HttpResponse +ingress http GET /getPath/{username} - verb postMissingTypes(HttpRequest) HttpResponse + export verb postMissingTypes(HttpRequest) HttpResponse +ingress http POST /postMissingTypes - verb postJsonPayload(HttpRequest) HttpResponse + export verb postJsonPayload(HttpRequest) HttpResponse +ingress http POST /postJsonPayload } `) diff --git a/backend/controller/ingress/ingress.go b/backend/controller/ingress/ingress.go index 7934d51388..da7a62f4db 100644 --- a/backend/controller/ingress/ingress.go +++ b/backend/controller/ingress/ingress.go @@ -58,15 +58,9 @@ func matchSegments(pattern, urlPath string, onMatch func(segment, value string)) return true } -func ValidateCallBody(body []byte, ref *schema.Ref, sch *schema.Schema) error { - verb := &schema.Verb{} - err := sch.ResolveRefToType(ref, verb) - if err != nil { - return err - } - +func ValidateCallBody(body []byte, verb *schema.Verb, sch *schema.Schema) error { var requestMap map[string]any - err = json.Unmarshal(body, &requestMap) + err := json.Unmarshal(body, &requestMap) if err != nil { return fmt.Errorf("HTTP request body is not valid JSON: %w", err) } diff --git a/backend/controller/ingress/ingress_test.go b/backend/controller/ingress/ingress_test.go index cd07fe718b..51e929537e 100644 --- a/backend/controller/ingress/ingress_test.go +++ b/backend/controller/ingress/ingress_test.go @@ -94,7 +94,7 @@ func TestValidation(t *testing.T) { schema: `module test { data Nested { intValue Int } data Test { mapValue {String: test.Nested} } }`, request: obj{"mapValue": obj{"key1": obj{"intValue": 10.0}, "key2": obj{"intValue": 20.0}}}}, {name: "OtherModuleRef", - schema: `module other { data Other { intValue Int } } module test { data Test { otherRef other.Other } }`, + schema: `module other { export data Other { intValue Int } } module test { data Test { otherRef other.Other } }`, request: obj{"otherRef": obj{"intValue": 10.0}}}, {name: "AllowedMissingFieldTypes", schema: ` diff --git a/backend/controller/ingress/request_test.go b/backend/controller/ingress/request_test.go index 3c7ff88971..775534b238 100644 --- a/backend/controller/ingress/request_test.go +++ b/backend/controller/ingress/request_test.go @@ -68,16 +68,16 @@ func TestBuildRequestBody(t *testing.T) { foo String } - verb getAlias(HttpRequest) HttpResponse + export verb getAlias(HttpRequest) HttpResponse +ingress http GET /getAlias - verb getPath(HttpRequest) HttpResponse + export verb getPath(HttpRequest) HttpResponse +ingress http GET /getPath/{username} - verb postMissingTypes(HttpRequest) HttpResponse + export verb postMissingTypes(HttpRequest) HttpResponse +ingress http POST /postMissingTypes - verb postJsonPayload(HttpRequest) HttpResponse + export verb postJsonPayload(HttpRequest) HttpResponse +ingress http POST /postJsonPayload } `) diff --git a/backend/protos/xyz/block/ftl/v1/schema/schema.pb.go b/backend/protos/xyz/block/ftl/v1/schema/schema.pb.go index 3ee5e2feb6..d1795d4d11 100644 --- a/backend/protos/xyz/block/ftl/v1/schema/schema.pb.go +++ b/backend/protos/xyz/block/ftl/v1/schema/schema.pb.go @@ -288,10 +288,11 @@ type Data struct { Pos *Position `protobuf:"bytes,1,opt,name=pos,proto3,oneof" json:"pos,omitempty"` Comments []string `protobuf:"bytes,2,rep,name=comments,proto3" json:"comments,omitempty"` - Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` - Fields []*Field `protobuf:"bytes,4,rep,name=fields,proto3" json:"fields,omitempty"` - Metadata []*Metadata `protobuf:"bytes,5,rep,name=metadata,proto3" json:"metadata,omitempty"` - TypeParameters []*TypeParameter `protobuf:"bytes,6,rep,name=typeParameters,proto3" json:"typeParameters,omitempty"` + Export bool `protobuf:"varint,3,opt,name=export,proto3" json:"export,omitempty"` + Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` + TypeParameters []*TypeParameter `protobuf:"bytes,5,rep,name=typeParameters,proto3" json:"typeParameters,omitempty"` + Fields []*Field `protobuf:"bytes,6,rep,name=fields,proto3" json:"fields,omitempty"` + Metadata []*Metadata `protobuf:"bytes,7,rep,name=metadata,proto3" json:"metadata,omitempty"` } func (x *Data) Reset() { @@ -340,6 +341,13 @@ func (x *Data) GetComments() []string { return nil } +func (x *Data) GetExport() bool { + if x != nil { + return x.Export + } + return false +} + func (x *Data) GetName() string { if x != nil { return x.Name @@ -347,23 +355,23 @@ func (x *Data) GetName() string { return "" } -func (x *Data) GetFields() []*Field { +func (x *Data) GetTypeParameters() []*TypeParameter { if x != nil { - return x.Fields + return x.TypeParameters } return nil } -func (x *Data) GetMetadata() []*Metadata { +func (x *Data) GetFields() []*Field { if x != nil { - return x.Metadata + return x.Fields } return nil } -func (x *Data) GetTypeParameters() []*TypeParameter { +func (x *Data) GetMetadata() []*Metadata { if x != nil { - return x.TypeParameters + return x.Metadata } return nil } @@ -583,9 +591,10 @@ type Enum struct { Pos *Position `protobuf:"bytes,1,opt,name=pos,proto3,oneof" json:"pos,omitempty"` Comments []string `protobuf:"bytes,2,rep,name=comments,proto3" json:"comments,omitempty"` - Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` - Type *Type `protobuf:"bytes,4,opt,name=type,proto3,oneof" json:"type,omitempty"` - Variants []*EnumVariant `protobuf:"bytes,5,rep,name=variants,proto3" json:"variants,omitempty"` + Export bool `protobuf:"varint,3,opt,name=export,proto3" json:"export,omitempty"` + Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` + Type *Type `protobuf:"bytes,5,opt,name=type,proto3,oneof" json:"type,omitempty"` + Variants []*EnumVariant `protobuf:"bytes,6,rep,name=variants,proto3" json:"variants,omitempty"` } func (x *Enum) Reset() { @@ -634,6 +643,13 @@ func (x *Enum) GetComments() []string { return nil } +func (x *Enum) GetExport() bool { + if x != nil { + return x.Export + } + return false +} + func (x *Enum) GetName() string { if x != nil { return x.Name @@ -2763,11 +2779,12 @@ type Verb struct { Runtime *VerbRuntime `protobuf:"bytes,31634,opt,name=runtime,proto3,oneof" json:"runtime,omitempty"` Pos *Position `protobuf:"bytes,1,opt,name=pos,proto3,oneof" json:"pos,omitempty"` - Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` - Comments []string `protobuf:"bytes,3,rep,name=comments,proto3" json:"comments,omitempty"` - Request *Type `protobuf:"bytes,4,opt,name=request,proto3" json:"request,omitempty"` - Response *Type `protobuf:"bytes,5,opt,name=response,proto3" json:"response,omitempty"` - Metadata []*Metadata `protobuf:"bytes,6,rep,name=metadata,proto3" json:"metadata,omitempty"` + Comments []string `protobuf:"bytes,2,rep,name=comments,proto3" json:"comments,omitempty"` + Export bool `protobuf:"varint,3,opt,name=export,proto3" json:"export,omitempty"` + Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` + Request *Type `protobuf:"bytes,5,opt,name=request,proto3" json:"request,omitempty"` + Response *Type `protobuf:"bytes,6,opt,name=response,proto3" json:"response,omitempty"` + Metadata []*Metadata `protobuf:"bytes,7,rep,name=metadata,proto3" json:"metadata,omitempty"` } func (x *Verb) Reset() { @@ -2816,18 +2833,25 @@ func (x *Verb) GetPos() *Position { return nil } -func (x *Verb) GetName() string { +func (x *Verb) GetComments() []string { if x != nil { - return x.Name + return x.Comments } - return "" + return nil } -func (x *Verb) GetComments() []string { +func (x *Verb) GetExport() bool { if x != nil { - return x.Comments + return x.Export } - return nil + return false +} + +func (x *Verb) GetName() string { + if x != nil { + return x.Name + } + return "" } func (x *Verb) GetRequest() *Type { @@ -2891,71 +2915,74 @@ var file_xyz_block_ftl_v1_schema_schema_proto_rawDesc = []byte{ 0x6d, 0x65, 0x12, 0x31, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, - 0x04, 0x74, 0x79, 0x70, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x70, 0x6f, 0x73, 0x22, 0xbf, 0x02, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x70, 0x6f, 0x73, 0x22, 0xd7, 0x02, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x38, 0x0a, 0x03, 0x70, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x03, 0x70, 0x6f, 0x73, 0x88, 0x01, 0x01, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x12, 0x0a, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x12, 0x36, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x1e, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, - 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, - 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x3d, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x78, 0x79, 0x7a, - 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x63, - 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x4e, 0x0a, 0x0e, 0x74, 0x79, 0x70, 0x65, 0x50, - 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x26, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, - 0x76, 0x31, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x50, 0x61, - 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, 0x0e, 0x74, 0x79, 0x70, 0x65, 0x50, 0x61, 0x72, - 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x70, 0x6f, 0x73, 0x22, - 0x90, 0x01, 0x0a, 0x08, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x03, - 0x70, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x78, 0x79, 0x7a, 0x2e, - 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x63, 0x68, - 0x65, 0x6d, 0x61, 0x2e, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x03, - 0x70, 0x6f, 0x73, 0x88, 0x01, 0x01, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x6f, - 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, - 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x70, - 0x6f, 0x73, 0x22, 0xe5, 0x02, 0x0a, 0x04, 0x44, 0x65, 0x63, 0x6c, 0x12, 0x33, 0x0a, 0x04, 0x64, - 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x78, 0x79, 0x7a, 0x2e, - 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x63, 0x68, - 0x65, 0x6d, 0x61, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x48, 0x00, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, - 0x12, 0x33, 0x0a, 0x04, 0x76, 0x65, 0x72, 0x62, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, - 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, - 0x31, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x56, 0x65, 0x72, 0x62, 0x48, 0x00, 0x52, - 0x04, 0x76, 0x65, 0x72, 0x62, 0x12, 0x3f, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, - 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, - 0x61, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x48, 0x00, 0x52, 0x08, 0x64, 0x61, - 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x65, 0x6e, 0x75, 0x6d, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, - 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x45, - 0x6e, 0x75, 0x6d, 0x48, 0x00, 0x52, 0x04, 0x65, 0x6e, 0x75, 0x6d, 0x12, 0x39, 0x0a, 0x06, 0x63, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x78, 0x79, - 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x73, - 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x48, 0x00, 0x52, 0x06, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x39, 0x0a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, + 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x16, 0x0a, 0x06, + 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x65, 0x78, + 0x70, 0x6f, 0x72, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x4e, 0x0a, 0x0e, 0x74, 0x79, 0x70, 0x65, + 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x26, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, + 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x50, + 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, 0x0e, 0x74, 0x79, 0x70, 0x65, 0x50, 0x61, + 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x36, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, + 0x64, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, + 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x63, 0x68, 0x65, + 0x6d, 0x61, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, + 0x12, 0x3d, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x07, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, + 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x42, + 0x06, 0x0a, 0x04, 0x5f, 0x70, 0x6f, 0x73, 0x22, 0x90, 0x01, 0x0a, 0x08, 0x44, 0x61, 0x74, 0x61, + 0x62, 0x61, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x03, 0x70, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x21, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, + 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x50, 0x6f, 0x73, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x03, 0x70, 0x6f, 0x73, 0x88, 0x01, 0x01, 0x12, 0x12, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x12, + 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x70, 0x6f, 0x73, 0x22, 0xe5, 0x02, 0x0a, 0x04, 0x44, + 0x65, 0x63, 0x6c, 0x12, 0x33, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1d, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, + 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x44, 0x61, 0x74, 0x61, + 0x48, 0x00, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x33, 0x0a, 0x04, 0x76, 0x65, 0x72, 0x62, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, - 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x48, 0x00, 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, - 0x74, 0x42, 0x07, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xfb, 0x01, 0x0a, 0x04, 0x45, - 0x6e, 0x75, 0x6d, 0x12, 0x38, 0x0a, 0x03, 0x70, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x21, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, - 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x50, 0x6f, 0x73, 0x69, 0x74, - 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x03, 0x70, 0x6f, 0x73, 0x88, 0x01, 0x01, 0x12, 0x1a, 0x0a, - 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x36, 0x0a, - 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x78, 0x79, + 0x2e, 0x56, 0x65, 0x72, 0x62, 0x48, 0x00, 0x52, 0x04, 0x76, 0x65, 0x72, 0x62, 0x12, 0x3f, 0x0a, + 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x21, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, + 0x76, 0x31, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, + 0x73, 0x65, 0x48, 0x00, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x33, + 0x0a, 0x04, 0x65, 0x6e, 0x75, 0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x78, + 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, + 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x48, 0x00, 0x52, 0x04, 0x65, + 0x6e, 0x75, 0x6d, 0x12, 0x39, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, + 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x48, 0x00, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x39, + 0x0a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, + 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, + 0x31, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x48, + 0x00, 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x42, 0x07, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x22, 0x93, 0x02, 0x0a, 0x04, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x38, 0x0a, 0x03, 0x70, + 0x6f, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, + 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x63, 0x68, 0x65, + 0x6d, 0x61, 0x2e, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x03, 0x70, + 0x6f, 0x73, 0x88, 0x01, 0x01, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x06, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x36, 0x0a, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x48, 0x01, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x88, 0x01, 0x01, 0x12, 0x40, 0x0a, 0x08, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, - 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, + 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x56, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x52, 0x08, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x73, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x70, 0x6f, 0x73, 0x42, @@ -3271,7 +3298,7 @@ var file_xyz_block_ftl_v1_schema_schema_proto_rawDesc = []byte{ 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x48, 0x00, 0x52, 0x09, 0x74, 0x79, 0x70, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x07, 0x0a, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xfe, 0x02, 0x0a, 0x04, 0x56, 0x65, 0x72, 0x62, 0x12, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x96, 0x03, 0x0a, 0x04, 0x56, 0x65, 0x72, 0x62, 0x12, 0x45, 0x0a, 0x07, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x92, 0xf7, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x56, 0x65, 0x72, @@ -3280,27 +3307,29 @@ var file_xyz_block_ftl_v1_schema_schema_proto_rawDesc = []byte{ 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x01, 0x52, 0x03, 0x70, 0x6f, 0x73, 0x88, 0x01, 0x01, - 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, - 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, - 0x12, 0x37, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1d, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, - 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x79, 0x70, 0x65, - 0x52, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x39, 0x0a, 0x08, 0x72, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x78, 0x79, - 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x73, - 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, - 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, - 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x42, - 0x06, 0x0a, 0x04, 0x5f, 0x70, 0x6f, 0x73, 0x42, 0x4e, 0x50, 0x01, 0x5a, 0x4a, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x54, 0x42, 0x44, 0x35, 0x34, 0x35, 0x36, 0x36, - 0x39, 0x37, 0x35, 0x2f, 0x66, 0x74, 0x6c, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2f, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x78, 0x79, 0x7a, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, - 0x2f, 0x66, 0x74, 0x6c, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x3b, 0x73, - 0x63, 0x68, 0x65, 0x6d, 0x61, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x16, 0x0a, 0x06, + 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x65, 0x78, + 0x70, 0x6f, 0x72, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x07, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x78, 0x79, 0x7a, 0x2e, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x63, 0x68, + 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x39, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, + 0x66, 0x74, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x54, 0x79, + 0x70, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x08, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, + 0x2e, 0x78, 0x79, 0x7a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x66, 0x74, 0x6c, 0x2e, 0x76, + 0x31, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x42, 0x0a, 0x0a, 0x08, 0x5f, + 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x70, 0x6f, 0x73, 0x42, + 0x4e, 0x50, 0x01, 0x5a, 0x4a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x54, 0x42, 0x44, 0x35, 0x34, 0x35, 0x36, 0x36, 0x39, 0x37, 0x35, 0x2f, 0x66, 0x74, 0x6c, 0x2f, + 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x78, + 0x79, 0x7a, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2f, 0x66, 0x74, 0x6c, 0x2f, 0x76, 0x31, 0x2f, + 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x3b, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x70, 0x62, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -3370,9 +3399,9 @@ var file_xyz_block_ftl_v1_schema_schema_proto_depIdxs = []int32{ 28, // 5: xyz.block.ftl.v1.schema.Config.pos:type_name -> xyz.block.ftl.v1.schema.Position 35, // 6: xyz.block.ftl.v1.schema.Config.type:type_name -> xyz.block.ftl.v1.schema.Type 28, // 7: xyz.block.ftl.v1.schema.Data.pos:type_name -> xyz.block.ftl.v1.schema.Position - 12, // 8: xyz.block.ftl.v1.schema.Data.fields:type_name -> xyz.block.ftl.v1.schema.Field - 20, // 9: xyz.block.ftl.v1.schema.Data.metadata:type_name -> xyz.block.ftl.v1.schema.Metadata - 36, // 10: xyz.block.ftl.v1.schema.Data.typeParameters:type_name -> xyz.block.ftl.v1.schema.TypeParameter + 36, // 8: xyz.block.ftl.v1.schema.Data.typeParameters:type_name -> xyz.block.ftl.v1.schema.TypeParameter + 12, // 9: xyz.block.ftl.v1.schema.Data.fields:type_name -> xyz.block.ftl.v1.schema.Field + 20, // 10: xyz.block.ftl.v1.schema.Data.metadata:type_name -> xyz.block.ftl.v1.schema.Metadata 28, // 11: xyz.block.ftl.v1.schema.Database.pos:type_name -> xyz.block.ftl.v1.schema.Position 5, // 12: xyz.block.ftl.v1.schema.Decl.data:type_name -> xyz.block.ftl.v1.schema.Data 40, // 13: xyz.block.ftl.v1.schema.Decl.verb:type_name -> xyz.block.ftl.v1.schema.Verb diff --git a/backend/protos/xyz/block/ftl/v1/schema/schema.proto b/backend/protos/xyz/block/ftl/v1/schema/schema.proto index 343fcf4680..de58c1977e 100644 --- a/backend/protos/xyz/block/ftl/v1/schema/schema.proto +++ b/backend/protos/xyz/block/ftl/v1/schema/schema.proto @@ -34,10 +34,11 @@ message Config { message Data { optional Position pos = 1; repeated string comments = 2; - string name = 3; - repeated Field fields = 4; - repeated Metadata metadata = 5; - repeated TypeParameter typeParameters = 6; + bool export = 3; + string name = 4; + repeated TypeParameter typeParameters = 5; + repeated Field fields = 6; + repeated Metadata metadata = 7; } message Database { @@ -61,9 +62,10 @@ message Decl { message Enum { optional Position pos = 1; repeated string comments = 2; - string name = 3; - optional Type type = 4; - repeated EnumVariant variants = 5; + bool export = 3; + string name = 4; + optional Type type = 5; + repeated EnumVariant variants = 6; } message EnumVariant { @@ -260,9 +262,10 @@ message Verb { optional VerbRuntime runtime = 31634; optional Position pos = 1; - string name = 2; - repeated string comments = 3; - Type request = 4; - Type response = 5; - repeated Metadata metadata = 6; + repeated string comments = 2; + bool export = 3; + string name = 4; + Type request = 5; + Type response = 6; + repeated Metadata metadata = 7; } diff --git a/backend/schema/builtin.go b/backend/schema/builtin.go index ab2f0a9e7b..1f581eca7d 100644 --- a/backend/schema/builtin.go +++ b/backend/schema/builtin.go @@ -7,7 +7,7 @@ const BuiltinsSource = ` // Built-in types for FTL. builtin module builtin { // HTTP request structure used for HTTP ingress verbs. - data HttpRequest { + export data HttpRequest { method String path String pathParameters {String: String} @@ -17,7 +17,7 @@ builtin module builtin { } // HTTP response structure used for HTTP ingress verbs. - data HttpResponse { + export data HttpResponse { status Int headers {String: [String]} // Either "body" or "error" must be present, not both. @@ -25,7 +25,7 @@ builtin module builtin { error Error? } - data Empty {} + export data Empty {} } ` diff --git a/backend/schema/config.go b/backend/schema/config.go index de0143c2d7..d39c980776 100644 --- a/backend/schema/config.go +++ b/backend/schema/config.go @@ -19,6 +19,7 @@ var _ Decl = (*Config)(nil) var _ Symbol = (*Config)(nil) func (s *Config) GetName() string { return s.Name } +func (s *Config) IsExported() bool { return false } func (s *Config) Position() Position { return s.Pos } func (s *Config) String() string { return fmt.Sprintf("config %s %s", s.Name, s.Type) } diff --git a/backend/schema/data.go b/backend/schema/data.go index ba17ff5d21..cc385a5e42 100644 --- a/backend/schema/data.go +++ b/backend/schema/data.go @@ -15,10 +15,11 @@ type Data struct { Pos Position `parser:"" protobuf:"1,optional"` Comments []string `parser:"@Comment*" protobuf:"2"` - Name string `parser:"'data' @Ident" protobuf:"3"` - TypeParameters []*TypeParameter `parser:"( '<' @@ (',' @@)* '>' )?" protobuf:"6"` - Fields []*Field `parser:"'{' @@* '}'" protobuf:"4"` - Metadata []Metadata `parser:"@@*" protobuf:"5"` + Export bool `parser:"@'export'?" protobuf:"3"` + Name string `parser:"'data' @Ident" protobuf:"4"` + TypeParameters []*TypeParameter `parser:"( '<' @@ (',' @@)* '>' )?" protobuf:"5"` + Fields []*Field `parser:"'{' @@* '}'" protobuf:"6"` + Metadata []Metadata `parser:"@@*" protobuf:"7"` } var _ Decl = (*Data)(nil) @@ -148,7 +149,8 @@ func (d *Data) schemaChildren() []Node { return children } -func (d *Data) GetName() string { return d.Name } +func (d *Data) GetName() string { return d.Name } +func (d *Data) IsExported() bool { return d.Export } func (d *Data) String() string { w := &strings.Builder{} @@ -164,6 +166,9 @@ func (d *Data) String() string { } typeParameters += ">" } + if d.Export { + fmt.Fprint(w, "export ") + } fmt.Fprintf(w, "data %s%s {\n", d.Name, typeParameters) for _, f := range d.Fields { fmt.Fprintln(w, indent(f.String())) @@ -175,9 +180,11 @@ func (d *Data) String() string { func (d *Data) ToProto() proto.Message { return &schemapb.Data{ - Pos: posToProto(d.Pos), - TypeParameters: nodeListToProto[*schemapb.TypeParameter](d.TypeParameters), + Pos: posToProto(d.Pos), + Name: d.Name, + Export: d.Export, + TypeParameters: nodeListToProto[*schemapb.TypeParameter](d.TypeParameters), Fields: nodeListToProto[*schemapb.Field](d.Fields), Comments: d.Comments, } @@ -185,8 +192,10 @@ func (d *Data) ToProto() proto.Message { func DataFromProto(s *schemapb.Data) *Data { return &Data{ - Pos: posFromProto(s.Pos), + Pos: posFromProto(s.Pos), + Name: s.Name, + Export: s.Export, TypeParameters: typeParametersToSchema(s.TypeParameters), Fields: fieldListToSchema(s.Fields), Comments: s.Comments, diff --git a/backend/schema/database.go b/backend/schema/database.go index a0938aa32f..2fbc1d92e0 100644 --- a/backend/schema/database.go +++ b/backend/schema/database.go @@ -42,7 +42,8 @@ func (d *Database) ToProto() proto.Message { } } -func (d *Database) GetName() string { return d.Name } +func (d *Database) GetName() string { return d.Name } +func (d *Database) IsExported() bool { return false } func DatabaseFromProto(s *schemapb.Database) *Database { return &Database{ diff --git a/backend/schema/enum.go b/backend/schema/enum.go index 71260b84fe..2481c1276e 100644 --- a/backend/schema/enum.go +++ b/backend/schema/enum.go @@ -13,9 +13,10 @@ type Enum struct { Pos Position `parser:"" protobuf:"1,optional"` Comments []string `parser:"@Comment*" protobuf:"2"` - Name string `parser:"'enum' @Ident" protobuf:"3"` - Type Type `parser:"(':' @@)?" protobuf:"4,optional"` - Variants []*EnumVariant `parser:"'{' @@* '}'" protobuf:"5"` + Export bool `parser:"@'export'?" protobuf:"3"` + Name string `parser:"'enum' @Ident" protobuf:"4"` + Type Type `parser:"(':' @@)?" protobuf:"5,optional"` + Variants []*EnumVariant `parser:"'{' @@* '}'" protobuf:"6"` } var _ Decl = (*Enum)(nil) @@ -26,6 +27,9 @@ func (e *Enum) Position() Position { return e.Pos } func (e *Enum) String() string { w := &strings.Builder{} fmt.Fprint(w, encodeComments(e.Comments)) + if e.Export { + fmt.Fprint(w, "export ") + } fmt.Fprintf(w, "enum %s", e.Name) if e.Type != nil { fmt.Fprintf(w, ": %s", e.Type) @@ -54,6 +58,7 @@ func (e *Enum) ToProto() proto.Message { Pos: posToProto(e.Pos), Comments: e.Comments, Name: e.Name, + Export: e.Export, Variants: nodeListToProto[*schemapb.EnumVariant](e.Variants), } if e.Type != nil { @@ -62,12 +67,14 @@ func (e *Enum) ToProto() proto.Message { return se } -func (e *Enum) GetName() string { return e.Name } +func (e *Enum) GetName() string { return e.Name } +func (e *Enum) IsExported() bool { return e.Export } func EnumFromProto(s *schemapb.Enum) *Enum { e := &Enum{ Pos: posFromProto(s.Pos), Name: s.Name, + Export: s.Export, Comments: s.Comments, Variants: enumVariantListToSchema(s.Variants), } diff --git a/backend/schema/module.go b/backend/schema/module.go index 423c658174..9592058d4e 100644 --- a/backend/schema/module.go +++ b/backend/schema/module.go @@ -174,7 +174,8 @@ func (m *Module) ToProto() proto.Message { } } -func (m *Module) GetName() string { return m.Name } +func (m *Module) GetName() string { return m.Name } +func (m *Module) IsExported() bool { return false } // ModuleFromProtoFile loads a module from the given proto-encoded file. func ModuleFromProtoFile(filename string) (*Module, error) { diff --git a/backend/schema/parser.go b/backend/schema/parser.go index f7648635de..b0163744f0 100644 --- a/backend/schema/parser.go +++ b/backend/schema/parser.go @@ -47,6 +47,7 @@ var ( participle.Lexer(Lexer), participle.Elide("Whitespace"), participle.Unquote(), + participle.UseLookahead(2), participle.Map(func(token lexer.Token) (lexer.Token, error) { token.Value = strings.TrimSpace(strings.TrimPrefix(token.Value, "//")) return token, nil @@ -142,6 +143,7 @@ type Named interface { type Decl interface { Symbol GetName() string + IsExported() bool schemaDecl() } diff --git a/backend/schema/schema_test.go b/backend/schema/schema_test.go index 4fa2c1c961..97fb402698 100644 --- a/backend/schema/schema_test.go +++ b/backend/schema/schema_test.go @@ -24,11 +24,11 @@ module todo { postgres database testdb - data CreateRequest { + export data CreateRequest { name {String: String}? +alias json "rqn" } - data CreateResponse { + export data CreateResponse { name [String] +alias json "rsn" } @@ -42,10 +42,10 @@ module todo { when Time } - verb create(todo.CreateRequest) todo.CreateResponse + export verb create(todo.CreateRequest) todo.CreateResponse +calls todo.destroy +database calls todo.testdb - verb destroy(builtin.HttpRequest) builtin.HttpResponse + export verb destroy(builtin.HttpRequest) builtin.HttpResponse +ingress http GET /todo/destroy/{name} verb scheduled(Unit) Unit @@ -60,7 +60,7 @@ module foo { Green = "Green" } - enum ColorInt: Int { + export enum ColorInt: Int { Red = 0 Blue = 1 Green = 2 @@ -76,6 +76,9 @@ module foo { B [String] C Int } + + verb callTodoCreate(todo.CreateRequest) todo.CreateResponse + +calls todo.create } ` assert.Equal(t, normaliseString(expected), normaliseString(testSchema.String())) @@ -264,7 +267,7 @@ func TestParsing(t *testing.T) { message String } - verb echo(builtin.HttpRequest) builtin.HttpResponse + export verb echo(builtin.HttpRequest) builtin.HttpResponse +ingress http GET /echo +calls time.time @@ -278,7 +281,7 @@ func TestParsing(t *testing.T) { time Time } - verb time(builtin.HttpRequest) builtin.HttpResponse + export verb time(builtin.HttpRequest) builtin.HttpResponse +ingress http GET /time } `, @@ -290,6 +293,7 @@ func TestParsing(t *testing.T) { &Data{Name: "EchoResponse", Fields: []*Field{{Name: "message", Type: &String{}}}}, &Verb{ Name: "echo", + Export: true, Request: &Ref{Module: "builtin", Name: "HttpRequest", TypeParameters: []Type{&Ref{Module: "echo", Name: "EchoRequest"}}}, Response: &Ref{Module: "builtin", Name: "HttpResponse", TypeParameters: []Type{&Ref{Module: "echo", Name: "EchoResponse"}, &String{}}}, Metadata: []Metadata{ @@ -305,6 +309,7 @@ func TestParsing(t *testing.T) { &Data{Name: "TimeResponse", Fields: []*Field{{Name: "time", Type: &Time{}}}}, &Verb{ Name: "time", + Export: true, Request: &Ref{Module: "builtin", Name: "HttpRequest", TypeParameters: []Type{&Ref{Module: "time", Name: "TimeRequest"}}}, Response: &Ref{Module: "builtin", Name: "HttpResponse", TypeParameters: []Type{&Ref{Module: "time", Name: "TimeResponse"}, &String{}}}, Metadata: []Metadata{ @@ -389,10 +394,10 @@ module todo { secret secretValue String postgres database testdb - data CreateRequest { + export data CreateRequest { name {String: String}? +alias json "rqn" } - data CreateResponse { + export data CreateResponse { name [String] +alias json "rsn" } data DestroyRequest { @@ -403,9 +408,9 @@ module todo { name String when Time } - verb create(todo.CreateRequest) todo.CreateResponse + export verb create(todo.CreateRequest) todo.CreateResponse +calls todo.destroy +database calls todo.testdb - verb destroy(builtin.HttpRequest) builtin.HttpResponse + export verb destroy(builtin.HttpRequest) builtin.HttpResponse +ingress http GET /todo/destroy/{name} verb scheduled(Unit) Unit +cron */10 * * 1-10,11-31 * * * @@ -427,7 +432,7 @@ func TestParseEnum(t *testing.T) { Green = "Green" } - enum ColorInt: Int { + export enum ColorInt: Int { Red = 0 Blue = 1 Green = 2 @@ -443,6 +448,9 @@ func TestParseEnum(t *testing.T) { A String B String } + + verb callTodoCreate(todo.CreateRequest) todo.CreateResponse + +calls todo.create } ` actual, err := ParseModuleString("", input) @@ -470,13 +478,15 @@ var testSchema = MustValidate(&Schema{ Type: "postgres", }, &Data{ - Name: "CreateRequest", + Name: "CreateRequest", + Export: true, Fields: []*Field{ {Name: "name", Type: &Optional{Type: &Map{Key: &String{}, Value: &String{}}}, Metadata: []Metadata{&MetadataAlias{Kind: AliasKindJSON, Alias: "rqn"}}}, }, }, &Data{ - Name: "CreateResponse", + Name: "CreateResponse", + Export: true, Fields: []*Field{ {Name: "name", Type: &Array{Element: &String{}}, Metadata: []Metadata{&MetadataAlias{Kind: AliasKindJSON, Alias: "rsn"}}}, }, @@ -495,6 +505,7 @@ var testSchema = MustValidate(&Schema{ }, }, &Verb{Name: "create", + Export: true, Request: &Ref{Module: "todo", Name: "CreateRequest"}, Response: &Ref{Module: "todo", Name: "CreateResponse"}, Metadata: []Metadata{ @@ -502,6 +513,7 @@ var testSchema = MustValidate(&Schema{ &MetadataDatabases{Calls: []*Ref{{Module: "todo", Name: "testdb"}}}, }}, &Verb{Name: "destroy", + Export: true, Request: &Ref{Module: "builtin", Name: "HttpRequest", TypeParameters: []Type{&Ref{Module: "todo", Name: "DestroyRequest"}}}, Response: &Ref{Module: "builtin", Name: "HttpResponse", TypeParameters: []Type{&Ref{Module: "todo", Name: "DestroyResponse"}, &String{}}}, Metadata: []Metadata{ @@ -541,8 +553,9 @@ var testSchema = MustValidate(&Schema{ }, }, &Enum{ - Name: "ColorInt", - Type: &Int{}, + Name: "ColorInt", + Type: &Int{}, + Export: true, Variants: []*EnumVariant{ {Name: "Red", Value: &IntValue{Value: 0}}, {Name: "Blue", Value: &IntValue{Value: 1}}, @@ -564,6 +577,12 @@ var testSchema = MustValidate(&Schema{ {Name: "B", Value: &TypeValue{Value: Type(&String{})}}, }, }, + &Verb{Name: "callTodoCreate", + Request: &Ref{Module: "todo", Name: "CreateRequest"}, + Response: &Ref{Module: "todo", Name: "CreateResponse"}, + Metadata: []Metadata{ + &MetadataCalls{Calls: []*Ref{{Module: "todo", Name: "create"}}}, + }}, }, }, }, diff --git a/backend/schema/secret.go b/backend/schema/secret.go index cc7d2e2c09..16e6bcbd02 100644 --- a/backend/schema/secret.go +++ b/backend/schema/secret.go @@ -19,6 +19,7 @@ var _ Decl = (*Secret)(nil) var _ Symbol = (*Secret)(nil) func (s *Secret) GetName() string { return s.Name } +func (s *Secret) IsExported() bool { return false } func (s *Secret) Position() Position { return s.Pos } func (s *Secret) String() string { return fmt.Sprintf("secret %s %s", s.Name, s.Type) } diff --git a/backend/schema/validate.go b/backend/schema/validate.go index 9493f4285b..ad2f931212 100644 --- a/backend/schema/validate.go +++ b/backend/schema/validate.go @@ -112,26 +112,27 @@ func ValidateModuleInSchema(schema *Schema, m optional.Option[*Module]) (*Schema switch n := n.(type) { case *Ref: if mdecl := scopes.Resolve(*n); mdecl != nil { - switch decl := mdecl.Symbol.(type) { - case *Verb, *Enum, *Database, *Config, *Secret: - if module, ok := mdecl.Module.Get(); ok { - n.Module = module.Name + if decl, ok := mdecl.Symbol.(Decl); ok { + if mod, ok := mdecl.Module.Get(); ok { + n.Module = mod.Name } - if len(n.TypeParameters) != 0 { - merr = append(merr, errorf(n, "reference to %s %q cannot have type parameters", typeName(decl), n.Name)) + + if n.Module != module.Name && !decl.IsExported() { + merr = append(merr, errorf(n, "%s %q must be exported", typeName(decl), n.String())) } - case *Data: - if module, ok := mdecl.Module.Get(); ok { - n.Module = module.Name + + if dataDecl, ok := decl.(*Data); ok { + if len(n.TypeParameters) != len(dataDecl.TypeParameters) { + merr = append(merr, errorf(n, "reference to data structure %s has %d type parameters, but %d were expected", + n.Name, len(n.TypeParameters), len(dataDecl.TypeParameters))) + } + } else if len(n.TypeParameters) != 0 && !decl.IsExported() { + merr = append(merr, errorf(n, "reference to %s %q cannot have type parameters", typeName(decl), n.Name)) } - if len(n.TypeParameters) != len(decl.TypeParameters) { - merr = append(merr, errorf(n, "reference to data structure %s has %d type parameters, but %d were expected", - n.Name, len(n.TypeParameters), len(decl.TypeParameters))) + } else { + if _, ok := mdecl.Symbol.(*TypeParameter); !ok { + merr = append(merr, errorf(n, "invalid reference %q at %q", n, mdecl.Symbol.Position())) } - - case *TypeParameter: - default: - merr = append(merr, errorf(n, "invalid reference %q at %q", n, mdecl.Symbol.Position())) } } else { merr = append(merr, errorf(n, "unknown reference %q", n)) diff --git a/backend/schema/validate_test.go b/backend/schema/validate_test.go index 02040e4c93..5b577d485a 100644 --- a/backend/schema/validate_test.go +++ b/backend/schema/validate_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/alecthomas/assert/v2" + "github.com/alecthomas/types/optional" "github.com/TBD54566975/ftl/internal/errors" "github.com/TBD54566975/ftl/internal/slices" @@ -18,12 +19,12 @@ func TestValidate(t *testing.T) { {name: "TwoModuleCycle", schema: ` module one { - verb one(Empty) Empty + export verb one(Empty) Empty +calls two.two } module two { - verb two(Empty) Empty + export verb two(Empty) Empty +calls one.one } `, @@ -36,28 +37,28 @@ func TestValidate(t *testing.T) { } module two { - verb two(Empty) Empty + export verb two(Empty) Empty +calls three.three } module three { - verb three(Empty) Empty + export verb three(Empty) Empty } `}, {name: "ThreeModulesCycle", schema: ` module one { - verb one(Empty) Empty + export verb one(Empty) Empty +calls two.two } module two { - verb two(Empty) Empty + export verb two(Empty) Empty +calls three.three } module three { - verb three(Empty) Empty + export verb three(Empty) Empty +calls one.one } `, @@ -67,11 +68,11 @@ func TestValidate(t *testing.T) { module one { verb a(Empty) Empty +calls two.a - verb b(Empty) Empty + export verb b(Empty) Empty } module two { - verb a(Empty) Empty + export verb a(Empty) Empty +calls one.b } `, @@ -89,49 +90,50 @@ func TestValidate(t *testing.T) { {name: "ValidIngressRequestType", schema: ` module one { - verb a(HttpRequest) HttpResponse + export verb a(HttpRequest) HttpResponse +ingress http GET /a } `}, {name: "InvalidIngressRequestType", schema: ` module one { - verb a(Empty) Empty + export verb a(Empty) Empty +ingress http GET /a } `, errs: []string{ - "3:13-13: ingress verb a: request type Empty must be builtin.HttpRequest", - "3:20-20: ingress verb a: response type Empty must be builtin.HttpRequest", + "3:20-20: ingress verb a: request type Empty must be builtin.HttpRequest", + "3:27-27: ingress verb a: response type Empty must be builtin.HttpRequest", }}, {name: "IngressBodyTypes", schema: ` module one { - verb bytes(HttpRequest) HttpResponse + export verb bytes(HttpRequest) HttpResponse +ingress http GET /bytes - verb string(HttpRequest) HttpResponse + export verb string(HttpRequest) HttpResponse +ingress http GET /string - verb data(HttpRequest) HttpResponse + export verb data(HttpRequest) HttpResponse +ingress http GET /data - + // Invalid types. - verb any(HttpRequest) HttpResponse + export verb any(HttpRequest) HttpResponse +ingress http GET /any - verb path(HttpRequest) HttpResponse + export verb path(HttpRequest) HttpResponse +ingress http GET /path/{invalid} - verb pathMissing(HttpRequest) HttpResponse + export verb pathMissing(HttpRequest) HttpResponse +ingress http GET /path/{missing} - verb pathFound(HttpRequest) HttpResponse + export verb pathFound(HttpRequest) HttpResponse +ingress http GET /path/{parameter} - data Path { + // Data comment + export data Path { parameter String } } `, errs: []string{ - "11:15-15: ingress verb any: request type HttpRequest must have a body of bytes, string, data structure, unit, float, int, bool, map, or array not Any", - "11:33-33: ingress verb any: response type HttpResponse must have a body of bytes, string, data structure, unit, float, int, bool, map, or array not Any", + "11:22-22: ingress verb any: request type HttpRequest must have a body of bytes, string, data structure, unit, float, int, bool, map, or array not Any", + "11:40-40: ingress verb any: response type HttpResponse must have a body of bytes, string, data structure, unit, float, int, bool, map, or array not Any", "14:31-31: ingress verb path: cannot use path parameter \"invalid\" with request type String, expected Data type", "16:31-31: ingress verb pathMissing: request type one.Path does not contain a field corresponding to the parameter \"missing\"", "16:7-7: duplicate http ingress GET /path/{} for 17:6:\"pathFound\" and 15:6:\"pathMissing\"", @@ -141,7 +143,7 @@ func TestValidate(t *testing.T) { schema: ` module one { data Data {} - verb one(HttpRequest<[one.Data]>) HttpResponse<[one.Data], Empty> + export verb one(HttpRequest<[one.Data]>) HttpResponse<[one.Data], Empty> +ingress http GET /one } `, @@ -162,7 +164,7 @@ func TestValidate(t *testing.T) { schema: ` module one { data Data {} - verb one(HttpRequest<[one.Data]>) HttpResponse<[one.Data], Empty> + export verb one(HttpRequest<[one.Data]>) HttpResponse<[one.Data], Empty> +ingress http GET /one +ingress http GET /two } @@ -188,10 +190,10 @@ func TestValidate(t *testing.T) { {name: "IngressBodyExternalType", schema: ` module two { - data Data {} + export data Data {} } module one { - verb a(HttpRequest) HttpResponse + export verb a(HttpRequest) HttpResponse +ingress http GET /a } `, @@ -265,3 +267,57 @@ func TestValidate(t *testing.T) { }) } } + +func TestValidateModuleWithSchema(t *testing.T) { + tests := []struct { + name string + schema string + moduleSchema string + errs []string + }{ + {name: "ValidModuleWithSchema", + schema: ` + module one { + export data Test {} + export verb one(Empty) Empty + } + `, + moduleSchema: ` + module two { + export verb two(Empty) one.Test + +calls one.one + }`, + }, + {name: "Non-exported verb call", + schema: ` + module one { + verb one(Empty) Empty + } + `, + moduleSchema: ` + module two { + export verb two(Empty) Empty + +calls one.one + }`, + errs: []string{ + `4:14-14: Verb "one.one" must be exported`, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + sch, err := ParseString("", test.schema) + assert.NoError(t, err) + module, err := ParseModuleString("", test.moduleSchema) + assert.NoError(t, err) + sch.Modules = append(sch.Modules, module) + _, err = ValidateModuleInSchema(sch, optional.Some[*Module](module)) + if test.errs == nil { + assert.NoError(t, err) + } else { + errs := slices.Map(errors.UnwrapAll(err), func(e error) string { return e.Error() }) + assert.Equal(t, test.errs, errs) + } + }) + } +} diff --git a/backend/schema/verb.go b/backend/schema/verb.go index b3db1ad0dc..6f29e7d4fd 100644 --- a/backend/schema/verb.go +++ b/backend/schema/verb.go @@ -13,11 +13,12 @@ import ( type Verb struct { Pos Position `parser:"" protobuf:"1,optional"` - Comments []string `parser:"@Comment*" protobuf:"3"` - Name string `parser:"'verb' @Ident" protobuf:"2"` - Request Type `parser:"'(' @@ ')'" protobuf:"4"` - Response Type `parser:"@@" protobuf:"5"` - Metadata []Metadata `parser:"@@*" protobuf:"6"` + Comments []string `parser:"@Comment*" protobuf:"2"` + Export bool `parser:"@'export'?" protobuf:"3"` + Name string `parser:"'verb' @Ident" protobuf:"4"` + Request Type `parser:"'(' @@ ')'" protobuf:"5"` + Response Type `parser:"@@" protobuf:"6"` + Metadata []Metadata `parser:"@@*" protobuf:"7"` } var _ Decl = (*Verb)(nil) @@ -36,11 +37,15 @@ func (v *Verb) schemaChildren() []Node { return children } -func (v *Verb) GetName() string { return v.Name } +func (v *Verb) GetName() string { return v.Name } +func (v *Verb) IsExported() bool { return v.Export } func (v *Verb) String() string { w := &strings.Builder{} fmt.Fprint(w, encodeComments(v.Comments)) + if v.Export { + fmt.Fprint(w, "export ") + } fmt.Fprintf(w, "verb %s(%s) %s", v.Name, v.Request, v.Response) fmt.Fprint(w, indent(encodeMetadata(v.Metadata))) return w.String() @@ -78,6 +83,7 @@ func (v *Verb) GetMetadataCronJob() optional.Option[*MetadataCronJob] { func (v *Verb) ToProto() proto.Message { return &schemapb.Verb{ Pos: posToProto(v.Pos), + Export: v.Export, Name: v.Name, Comments: v.Comments, Request: typeToProto(v.Request), @@ -89,6 +95,7 @@ func (v *Verb) ToProto() proto.Message { func VerbFromProto(s *schemapb.Verb) *Verb { return &Verb{ Pos: posFromProto(s.Pos), + Export: s.Export, Name: s.Name, Comments: s.Comments, Request: typeToSchema(s.Request), 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 f190081595..dda3ef20ee 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 @@ -227,24 +227,29 @@ export class Data extends Message { comments: string[] = []; /** - * @generated from field: string name = 3; + * @generated from field: bool export = 3; + */ + export = false; + + /** + * @generated from field: string name = 4; */ name = ""; /** - * @generated from field: repeated xyz.block.ftl.v1.schema.Field fields = 4; + * @generated from field: repeated xyz.block.ftl.v1.schema.TypeParameter typeParameters = 5; */ - fields: Field[] = []; + typeParameters: TypeParameter[] = []; /** - * @generated from field: repeated xyz.block.ftl.v1.schema.Metadata metadata = 5; + * @generated from field: repeated xyz.block.ftl.v1.schema.Field fields = 6; */ - metadata: Metadata[] = []; + fields: Field[] = []; /** - * @generated from field: repeated xyz.block.ftl.v1.schema.TypeParameter typeParameters = 6; + * @generated from field: repeated xyz.block.ftl.v1.schema.Metadata metadata = 7; */ - typeParameters: TypeParameter[] = []; + metadata: Metadata[] = []; constructor(data?: PartialMessage) { super(); @@ -256,10 +261,11 @@ export class Data extends Message { static readonly fields: FieldList = proto3.util.newFieldList(() => [ { no: 1, name: "pos", kind: "message", T: Position, opt: true }, { no: 2, name: "comments", kind: "scalar", T: 9 /* ScalarType.STRING */, repeated: true }, - { no: 3, name: "name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, - { no: 4, name: "fields", kind: "message", T: Field, repeated: true }, - { no: 5, name: "metadata", kind: "message", T: Metadata, repeated: true }, - { no: 6, name: "typeParameters", kind: "message", T: TypeParameter, repeated: true }, + { no: 3, name: "export", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, + { no: 4, name: "name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 5, name: "typeParameters", kind: "message", T: TypeParameter, repeated: true }, + { no: 6, name: "fields", kind: "message", T: Field, repeated: true }, + { no: 7, name: "metadata", kind: "message", T: Metadata, repeated: true }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): Data { @@ -427,17 +433,22 @@ export class Enum extends Message { comments: string[] = []; /** - * @generated from field: string name = 3; + * @generated from field: bool export = 3; + */ + export = false; + + /** + * @generated from field: string name = 4; */ name = ""; /** - * @generated from field: optional xyz.block.ftl.v1.schema.Type type = 4; + * @generated from field: optional xyz.block.ftl.v1.schema.Type type = 5; */ type?: Type; /** - * @generated from field: repeated xyz.block.ftl.v1.schema.EnumVariant variants = 5; + * @generated from field: repeated xyz.block.ftl.v1.schema.EnumVariant variants = 6; */ variants: EnumVariant[] = []; @@ -451,9 +462,10 @@ export class Enum extends Message { static readonly fields: FieldList = proto3.util.newFieldList(() => [ { no: 1, name: "pos", kind: "message", T: Position, opt: true }, { no: 2, name: "comments", kind: "scalar", T: 9 /* ScalarType.STRING */, repeated: true }, - { no: 3, name: "name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, - { no: 4, name: "type", kind: "message", T: Type, opt: true }, - { no: 5, name: "variants", kind: "message", T: EnumVariant, repeated: true }, + { no: 3, name: "export", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, + { no: 4, name: "name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 5, name: "type", kind: "message", T: Type, opt: true }, + { no: 6, name: "variants", kind: "message", T: EnumVariant, repeated: true }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): Enum { @@ -2019,27 +2031,32 @@ export class Verb extends Message { pos?: Position; /** - * @generated from field: string name = 2; + * @generated from field: repeated string comments = 2; */ - name = ""; + comments: string[] = []; /** - * @generated from field: repeated string comments = 3; + * @generated from field: bool export = 3; */ - comments: string[] = []; + export = false; /** - * @generated from field: xyz.block.ftl.v1.schema.Type request = 4; + * @generated from field: string name = 4; + */ + name = ""; + + /** + * @generated from field: xyz.block.ftl.v1.schema.Type request = 5; */ request?: Type; /** - * @generated from field: xyz.block.ftl.v1.schema.Type response = 5; + * @generated from field: xyz.block.ftl.v1.schema.Type response = 6; */ response?: Type; /** - * @generated from field: repeated xyz.block.ftl.v1.schema.Metadata metadata = 6; + * @generated from field: repeated xyz.block.ftl.v1.schema.Metadata metadata = 7; */ metadata: Metadata[] = []; @@ -2053,11 +2070,12 @@ export class Verb extends Message { static readonly fields: FieldList = proto3.util.newFieldList(() => [ { no: 31634, name: "runtime", kind: "message", T: VerbRuntime, opt: true }, { no: 1, name: "pos", kind: "message", T: Position, opt: true }, - { no: 2, name: "name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, - { no: 3, name: "comments", kind: "scalar", T: 9 /* ScalarType.STRING */, repeated: true }, - { no: 4, name: "request", kind: "message", T: Type }, - { no: 5, name: "response", kind: "message", T: Type }, - { no: 6, name: "metadata", kind: "message", T: Metadata, repeated: true }, + { no: 2, name: "comments", kind: "scalar", T: 9 /* ScalarType.STRING */, repeated: true }, + { no: 3, name: "export", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, + { no: 4, name: "name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 5, name: "request", kind: "message", T: Type }, + { no: 6, name: "response", kind: "message", T: Type }, + { no: 7, name: "metadata", kind: "message", T: Metadata, repeated: true }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): Verb { diff --git a/go-runtime/compile/parser.go b/go-runtime/compile/parser.go index 6169a057b4..488b55747a 100644 --- a/go-runtime/compile/parser.go +++ b/go-runtime/compile/parser.go @@ -24,6 +24,10 @@ type directiveWrapper struct { //sumtype:decl type directive interface{ directive() } +type exportable interface { + IsExported() bool +} + type directiveVerb struct { Pos lexer.Position @@ -38,6 +42,9 @@ func (d *directiveVerb) String() string { } return "ftl:verb" } +func (d *directiveVerb) IsExported() bool { + return d.Export +} type directiveData struct { Pos lexer.Position @@ -53,6 +60,9 @@ func (d *directiveData) String() string { } return "ftl:data" } +func (d *directiveData) IsExported() bool { + return d.Export +} type directiveEnum struct { Pos lexer.Position @@ -68,6 +78,9 @@ func (d *directiveEnum) String() string { } return "ftl:enum" } +func (d *directiveEnum) IsExported() bool { + return d.Export +} type directiveIngress struct { Pos schema.Position diff --git a/go-runtime/compile/schema.go b/go-runtime/compile/schema.go index 9840a2c04f..e1880f3708 100644 --- a/go-runtime/compile/schema.go +++ b/go-runtime/compile/schema.go @@ -236,7 +236,7 @@ func parseConfigDecl(pctx *parseContext, node *ast.CallExpr, fn *types.Func) { // Type parameter tp := pctx.pkg.TypesInfo.Types[index.Index].Type - st, ok := visitType(pctx, index.Index.Pos(), tp).Get() + st, ok := visitType(pctx, index.Index.Pos(), tp, false).Get() if !ok { pctx.errors.add(errorf(index.Index, "unsupported type %q", tp)) return @@ -409,14 +409,19 @@ func visitGenDecl(pctx *parseContext, node *ast.GenDecl) { pctx.errors.add(errorf(node, "ftl directive must be in the module package")) return } + isExported := false + if exportableDir, ok := dir.(exportable); ok { + isExported = exportableDir.IsExported() + } if t, ok := node.Specs[0].(*ast.TypeSpec); ok { if _, ok := pctx.pkg.TypesInfo.TypeOf(t.Type).Underlying().(*types.Basic); ok { enum := &schema.Enum{ Pos: goPosToSchemaPos(node.Pos()), Comments: visitComments(node.Doc), Name: strcase.ToUpperCamel(t.Name.Name), + Export: isExported, } - if typ, ok := visitType(pctx, node.Pos(), pctx.pkg.TypesInfo.TypeOf(t.Type)).Get(); ok { + if typ, ok := visitType(pctx, node.Pos(), pctx.pkg.TypesInfo.TypeOf(t.Type), isExported).Get(); ok { enum.Type = typ } else { pctx.errors.add(errorf(node, "unsupported type %q", @@ -425,7 +430,7 @@ func visitGenDecl(pctx *parseContext, node *ast.GenDecl) { pctx.enums[t.Name.Name] = enum pctx.nativeNames[enum] = t.Name.Name } - visitType(pctx, node.Pos(), pctx.pkg.TypesInfo.Defs[t.Name].Type()) + visitType(pctx, node.Pos(), pctx.pkg.TypesInfo.Defs[t.Name].Type(), isExported) } case *directiveIngress, *directiveCronJob: @@ -493,10 +498,12 @@ func visitFuncDecl(pctx *parseContext, node *ast.FuncDecl) (verb *schema.Verb) { } var metadata []schema.Metadata isVerb := false + isExported := false for _, dir := range directives { switch dir := dir.(type) { case *directiveVerb: isVerb = true + isExported = dir.Export if pctx.module.Name == "" { pctx.module.Name = pctx.pkg.Name } else if pctx.module.Name != pctx.pkg.Name { @@ -504,6 +511,7 @@ func visitFuncDecl(pctx *parseContext, node *ast.FuncDecl) (verb *schema.Verb) { } case *directiveIngress: isVerb = true + isExported = true typ := dir.Type if typ == "" { typ = "http" @@ -516,6 +524,7 @@ func visitFuncDecl(pctx *parseContext, node *ast.FuncDecl) (verb *schema.Verb) { }) case *directiveCronJob: isVerb = true + isExported = false metadata = append(metadata, &schema.MetadataCronJob{ Pos: dir.Pos, Cron: dir.Cron, @@ -547,13 +556,13 @@ func visitFuncDecl(pctx *parseContext, node *ast.FuncDecl) (verb *schema.Verb) { var req optional.Option[schema.Type] if reqt.Ok() { - req = visitType(pctx, node.Pos(), params.At(1).Type()) + req = visitType(pctx, node.Pos(), params.At(1).Type(), isExported) } else { req = optional.Some[schema.Type](&schema.Unit{}) } var resp optional.Option[schema.Type] if respt.Ok() { - resp = visitType(pctx, node.Pos(), results.At(0).Type()) + resp = visitType(pctx, node.Pos(), results.At(0).Type(), isExported) } else { resp = optional.Some[schema.Type](&schema.Unit{}) } @@ -570,6 +579,7 @@ func visitFuncDecl(pctx *parseContext, node *ast.FuncDecl) (verb *schema.Verb) { verb = &schema.Verb{ Pos: goPosToSchemaPos(node.Pos()), Comments: visitComments(node.Doc), + Export: isExported, Name: strcase.ToLowerCamel(node.Name.Name), Request: reqV, Response: resV, @@ -596,7 +606,7 @@ func ftlModuleFromGoModule(pkgPath string) optional.Option[string] { return optional.Some(strings.TrimSuffix(parts[1], "_test")) } -func visitStruct(pctx *parseContext, pos token.Pos, tnode types.Type) optional.Option[*schema.Ref] { +func visitStruct(pctx *parseContext, pos token.Pos, tnode types.Type, isExported bool) optional.Option[*schema.Ref] { named, ok := tnode.(*types.Named) if !ok { pctx.errors.add(noEndColumnErrorf(pos, "expected named type but got %s", tnode)) @@ -615,7 +625,7 @@ func visitStruct(pctx *parseContext, pos token.Pos, tnode types.Type) optional.O Name: named.Obj().Name(), } for i := range named.TypeArgs().Len() { - if typeArg, ok := visitType(pctx, pos, named.TypeArgs().At(i)).Get(); ok { + if typeArg, ok := visitType(pctx, pos, named.TypeArgs().At(i), isExported).Get(); ok { // Fully qualify the Ref if needed if ref, okArg := typeArg.(*schema.Ref); okArg { if ref.Module == "" { @@ -630,8 +640,9 @@ func visitStruct(pctx *parseContext, pos token.Pos, tnode types.Type) optional.O } out := &schema.Data{ - Pos: goPosToSchemaPos(pos), - Name: strcase.ToUpperCamel(named.Obj().Name()), + Pos: goPosToSchemaPos(pos), + Name: strcase.ToUpperCamel(named.Obj().Name()), + Export: isExported, } pctx.nativeNames[out] = named.Obj().Name() dataRef := &schema.Ref{ @@ -645,7 +656,7 @@ func visitStruct(pctx *parseContext, pos token.Pos, tnode types.Type) optional.O Pos: goPosToSchemaPos(pos), Name: param.Obj().Name(), }) - if typeArg, ok := visitType(pctx, pos, named.TypeArgs().At(i)).Get(); ok { + if typeArg, ok := visitType(pctx, pos, named.TypeArgs().At(i), isExported).Get(); ok { dataRef.TypeParameters = append(dataRef.TypeParameters, typeArg) } } @@ -686,7 +697,7 @@ func visitStruct(pctx *parseContext, pos token.Pos, tnode types.Type) optional.O fieldErrors := false for i := range s.NumFields() { f := s.Field(i) - if ft, ok := visitType(pctx, f.Pos(), f.Type()).Get(); ok { + if ft, ok := visitType(pctx, f.Pos(), f.Type(), isExported).Get(); ok { // Check if field is exported if len(f.Name()) > 0 && unicode.IsLower(rune(f.Name()[0])) { pctx.errors.add(tokenErrorf(f.Pos(), f.Name(), @@ -754,14 +765,14 @@ func visitConst(pctx *parseContext, cnode *types.Const) optional.Option[schema.V return optional.None[schema.Value]() } -func visitType(pctx *parseContext, pos token.Pos, tnode types.Type) optional.Option[schema.Type] { +func visitType(pctx *parseContext, pos token.Pos, tnode types.Type, isExported bool) optional.Option[schema.Type] { if tparam, ok := tnode.(*types.TypeParam); ok { return optional.Some[schema.Type](&schema.Ref{Pos: goPosToSchemaPos(pos), Name: tparam.Obj().Id()}) } switch underlying := tnode.Underlying().(type) { case *types.Basic: if named, ok := tnode.(*types.Named); ok { - if _, ok := visitType(pctx, pos, named.Underlying()).Get(); !ok { + if _, ok := visitType(pctx, pos, named.Underlying(), isExported).Get(); !ok { return optional.None[schema.Type]() } nodePath := named.Obj().Pkg().Path() @@ -810,7 +821,7 @@ func visitType(pctx *parseContext, pos token.Pos, tnode types.Type) optional.Opt case *types.Struct: named, ok := tnode.(*types.Named) if !ok { - if ref, ok := visitStruct(pctx, pos, tnode).Get(); ok { + if ref, ok := visitStruct(pctx, pos, tnode, isExported).Get(); ok { return optional.Some[schema.Type](ref) } return optional.None[schema.Type]() @@ -825,7 +836,7 @@ func visitType(pctx *parseContext, pos token.Pos, tnode types.Type) optional.Opt return optional.Some[schema.Type](&schema.Unit{Pos: goPosToSchemaPos(pos)}) case "github.com/TBD54566975/ftl/go-runtime/ftl.Option": - if underlying, ok := visitType(pctx, pos, named.TypeArgs().At(0)).Get(); ok { + if underlying, ok := visitType(pctx, pos, named.TypeArgs().At(0), isExported).Get(); ok { return optional.Some[schema.Type](&schema.Optional{Pos: goPosToSchemaPos(pos), Type: underlying}) } return optional.None[schema.Type]() @@ -836,17 +847,17 @@ func visitType(pctx *parseContext, pos token.Pos, tnode types.Type) optional.Opt pctx.errors.add(noEndColumnErrorf(pos, "unsupported external type %s", nodePath+"."+named.Obj().Name())) return optional.None[schema.Type]() } - if ref, ok := visitStruct(pctx, pos, tnode).Get(); ok { + if ref, ok := visitStruct(pctx, pos, tnode, isExported).Get(); ok { return optional.Some[schema.Type](ref) } return optional.None[schema.Type]() } case *types.Map: - return visitMap(pctx, pos, underlying) + return visitMap(pctx, pos, underlying, isExported) case *types.Slice: - return visitSlice(pctx, pos, underlying) + return visitSlice(pctx, pos, underlying, isExported) case *types.Interface: if underlying.String() == "any" { @@ -859,13 +870,13 @@ func visitType(pctx *parseContext, pos token.Pos, tnode types.Type) optional.Opt } } -func visitMap(pctx *parseContext, pos token.Pos, tnode *types.Map) optional.Option[schema.Type] { - key, ok := visitType(pctx, pos, tnode.Key()).Get() +func visitMap(pctx *parseContext, pos token.Pos, tnode *types.Map, isExported bool) optional.Option[schema.Type] { + key, ok := visitType(pctx, pos, tnode.Key(), isExported).Get() if !ok { return optional.None[schema.Type]() } - value, ok := visitType(pctx, pos, tnode.Elem()).Get() + value, ok := visitType(pctx, pos, tnode.Elem(), isExported).Get() if !ok { return optional.None[schema.Type]() } @@ -877,12 +888,12 @@ func visitMap(pctx *parseContext, pos token.Pos, tnode *types.Map) optional.Opti }) } -func visitSlice(pctx *parseContext, pos token.Pos, tnode *types.Slice) optional.Option[schema.Type] { +func visitSlice(pctx *parseContext, pos token.Pos, tnode *types.Slice, isExported bool) optional.Option[schema.Type] { // If it's a []byte, treat it as a Bytes type. if basic, ok := tnode.Elem().Underlying().(*types.Basic); ok && basic.Kind() == types.Byte { return optional.Some[schema.Type](&schema.Bytes{Pos: goPosToSchemaPos(pos)}) } - value, ok := visitType(pctx, pos, tnode.Elem()).Get() + value, ok := visitType(pctx, pos, tnode.Elem(), isExported).Get() if !ok { return optional.None[schema.Type]() } diff --git a/go-runtime/compile/schema_test.go b/go-runtime/compile/schema_test.go index f79d979486..5efcc6c701 100644 --- a/go-runtime/compile/schema_test.go +++ b/go-runtime/compile/schema_test.go @@ -52,7 +52,7 @@ func TestExtractModuleSchema(t *testing.T) { postgres database testDb - enum Color: String { + export enum Color: String { Red = "Red" Blue = "Blue" Green = "Green" @@ -85,6 +85,10 @@ func TestExtractModuleSchema(t *testing.T) { field String } + export data ExportedData { + field String + } + data Nested { } @@ -113,7 +117,10 @@ func TestExtractModuleSchema(t *testing.T) { data SourceResp { } - verb nothing(Unit) Unit + export verb http(builtin.HttpRequest) builtin.HttpResponse + +ingress http GET /get + + export verb nothing(Unit) Unit verb sink(one.SinkReq) Unit @@ -211,7 +218,7 @@ func TestParseDirectives(t *testing.T) { func TestParseTypesTime(t *testing.T) { timeRef := mustLoadRef("time", "Time").Type() - parsed, ok := visitType(nil, token.NoPos, timeRef).Get() + parsed, ok := visitType(nil, token.NoPos, timeRef, false).Get() assert.True(t, ok) _, ok = parsed.(*schema.Time) assert.True(t, ok) @@ -230,7 +237,7 @@ func TestParseBasicTypes(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - parsed, ok := visitType(nil, token.NoPos, tt.input).Get() + parsed, ok := visitType(nil, token.NoPos, tt.input, false).Get() assert.True(t, ok) assert.Equal(t, tt.expected, parsed) }) diff --git a/go-runtime/compile/testdata/one/one.go b/go-runtime/compile/testdata/one/one.go index fa094e29ff..572b5a67d4 100644 --- a/go-runtime/compile/testdata/one/one.go +++ b/go-runtime/compile/testdata/one/one.go @@ -4,12 +4,13 @@ import ( "context" "time" + "ftl/builtin" "ftl/two" "github.com/TBD54566975/ftl/go-runtime/ftl" ) -//ftl:enum +//ftl:enum export type Color string const ( @@ -73,6 +74,11 @@ type Config struct { Field string } +//ftl:data export +type ExportedData struct { + Field string +} + var configValue = ftl.Config[Config]("configValue") var secretValue = ftl.Secret[string]("secretValue") var testDb = ftl.PostgresDatabase("testDb") @@ -100,7 +106,12 @@ func Source(ctx context.Context) (SourceResp, error) { return SourceResp{}, nil } -//ftl:verb +//ftl:verb export func Nothing(ctx context.Context) error { return nil } + +//ftl:ingress http GET /get +func Http(ctx context.Context, req builtin.HttpRequest[Req]) (builtin.HttpResponse[Resp, ftl.Unit], error) { + return builtin.HttpResponse[Resp, ftl.Unit]{}, nil +} diff --git a/integration/testdata/go/time/time.go b/integration/testdata/go/time/time.go index 962430cd12..e18f7c0e26 100644 --- a/integration/testdata/go/time/time.go +++ b/integration/testdata/go/time/time.go @@ -12,7 +12,7 @@ type TimeResponse struct { // Time returns the current time. // -//ftl:verb +//ftl:verb export func Time(ctx context.Context, req TimeRequest) (TimeResponse, error) { return TimeResponse{Time: time.Now()}, nil }