From c3c7aa6019b2c50b36f22999734d6ba386d78eb0 Mon Sep 17 00:00:00 2001 From: Safeer Jiwan Date: Tue, 23 Jul 2024 14:50:32 -0700 Subject: [PATCH] fix: ensure topic name matches idiomatic variable name (#1958) Fixes #1919 --- .../pubsub/testdata/go/publisher/publisher.go | 12 ++++---- .../testdata/go/subscriber/subscriber.go | 4 +-- backend/schema/strcase/case.go | 12 ++++++++ backend/schema/strcase/case_test.go | 28 +++++++++++++++++++ cmd/ftl/cmd_new.go | 1 + docs/content/docs/reference/pubsub.md | 6 ++-- .../external_module.go.tmpl | 2 +- go-runtime/compile/schema_test.go | 8 +++--- go-runtime/compile/testdata/pubsub/pubsub.go | 14 +++++----- .../ftl/ftltest/testdata/go/pubsub/pubsub.go | 6 ++-- .../ftltest/testdata/go/pubsub/pubsub_test.go | 4 +-- go-runtime/schema/subscription/analyzer.go | 7 ++--- go-runtime/schema/topic/analyzer.go | 23 +++++++++++++-- internal/scaffold.go | 1 + lsp/hoveritems.go | 2 +- lsp/markdown/completion/pubSubTopic.md | 2 +- 16 files changed, 95 insertions(+), 37 deletions(-) diff --git a/backend/controller/pubsub/testdata/go/publisher/publisher.go b/backend/controller/pubsub/testdata/go/publisher/publisher.go index 62d4872514..38af05f989 100644 --- a/backend/controller/pubsub/testdata/go/publisher/publisher.go +++ b/backend/controller/pubsub/testdata/go/publisher/publisher.go @@ -9,7 +9,7 @@ import ( ) //ftl:export -var topic = ftl.Topic[PubSubEvent]("test_topic") +var TestTopic = ftl.Topic[PubSubEvent]("test_topic") type PubSubEvent struct { Time time.Time @@ -21,7 +21,7 @@ func PublishTen(ctx context.Context) error { for i := 0; i < 10; i++ { t := time.Now() logger.Infof("Publishing %v", t) - err := topic.Publish(ctx, PubSubEvent{Time: t}) + err := TestTopic.Publish(ctx, PubSubEvent{Time: t}) if err != nil { return err } @@ -34,16 +34,16 @@ func PublishOne(ctx context.Context) error { logger := ftl.LoggerFromContext(ctx) t := time.Now() logger.Infof("Publishing %v", t) - return topic.Publish(ctx, PubSubEvent{Time: t}) + return TestTopic.Publish(ctx, PubSubEvent{Time: t}) } //ftl:export -var topic2 = ftl.Topic[PubSubEvent]("topic2") +var Topic2 = ftl.Topic[PubSubEvent]("topic_2") //ftl:verb func PublishOneToTopic2(ctx context.Context) error { logger := ftl.LoggerFromContext(ctx) t := time.Now() - logger.Infof("Publishing to topic2 %v", t) - return topic2.Publish(ctx, PubSubEvent{Time: t}) + logger.Infof("Publishing to topic_2 %v", t) + return Topic2.Publish(ctx, PubSubEvent{Time: t}) } diff --git a/backend/controller/pubsub/testdata/go/subscriber/subscriber.go b/backend/controller/pubsub/testdata/go/subscriber/subscriber.go index 6d425d6cd1..784ea77b6b 100644 --- a/backend/controller/pubsub/testdata/go/subscriber/subscriber.go +++ b/backend/controller/pubsub/testdata/go/subscriber/subscriber.go @@ -9,7 +9,7 @@ import ( "github.com/TBD54566975/ftl/go-runtime/ftl" // Import the FTL SDK. ) -var _ = ftl.Subscription(publisher.Test_topic, "test_subscription") +var _ = ftl.Subscription(publisher.TestTopic, "test_subscription") //ftl:verb //ftl:subscribe test_subscription @@ -30,6 +30,6 @@ func ConsumeButFailAndRetry(ctx context.Context, req publisher.PubSubEvent) erro //ftl:verb func PublishToExternalModule(ctx context.Context) error { // Get around compile-time checks - var topic = publisher.Test_topic + var topic = publisher.TestTopic return topic.Publish(ctx, publisher.PubSubEvent{Time: time.Now()}) } diff --git a/backend/schema/strcase/case.go b/backend/schema/strcase/case.go index bbad861e0b..9dd7019892 100644 --- a/backend/schema/strcase/case.go +++ b/backend/schema/strcase/case.go @@ -33,6 +33,18 @@ func ToUpperCamel(s string) string { return strings.Join(parts, "") } +func ToUpperStrippedCamel(s string) string { + parts := split(s) + out := make([]string, 0, len(parts)*2) + for i := range parts { + if parts[i] == "-" || parts[i] == "_" { + continue + } + out = append(out, title(parts[i])) + } + return strings.Join(out, "") +} + func ToLowerSnake(s string) string { parts := split(s) out := make([]string, 0, len(parts)*2) diff --git a/backend/schema/strcase/case_test.go b/backend/schema/strcase/case_test.go index 4a9b88d7d5..f6e4ab30ad 100644 --- a/backend/schema/strcase/case_test.go +++ b/backend/schema/strcase/case_test.go @@ -86,6 +86,34 @@ func TestUpperCamelCase(t *testing.T) { } } +func TestUpperStrippedCamel(t *testing.T) { + for _, tt := range []struct { + input string + expected string + }{ + {"lowercase", "Lowercase"}, + {"Class", "Class"}, + {"MyClass", "MyClass"}, + {"MyC", "MyC"}, + {"HTML", "Html"}, + {"PDFLoader", "PdfLoader"}, + {"AString", "AString"}, + {"SimpleXMLParser", "SimpleXmlParser"}, + {"vimRPCPlugin", "VimRpcPlugin"}, + {"GL11Version", "Gl11Version"}, + {"99Bottles", "99Bottles"}, + {"May5", "May5"}, + {"BFG9000", "Bfg9000"}, + {"BöseÜberraschung", "BöseÜberraschung"}, + {"snake_case", "SnakeCase"}, + {"snake_Case_Caps", "SnakeCaseCaps"}, + {"kebab-numbers99", "KebabNumbers99"}, + } { + actual := ToUpperStrippedCamel(tt.input) + assert.Equal(t, tt.expected, actual, "UpperStrippedCamel(%q) = %v; want %v", tt.input, actual, tt.expected) + } +} + func TestLowerSnake(t *testing.T) { for _, tt := range []struct { input string diff --git a/cmd/ftl/cmd_new.go b/cmd/ftl/cmd_new.go index 1afa32ad1f..7f5c2fc58c 100644 --- a/cmd/ftl/cmd_new.go +++ b/cmd/ftl/cmd_new.go @@ -168,6 +168,7 @@ var scaffoldFuncs = template.FuncMap{ "screamingSnake": strcase.ToUpperSnake, "camel": strcase.ToUpperCamel, "lowerCamel": strcase.ToLowerCamel, + "strippedCamel": strcase.ToUpperStrippedCamel, "kebab": strcase.ToLowerKebab, "screamingKebab": strcase.ToUpperKebab, "upper": strings.ToUpper, diff --git a/docs/content/docs/reference/pubsub.md b/docs/content/docs/reference/pubsub.md index 11fad3cb03..b50dc3c8e3 100644 --- a/docs/content/docs/reference/pubsub.md +++ b/docs/content/docs/reference/pubsub.md @@ -18,13 +18,13 @@ FTL has first-class support for PubSub, modelled on the concepts of topics (wher First, declare a new topic: ```go -var invoicesTopic = ftl.Topic[Invoice]("invoices") +var Invoices = ftl.Topic[Invoice]("invoices") ``` Then declare each subscription on the topic: ```go -var _ = ftl.Subscription(invoicesTopic, "emailInvoices") +var _ = ftl.Subscription(Invoices, "emailInvoices") ``` And finally define a Sink to consume from the subscription: @@ -39,7 +39,7 @@ func SendInvoiceEmail(ctx context.Context, in Invoice) error { Events can be published to a topic like so: ```go -invoicesTopic.Publish(ctx, Invoice{...}) +Invoices.Publish(ctx, Invoice{...}) ``` > **NOTE!** diff --git a/go-runtime/compile/external-module-template/.ftl/go/modules/{{ .Module.Name }}/external_module.go.tmpl b/go-runtime/compile/external-module-template/.ftl/go/modules/{{ .Module.Name }}/external_module.go.tmpl index 8b22db023d..577548dd4c 100644 --- a/go-runtime/compile/external-module-template/.ftl/go/modules/{{ .Module.Name }}/external_module.go.tmpl +++ b/go-runtime/compile/external-module-template/.ftl/go/modules/{{ .Module.Name }}/external_module.go.tmpl @@ -23,7 +23,7 @@ var _ = context.Background // {{- end}} {{- if is "Topic" .}} -var {{.Name|title}} = ftl.Topic[{{type $.Module .Event}}]("{{.Name}}") +var {{.Name|strippedCamel}} = ftl.Topic[{{type $.Module .Event}}]("{{.Name}}") {{- else if and (is "Enum" .) .IsValueEnum}} {{- $enumName := .Name}} //ftl:enum diff --git a/go-runtime/compile/schema_test.go b/go-runtime/compile/schema_test.go index 5dcfc3d251..93a260b8cf 100644 --- a/go-runtime/compile/schema_test.go +++ b/go-runtime/compile/schema_test.go @@ -404,10 +404,10 @@ func TestExtractModulePubSub(t *testing.T) { actual := schema.Normalise(r.Module) expected := `module pubsub { topic payins pubsub.PayinEvent - // publicBroadcast is a topic that broadcasts payin events to the public. + // public_broadcast is a topic that broadcasts payin events to the public. // out of order with subscription registration to test ordering doesn't matter. - export topic publicBroadcast pubsub.PayinEvent - subscription broadcastSubscription pubsub.publicBroadcast + export topic public_broadcast pubsub.PayinEvent + subscription broadcastSubscription pubsub.public_broadcast subscription paymentProcessing pubsub.payins export data PayinEvent { @@ -439,7 +439,7 @@ func TestExtractModuleSubscriber(t *testing.T) { assert.Equal(t, nil, r.Errors, "expected no schema errors") actual := schema.Normalise(r.Module) expected := `module subscriber { - subscription subscriptionToExternalTopic pubsub.publicBroadcast + subscription subscriptionToExternalTopic pubsub.public_broadcast verb consumesSubscriptionFromExternalTopic(pubsub.PayinEvent) Unit +subscribe subscriptionToExternalTopic diff --git a/go-runtime/compile/testdata/pubsub/pubsub.go b/go-runtime/compile/testdata/pubsub/pubsub.go index 9b85274189..2a729f4027 100644 --- a/go-runtime/compile/testdata/pubsub/pubsub.go +++ b/go-runtime/compile/testdata/pubsub/pubsub.go @@ -13,7 +13,7 @@ type PayinEvent struct { //ftl:verb func Payin(ctx context.Context) error { - if err := payinsVar.Publish(ctx, PayinEvent{Name: "Test"}); err != nil { + if err := Payins.Publish(ctx, PayinEvent{Name: "Test"}); err != nil { return fmt.Errorf("failed to publish event: %w", err) } return nil @@ -26,21 +26,21 @@ func ProcessPayin(ctx context.Context, event PayinEvent) error { return nil } -var _ = ftl.Subscription(payinsVar, "paymentProcessing") +var _ = ftl.Subscription(Payins, "paymentProcessing") -var payinsVar = ftl.Topic[PayinEvent]("payins") +var Payins = ftl.Topic[PayinEvent]("payins") -var _ = ftl.Subscription(broadcast, "broadcastSubscription") +var _ = ftl.Subscription(PublicBroadcast, "broadcastSubscription") -// publicBroadcast is a topic that broadcasts payin events to the public. +// public_broadcast is a topic that broadcasts payin events to the public. // out of order with subscription registration to test ordering doesn't matter. // //ftl:export -var broadcast = ftl.Topic[PayinEvent]("publicBroadcast") +var PublicBroadcast = ftl.Topic[PayinEvent]("public_broadcast") //ftl:verb export func Broadcast(ctx context.Context) error { - if err := broadcast.Publish(ctx, PayinEvent{Name: "Broadcast"}); err != nil { + if err := PublicBroadcast.Publish(ctx, PayinEvent{Name: "Broadcast"}); err != nil { return fmt.Errorf("failed to publish broadcast event: %w", err) } return nil diff --git a/go-runtime/ftl/ftltest/testdata/go/pubsub/pubsub.go b/go-runtime/ftl/ftltest/testdata/go/pubsub/pubsub.go index 7438c01f81..5acde43143 100644 --- a/go-runtime/ftl/ftltest/testdata/go/pubsub/pubsub.go +++ b/go-runtime/ftl/ftltest/testdata/go/pubsub/pubsub.go @@ -10,8 +10,8 @@ import ( ) //ftl:export -var topic = ftl.Topic[Event]("topic") -var subscription = ftl.Subscription(topic, "subscription") +var Topic = ftl.Topic[Event]("topic") +var subscription = ftl.Subscription(Topic, "subscription") //ftl:data type Event struct { @@ -20,7 +20,7 @@ type Event struct { //ftl:verb func PublishToTopicOne(ctx context.Context, event Event) error { - return topic.Publish(ctx, event) + return Topic.Publish(ctx, event) } //ftl:subscribe subscription diff --git a/go-runtime/ftl/ftltest/testdata/go/pubsub/pubsub_test.go b/go-runtime/ftl/ftltest/testdata/go/pubsub/pubsub_test.go index 9778d03156..78ce174464 100644 --- a/go-runtime/ftl/ftltest/testdata/go/pubsub/pubsub_test.go +++ b/go-runtime/ftl/ftltest/testdata/go/pubsub/pubsub_test.go @@ -23,7 +23,7 @@ func TestSubscriberReturningErrors(t *testing.T) { } ftltest.WaitForSubscriptionsToComplete(ctx) assert.Equal(t, count, len(ftltest.ErrorsForSubscription(ctx, subscription))) - assert.Equal(t, count, len(ftltest.EventsForTopic(ctx, topic))) + assert.Equal(t, count, len(ftltest.EventsForTopic(ctx, Topic))) } func TestMultipleMultipleFakeSubscribers(t *testing.T) { @@ -50,6 +50,6 @@ func TestMultipleMultipleFakeSubscribers(t *testing.T) { } ftltest.WaitForSubscriptionsToComplete(ctx) assert.Equal(t, 0, len(ftltest.ErrorsForSubscription(ctx, subscription))) - assert.Equal(t, count, len(ftltest.EventsForTopic(ctx, topic))) + assert.Equal(t, count, len(ftltest.EventsForTopic(ctx, Topic))) assert.Equal(t, count, counter.Load()) } diff --git a/go-runtime/schema/subscription/analyzer.go b/go-runtime/schema/subscription/analyzer.go index 2622626f86..3f4ee872ef 100644 --- a/go-runtime/schema/subscription/analyzer.go +++ b/go-runtime/schema/subscription/analyzer.go @@ -3,9 +3,9 @@ package subscription import ( "go/ast" "go/types" - "strings" "github.com/TBD54566975/ftl/backend/schema" + "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" @@ -91,10 +91,7 @@ func extractSubscription(pass *analysis.Pass, obj types.Object, node *ast.CallEx common.Errorf(pass, node, "subscription registration must have a topic") return optional.None[*schema.Subscription]() } - name := strings.ToLower(string(varName[0])) - if len(varName) > 1 { - name += varName[1:] - } + name := strcase.ToLowerSnake(varName) topicRef = &schema.Ref{ Module: moduleIdent.Name, Name: name, diff --git a/go-runtime/schema/topic/analyzer.go b/go-runtime/schema/topic/analyzer.go index 177b8f9deb..70231de6b3 100644 --- a/go-runtime/schema/topic/analyzer.go +++ b/go-runtime/schema/topic/analyzer.go @@ -5,6 +5,7 @@ import ( "go/types" "github.com/TBD54566975/ftl/backend/schema" + "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" @@ -47,7 +48,7 @@ func Extract(pass *analysis.Pass) (interface{}, error) { return common.NewExtractorResult(pass), nil } -// expects: _ = ftl.Topic[EventType]("name_literal") +// 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] { indexExpr, ok := callExpr.Fun.(*ast.IndexExpr) if !ok { @@ -60,9 +61,27 @@ func extractTopic(pass *analysis.Pass, node *ast.GenDecl, callExpr *ast.CallExpr return optional.None[*schema.Topic]() } + topicName := common.ExtractStringLiteralArg(pass, callExpr, 0) + expTopicName := strcase.ToLowerSnake(topicName) + if topicName != expTopicName { + common.Errorf(pass, node, "unsupported topic name %q, did you mean to use %q?", topicName, expTopicName) + return optional.None[*schema.Topic]() + } + + if len(node.Specs) > 0 { + if t, ok := node.Specs[0].(*ast.ValueSpec); ok { + varName := t.Names[0].Name + expVarName := strcase.ToUpperStrippedCamel(topicName) + if varName != expVarName { + common.Errorf(pass, node, "unexpected topic variable name %q, did you mean %q?", varName, expVarName) + return optional.None[*schema.Topic]() + } + } + } + topic := &schema.Topic{ Pos: common.GoPosToSchemaPos(pass.Fset, node.Pos()), - Name: common.ExtractStringLiteralArg(pass, callExpr, 0), + Name: topicName, Event: typeParamType, } common.ApplyMetadata[*schema.Subscription](pass, obj, func(md *common.ExtractedMetadata) { diff --git a/internal/scaffold.go b/internal/scaffold.go index 6c37b32f04..652fd6f14d 100644 --- a/internal/scaffold.go +++ b/internal/scaffold.go @@ -30,6 +30,7 @@ var scaffoldFuncs = scaffolder.FuncMap{ "screamingSnake": strcase.ToUpperSnake, "camel": strcase.ToUpperCamel, "lowerCamel": strcase.ToLowerCamel, + "strippedCamel": strcase.ToUpperStrippedCamel, "kebab": strcase.ToLowerKebab, "screamingKebab": strcase.ToUpperKebab, "upper": strings.ToUpper, diff --git a/lsp/hoveritems.go b/lsp/hoveritems.go index 94a9fd1d17..7ad9b973d5 100644 --- a/lsp/hoveritems.go +++ b/lsp/hoveritems.go @@ -6,7 +6,7 @@ var hoverMap = map[string]string{ "//ftl:enum": "## Type enums (sum types)\n\n[Sum types](https://en.wikipedia.org/wiki/Tagged_union) are supported by FTL's type system, but aren't directly supported by Go. However they can be approximated with the use of [sealed interfaces](https://blog.chewxy.com/2018/03/18/golang-interfaces/). To declare a sum type in FTL use the comment directive `//ftl:enum`:\n\n```go\n//ftl:enum\ntype Animal interface { animal() }\n\ntype Cat struct {}\nfunc (Cat) animal() {}\n\ntype Dog struct {}\nfunc (Dog) animal() {}\n```\n## Value enums\n\nA value enum is an enumerated set of string or integer values.\n\n```go\n//ftl:enum\ntype Colour string\n\nconst (\n Red Colour = \"red\"\n Green Colour = \"green\"\n Blue Colour = \"blue\"\n)\n```\n", "//ftl:ingress": "## HTTP Ingress\n\nVerbs annotated with `ftl:ingress` will be exposed via HTTP (`http` is the default ingress type). These endpoints will then be available on one of our default `ingress` ports (local development defaults to `http://localhost:8891`).\n\nThe following will be available at `http://localhost:8891/http/users/123/posts?postId=456`.\n\n```go\ntype GetRequest struct {\n\tUserID string `json:\"userId\"`\n\tPostID string `json:\"postId\"`\n}\n\ntype GetResponse struct {\n\tMessage string `json:\"msg\"`\n}\n\n//ftl:ingress GET /http/users/{userId}/posts\nfunc Get(ctx context.Context, req builtin.HttpRequest[GetRequest]) (builtin.HttpResponse[GetResponse, ErrorResponse], error) {\n // ...\n}\n```\n\n> **NOTE!**\n> The `req` and `resp` types of HTTP `ingress` [verbs](../verbs) must be `builtin.HttpRequest` and `builtin.HttpResponse` respectively. These types provide the necessary fields for HTTP `ingress` (`headers`, `statusCode`, etc.)\n> \n> You will need to import `ftl/builtin`.\n\nKey points to note\n\n- `path`, `query`, and `body` parameters are automatically mapped to the `req` and `resp` structures. In the example above, `{userId}` is extracted from the path parameter and `postId` is extracted from the query parameter.\n- `ingress` verbs will be automatically exported by default.\n", "//ftl:retry": "## Retries\n\nAny verb called asynchronously (specifically, PubSub subscribers and FSM states), may optionally specify a basic exponential backoff retry policy via a Go comment directive. The directive has the following syntax:\n\n```go\n//ftl:retry [] []\n```\n\n`attempts` and `max-backoff` default to unlimited if not specified.\n\nFor example, the following function will retry up to 10 times, with a delay of 5s, 10s, 20s, 40s, 60s, 60s, etc.\n\n```go\n//ftl:retry 10 5s 1m\nfunc Invoiced(ctx context.Context, in Invoice) error {\n // ...\n}\n```\n", - "//ftl:subscribe": "## PubSub\n\nFTL has first-class support for PubSub, modelled on the concepts of topics (where events are sent), subscriptions (a cursor over the topic), and subscribers (functions events are delivered to). Subscribers are, as you would expect, sinks. Each subscription is a cursor over the topic it is associated with. Each topic may have multiple subscriptions. Each subscription may have multiple subscribers, in which case events will be distributed among them.\n\nFirst, declare a new topic:\n\n```go\nvar invoicesTopic = ftl.Topic[Invoice](\"invoices\")\n```\n\nThen declare each subscription on the topic:\n\n```go\nvar _ = ftl.Subscription(invoicesTopic, \"emailInvoices\")\n```\n\nAnd finally define a Sink to consume from the subscription:\n\n```go\n//ftl:subscribe emailInvoices\nfunc SendInvoiceEmail(ctx context.Context, in Invoice) error {\n // ...\n}\n```\n\nEvents can be published to a topic like so:\n\n```go\ninvoicesTopic.Publish(ctx, Invoice{...})\n```\n\n> **NOTE!**\n> PubSub topics cannot be published to from outside the module that declared them, they can only be subscribed to. That is, if a topic is declared in module `A`, module `B` cannot publish to it.\n", + "//ftl:subscribe": "## PubSub\n\nFTL has first-class support for PubSub, modelled on the concepts of topics (where events are sent), subscriptions (a cursor over the topic), and subscribers (functions events are delivered to). Subscribers are, as you would expect, sinks. Each subscription is a cursor over the topic it is associated with. Each topic may have multiple subscriptions. Each subscription may have multiple subscribers, in which case events will be distributed among them.\n\nFirst, declare a new topic:\n\n```go\nvar Invoices = ftl.Topic[Invoice](\"invoices\")\n```\n\nThen declare each subscription on the topic:\n\n```go\nvar _ = ftl.Subscription(Invoices, \"emailInvoices\")\n```\n\nAnd finally define a Sink to consume from the subscription:\n\n```go\n//ftl:subscribe emailInvoices\nfunc SendInvoiceEmail(ctx context.Context, in Invoice) error {\n // ...\n}\n```\n\nEvents can be published to a topic like so:\n\n```go\nInvoices.Publish(ctx, Invoice{...})\n```\n\n> **NOTE!**\n> PubSub topics cannot be published to from outside the module that declared them, they can only be subscribed to. That is, if a topic is declared in module `A`, module `B` cannot publish to it.\n", "//ftl:typealias": "## Type aliases\n\nA type alias is an alternate name for an existing type. It can be declared like so:\n\n```go\n//ftl:typealias\ntype Alias Target\n```\n\neg.\n\n```go\n//ftl:typealias\ntype UserID string\n```\n", "//ftl:verb": "## Verbs\n\n## Defining Verbs\n\nTo declare a Verb, write a normal Go function with the following signature, annotated with the Go [comment directive](https://tip.golang.org/doc/comment#syntax) `//ftl:verb`:\n\n```go\n//ftl:verb\nfunc F(context.Context, In) (Out, error) { }\n```\n\neg.\n\n```go\ntype EchoRequest struct {}\n\ntype EchoResponse struct {}\n\n//ftl:verb\nfunc Echo(ctx context.Context, in EchoRequest) (EchoResponse, error) {\n // ...\n}\n```\n\nBy default verbs are only [visible](../visibility) to other verbs in the same module.\n\n## Calling Verbs\n\nTo call a verb use `ftl.Call()`. eg.\n\n```go\nout, err := ftl.Call(ctx, echo.Echo, echo.EchoRequest{})\n```\n", } diff --git a/lsp/markdown/completion/pubSubTopic.md b/lsp/markdown/completion/pubSubTopic.md index 25507b7b26..e3de2df4a8 100644 --- a/lsp/markdown/completion/pubSubTopic.md +++ b/lsp/markdown/completion/pubSubTopic.md @@ -1,7 +1,7 @@ Declare a PubSub topic. ```go -var invoicesTopic = ftl.Topic[Invoice]("invoices") +var Invoices = ftl.Topic[Invoice]("invoices") ``` See https://tbd54566975.github.io/ftl/docs/reference/pubsub/