diff --git a/backend/controller/ingress/testdata/go/httpingress/httpingress.go b/backend/controller/ingress/testdata/go/httpingress/httpingress.go index 9aa941bd98..0958725f31 100644 --- a/backend/controller/ingress/testdata/go/httpingress/httpingress.go +++ b/backend/controller/ingress/testdata/go/httpingress/httpingress.go @@ -6,8 +6,8 @@ import ( "ftl/builtin" - lib "github.com/TBD54566975/ftl/go-runtime/compile/testdata" "github.com/TBD54566975/ftl/go-runtime/ftl" // Import the FTL SDK. + lib "github.com/TBD54566975/ftl/go-runtime/schema/testdata" ) type GetRequest struct { diff --git a/go-runtime/compile/build.go b/go-runtime/compile/build.go index 4d07579705..4a440fd2eb 100644 --- a/go-runtime/compile/build.go +++ b/go-runtime/compile/build.go @@ -154,7 +154,7 @@ func Build(ctx context.Context, projectRootDir, moduleDir string, sch *schema.Sc } logger.Debugf("Extracting schema") - result, err := ExtractModuleSchema(config.Dir, sch) + result, err := extract.Extract(config.Dir) if err != nil { return err } @@ -717,7 +717,7 @@ func writeSchemaErrors(config moduleconfig.ModuleConfig, errors []*schema.Error) return os.WriteFile(config.Abs().Errors, elBytes, 0600) } -func getLocalSumTypes(module *schema.Module, nativeNames NativeNames) []goSumType { +func getLocalSumTypes(module *schema.Module, nativeNames extract.NativeNames) []goSumType { sumTypes := make(map[string]goSumType) for _, d := range module.Decls { e, ok := d.(*schema.Enum) @@ -772,7 +772,7 @@ func getLocalExternalTypes(module *schema.Module) ([]goExternalType, error) { // getRegisteredTypesExternalToModule returns all sum types and external types that are not defined in the given module. // These are the types that must be registered in the main module. -func getRegisteredTypes(module *schema.Module, sch *schema.Schema, nativeNames NativeNames) ([]goSumType, []goExternalType, error) { +func getRegisteredTypes(module *schema.Module, sch *schema.Schema, nativeNames extract.NativeNames) ([]goSumType, []goExternalType, error) { sumTypes := make(map[string]goSumType) externalTypes := make(map[string]sets.Set[string]) // register sum types from other modules @@ -838,7 +838,7 @@ func getRegisteredTypes(module *schema.Module, sch *schema.Schema, nativeNames N return stOut, etOut, nil } -func getGoSumType(enum *schema.Enum, nativeNames NativeNames) optional.Option[goSumType] { +func getGoSumType(enum *schema.Enum, nativeNames extract.NativeNames) optional.Option[goSumType] { if enum.IsValueEnum() { return optional.None[goSumType]() } @@ -936,79 +936,6 @@ func getRegisteredTypesExternalToModule(module *schema.Module, sch *schema.Schem return externalTypes } -// ExtractModuleSchema statically parses Go FTL module source into a schema.Module -// -// TODO: once migrated off of the legacy extractor, we can inline `extract.Extract(dir)` and delete this -// function -func ExtractModuleSchema(dir string, sch *schema.Schema) (extract.Result, error) { - result, err := extract.Extract(dir) - if err != nil { - return extract.Result{}, err - } - - // merge with legacy results for now - if err = legacyExtractModuleSchema(dir, sch, &result); err != nil { - return extract.Result{}, err - } - - schema.SortErrorsByPosition(result.Errors) - if schema.ContainsTerminalError(result.Errors) { - return result, nil - } - err = schema.ValidateModule(result.Module) - if err != nil { - return extract.Result{}, err - } - updateVisibility(result.Module) - return result, nil -} - -// TODO: delete all of this once it's handled by the finalizer -func updateVisibility(module *schema.Module) { - for _, d := range module.Decls { - if d.IsExported() { - updateTransitiveVisibility(d, module) - } - } -} - -// TODO: delete -func updateTransitiveVisibility(d schema.Decl, module *schema.Module) { - if !d.IsExported() { - return - } - - _ = schema.Visit(d, func(n schema.Node, next func() error) error { //nolint:errcheck - ref, ok := n.(*schema.Ref) - if !ok { - return next() - } - - resolved := module.Resolve(*ref) - if resolved == nil || resolved.Symbol == nil { - return next() - } - - if decl, ok := resolved.Symbol.(schema.Decl); ok { - switch t := decl.(type) { - case *schema.Data: - t.Export = true - case *schema.Enum: - t.Export = true - case *schema.TypeAlias: - t.Export = true - case *schema.Topic: - t.Export = true - case *schema.Verb: - t.Export = true - case *schema.Database, *schema.Config, *schema.FSM, *schema.Secret, *schema.Subscription: - } - updateTransitiveVisibility(decl, module) - } - return next() - }) -} - func goVerbFromQualifiedName(qualifiedName string) (goVerb, error) { lastDotIndex := strings.LastIndex(qualifiedName, ".") if lastDotIndex == -1 { diff --git a/go-runtime/compile/parser.go b/go-runtime/compile/parser.go deleted file mode 100644 index b803172e26..0000000000 --- a/go-runtime/compile/parser.go +++ /dev/null @@ -1,325 +0,0 @@ -package compile - -// TODO: This file is now duplicated in go-runtime/schema/analyzers/parser.go. It should be removed -// from here once the schema extraction refactoring is complete. - -import ( - "errors" - "fmt" - "go/ast" - "go/token" - "strconv" - "strings" - - "github.com/alecthomas/participle/v2" - - "github.com/TBD54566975/ftl/backend/schema" - "github.com/TBD54566975/ftl/internal/cron" -) - -// This file contains a parser for Go FTL directives. -// -// eg. //ftl:ingress http GET /foo/bar - -type directiveWrapper struct { - Directive directive `parser:"'ftl' ':' @@"` -} - -//sumtype:decl -type directive interface { - directive() - SetPosition(pos schema.Position) - GetPosition() schema.Position -} - -type directiveVerb struct { - Pos schema.Position - - Verb bool `parser:"@'verb'"` - Export bool `parser:"@'export'?"` -} - -func (*directiveVerb) directive() {} - -func (d *directiveVerb) SetPosition(pos schema.Position) { - d.Pos = pos -} - -func (d *directiveVerb) GetPosition() schema.Position { - return d.Pos -} - -func (d *directiveVerb) String() string { - if d.Export { - return "ftl:verb export" - } - return "ftl:verb" -} -func (d *directiveVerb) IsExported() bool { - return d.Export -} - -type directiveData struct { - Pos schema.Position - - Data bool `parser:"@'data'"` - Export bool `parser:"@'export'?"` -} - -func (*directiveData) directive() {} - -func (d *directiveData) SetPosition(pos schema.Position) { - d.Pos = pos -} - -func (d *directiveData) GetPosition() schema.Position { - return d.Pos -} - -func (d *directiveData) String() string { - if d.Export { - return "ftl:data export" - } - return "ftl:data" -} -func (d *directiveData) IsExported() bool { - return d.Export -} - -type directiveEnum struct { - Pos schema.Position - - Enum bool `parser:"@'enum'"` - Export bool `parser:"@'export'?"` -} - -func (*directiveEnum) directive() {} - -func (d *directiveEnum) SetPosition(pos schema.Position) { - d.Pos = pos -} - -func (d *directiveEnum) GetPosition() schema.Position { - return d.Pos -} - -func (d *directiveEnum) String() string { - if d.Export { - return "ftl:enum export" - } - return "ftl:enum" -} -func (d *directiveEnum) IsExported() bool { - return d.Export -} - -type directiveTypeAlias struct { - Pos schema.Position - - TypeAlias bool `parser:"@'typealias'"` - Export bool `parser:"@'export'?"` -} - -func (*directiveTypeAlias) directive() {} - -func (d *directiveTypeAlias) SetPosition(pos schema.Position) { - d.Pos = pos -} - -func (d *directiveTypeAlias) GetPosition() schema.Position { - return d.Pos -} - -func (d *directiveTypeAlias) String() string { - if d.Export { - return "ftl:typealias export" - } - return "ftl:typealias" -} -func (d *directiveTypeAlias) IsExported() bool { - return d.Export -} - -type directiveIngress struct { - Pos schema.Position - - Type string `parser:"'ingress' @('http')?"` - Method string `parser:"@('GET' | 'POST' | 'PUT' | 'DELETE')"` - Path []schema.IngressPathComponent `parser:"('/' @@)+"` -} - -func (*directiveIngress) directive() {} - -func (d *directiveIngress) SetPosition(pos schema.Position) { - d.Pos = pos -} - -func (d *directiveIngress) GetPosition() schema.Position { - return d.Pos -} - -func (d *directiveIngress) String() string { - w := &strings.Builder{} - fmt.Fprintf(w, "ftl:ingress %s", d.Method) - for _, p := range d.Path { - fmt.Fprintf(w, "/%s", p) - } - return w.String() -} - -type directiveCronJob struct { - Pos schema.Position - - Cron cron.Pattern `parser:"'cron' @@"` -} - -func (*directiveCronJob) directive() {} - -func (d *directiveCronJob) SetPosition(pos schema.Position) { - d.Pos = pos -} - -func (d *directiveCronJob) GetPosition() schema.Position { - return d.Pos -} - -func (d *directiveCronJob) String() string { - return fmt.Sprintf("ftl:cron %s", d.Cron) -} - -type directiveRetry struct { - Pos schema.Position - - Count *int `parser:"'retry' (@Number Whitespace)?"` - MinBackoff string `parser:"@(Number (?! Whitespace) Ident)?"` - MaxBackoff string `parser:"@(Number (?! Whitespace) Ident)?"` -} - -func (*directiveRetry) directive() {} - -func (d *directiveRetry) SetPosition(pos schema.Position) { - d.Pos = pos -} - -func (d *directiveRetry) GetPosition() schema.Position { - return d.Pos -} - -func (d *directiveRetry) String() string { - components := []string{"retry"} - if d.Count != nil { - components = append(components, strconv.Itoa(*d.Count)) - } - components = append(components, d.MinBackoff) - if len(d.MaxBackoff) > 0 { - components = append(components, d.MaxBackoff) - } - return strings.Join(components, " ") -} - -// used to subscribe a sink to a subscription -type directiveSubscriber struct { - Pos schema.Position - - Name string `parser:"'subscribe' @Ident"` -} - -func (*directiveSubscriber) directive() {} - -func (d *directiveSubscriber) SetPosition(pos schema.Position) { - d.Pos = pos -} - -func (d *directiveSubscriber) GetPosition() schema.Position { - return d.Pos -} - -func (d *directiveSubscriber) String() string { - return fmt.Sprintf("subscribe %s", d.Name) -} - -// most declarations include export in other directives, but some don't have any other way. -type directiveExport struct { - Pos schema.Position - - Export bool `parser:"@'export'"` -} - -func (*directiveExport) directive() {} - -func (d *directiveExport) SetPosition(pos schema.Position) { - d.Pos = pos -} - -func (d *directiveExport) GetPosition() schema.Position { - return d.Pos -} - -func (d *directiveExport) String() string { - return "ftl:export" -} - -// DirectiveTypeMap is used to declare a native type to deserialize to in a given runtime. -type directiveTypeMap struct { - Pos schema.Position - - Runtime string `parser:"'typemap' @('go' | 'kotlin')"` - NativeName string `parser:"@String"` -} - -func (*directiveTypeMap) directive() {} - -func (d *directiveTypeMap) String() string { - return fmt.Sprintf("typemap %s %q", d.Runtime, d.NativeName) -} -func (d *directiveTypeMap) SetPosition(pos schema.Position) { - d.Pos = pos -} -func (d *directiveTypeMap) GetPosition() schema.Position { - return d.Pos -} - -var directiveParser = participle.MustBuild[directiveWrapper]( - participle.Lexer(schema.Lexer), - participle.Elide("Whitespace"), - participle.Unquote(), - participle.UseLookahead(2), - participle.Union[directive](&directiveVerb{}, &directiveData{}, &directiveEnum{}, &directiveTypeAlias{}, - &directiveIngress{}, &directiveCronJob{}, &directiveRetry{}, &directiveSubscriber{}, &directiveExport{}, - &directiveTypeMap{}), - participle.Union[schema.IngressPathComponent](&schema.IngressPathLiteral{}, &schema.IngressPathParameter{}), -) - -func parseDirectives(node ast.Node, fset *token.FileSet, docs *ast.CommentGroup) ([]directive, *schema.Error) { - if docs == nil { - return nil, nil - } - directives := []directive{} - for _, line := range docs.List { - if !strings.HasPrefix(line.Text, "//ftl:") { - continue - } - pos := fset.Position(line.Pos()) - ppos := schema.Position{ - Filename: pos.Filename, - Line: pos.Line, - Column: pos.Column + 2, // Skip "//" - } - - directive, err := directiveParser.ParseString(pos.Filename, line.Text[2:]) - if err != nil { - var scerr *schema.Error - var perr participle.Error - if errors.As(err, &perr) { - scerr = schema.Errorf(ppos, ppos.Column, "%s", perr.Message()) - } else { - scerr = wrapf(node, err, "") - } - return nil, scerr - } - - directive.Directive.SetPosition(ppos) - directives = append(directives, directive.Directive) - } - return directives, nil -} diff --git a/go-runtime/compile/schema.go b/go-runtime/compile/schema.go deleted file mode 100644 index 688bc6ace4..0000000000 --- a/go-runtime/compile/schema.go +++ /dev/null @@ -1,319 +0,0 @@ -package compile - -import ( - "fmt" - "go/ast" - "go/token" - "go/types" - "strconv" - "strings" - - "github.com/alecthomas/types/optional" - "golang.org/x/exp/maps" - - "github.com/TBD54566975/ftl/backend/schema" - "github.com/TBD54566975/ftl/backend/schema/strcase" - extract "github.com/TBD54566975/ftl/go-runtime/schema" - "github.com/TBD54566975/ftl/internal/goast" - "github.com/TBD54566975/golang-tools/go/packages" -) - -var ( - fset = token.NewFileSet() - - ftlFSMFuncPath = "github.com/TBD54566975/ftl/go-runtime/ftl.FSM" - ftlTransitionFuncPath = "github.com/TBD54566975/ftl/go-runtime/ftl.Transition" - ftlStartFuncPath = "github.com/TBD54566975/ftl/go-runtime/ftl.Start" -) - -// NativeNames is a map of top-level declarations to their native Go names. -type NativeNames map[schema.Node]string - -func unexpectedDirectiveErrorf(dir directive, format string, args ...interface{}) *schema.Error { - return schema.Errorf(dir.GetPosition(), 0, format, args...) -} - -func errorf(node ast.Node, format string, args ...interface{}) *schema.Error { - pos, endCol := goNodePosToSchemaPos(node) - return schema.Errorf(pos, endCol, format, args...) -} - -//nolint:unparam -func wrapf(node ast.Node, err error, format string, args ...interface{}) *schema.Error { - pos, endCol := goNodePosToSchemaPos(node) - return schema.Wrapf(pos, endCol, err, format, args...) -} - -type errorSet map[string]*schema.Error - -func (e errorSet) add(err *schema.Error) { - e[err.Error()] = err -} - -func legacyExtractModuleSchema(dir string, sch *schema.Schema, out *extract.Result) error { - pkgs, err := packages.Load(&packages.Config{ - Dir: dir, - Fset: fset, - Mode: packages.NeedName | packages.NeedFiles | packages.NeedSyntax | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedImports, - }, "./...") - if err != nil { - return err - } - if len(pkgs) == 0 { - return fmt.Errorf("no packages found in %q, does \"go mod tidy\" need to be run?", dir) - } - - for _, pkg := range pkgs { - if len(strings.Split(pkg.PkgPath, "/")) > 2 { - // skip subpackages of a module - continue - } - pctx := newParseContext(pkg, pkgs, sch, out) - for _, file := range pkg.Syntax { - err := goast.Visit(file, func(stack []ast.Node, next func() error) (err error) { - node := stack[len(stack)-1] - switch node := node.(type) { - case *ast.CallExpr: - visitCallExpr(pctx, node, stack) - - default: - } - return next() - }) - if err != nil { - return err - } - } - if len(pctx.errors) > 0 { - out.Errors = append(out.Errors, maps.Values(pctx.errors)...) - } - } - return nil -} - -func extractStringLiteralArg(node *ast.CallExpr, argIndex int) (string, *schema.Error) { - if argIndex >= len(node.Args) { - return "", errorf(node, "expected string argument at index %d", argIndex) - } - - literal, ok := node.Args[argIndex].(*ast.BasicLit) - if !ok || literal.Kind != token.STRING { - return "", errorf(node, "expected string literal for argument at index %d", argIndex) - } - - s, err := strconv.Unquote(literal.Value) - if err != nil { - return "", wrapf(node, err, "") - } - if s == "" { - return "", errorf(node, "expected non-empty string literal for argument at index %d", argIndex) - } - return s, nil -} - -func visitCallExpr(pctx *parseContext, node *ast.CallExpr, stack []ast.Node) { - _, fn := deref[*types.Func](pctx.pkg, node.Fun) - if fn == nil { - return - } - if fn.FullName() == ftlFSMFuncPath { - parseFSMDecl(pctx, node, stack) - } -} - -func parseVerbRef(pctx *parseContext, node ast.Expr) *schema.Ref { - _, verbFn := deref[*types.Func](pctx.pkg, node) - if verbFn == nil { - return nil - } - moduleName, ok := ftlModuleFromGoModule(verbFn.Pkg().Path()).Get() - if !ok { - return nil - } - return &schema.Ref{ - Pos: goPosToSchemaPos(node.Pos()), - Module: moduleName, - Name: strcase.ToLowerCamel(verbFn.Name()), - } -} - -func parseFSMDecl(pctx *parseContext, node *ast.CallExpr, stack []ast.Node) { - name, schemaErr := extractStringLiteralArg(node, 0) - if schemaErr != nil { - pctx.errors.add(schemaErr) - return - } - if !schema.ValidateName(name) { - pctx.errors.add(errorf(node, "FSM names must be valid identifiers")) - } - - fsm := &schema.FSM{ - Pos: goPosToSchemaPos(node.Pos()), - Name: name, - Metadata: []schema.Metadata{}, - } - pctx.module.Decls = append(pctx.module.Decls, fsm) - - for _, arg := range node.Args[1:] { - call, ok := arg.(*ast.CallExpr) - if !ok { - pctx.errors.add(errorf(arg, "expected call to Start or Transition")) - continue - } - _, fn := deref[*types.Func](pctx.pkg, call.Fun) - if fn == nil { - pctx.errors.add(errorf(call, "expected call to Start or Transition")) - continue - } - parseFSMTransition(pctx, call, fn, fsm) - } - - varDecl, ok := varDeclForStack(stack) - if !ok { - return - } - _, directives := commentsAndDirectivesForVar(pctx, varDecl, stack) - for _, dir := range directives { - if retryDir, ok := dir.(*directiveRetry); ok { - fsm.Metadata = append(fsm.Metadata, &schema.MetadataRetry{ - Pos: retryDir.Pos, - Count: retryDir.Count, - MinBackoff: retryDir.MinBackoff, - MaxBackoff: retryDir.MaxBackoff, - }) - } else { - pctx.errors.add(unexpectedDirectiveErrorf(dir, "unexpected directive %q attached for FSM", dir)) - } - } -} - -// Parse a Start or Transition call in an FSM declaration and add it to the FSM. -func parseFSMTransition(pctx *parseContext, node *ast.CallExpr, fn *types.Func, fsm *schema.FSM) { - refs := make([]*schema.Ref, len(node.Args)) - for i, arg := range node.Args { - ref := parseVerbRef(pctx, arg) - if ref == nil { - pctx.errors.add(errorf(arg, "expected a reference to a sink")) - return - } - refs[i] = ref - } - switch fn.FullName() { - case ftlStartFuncPath: - if len(refs) != 1 { - pctx.errors.add(errorf(node, "expected one reference to a sink")) - return - } - fsm.Start = append(fsm.Start, refs...) - - case ftlTransitionFuncPath: - if len(refs) != 2 { - pctx.errors.add(errorf(node, "expected two references to sinks")) - return - } - fsm.Transitions = append(fsm.Transitions, &schema.FSMTransition{ - Pos: goPosToSchemaPos(node.Pos()), - From: refs[0], - To: refs[1], - }) - - default: - pctx.errors.add(errorf(node, "expected call to Start or Transition")) - } -} - -// varDeclForCall finds the variable being set in the stack -func varDeclForStack(stack []ast.Node) (varDecl *ast.GenDecl, ok bool) { - for i := len(stack) - 1; i >= 0; i-- { - if decl, ok := stack[i].(*ast.GenDecl); ok && decl.Tok == token.VAR { - return decl, true - } - } - return nil, false -} - -// commentsAndDirectivesForVar extracts comments and directives from a variable declaration -func commentsAndDirectivesForVar(pctx *parseContext, variableDecl *ast.GenDecl, stack []ast.Node) (comments []string, directives []directive) { - if variableDecl.Doc == nil { - return []string{}, []directive{} - } - directives, schemaErr := parseDirectives(stack[len(stack)-1], fset, variableDecl.Doc) - if schemaErr != nil { - pctx.errors.add(schemaErr) - } - return parseComments(variableDecl.Doc), directives -} - -func goPosToSchemaPos(pos token.Pos) schema.Position { - p := fset.Position(pos) - return schema.Position{Filename: p.Filename, Line: p.Line, Column: p.Column, Offset: p.Offset} -} - -func goNodePosToSchemaPos(node ast.Node) (schema.Position, int) { - p := fset.Position(node.Pos()) - return schema.Position{Filename: p.Filename, Line: p.Line, Column: p.Column, Offset: p.Offset}, fset.Position(node.End()).Column -} - -func parseComments(doc *ast.CommentGroup) []string { - comments := []string{} - if doc := doc.Text(); doc != "" { - comments = strings.Split(strings.TrimSpace(doc), "\n") - } - return comments -} - -func ftlModuleFromGoModule(pkgPath string) optional.Option[string] { - parts := strings.Split(pkgPath, "/") - if parts[0] != "ftl" { - return optional.None[string]() - } - return optional.Some(strings.TrimSuffix(parts[1], "_test")) -} - -func deref[T types.Object](pkg *packages.Package, node ast.Expr) (string, T) { - var obj T - switch node := node.(type) { - case *ast.Ident: - obj, _ = pkg.TypesInfo.Uses[node].(T) - return "", obj - - case *ast.SelectorExpr: - x, ok := node.X.(*ast.Ident) - if !ok { - return "", obj - } - obj, _ = pkg.TypesInfo.Uses[node.Sel].(T) - return x.Name, obj - - case *ast.IndexExpr: - return deref[T](pkg, node.X) - - default: - return "", obj - } -} - -type parseContext struct { - pkg *packages.Package - pkgs []*packages.Package - module *schema.Module - nativeNames NativeNames - errors errorSet - schema *schema.Schema - topicsByPos map[schema.Position]*schema.Topic -} - -func newParseContext(pkg *packages.Package, pkgs []*packages.Package, sch *schema.Schema, out *extract.Result) *parseContext { - if out.NativeNames == nil { - out.NativeNames = NativeNames{} - } - return &parseContext{ - pkg: pkg, - pkgs: pkgs, - module: out.Module, - nativeNames: out.NativeNames, - errors: errorSet{}, - schema: sch, - topicsByPos: map[schema.Position]*schema.Topic{}, - } -} diff --git a/go-runtime/compile/testdata/go/echo/go.mod b/go-runtime/compile/testdata/go/echo/go.mod index 1ea6faa28c..bf24080de2 100644 --- a/go-runtime/compile/testdata/go/echo/go.mod +++ b/go-runtime/compile/testdata/go/echo/go.mod @@ -2,7 +2,7 @@ module ftl/echo go 1.22.2 -replace github.com/TBD54566975/ftl => ./../../../../.. +replace github.com/TBD54566975/ftl => ../../../../.. require github.com/TBD54566975/ftl v0.0.0-00010101000000-000000000000 diff --git a/go-runtime/compile/testdata/go/external/external.go b/go-runtime/compile/testdata/go/external/external.go index 66f921a345..657c0e5372 100644 --- a/go-runtime/compile/testdata/go/external/external.go +++ b/go-runtime/compile/testdata/go/external/external.go @@ -3,7 +3,7 @@ package external import ( "context" - lib "github.com/TBD54566975/ftl/go-runtime/compile/testdata" + lib "github.com/TBD54566975/ftl/go-runtime/schema/testdata" ) type AliasedExternal lib.NonFTLType @@ -18,4 +18,4 @@ func (ExternalTypeVariant) tag() {} //ftl:verb func Echo(ctx context.Context, req AliasedExternal) (AliasedExternal, error) { return req, nil -} \ No newline at end of file +} diff --git a/go-runtime/compile/testdata/go/external/go.mod b/go-runtime/compile/testdata/go/external/go.mod index 27297e876c..18d4ea8be6 100644 --- a/go-runtime/compile/testdata/go/external/go.mod +++ b/go-runtime/compile/testdata/go/external/go.mod @@ -4,4 +4,4 @@ go 1.22.2 require github.com/TBD54566975/ftl v0.206.1 -replace github.com/TBD54566975/ftl => ./../../../../.. +replace github.com/TBD54566975/ftl => ../../../.. diff --git a/go-runtime/compile/testdata/go/notexportedverb/go.mod b/go-runtime/compile/testdata/go/notexportedverb/go.mod index 8968b0189a..cd61a4f3e6 100644 --- a/go-runtime/compile/testdata/go/notexportedverb/go.mod +++ b/go-runtime/compile/testdata/go/notexportedverb/go.mod @@ -43,4 +43,4 @@ require ( google.golang.org/protobuf v1.34.2 // indirect ) -replace github.com/TBD54566975/ftl => ./../../../../.. +replace github.com/TBD54566975/ftl => ../../../../.. diff --git a/go-runtime/compile/testdata/go/time/go.mod b/go-runtime/compile/testdata/go/time/go.mod index 406abdfc9a..ce2a3974c8 100644 --- a/go-runtime/compile/testdata/go/time/go.mod +++ b/go-runtime/compile/testdata/go/time/go.mod @@ -2,4 +2,4 @@ module ftl/time go 1.22.2 -replace github.com/TBD54566975/ftl => ./../../../../.. +replace github.com/TBD54566975/ftl => ../../../../.. diff --git a/go-runtime/compile/testdata/go/two/two.go b/go-runtime/compile/testdata/go/two/two.go index 0dd6e78fa6..25ec35c6de 100644 --- a/go-runtime/compile/testdata/go/two/two.go +++ b/go-runtime/compile/testdata/go/two/two.go @@ -3,8 +3,8 @@ package two import ( "context" - lib "github.com/TBD54566975/ftl/go-runtime/compile/testdata" "github.com/TBD54566975/ftl/go-runtime/ftl" + lib "github.com/TBD54566975/ftl/go-runtime/schema/testdata" ) //ftl:enum export diff --git a/go-runtime/compile/testdata/go/undefinedverb/go.mod b/go-runtime/compile/testdata/go/undefinedverb/go.mod index 8968b0189a..cd61a4f3e6 100644 --- a/go-runtime/compile/testdata/go/undefinedverb/go.mod +++ b/go-runtime/compile/testdata/go/undefinedverb/go.mod @@ -43,4 +43,4 @@ require ( google.golang.org/protobuf v1.34.2 // indirect ) -replace github.com/TBD54566975/ftl => ./../../../../.. +replace github.com/TBD54566975/ftl => ../../../../.. diff --git a/go-runtime/schema/common/common.go b/go-runtime/schema/common/common.go index 10d9e83753..2f26cb3ada 100644 --- a/go-runtime/schema/common/common.go +++ b/go-runtime/schema/common/common.go @@ -67,6 +67,15 @@ func NewDeclExtractor[T schema.Decl, N ast.Node](name string, extractFunc Extrac return NewExtractor(name, (*DefaultFact[Tag])(nil), runExtractDeclsFunc[T, N](extractFunc)) } +// ExtractCallDeclFunc extracts a schema declaration from the given node. +type ExtractCallDeclFunc[T schema.Decl] func(pass *analysis.Pass, object types.Object, node *ast.GenDecl, callExpr *ast.CallExpr, callPath string) optional.Option[T] + +// NewCallDeclExtractor extracts declarations from call expressions, e.g. `ftl.Subscription("name")`. +func NewCallDeclExtractor[T schema.Decl](name string, extractFunc ExtractCallDeclFunc[T], callPaths ...string) *analysis.Analyzer { + type Tag struct{} // Tag uniquely identifies the fact type for this extractor. + return NewExtractor(name, (*DefaultFact[Tag])(nil), runExtractCallDeclsFunc[T](extractFunc, callPaths...)) +} + // ExtractorResult contains the results of an extraction pass. type ExtractorResult struct { Facts []analysis.ObjectFact @@ -116,6 +125,45 @@ func runExtractDeclsFunc[T schema.Decl, N ast.Node](extractFunc ExtractDeclFunc[ } } +func runExtractCallDeclsFunc[T schema.Decl](extractFunc ExtractCallDeclFunc[T], callPaths ...string) func(pass *analysis.Pass) (interface{}, error) { + return func(pass *analysis.Pass) (interface{}, error) { + in := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) //nolint:forcetypeassert + nodeFilter := []ast.Node{ + (*ast.GenDecl)(nil), + } + in.Preorder(nodeFilter, func(n ast.Node) { + node := n.(*ast.GenDecl) //nolint:forcetypeassert + callExpr, ok := CallExprFromVar(node).Get() + if !ok { + return + } + obj, ok := GetObjectForNode(pass.TypesInfo, node).Get() + if !ok { + return + } + _, fn := Deref[*types.Func](pass, callExpr.Fun) + if fn == nil { + return + } + callPath := fn.FullName() + var matchesPath bool + for _, path := range callPaths { + if callPath == path { + matchesPath = true + } + } + if !matchesPath { + return + } + decl := extractFunc(pass, obj, node, callExpr, callPath) + if d, ok := decl.Get(); ok { + MarkSchemaDecl(pass, obj, d) + } + }) + return NewExtractorResult(pass), nil + } +} + // ExtractComments extracts the comments from the given comment group. func ExtractComments(doc *ast.CommentGroup) []string { if doc == nil { diff --git a/go-runtime/schema/common/directive.go b/go-runtime/schema/common/directive.go index f37cd2f6fa..962b7c1f94 100644 --- a/go-runtime/schema/common/directive.go +++ b/go-runtime/schema/common/directive.go @@ -302,7 +302,7 @@ func (*DirectiveTypeMap) MustAnnotate() []ast.Node { return []ast.Node{&ast.GenDecl{}} } -var directiveParser = participle.MustBuild[directiveWrapper]( +var DirectiveParser = participle.MustBuild[directiveWrapper]( participle.Lexer(schema.Lexer), participle.Elide("Whitespace"), participle.Unquote(), @@ -324,7 +324,7 @@ func ParseDirectives(pass *analysis.Pass, node ast.Node, docs *ast.CommentGroup) } pos := pass.Fset.Position(line.Pos()) // TODO: We need to adjust position information embedded in the schema. - directive, err := directiveParser.ParseString(pos.Filename, line.Text[2:]) + directive, err := DirectiveParser.ParseString(pos.Filename, line.Text[2:]) file := pass.Fset.File(node.Pos()) startPos := file.Pos(file.Offset(line.Pos()) + 2) if err != nil { diff --git a/go-runtime/schema/database/analyzer.go b/go-runtime/schema/database/analyzer.go index 080c536f5a..dfe25d9fc6 100644 --- a/go-runtime/schema/database/analyzer.go +++ b/go-runtime/schema/database/analyzer.go @@ -7,57 +7,24 @@ import ( "github.com/TBD54566975/ftl/backend/schema" "github.com/TBD54566975/ftl/go-runtime/schema/common" "github.com/TBD54566975/golang-tools/go/analysis" - "github.com/TBD54566975/golang-tools/go/analysis/passes/inspect" - "github.com/TBD54566975/golang-tools/go/ast/inspector" "github.com/alecthomas/types/optional" ) const ftlPostgresDBFuncPath = "github.com/TBD54566975/ftl/go-runtime/ftl.PostgresDatabase" // Extractor extracts databases to the module schema. -var Extractor = common.NewExtractor("database", (*Fact)(nil), Extract) +var Extractor = common.NewCallDeclExtractor[*schema.Database]("database", Extract, ftlPostgresDBFuncPath) -type Tag struct{} // Tag uniquely identifies the fact type for this extractor. -type Fact = common.DefaultFact[Tag] - -func Extract(pass *analysis.Pass) (interface{}, error) { - in := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) //nolint:forcetypeassert - nodeFilter := []ast.Node{ - (*ast.GenDecl)(nil), +func Extract(pass *analysis.Pass, obj types.Object, node *ast.GenDecl, callExpr *ast.CallExpr, + callPath string) optional.Option[*schema.Database] { + var comments []string + if md, ok := common.GetFactForObject[*common.ExtractedMetadata](pass, obj).Get(); ok { + comments = md.Comments } - in.Preorder(nodeFilter, func(n ast.Node) { - node := n.(*ast.GenDecl) //nolint:forcetypeassert - callExpr, ok := common.CallExprFromVar(node).Get() - if !ok { - return - } - - _, fn := common.Deref[*types.Func](pass, callExpr.Fun) - if fn == nil { - return - } - - obj, ok := common.GetObjectForNode(pass.TypesInfo, node).Get() - if !ok { - return - } - - var comments []string - if md, ok := common.GetFactForObject[*common.ExtractedMetadata](pass, obj).Get(); ok { - comments = md.Comments - } - - var decl optional.Option[*schema.Database] - if fn.FullName() == ftlPostgresDBFuncPath { - decl = extractDatabase(pass, callExpr, schema.PostgresDatabaseType, comments) - } - - if d, ok := decl.Get(); ok { - common.MarkSchemaDecl(pass, obj, d) - } - }) - - return common.NewExtractorResult(pass), nil + if callPath == ftlPostgresDBFuncPath { + return extractDatabase(pass, callExpr, schema.PostgresDatabaseType, comments) + } + return optional.None[*schema.Database]() } func extractDatabase( diff --git a/go-runtime/schema/extract.go b/go-runtime/schema/extract.go index b84a26b016..65386e7d5f 100644 --- a/go-runtime/schema/extract.go +++ b/go-runtime/schema/extract.go @@ -11,6 +11,7 @@ import ( "github.com/TBD54566975/ftl/go-runtime/schema/data" "github.com/TBD54566975/ftl/go-runtime/schema/database" "github.com/TBD54566975/ftl/go-runtime/schema/enum" + "github.com/TBD54566975/ftl/go-runtime/schema/fsm" "github.com/TBD54566975/ftl/go-runtime/schema/subscription" "github.com/TBD54566975/ftl/go-runtime/schema/topic" "github.com/TBD54566975/ftl/go-runtime/schema/typeenum" @@ -57,6 +58,7 @@ var Extractors = [][]*analysis.Analyzer{ configsecret.Extractor, data.Extractor, database.Extractor, + fsm.Extractor, topic.Extractor, typealias.Extractor, typeenumvariant.Extractor, @@ -78,12 +80,15 @@ var Extractors = [][]*analysis.Analyzer{ }, } +// NativeNames is a map of top-level declarations to their native Go names. +type NativeNames map[schema.Node]string + // Result contains the final schema extraction result. type Result struct { // Module is the extracted module schema. Module *schema.Module // NativeNames maps schema nodes to their native Go names. - NativeNames map[schema.Node]string + NativeNames NativeNames // Errors is a list of errors encountered during schema extraction. Errors []*schema.Error } @@ -123,7 +128,7 @@ type combinedData struct { module *schema.Module errs []*schema.Error - nativeNames map[schema.Node]string + nativeNames NativeNames functionCalls map[types.Object]sets.Set[types.Object] verbCalls map[types.Object]sets.Set[*schema.Ref] refResults map[schema.RefKey]refResult @@ -137,7 +142,7 @@ type combinedData struct { func newCombinedData(diagnostics []analysis.SimpleDiagnostic) *combinedData { return &combinedData{ errs: diagnosticsToSchemaErrors(diagnostics), - nativeNames: make(map[schema.Node]string), + nativeNames: make(NativeNames), functionCalls: make(map[types.Object]sets.Set[types.Object]), verbCalls: make(map[types.Object]sets.Set[*schema.Ref]), refResults: make(map[schema.RefKey]refResult), @@ -345,7 +350,11 @@ func combineAllPackageResults(results map[*analysis.Analyzer][]any, diagnostics } } - return cd.toResult(), nil + result := cd.toResult() + if schema.ContainsTerminalError(result.Errors) { + return result, nil + } + return result, schema.ValidateModule(result.Module) //nolint:wrapcheck } // updateTransitiveVisibility updates any decls that are transitively visible from d. diff --git a/go-runtime/schema/fsm/analyzer.go b/go-runtime/schema/fsm/analyzer.go new file mode 100644 index 0000000000..2ed87f22b8 --- /dev/null +++ b/go-runtime/schema/fsm/analyzer.go @@ -0,0 +1,111 @@ +package fsm + +import ( + "go/ast" + "go/types" + + "github.com/TBD54566975/ftl/backend/schema/strcase" + "github.com/alecthomas/types/optional" + + "github.com/TBD54566975/ftl/backend/schema" + "github.com/TBD54566975/ftl/go-runtime/schema/common" + "github.com/TBD54566975/golang-tools/go/analysis" +) + +const ( + ftlFSMFuncPath = "github.com/TBD54566975/ftl/go-runtime/ftl.FSM" + ftlTransitionFuncPath = "github.com/TBD54566975/ftl/go-runtime/ftl.Transition" + ftlStartFuncPath = "github.com/TBD54566975/ftl/go-runtime/ftl.Start" +) + +// Extractor extracts FSMs. +var Extractor = common.NewCallDeclExtractor[*schema.FSM]("fsm", Extract, ftlFSMFuncPath) + +func Extract(pass *analysis.Pass, obj types.Object, node *ast.GenDecl, callExpr *ast.CallExpr, callPath string) optional.Option[*schema.FSM] { + name := common.ExtractStringLiteralArg(pass, callExpr, 0) + if !schema.ValidateName(name) { + common.Errorf(pass, callExpr, "FSM names must be valid identifiers") + } + + fsm := &schema.FSM{ + Pos: common.GoPosToSchemaPos(pass.Fset, callExpr.Pos()), + Name: name, + Metadata: []schema.Metadata{}, + } + + for _, arg := range callExpr.Args[1:] { + call, ok := arg.(*ast.CallExpr) + if !ok { + common.Errorf(pass, arg, "expected call to Start or Transition") + continue + } + _, fn := common.Deref[*types.Func](pass, call.Fun) + if fn == nil { + common.Errorf(pass, call, "expected call to Start or Transition") + continue + } + parseFSMTransition(pass, call, fn, fsm) + } + + if md, ok := common.GetFactForObject[*common.ExtractedMetadata](pass, obj).Get(); ok { + for _, m := range md.Metadata { + if _, ok := m.(*schema.MetadataRetry); !ok { + common.Errorf(pass, callExpr, "unexpected metadata %q attached for FSM", m) + } + } + fsm.Comments = md.Comments + fsm.Metadata = md.Metadata + } + return optional.Some(fsm) +} + +// Parse a Start or Transition call in an FSM declaration and add it to the FSM. +func parseFSMTransition(pass *analysis.Pass, node *ast.CallExpr, fn *types.Func, fsm *schema.FSM) { + refs := make([]*schema.Ref, len(node.Args)) + for i, arg := range node.Args { + ref := parseVerbRef(pass, arg) + if ref == nil { + common.Errorf(pass, arg, "expected a reference to a sink") + return + } + refs[i] = ref + } + switch fn.FullName() { + case ftlStartFuncPath: + if len(refs) != 1 { + common.Errorf(pass, node, "expected one reference to a sink") + return + } + fsm.Start = append(fsm.Start, refs...) + + case ftlTransitionFuncPath: + if len(refs) != 2 { + common.Errorf(pass, node, "expected two references to sinks") + return + } + fsm.Transitions = append(fsm.Transitions, &schema.FSMTransition{ + Pos: common.GoPosToSchemaPos(pass.Fset, node.Pos()), + From: refs[0], + To: refs[1], + }) + + default: + common.Errorf(pass, node, "expected call to Start or Transition") + } +} + +func parseVerbRef(pass *analysis.Pass, node ast.Expr) *schema.Ref { + _, verbFn := common.Deref[*types.Func](pass, node) + if verbFn == nil { + return nil + } + moduleName, err := common.FtlModuleFromGoPackage(verbFn.Pkg().Path()) + if err != nil { + return nil + } + return &schema.Ref{ + Pos: common.GoPosToSchemaPos(pass.Fset, node.Pos()), + Module: moduleName, + Name: strcase.ToLowerCamel(verbFn.Name()), + } +} diff --git a/go-runtime/schema/metadata/analyzer.go b/go-runtime/schema/metadata/analyzer.go index 2552b0c1fb..f2bdd0c1eb 100644 --- a/go-runtime/schema/metadata/analyzer.go +++ b/go-runtime/schema/metadata/analyzer.go @@ -116,7 +116,6 @@ func extractMetadata(pass *analysis.Pass, node ast.Node, doc *ast.CommentGroup) Cron: dt.Cron.String(), }) case *common.DirectiveRetry: - newSchType = &schema.Verb{} metadata = append(metadata, &schema.MetadataRetry{ Pos: common.GoPosToSchemaPos(pass.Fset, dt.Pos), Count: dt.Count, diff --git a/go-runtime/schema/extract_test.go b/go-runtime/schema/schema_fuzz_test.go similarity index 97% rename from go-runtime/schema/extract_test.go rename to go-runtime/schema/schema_fuzz_test.go index 9f8d95f115..b5eee81760 100644 --- a/go-runtime/schema/extract_test.go +++ b/go-runtime/schema/schema_fuzz_test.go @@ -10,7 +10,6 @@ import ( "text/template" "github.com/TBD54566975/ftl/backend/schema" - "github.com/TBD54566975/ftl/internal/slices" "github.com/alecthomas/assert/v2" ) @@ -365,7 +364,3 @@ module test { return result.String() } - -func normaliseString(s string) string { - return strings.TrimSpace(strings.Join(slices.Map(strings.Split(s, "\n"), strings.TrimSpace), "\n")) -} diff --git a/go-runtime/compile/schema_test.go b/go-runtime/schema/schema_test.go similarity index 88% rename from go-runtime/compile/schema_test.go rename to go-runtime/schema/schema_test.go index b843648484..6bd9ddd40f 100644 --- a/go-runtime/compile/schema_test.go +++ b/go-runtime/schema/schema_test.go @@ -1,4 +1,4 @@ -package compile +package schema import ( "context" @@ -8,6 +8,7 @@ import ( "strings" "testing" + "github.com/TBD54566975/ftl/go-runtime/schema/common" "github.com/alecthomas/assert/v2" "github.com/alecthomas/participle/v2/lexer" @@ -45,9 +46,9 @@ func TestExtractModuleSchema(t *testing.T) { if testing.Short() { t.SkipNow() } - assert.NoError(t, prebuildTestModule(t, "testdata/go/one", "testdata/go/two")) + assert.NoError(t, prebuildTestModule(t, "testdata/one", "testdata/two")) - r, err := ExtractModuleSchema("testdata/go/one", &schema.Schema{}) + r, err := Extract("testdata/one") assert.NoError(t, err) actual := schema.Normalise(r.Module) expected := `module one { @@ -183,9 +184,9 @@ func TestExtractModuleSchemaTwo(t *testing.T) { t.SkipNow() } - assert.NoError(t, prebuildTestModule(t, "testdata/go/two")) + assert.NoError(t, prebuildTestModule(t, "testdata/two")) - r, err := ExtractModuleSchema("testdata/go/two", &schema.Schema{}) + r, err := Extract("testdata/two") assert.NoError(t, err) for _, e := range r.Errors { // only warns @@ -195,17 +196,17 @@ func TestExtractModuleSchemaTwo(t *testing.T) { expected := `module two { typealias ExplicitAliasAlias Any +typemap kotlin "com.foo.bar.NonFTLType" - +typemap go "github.com/TBD54566975/ftl/go-runtime/compile/testdata.lib.NonFTLType" + +typemap go "github.com/TBD54566975/ftl/go-runtime/schema/testdata.lib.NonFTLType" typealias ExplicitAliasType Any +typemap kotlin "com.foo.bar.NonFTLType" - +typemap go "github.com/TBD54566975/ftl/go-runtime/compile/testdata.lib.NonFTLType" + +typemap go "github.com/TBD54566975/ftl/go-runtime/schema/testdata.lib.NonFTLType" typealias TransitiveAliasAlias Any - +typemap go "github.com/TBD54566975/ftl/go-runtime/compile/testdata.lib.NonFTLType" + +typemap go "github.com/TBD54566975/ftl/go-runtime/schema/testdata.lib.NonFTLType" typealias TransitiveAliasType Any - +typemap go "github.com/TBD54566975/ftl/go-runtime/compile/testdata.lib.NonFTLType" + +typemap go "github.com/TBD54566975/ftl/go-runtime/schema/testdata.lib.NonFTLType" export enum TwoEnum: String { Blue = "Blue" @@ -265,7 +266,7 @@ func TestExtractModuleSchemaFSM(t *testing.T) { if testing.Short() { t.SkipNow() } - r, err := ExtractModuleSchema("testdata/fsm", &schema.Schema{}) + r, err := Extract("testdata/fsm") assert.NoError(t, err) assert.Equal(t, nil, r.Errors, "expected no schema errors") actual := schema.Normalise(r.Module) @@ -317,7 +318,7 @@ func TestExtractModuleSchemaNamedTypes(t *testing.T) { t.SkipNow() } assert.NoError(t, prebuildTestModule(t, "testdata/named", "testdata/namedext")) - r, err := ExtractModuleSchema("testdata/named", &schema.Schema{}) + r, err := Extract("testdata/named") assert.NoError(t, err) assert.Equal(t, nil, r.Errors, "expected no schema errors") actual := schema.Normalise(r.Module) @@ -370,7 +371,7 @@ func TestExtractModuleSchemaParent(t *testing.T) { t.SkipNow() } assert.NoError(t, prebuildTestModule(t, "testdata/parent")) - r, err := ExtractModuleSchema("testdata/parent", &schema.Schema{}) + r, err := Extract("testdata/parent") assert.NoError(t, err) assert.Equal(t, nil, r.Errors, "expected no schema errors") actual := schema.Normalise(r.Module) @@ -412,7 +413,7 @@ func TestExtractModulePubSub(t *testing.T) { assert.NoError(t, prebuildTestModule(t, "testdata/pubsub")) - r, err := ExtractModuleSchema("testdata/pubsub", &schema.Schema{}) + r, err := Extract("testdata/pubsub") assert.NoError(t, err) assert.Equal(t, nil, r.Errors, "expected no schema errors") actual := schema.Normalise(r.Module) @@ -448,7 +449,7 @@ func TestExtractModuleSubscriber(t *testing.T) { t.SkipNow() } assert.NoError(t, prebuildTestModule(t, "testdata/pubsub", "testdata/subscriber")) - r, err := ExtractModuleSchema("testdata/subscriber", &schema.Schema{}) + r, err := Extract("testdata/subscriber") assert.NoError(t, err) assert.Equal(t, nil, r.Errors, "expected no schema errors") actual := schema.Normalise(r.Module) @@ -466,17 +467,17 @@ func TestParsedirectives(t *testing.T) { tests := []struct { name string input string - expected directive + expected common.Directive }{ - {name: "Verb", input: "ftl:verb", expected: &directiveVerb{Verb: true}}, - {name: "Verb export", input: "ftl:verb export", expected: &directiveVerb{Verb: true, Export: true}}, - {name: "Data", input: "ftl:data", expected: &directiveData{Data: true}}, - {name: "Data export", input: "ftl:data export", expected: &directiveData{Data: true, Export: true}}, - {name: "Enum", input: "ftl:enum", expected: &directiveEnum{Enum: true}}, - {name: "Enum export", input: "ftl:enum export", expected: &directiveEnum{Enum: true, Export: true}}, - {name: "TypeAlias", input: "ftl:typealias", expected: &directiveTypeAlias{TypeAlias: true}}, - {name: "TypeAlias export", input: "ftl:typealias export", expected: &directiveTypeAlias{TypeAlias: true, Export: true}}, - {name: "Ingress", input: `ftl:ingress GET /foo`, expected: &directiveIngress{ + {name: "Verb", input: "ftl:verb", expected: &common.DirectiveVerb{Verb: true}}, + {name: "Verb export", input: "ftl:verb export", expected: &common.DirectiveVerb{Verb: true, Export: true}}, + {name: "Data", input: "ftl:data", expected: &common.DirectiveData{Data: true}}, + {name: "Data export", input: "ftl:data export", expected: &common.DirectiveData{Data: true, Export: true}}, + {name: "Enum", input: "ftl:enum", expected: &common.DirectiveEnum{Enum: true}}, + {name: "Enum export", input: "ftl:enum export", expected: &common.DirectiveEnum{Enum: true, Export: true}}, + {name: "TypeAlias", input: "ftl:typealias", expected: &common.DirectiveTypeAlias{TypeAlias: true}}, + {name: "TypeAlias export", input: "ftl:typealias export", expected: &common.DirectiveTypeAlias{TypeAlias: true, Export: true}}, + {name: "Ingress", input: `ftl:ingress GET /foo`, expected: &common.DirectiveIngress{ Method: "GET", Path: []schema.IngressPathComponent{ &schema.IngressPathLiteral{ @@ -484,7 +485,7 @@ func TestParsedirectives(t *testing.T) { }, }, }}, - {name: "Ingress", input: `ftl:ingress GET /test_path/{something}/987-Your_File.txt%7E%21Misc%2A%28path%29info%40abc%3Fxyz`, expected: &directiveIngress{ + {name: "Ingress", input: `ftl:ingress GET /test_path/{something}/987-Your_File.txt%7E%21Misc%2A%28path%29info%40abc%3Fxyz`, expected: &common.DirectiveIngress{ Method: "GET", Path: []schema.IngressPathComponent{ &schema.IngressPathLiteral{ @@ -501,7 +502,7 @@ func TestParsedirectives(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := directiveParser.ParseString("", tt.input) + got, err := common.DirectiveParser.ParseString("", tt.input) assert.NoError(t, err) assert.Equal(t, tt.expected, got.Directive, assert.Exclude[lexer.Position](), assert.Exclude[schema.Position]()) }) @@ -524,7 +525,7 @@ func TestErrorReporting(t *testing.T) { assert.NoError(t, err) err = exec.Command(ctx, log.Debug, "testdata/failing", "go", "mod", "tidy").RunBuffered(ctx) assert.NoError(t, err) - r, err := ExtractModuleSchema("testdata/failing", &schema.Schema{}) + r, err := Extract("testdata/failing") assert.NoError(t, err) var actualParent []string @@ -594,9 +595,9 @@ func TestErrorReporting(t *testing.T) { // failing/child/child.go expectedChild := []string{ `9:2-6: unsupported type "uint64" for field "Body"`, - `14:2-7: unsupported type "github.com/TBD54566975/ftl/go-runtime/compile/testdata.NonFTLType" for field "Field"`, - `14:8-8: unsupported external type "github.com/TBD54566975/ftl/go-runtime/compile/testdata.NonFTLType"; see FTL docs on using external types: tbd54566975.github.io/ftl/docs/reference/externaltypes/`, - `19:6-41: declared type github.com/blah.lib.NonFTLType in typemap does not match native type github.com/TBD54566975/ftl/go-runtime/compile/testdata.lib.NonFTLType`, + `14:2-7: unsupported type "github.com/TBD54566975/ftl/go-runtime/schema/testdata.NonFTLType" for field "Field"`, + `14:8-8: unsupported external type "github.com/TBD54566975/ftl/go-runtime/schema/testdata.NonFTLType"; see FTL docs on using external types: tbd54566975.github.io/ftl/docs/reference/externaltypes/`, + `19:6-41: declared type github.com/blah.lib.NonFTLType in typemap does not match native type github.com/TBD54566975/ftl/go-runtime/schema/testdata.lib.NonFTLType`, `24:6-6: multiple Go type mappings found for "ftl/failing/child.MultipleMappings"`, `34:2-13: enum variant "SameVariant" conflicts with existing enum variant of "EnumVariantConflictParent" at "196:2"`, } @@ -614,7 +615,7 @@ func TestValidationFailures(t *testing.T) { assert.NoError(t, err) err = exec.Command(ctx, log.Debug, "testdata/validation", "go", "mod", "tidy").RunBuffered(ctx) assert.NoError(t, err) - _, err = ExtractModuleSchema("testdata/validation", &schema.Schema{}) + _, err = Extract("testdata/validation") assert.Error(t, err) errs := errors.UnwrapAll(err) diff --git a/go-runtime/schema/subscription/analyzer.go b/go-runtime/schema/subscription/analyzer.go index 3f4ee872ef..1e72009592 100644 --- a/go-runtime/schema/subscription/analyzer.go +++ b/go-runtime/schema/subscription/analyzer.go @@ -8,8 +8,6 @@ import ( "github.com/TBD54566975/ftl/backend/schema/strcase" "github.com/TBD54566975/ftl/go-runtime/schema/common" "github.com/TBD54566975/golang-tools/go/analysis" - "github.com/TBD54566975/golang-tools/go/analysis/passes/inspect" - "github.com/TBD54566975/golang-tools/go/ast/inspector" "github.com/alecthomas/types/optional" ) @@ -18,55 +16,28 @@ const ( ) // Extractor extracts subscriptions. -var Extractor = common.NewExtractor("subscription", (*Fact)(nil), Extract) - -type Tag struct{} // Tag uniquely identifies the fact type for this extractor. -type Fact = common.DefaultFact[Tag] - -func Extract(pass *analysis.Pass) (interface{}, error) { - in := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) //nolint:forcetypeassert - nodeFilter := []ast.Node{ - (*ast.GenDecl)(nil), - } - in.Preorder(nodeFilter, func(n ast.Node) { - node := n.(*ast.GenDecl) //nolint:forcetypeassert - callExpr, ok := common.CallExprFromVar(node).Get() - if !ok { - return - } - if !common.FuncPathEquals(pass, callExpr, ftlSubscriptionFuncPath) { - return - } - obj, ok := common.GetObjectForNode(pass.TypesInfo, node).Get() - if !ok { - return - } - if s, ok := extractSubscription(pass, obj, callExpr).Get(); ok { - common.MarkSchemaDecl(pass, obj, s) - } - }) - return common.NewExtractorResult(pass), nil -} +var Extractor = common.NewCallDeclExtractor[*schema.Subscription]("subscription", Extract, ftlSubscriptionFuncPath) // expects: var _ = ftl.Subscription(topicHandle, "name_literal") -func extractSubscription(pass *analysis.Pass, obj types.Object, node *ast.CallExpr) optional.Option[*schema.Subscription] { +func Extract(pass *analysis.Pass, obj types.Object, node *ast.GenDecl, callExpr *ast.CallExpr, + callPath string) optional.Option[*schema.Subscription] { var topicRef *schema.Ref - if len(node.Args) != 2 { - common.Errorf(pass, node, "subscription registration must have exactly two arguments") + if len(callExpr.Args) != 2 { + common.Errorf(pass, callExpr, "subscription registration must have exactly two arguments") return optional.None[*schema.Subscription]() } - if topicIdent, ok := node.Args[0].(*ast.Ident); ok { + if topicIdent, ok := callExpr.Args[0].(*ast.Ident); ok { // Topic is within module // we will find the subscription name from the string literal parameter object := pass.TypesInfo.ObjectOf(topicIdent) fact, ok := common.GetFactForObject[*common.ExtractedDecl](pass, object).Get() if !ok || fact.Decl == nil { - common.Errorf(pass, node, "could not find topic declaration for topic variable") + common.Errorf(pass, callExpr, "could not find topic declaration for topic variable") return optional.None[*schema.Subscription]() } topic, ok := fact.Decl.(*schema.Topic) if !ok { - common.Errorf(pass, node, "could not find topic declaration for topic variable") + common.Errorf(pass, callExpr, "could not find topic declaration for topic variable") return optional.None[*schema.Subscription]() } @@ -78,17 +49,17 @@ func extractSubscription(pass *analysis.Pass, obj types.Object, node *ast.CallEx Module: moduleName, Name: topic.Name, } - } else if topicSelExp, ok := node.Args[0].(*ast.SelectorExpr); ok { + } else if topicSelExp, ok := callExpr.Args[0].(*ast.SelectorExpr); ok { // External topic // we will derive subscription name from generated variable name moduleIdent, moduleOk := topicSelExp.X.(*ast.Ident) if !moduleOk { - common.Errorf(pass, node, "subscription registration must have a topic") + common.Errorf(pass, callExpr, "subscription registration must have a topic") return optional.None[*schema.Subscription]() } varName := topicSelExp.Sel.Name if varName == "" { - common.Errorf(pass, node, "subscription registration must have a topic") + common.Errorf(pass, callExpr, "subscription registration must have a topic") return optional.None[*schema.Subscription]() } name := strcase.ToLowerSnake(varName) @@ -97,13 +68,13 @@ func extractSubscription(pass *analysis.Pass, obj types.Object, node *ast.CallEx Name: name, } } else { - common.Errorf(pass, node, "subscription registration must have a topic") + common.Errorf(pass, callExpr, "subscription registration must have a topic") return optional.None[*schema.Subscription]() } subscription := &schema.Subscription{ - Pos: common.GoPosToSchemaPos(pass.Fset, node.Pos()), - Name: common.ExtractStringLiteralArg(pass, node, 1), + Pos: common.GoPosToSchemaPos(pass.Fset, callExpr.Pos()), + Name: common.ExtractStringLiteralArg(pass, callExpr, 1), Topic: topicRef, } common.ApplyMetadata[*schema.Subscription](pass, obj, func(md *common.ExtractedMetadata) { diff --git a/go-runtime/compile/testdata/failing/child/child.go b/go-runtime/schema/testdata/failing/child/child.go similarity index 74% rename from go-runtime/compile/testdata/failing/child/child.go rename to go-runtime/schema/testdata/failing/child/child.go index 9521e1633a..e51e75175d 100644 --- a/go-runtime/compile/testdata/failing/child/child.go +++ b/go-runtime/schema/testdata/failing/child/child.go @@ -1,8 +1,8 @@ package child import ( - lib "github.com/TBD54566975/ftl/go-runtime/compile/testdata" "github.com/TBD54566975/ftl/go-runtime/ftl" + lib "github.com/TBD54566975/ftl/go-runtime/schema/testdata" ) type BadChildStruct struct { @@ -19,8 +19,8 @@ type UnaliasedExternalType struct { type WrongMappingExternal lib.NonFTLType //ftl:typealias -//ftl:typemap go "github.com/TBD54566975/ftl/go-runtime/compile/testdata.lib.NonFTLType" -//ftl:typemap go "github.com/TBD54566975/ftl/go-runtime/compile/testdata.lib.NonFTLType" +//ftl:typemap go "github.com/TBD54566975/ftl/go-runtime/schema/testdata.lib.NonFTLType" +//ftl:typemap go "github.com/TBD54566975/ftl/go-runtime/schema/testdata.lib.NonFTLType" type MultipleMappings lib.NonFTLType //ftl:data diff --git a/go-runtime/compile/testdata/failing/failing.go b/go-runtime/schema/testdata/failing/failing.go similarity index 98% rename from go-runtime/compile/testdata/failing/failing.go rename to go-runtime/schema/testdata/failing/failing.go index edfd834992..231518bcbe 100644 --- a/go-runtime/compile/testdata/failing/failing.go +++ b/go-runtime/schema/testdata/failing/failing.go @@ -3,8 +3,8 @@ package failing import ( "context" - lib "github.com/TBD54566975/ftl/go-runtime/compile/testdata" "github.com/TBD54566975/ftl/go-runtime/ftl" + lib "github.com/TBD54566975/ftl/go-runtime/schema/testdata" "ftl/failing/child" ps "ftl/pubsub" diff --git a/go-runtime/compile/testdata/failing/ftl.toml b/go-runtime/schema/testdata/failing/ftl.toml similarity index 100% rename from go-runtime/compile/testdata/failing/ftl.toml rename to go-runtime/schema/testdata/failing/ftl.toml diff --git a/go-runtime/compile/testdata/failing/go.mod b/go-runtime/schema/testdata/failing/go.mod similarity index 100% rename from go-runtime/compile/testdata/failing/go.mod rename to go-runtime/schema/testdata/failing/go.mod diff --git a/go-runtime/compile/testdata/failing/go.sum b/go-runtime/schema/testdata/failing/go.sum similarity index 100% rename from go-runtime/compile/testdata/failing/go.sum rename to go-runtime/schema/testdata/failing/go.sum diff --git a/go-runtime/compile/testdata/fsm/fsm.go b/go-runtime/schema/testdata/fsm/fsm.go similarity index 100% rename from go-runtime/compile/testdata/fsm/fsm.go rename to go-runtime/schema/testdata/fsm/fsm.go diff --git a/go-runtime/compile/testdata/fsm/ftl.toml b/go-runtime/schema/testdata/fsm/ftl.toml similarity index 100% rename from go-runtime/compile/testdata/fsm/ftl.toml rename to go-runtime/schema/testdata/fsm/ftl.toml diff --git a/go-runtime/compile/testdata/fsm/go.mod b/go-runtime/schema/testdata/fsm/go.mod similarity index 97% rename from go-runtime/compile/testdata/fsm/go.mod rename to go-runtime/schema/testdata/fsm/go.mod index 48ac44e194..5f1c76951f 100644 --- a/go-runtime/compile/testdata/fsm/go.mod +++ b/go-runtime/schema/testdata/fsm/go.mod @@ -43,4 +43,4 @@ require ( google.golang.org/protobuf v1.34.2 // indirect ) -replace github.com/TBD54566975/ftl => ../../../../ +replace github.com/TBD54566975/ftl => ../../../.. diff --git a/go-runtime/compile/testdata/fsm/go.sum b/go-runtime/schema/testdata/fsm/go.sum similarity index 100% rename from go-runtime/compile/testdata/fsm/go.sum rename to go-runtime/schema/testdata/fsm/go.sum diff --git a/go-runtime/compile/testdata/lib.go b/go-runtime/schema/testdata/lib.go similarity index 100% rename from go-runtime/compile/testdata/lib.go rename to go-runtime/schema/testdata/lib.go diff --git a/go-runtime/compile/testdata/named/ftl.toml b/go-runtime/schema/testdata/named/ftl.toml similarity index 100% rename from go-runtime/compile/testdata/named/ftl.toml rename to go-runtime/schema/testdata/named/ftl.toml diff --git a/go-runtime/compile/testdata/named/go.mod b/go-runtime/schema/testdata/named/go.mod similarity index 100% rename from go-runtime/compile/testdata/named/go.mod rename to go-runtime/schema/testdata/named/go.mod diff --git a/go-runtime/compile/testdata/named/go.sum b/go-runtime/schema/testdata/named/go.sum similarity index 100% rename from go-runtime/compile/testdata/named/go.sum rename to go-runtime/schema/testdata/named/go.sum diff --git a/go-runtime/compile/testdata/named/named.go b/go-runtime/schema/testdata/named/named.go similarity index 100% rename from go-runtime/compile/testdata/named/named.go rename to go-runtime/schema/testdata/named/named.go diff --git a/go-runtime/compile/testdata/namedext/ftl.toml b/go-runtime/schema/testdata/namedext/ftl.toml similarity index 100% rename from go-runtime/compile/testdata/namedext/ftl.toml rename to go-runtime/schema/testdata/namedext/ftl.toml diff --git a/go-runtime/compile/testdata/namedext/go.mod b/go-runtime/schema/testdata/namedext/go.mod similarity index 100% rename from go-runtime/compile/testdata/namedext/go.mod rename to go-runtime/schema/testdata/namedext/go.mod diff --git a/go-runtime/compile/testdata/namedext/go.sum b/go-runtime/schema/testdata/namedext/go.sum similarity index 100% rename from go-runtime/compile/testdata/namedext/go.sum rename to go-runtime/schema/testdata/namedext/go.sum diff --git a/go-runtime/compile/testdata/namedext/namedext.go b/go-runtime/schema/testdata/namedext/namedext.go similarity index 100% rename from go-runtime/compile/testdata/namedext/namedext.go rename to go-runtime/schema/testdata/namedext/namedext.go diff --git a/go-runtime/schema/testdata/one/ftl.toml b/go-runtime/schema/testdata/one/ftl.toml new file mode 100644 index 0000000000..b80d8398e3 --- /dev/null +++ b/go-runtime/schema/testdata/one/ftl.toml @@ -0,0 +1,2 @@ +module = "one" +language = "go" diff --git a/go-runtime/schema/testdata/one/go.mod b/go-runtime/schema/testdata/one/go.mod new file mode 100644 index 0000000000..ad6baf64de --- /dev/null +++ b/go-runtime/schema/testdata/one/go.mod @@ -0,0 +1,46 @@ +module ftl/one + +go 1.22.2 + +replace github.com/TBD54566975/ftl => ../../../.. + +require github.com/TBD54566975/ftl v0.150.3 + +require ( + connectrpc.com/connect v1.16.1 // indirect + connectrpc.com/grpcreflect v1.2.0 // indirect + connectrpc.com/otelconnect v0.7.0 // indirect + github.com/alecthomas/atomic v0.1.0-alpha2 // indirect + github.com/alecthomas/concurrency v0.0.2 // indirect + github.com/alecthomas/participle/v2 v2.1.1 // indirect + github.com/alecthomas/types v0.16.0 // indirect + github.com/alessio/shellescape v1.4.2 // indirect + github.com/benbjohnson/clock v1.3.5 // indirect + github.com/danieljoos/wincred v1.2.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.6.0 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jpillora/backoff v1.0.0 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/multiformats/go-base36 v0.2.0 // indirect + github.com/puzpuzpuz/xsync/v3 v3.3.1 // indirect + github.com/swaggest/jsonschema-go v0.3.72 // indirect + github.com/swaggest/refl v1.3.0 // indirect + github.com/zalando/go-keyring v0.2.5 // indirect + go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect + golang.org/x/crypto v0.25.0 // indirect + golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect + golang.org/x/mod v0.19.0 // indirect + golang.org/x/net v0.27.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/text v0.16.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect +) diff --git a/go-runtime/compile/testdata/parent/go.sum b/go-runtime/schema/testdata/one/go.sum similarity index 100% rename from go-runtime/compile/testdata/parent/go.sum rename to go-runtime/schema/testdata/one/go.sum diff --git a/go-runtime/schema/testdata/one/one.go b/go-runtime/schema/testdata/one/one.go new file mode 100644 index 0000000000..9195a76633 --- /dev/null +++ b/go-runtime/schema/testdata/one/one.go @@ -0,0 +1,202 @@ +package one + +import ( + "context" + "time" + + "ftl/builtin" + "ftl/two" + + "github.com/TBD54566975/ftl/go-runtime/ftl" +) + +//ftl:enum export +type Color string + +const ( + Red Color = "Red" + Blue Color = "Blue" + Green Color = "Green" +) + +// Comments about ColorInt. +// +//ftl:enum +type ColorInt int + +const ( + // RedInt is a color. + RedInt ColorInt = 0 + BlueInt ColorInt = 1 + // GreenInt is also a color. + GreenInt ColorInt = 2 +) + +//ftl:enum +type SimpleIota int + +const ( + Zero SimpleIota = iota + One + Two +) + +//ftl:enum +type IotaExpr int + +const ( + First IotaExpr = iota*2 + 1 + Second + Third +) + +//ftl:enum +type BlobOrList interface{ blobOrList() } + +type Blob string + +func (Blob) blobOrList() {} + +type List []string + +func (List) blobOrList() {} + +type Nested struct { +} + +//ftl:enum +type TypeEnum interface { + tag() +} + +type Option ftl.Option[string] + +func (Option) tag() {} + +type InlineStruct struct{} + +func (InlineStruct) tag() {} + +type AliasedStruct UnderlyingStruct + +func (AliasedStruct) tag() {} + +type UnderlyingStruct struct{} + +type ValueEnum ColorInt + +func (ValueEnum) tag() {} + +//ftl:enum +type PrivateEnum interface{ privateEnum() } + +//ftl:data export +type ExportedStruct struct{} + +func (ExportedStruct) privateEnum() {} + +//ftl:data +type PrivateStruct struct{} + +func (PrivateStruct) privateEnum() {} + +type WithoutDirectiveStruct struct{} + +func (WithoutDirectiveStruct) privateEnum() {} + +type Req struct { + Int int + Float float64 + String string + Slice []string + Map map[string]string + Nested Nested + Optional ftl.Option[Nested] + Time time.Time + User two.User `json:"u"` + Bytes []byte + LocalValueEnumRef Color + LocalTypeEnumRef BlobOrList + ExternalValueEnumRef two.TwoEnum + ExternalTypeEnumRef two.TypeEnum +} +type Resp struct{} + +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") + +//ftl:verb +func Verb(ctx context.Context, req Req) (Resp, error) { + return Resp{}, nil +} + +const Yellow Color = "Yellow" + +const YellowInt ColorInt = 3 + +type SinkReq struct{} + +//ftl:verb +func Sink(ctx context.Context, req SinkReq) error { + return nil +} + +type SourceResp struct{} + +//ftl:verb +func Source(ctx context.Context) (SourceResp, error) { + return SourceResp{}, nil +} + +//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 +} + +//ftl:data +type DataWithType[T any] struct { + Value T +} + +type NonFTLInterface interface { + NonFTLInterface() +} + +type NonFTLStruct struct { + Name string +} + +func (NonFTLStruct) NonFTLInterface() {} + +//ftl:verb +func StringToTime(ctx context.Context, input string) (time.Time, error) { + return time.Parse(time.RFC3339, input) +} + +//ftl:verb +func BatchStringToTime(ctx context.Context, input []string) ([]time.Time, error) { + var output = []time.Time{} + for _, s := range input { + t, err := time.Parse(time.RFC3339, s) + if err != nil { + return nil, err + } + output = append(output, t) + } + return output, nil +} diff --git a/go-runtime/compile/testdata/parent/child/child.go b/go-runtime/schema/testdata/parent/child/child.go similarity index 100% rename from go-runtime/compile/testdata/parent/child/child.go rename to go-runtime/schema/testdata/parent/child/child.go diff --git a/go-runtime/compile/testdata/parent/ftl.toml b/go-runtime/schema/testdata/parent/ftl.toml similarity index 100% rename from go-runtime/compile/testdata/parent/ftl.toml rename to go-runtime/schema/testdata/parent/ftl.toml diff --git a/go-runtime/compile/testdata/parent/go.mod b/go-runtime/schema/testdata/parent/go.mod similarity index 100% rename from go-runtime/compile/testdata/parent/go.mod rename to go-runtime/schema/testdata/parent/go.mod diff --git a/go-runtime/compile/testdata/pubsub/go.sum b/go-runtime/schema/testdata/parent/go.sum similarity index 100% rename from go-runtime/compile/testdata/pubsub/go.sum rename to go-runtime/schema/testdata/parent/go.sum diff --git a/go-runtime/compile/testdata/parent/parent.go b/go-runtime/schema/testdata/parent/parent.go similarity index 100% rename from go-runtime/compile/testdata/parent/parent.go rename to go-runtime/schema/testdata/parent/parent.go diff --git a/go-runtime/compile/testdata/pubsub/ftl.toml b/go-runtime/schema/testdata/pubsub/ftl.toml similarity index 100% rename from go-runtime/compile/testdata/pubsub/ftl.toml rename to go-runtime/schema/testdata/pubsub/ftl.toml diff --git a/go-runtime/compile/testdata/pubsub/go.mod b/go-runtime/schema/testdata/pubsub/go.mod similarity index 100% rename from go-runtime/compile/testdata/pubsub/go.mod rename to go-runtime/schema/testdata/pubsub/go.mod diff --git a/go-runtime/compile/testdata/subscriber/go.sum b/go-runtime/schema/testdata/pubsub/go.sum similarity index 100% rename from go-runtime/compile/testdata/subscriber/go.sum rename to go-runtime/schema/testdata/pubsub/go.sum diff --git a/go-runtime/compile/testdata/pubsub/pubsub.go b/go-runtime/schema/testdata/pubsub/pubsub.go similarity index 100% rename from go-runtime/compile/testdata/pubsub/pubsub.go rename to go-runtime/schema/testdata/pubsub/pubsub.go diff --git a/go-runtime/compile/testdata/subscriber/ftl.toml b/go-runtime/schema/testdata/subscriber/ftl.toml similarity index 100% rename from go-runtime/compile/testdata/subscriber/ftl.toml rename to go-runtime/schema/testdata/subscriber/ftl.toml diff --git a/go-runtime/compile/testdata/subscriber/go.mod b/go-runtime/schema/testdata/subscriber/go.mod similarity index 100% rename from go-runtime/compile/testdata/subscriber/go.mod rename to go-runtime/schema/testdata/subscriber/go.mod diff --git a/go-runtime/compile/testdata/validation/go.sum b/go-runtime/schema/testdata/subscriber/go.sum similarity index 100% rename from go-runtime/compile/testdata/validation/go.sum rename to go-runtime/schema/testdata/subscriber/go.sum diff --git a/go-runtime/compile/testdata/subscriber/subscriber.go b/go-runtime/schema/testdata/subscriber/subscriber.go similarity index 100% rename from go-runtime/compile/testdata/subscriber/subscriber.go rename to go-runtime/schema/testdata/subscriber/subscriber.go diff --git a/go-runtime/schema/testdata/two/ftl.toml b/go-runtime/schema/testdata/two/ftl.toml new file mode 100644 index 0000000000..9d4e07a631 --- /dev/null +++ b/go-runtime/schema/testdata/two/ftl.toml @@ -0,0 +1,2 @@ +module = "two" +language = "go" diff --git a/go-runtime/schema/testdata/two/go.mod b/go-runtime/schema/testdata/two/go.mod new file mode 100644 index 0000000000..266634dcd1 --- /dev/null +++ b/go-runtime/schema/testdata/two/go.mod @@ -0,0 +1,46 @@ +module ftl/two + +go 1.22.2 + +replace github.com/TBD54566975/ftl => ../../../.. + +require github.com/TBD54566975/ftl v0.150.3 + +require ( + connectrpc.com/connect v1.16.1 // indirect + connectrpc.com/grpcreflect v1.2.0 // indirect + connectrpc.com/otelconnect v0.7.0 // indirect + github.com/alecthomas/atomic v0.1.0-alpha2 // indirect + github.com/alecthomas/concurrency v0.0.2 // indirect + github.com/alecthomas/participle/v2 v2.1.1 // indirect + github.com/alecthomas/types v0.16.0 // indirect + github.com/alessio/shellescape v1.4.2 // indirect + github.com/benbjohnson/clock v1.3.5 // indirect + github.com/danieljoos/wincred v1.2.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.6.0 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jpillora/backoff v1.0.0 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/multiformats/go-base36 v0.2.0 // indirect + github.com/puzpuzpuz/xsync/v3 v3.3.1 // indirect + github.com/swaggest/jsonschema-go v0.3.72 // indirect + github.com/swaggest/refl v1.3.0 // indirect + github.com/zalando/go-keyring v0.2.5 // indirect + go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect + golang.org/x/crypto v0.25.0 // indirect + golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect + golang.org/x/mod v0.19.0 // indirect + golang.org/x/net v0.27.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/text v0.16.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect +) diff --git a/go-runtime/schema/testdata/two/go.sum b/go-runtime/schema/testdata/two/go.sum new file mode 100644 index 0000000000..ebaa5b32db --- /dev/null +++ b/go-runtime/schema/testdata/two/go.sum @@ -0,0 +1,146 @@ +connectrpc.com/connect v1.16.1 h1:rOdrK/RTI/7TVnn3JsVxt3n028MlTRwmK5Q4heSpjis= +connectrpc.com/connect v1.16.1/go.mod h1:XpZAduBQUySsb4/KO5JffORVkDI4B6/EYPi7N8xpNZw= +connectrpc.com/grpcreflect v1.2.0 h1:Q6og1S7HinmtbEuBvARLNwYmTbhEGRpHDhqrPNlmK+U= +connectrpc.com/grpcreflect v1.2.0/go.mod h1:nwSOKmE8nU5u/CidgHtPYk1PFI3U9ignz7iDMxOYkSY= +connectrpc.com/otelconnect v0.7.0 h1:ZH55ZZtcJOTKWWLy3qmL4Pam4RzRWBJFOqTPyAqCXkY= +connectrpc.com/otelconnect v0.7.0/go.mod h1:Bt2ivBymHZHqxvo4HkJ0EwHuUzQN6k2l0oH+mp/8nwc= +github.com/TBD54566975/scaffolder v1.0.0 h1:QUFSy2wVzumLDg7IHcKC6AP+IYyqWe9Wxiu72nZn5qU= +github.com/TBD54566975/scaffolder v1.0.0/go.mod h1:auVpczIbOAdIhYDVSruIw41DanxOKB9bSvjf6MEl7Fs= +github.com/alecthomas/assert/v2 v2.10.0 h1:jjRCHsj6hBJhkmhznrCzoNpbA3zqy0fYiUcYZP/GkPY= +github.com/alecthomas/assert/v2 v2.10.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= +github.com/alecthomas/atomic v0.1.0-alpha2/go.mod h1:zD6QGEyw49HIq19caJDc2NMXAy8rNi9ROrxtMXATfyI= +github.com/alecthomas/concurrency v0.0.2 h1:Q3kGPtLbleMbH9lHX5OBFvJygfyFw29bXZKBg+IEVuo= +github.com/alecthomas/concurrency v0.0.2/go.mod h1:GmuQb/iHX7mbNtPlC/WDzEFxDMB0HYFer2Qda9QTs7w= +github.com/alecthomas/participle/v2 v2.1.1 h1:hrjKESvSqGHzRb4yW1ciisFJ4p3MGYih6icjJvbsmV8= +github.com/alecthomas/participle/v2 v2.1.1/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/alecthomas/types v0.16.0 h1:o9+JSwCRB6DDaWDeR/Mg7v/zh3R+MlknM6DrnDyY7U0= +github.com/alecthomas/types v0.16.0/go.mod h1:Tswm0qQpjpVq8rn70OquRsUtFxbQKub/8TMyYYGI0+k= +github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0= +github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= +github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= +github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/bool64/dev v0.2.35 h1:M17TLsO/pV2J7PYI/gpe3Ua26ETkzZGb+dC06eoMqlk= +github.com/bool64/dev v0.2.35/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= +github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= +github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs= +github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE= +github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= +github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= +github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= +github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/puzpuzpuz/xsync/v3 v3.3.1 h1:vZPJk3OOfoaSjy3cdTX3BZxhDCUVp9SqdHnd+ilGlbQ= +github.com/puzpuzpuz/xsync/v3 v3.3.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ= +github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU= +github.com/swaggest/jsonschema-go v0.3.72 h1:IHaGlR1bdBUBPfhe4tfacN2TGAPKENEGiNyNzvnVHv4= +github.com/swaggest/jsonschema-go v0.3.72/go.mod h1:OrGyEoVqpfSFJ4Am4V/FQcQ3mlEC1vVeleA+5ggbVW4= +github.com/swaggest/refl v1.3.0 h1:PEUWIku+ZznYfsoyheF97ypSduvMApYyGkYF3nabS0I= +github.com/swaggest/refl v1.3.0/go.mod h1:3Ujvbmh1pfSbDYjC6JGG7nMgPvpG0ehQL4iNonnLNbg= +github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/zalando/go-keyring v0.2.5 h1:Bc2HHpjALryKD62ppdEzaFG6VxL6Bc+5v0LYpN8Lba8= +github.com/zalando/go-keyring v0.2.5/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= +go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08= +go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= +golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= +golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= +modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= +modernc.org/libc v1.52.1 h1:uau0VoiT5hnR+SpoWekCKbLqm7v6dhRL3hI+NQhgN3M= +modernc.org/libc v1.52.1/go.mod h1:HR4nVzFDSDizP620zcMCgjb1/8xk2lg5p/8yjfGv1IQ= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= +modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= +modernc.org/sqlite v1.30.1 h1:YFhPVfu2iIgUf9kuA1CR7iiHdcEEsI2i+yjRYHscyxk= +modernc.org/sqlite v1.30.1/go.mod h1:DUmsiWQDaAvU4abhc/N+djlom/L2o8f7gZ95RCvyoLU= +modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/go-runtime/schema/testdata/two/two.go b/go-runtime/schema/testdata/two/two.go new file mode 100644 index 0000000000..25ec35c6de --- /dev/null +++ b/go-runtime/schema/testdata/two/two.go @@ -0,0 +1,116 @@ +package two + +import ( + "context" + + "github.com/TBD54566975/ftl/go-runtime/ftl" + lib "github.com/TBD54566975/ftl/go-runtime/schema/testdata" +) + +//ftl:enum export +type TwoEnum string + +const ( + Red TwoEnum = "Red" + Blue TwoEnum = "Blue" + Green TwoEnum = "Green" +) + +//ftl:enum export +type TypeEnum interface{ typeEnum() } + +type Scalar string + +func (Scalar) typeEnum() {} + +type List []string + +func (List) typeEnum() {} + +//ftl:data export +type Exported struct { +} + +func (Exported) typeEnum() {} + +type WithoutDirective struct{} + +func (WithoutDirective) typeEnum() {} + +type User struct { + Name string +} + +type Payload[T any] struct { + Body T +} + +type UserResponse struct { + User User +} + +//ftl:verb export +func Two(ctx context.Context, req Payload[string]) (Payload[string], error) { + return Payload[string]{}, nil +} + +//ftl:verb export +func Three(ctx context.Context, req Payload[string]) (Payload[string], error) { + return Payload[string]{}, nil +} + +//ftl:verb export +func CallsTwo(ctx context.Context, req Payload[string]) (Payload[string], error) { + return ftl.Call(ctx, Two, req) +} + +//ftl:verb export +func CallsTwoAndThree(ctx context.Context, req Payload[string]) (Payload[string], error) { + err := transitiveVerbCall(ctx, req) + return Payload[string]{}, err +} + +//ftl:verb export +func ReturnsUser(ctx context.Context) (UserResponse, error) { + return UserResponse{ + User: User{ + Name: "John Doe", + }, + }, nil +} + +//ftl:data +type NonFTLField struct { + ExplicitType ExplicitAliasType + ExplicitAlias ExplicitAliasAlias + TransitiveType TransitiveAliasType + TransitiveAlias TransitiveAliasAlias +} + +//ftl:typealias +//ftl:typemap kotlin "com.foo.bar.NonFTLType" +type ExplicitAliasType lib.NonFTLType + +//ftl:typealias +//ftl:typemap kotlin "com.foo.bar.NonFTLType" +type ExplicitAliasAlias = lib.NonFTLType + +type TransitiveAliasType lib.NonFTLType + +type TransitiveAliasAlias = lib.NonFTLType + +type TransitiveAlias lib.NonFTLType + +func transitiveVerbCall(ctx context.Context, req Payload[string]) error { + _, err := ftl.Call(ctx, Two, req) + if err != nil { + return err + } + err = superTransitiveVerbCall(ctx, req) + return err +} + +func superTransitiveVerbCall(ctx context.Context, req Payload[string]) error { + _, err := ftl.Call(ctx, Three, req) + return err +} diff --git a/go-runtime/compile/testdata/validation/ftl.toml b/go-runtime/schema/testdata/validation/ftl.toml similarity index 100% rename from go-runtime/compile/testdata/validation/ftl.toml rename to go-runtime/schema/testdata/validation/ftl.toml diff --git a/go-runtime/compile/testdata/validation/go.mod b/go-runtime/schema/testdata/validation/go.mod similarity index 100% rename from go-runtime/compile/testdata/validation/go.mod rename to go-runtime/schema/testdata/validation/go.mod diff --git a/go-runtime/schema/testdata/validation/go.sum b/go-runtime/schema/testdata/validation/go.sum new file mode 100644 index 0000000000..ebaa5b32db --- /dev/null +++ b/go-runtime/schema/testdata/validation/go.sum @@ -0,0 +1,146 @@ +connectrpc.com/connect v1.16.1 h1:rOdrK/RTI/7TVnn3JsVxt3n028MlTRwmK5Q4heSpjis= +connectrpc.com/connect v1.16.1/go.mod h1:XpZAduBQUySsb4/KO5JffORVkDI4B6/EYPi7N8xpNZw= +connectrpc.com/grpcreflect v1.2.0 h1:Q6og1S7HinmtbEuBvARLNwYmTbhEGRpHDhqrPNlmK+U= +connectrpc.com/grpcreflect v1.2.0/go.mod h1:nwSOKmE8nU5u/CidgHtPYk1PFI3U9ignz7iDMxOYkSY= +connectrpc.com/otelconnect v0.7.0 h1:ZH55ZZtcJOTKWWLy3qmL4Pam4RzRWBJFOqTPyAqCXkY= +connectrpc.com/otelconnect v0.7.0/go.mod h1:Bt2ivBymHZHqxvo4HkJ0EwHuUzQN6k2l0oH+mp/8nwc= +github.com/TBD54566975/scaffolder v1.0.0 h1:QUFSy2wVzumLDg7IHcKC6AP+IYyqWe9Wxiu72nZn5qU= +github.com/TBD54566975/scaffolder v1.0.0/go.mod h1:auVpczIbOAdIhYDVSruIw41DanxOKB9bSvjf6MEl7Fs= +github.com/alecthomas/assert/v2 v2.10.0 h1:jjRCHsj6hBJhkmhznrCzoNpbA3zqy0fYiUcYZP/GkPY= +github.com/alecthomas/assert/v2 v2.10.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= +github.com/alecthomas/atomic v0.1.0-alpha2/go.mod h1:zD6QGEyw49HIq19caJDc2NMXAy8rNi9ROrxtMXATfyI= +github.com/alecthomas/concurrency v0.0.2 h1:Q3kGPtLbleMbH9lHX5OBFvJygfyFw29bXZKBg+IEVuo= +github.com/alecthomas/concurrency v0.0.2/go.mod h1:GmuQb/iHX7mbNtPlC/WDzEFxDMB0HYFer2Qda9QTs7w= +github.com/alecthomas/participle/v2 v2.1.1 h1:hrjKESvSqGHzRb4yW1ciisFJ4p3MGYih6icjJvbsmV8= +github.com/alecthomas/participle/v2 v2.1.1/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/alecthomas/types v0.16.0 h1:o9+JSwCRB6DDaWDeR/Mg7v/zh3R+MlknM6DrnDyY7U0= +github.com/alecthomas/types v0.16.0/go.mod h1:Tswm0qQpjpVq8rn70OquRsUtFxbQKub/8TMyYYGI0+k= +github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0= +github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= +github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= +github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/bool64/dev v0.2.35 h1:M17TLsO/pV2J7PYI/gpe3Ua26ETkzZGb+dC06eoMqlk= +github.com/bool64/dev v0.2.35/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg= +github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= +github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs= +github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE= +github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= +github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= +github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= +github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/puzpuzpuz/xsync/v3 v3.3.1 h1:vZPJk3OOfoaSjy3cdTX3BZxhDCUVp9SqdHnd+ilGlbQ= +github.com/puzpuzpuz/xsync/v3 v3.3.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ= +github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU= +github.com/swaggest/jsonschema-go v0.3.72 h1:IHaGlR1bdBUBPfhe4tfacN2TGAPKENEGiNyNzvnVHv4= +github.com/swaggest/jsonschema-go v0.3.72/go.mod h1:OrGyEoVqpfSFJ4Am4V/FQcQ3mlEC1vVeleA+5ggbVW4= +github.com/swaggest/refl v1.3.0 h1:PEUWIku+ZznYfsoyheF97ypSduvMApYyGkYF3nabS0I= +github.com/swaggest/refl v1.3.0/go.mod h1:3Ujvbmh1pfSbDYjC6JGG7nMgPvpG0ehQL4iNonnLNbg= +github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/zalando/go-keyring v0.2.5 h1:Bc2HHpjALryKD62ppdEzaFG6VxL6Bc+5v0LYpN8Lba8= +github.com/zalando/go-keyring v0.2.5/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= +go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08= +go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= +golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= +golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= +modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= +modernc.org/libc v1.52.1 h1:uau0VoiT5hnR+SpoWekCKbLqm7v6dhRL3hI+NQhgN3M= +modernc.org/libc v1.52.1/go.mod h1:HR4nVzFDSDizP620zcMCgjb1/8xk2lg5p/8yjfGv1IQ= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= +modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= +modernc.org/sqlite v1.30.1 h1:YFhPVfu2iIgUf9kuA1CR7iiHdcEEsI2i+yjRYHscyxk= +modernc.org/sqlite v1.30.1/go.mod h1:DUmsiWQDaAvU4abhc/N+djlom/L2o8f7gZ95RCvyoLU= +modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/go-runtime/compile/testdata/validation/validation.go b/go-runtime/schema/testdata/validation/validation.go similarity index 100% rename from go-runtime/compile/testdata/validation/validation.go rename to go-runtime/schema/testdata/validation/validation.go diff --git a/go-runtime/schema/topic/analyzer.go b/go-runtime/schema/topic/analyzer.go index 1eb026c768..0ac10a1d60 100644 --- a/go-runtime/schema/topic/analyzer.go +++ b/go-runtime/schema/topic/analyzer.go @@ -8,8 +8,6 @@ import ( "github.com/TBD54566975/ftl/backend/schema/strcase" "github.com/TBD54566975/ftl/go-runtime/schema/common" "github.com/TBD54566975/golang-tools/go/analysis" - "github.com/TBD54566975/golang-tools/go/analysis/passes/inspect" - "github.com/TBD54566975/golang-tools/go/ast/inspector" "github.com/alecthomas/types/optional" ) @@ -18,38 +16,11 @@ const ( ) // Extractor extracts topics. -var Extractor = common.NewExtractor("topic", (*Fact)(nil), Extract) - -type Tag struct{} // Tag uniquely identifies the fact type for this extractor. -type Fact = common.DefaultFact[Tag] - -func Extract(pass *analysis.Pass) (interface{}, error) { - in := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) //nolint:forcetypeassert - nodeFilter := []ast.Node{ - (*ast.GenDecl)(nil), - } - in.Preorder(nodeFilter, func(n ast.Node) { - node := n.(*ast.GenDecl) //nolint:forcetypeassert - callExpr, ok := common.CallExprFromVar(node).Get() - if !ok { - return - } - if !common.FuncPathEquals(pass, callExpr, ftlTopicFuncPath) { - return - } - obj, ok := common.GetObjectForNode(pass.TypesInfo, node).Get() - if !ok { - return - } - if topic, ok := extractTopic(pass, node, callExpr, obj).Get(); ok { - common.MarkSchemaDecl(pass, obj, topic) - } - }) - return common.NewExtractorResult(pass), nil -} +var Extractor = common.NewCallDeclExtractor[*schema.Topic]("topic", Extract, ftlTopicFuncPath) // expects: var NameLiteral = ftl.Topic[EventType]("name_literal") -func extractTopic(pass *analysis.Pass, node *ast.GenDecl, callExpr *ast.CallExpr, obj types.Object) optional.Option[*schema.Topic] { +func Extract(pass *analysis.Pass, obj types.Object, node *ast.GenDecl, callExpr *ast.CallExpr, + callPath string) optional.Option[*schema.Topic] { indexExpr, ok := callExpr.Fun.(*ast.IndexExpr) if !ok { common.Errorf(pass, node, "must have an event type as a type parameter")