From 18c2058ddfba53158506a1a0eada3a7460047f89 Mon Sep 17 00:00:00 2001 From: Denise Li Date: Wed, 5 Jun 2024 19:41:33 -0400 Subject: [PATCH] feat: add Mock method to ftl.Map (#1616) Fixes: https://github.com/TBD54566975/ftl/issues/1586 --- go-runtime/compile/testdata/validation/go.sum | 12 +- go-runtime/ftl/ftltest/fake.go | 60 +++++++- go-runtime/ftl/ftltest/ftltest.go | 47 +++++- go-runtime/ftl/integration_test.go | 17 +++ go-runtime/ftl/map.go | 19 ++- go-runtime/ftl/map_test.go | 13 +- go-runtime/ftl/testdata/go/mapper/ftl.toml | 2 + go-runtime/ftl/testdata/go/mapper/go.mod | 45 ++++++ go-runtime/ftl/testdata/go/mapper/go.sum | 136 ++++++++++++++++++ go-runtime/ftl/testdata/go/mapper/mapper.go | 33 +++++ .../ftl/testdata/go/mapper/mapper_test.go | 31 ++++ go-runtime/internal/api.go | 3 + go-runtime/internal/impl.go | 8 ++ 13 files changed, 410 insertions(+), 16 deletions(-) create mode 100644 go-runtime/ftl/integration_test.go create mode 100644 go-runtime/ftl/testdata/go/mapper/ftl.toml create mode 100644 go-runtime/ftl/testdata/go/mapper/go.mod create mode 100644 go-runtime/ftl/testdata/go/mapper/go.sum create mode 100644 go-runtime/ftl/testdata/go/mapper/mapper.go create mode 100644 go-runtime/ftl/testdata/go/mapper/mapper_test.go diff --git a/go-runtime/compile/testdata/validation/go.sum b/go-runtime/compile/testdata/validation/go.sum index 03d1ea2819..d4b84ece19 100644 --- a/go-runtime/compile/testdata/validation/go.sum +++ b/go-runtime/compile/testdata/validation/go.sum @@ -4,6 +4,8 @@ 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/concurrency v0.0.2 h1:Q3kGPtLbleMbH9lHX5OBFvJygfyFw29bXZKBg+IEVuo= @@ -62,6 +64,8 @@ github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9 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/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= @@ -122,14 +126,14 @@ 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.49.3 h1:j2MRCRdwJI2ls/sGbeSk0t2bypOG/uvPZUsGQFDulqg= -modernc.org/libc v1.49.3/go.mod h1:yMZuGkn7pXbKfoT/M35gFJOAEdSKdxL0q64sF7KqCDo= +modernc.org/libc v1.50.9 h1:hIWf1uz55lorXQhfoEoezdUHjxzuO6ceshET/yWjSjk= +modernc.org/libc v1.50.9/go.mod h1:15P6ublJ9FJR8YQCGy8DeQ2Uwur7iW9Hserr/T3OFZE= 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.29.10 h1:3u93dz83myFnMilBGCOLbr+HjklS6+5rJLx4q86RDAg= -modernc.org/sqlite v1.29.10/go.mod h1:ItX2a1OVGgNsFh6Dv60JQvGfJfTPHPVpV6DF59akYOA= +modernc.org/sqlite v1.30.0 h1:8YhPUs/HTnlEgErn/jSYQTwHN/ex8CjHHjg+K9iG7LM= +modernc.org/sqlite v1.30.0/go.mod h1:cgkTARJ9ugeXSNaLBPK3CqbOe7Ec7ZhWPoMFGldEYEw= 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= diff --git a/go-runtime/ftl/ftltest/fake.go b/go-runtime/ftl/ftltest/fake.go index ad896922e2..ad07c1a358 100644 --- a/go-runtime/ftl/ftltest/fake.go +++ b/go-runtime/ftl/ftltest/fake.go @@ -2,17 +2,28 @@ package ftltest import ( "context" + "fmt" + "reflect" + "strings" "github.com/TBD54566975/ftl/go-runtime/internal" ) type fakeFTL struct { - fsm *fakeFSMManager + fsm *fakeFSMManager + mockMaps map[uintptr]mapImpl + allowMapCalls bool } +// mapImpl is a function that takes an object and returns an object of a potentially different +// type but is not constrained by input/output type like ftl.Map. +type mapImpl func(context.Context) (any, error) + func newFakeFTL() *fakeFTL { return &fakeFTL{ - fsm: newFakeFSMManager(), + fsm: newFakeFSMManager(), + mockMaps: make(map[uintptr]mapImpl), + allowMapCalls: false, } } @@ -25,3 +36,48 @@ func (f *fakeFTL) FSMSend(ctx context.Context, fsm string, instance string, even func (f *fakeFTL) PublishEvent(ctx context.Context, topic string, event any) error { panic("not implemented") } + +// addMapMock saves a new mock of ftl.Map to the internal map in fakeFTL. +// +// mockMap provides the whole mock implemention, so it gets called in place of both `fn` +// and `getter` in ftl.Map. +func (f *fakeFTL) addMapMock(mapper any, mockMap func(context.Context) (any, error)) { + key := makeMapKey(mapper) + f.mockMaps[key] = mockMap +} + +func (f *fakeFTL) startAllowingMapCalls() { + f.allowMapCalls = true +} + +func (f *fakeFTL) CallMap(ctx context.Context, mapper any, mapImpl func(context.Context) (any, error)) any { + key := makeMapKey(mapper) + mockMap, ok := f.mockMaps[key] + if ok { + return actuallyCallMap(ctx, mockMap) + } + if f.allowMapCalls { + return actuallyCallMap(ctx, mapImpl) + } + panic("map calls not allowed in tests by default. ftltest.Context should be instantiated with either ftltest.WithMapsAllowed() or a mock for the specific map being called using ftltest.WhenMap(...)") +} + +func makeMapKey(mapper any) uintptr { + v := reflect.ValueOf(mapper) + if v.Kind() != reflect.Pointer { + panic("fakeFTL received object that was not a pointer, expected *MapHandle") + } + underlying := v.Elem().Type().Name() + if !strings.HasPrefix(underlying, "MapHandle[") { + panic(fmt.Sprintf("fakeFTL received *%s, expected *MapHandle", underlying)) + } + return v.Pointer() +} + +func actuallyCallMap(ctx context.Context, impl mapImpl) any { + out, err := impl(ctx) + if err != nil { + panic(err) + } + return out +} diff --git a/go-runtime/ftl/ftltest/ftltest.go b/go-runtime/ftl/ftltest/ftltest.go index e5fcd9d74f..5bae1e7c82 100644 --- a/go-runtime/ftl/ftltest/ftltest.go +++ b/go-runtime/ftl/ftltest/ftltest.go @@ -37,16 +37,17 @@ type Option func(context.Context, *OptionsState) error // Context suitable for use in testing FTL verbs with provided options func Context(options ...Option) context.Context { - ctx := log.ContextWithNewDefaultLogger(context.Background()) - ctx = internal.WithContext(ctx, newFakeFTL()) - name := reflection.Module() - state := &OptionsState{ configs: make(map[string][]byte), secrets: make(map[string][]byte), databases: make(map[string]modulecontext.Database), mockVerbs: make(map[schema.RefKey]modulecontext.Verb), } + + ctx := log.ContextWithNewDefaultLogger(context.Background()) + ctx = internal.WithContext(ctx, newFakeFTL()) + name := reflection.Module() + for _, option := range options { err := option(ctx, state) if err != nil { @@ -322,3 +323,41 @@ func WithCallsAllowedWithinModule() Option { return nil } } + +// WhenMap injects a fake implementation of a Mapping function +// +// To be used when setting up a context for a test: +// +// ctx := ftltest.Context( +// ftltest.WhenMap(Example.MapHandle, func(ctx context.Context) (U, error) { +// // ... +// }), +// // ... other options +// ) +func WhenMap[T, U any](mapper *ftl.MapHandle[T, U], fake func(context.Context) (any, error)) Option { + return func(ctx context.Context, state *OptionsState) error { + someFTL := internal.FromContext(ctx) + fakeFTL, ok := someFTL.(*fakeFTL) + if !ok { + return fmt.Errorf("could not retrieve fakeFTL for saving a mock Map in test") + } + fakeFTL.addMapMock(mapper, fake) + return nil + } +} + +// WithMapsAllowed allows all `ftl.Map` calls to pass through to their original +// implementation. +// +// Any overrides provided by calling WhenMap(...) will take precedence. +func WithMapsAllowed() Option { + return func(ctx context.Context, state *OptionsState) error { + someFTL := internal.FromContext(ctx) + fakeFTL, ok := someFTL.(*fakeFTL) + if !ok { + return fmt.Errorf("could not retrieve fakeFTL for saving a mock Map in test") + } + fakeFTL.startAllowingMapCalls() + return nil + } +} diff --git a/go-runtime/ftl/integration_test.go b/go-runtime/ftl/integration_test.go new file mode 100644 index 0000000000..155156a76a --- /dev/null +++ b/go-runtime/ftl/integration_test.go @@ -0,0 +1,17 @@ +//go:build integration + +package ftl + +import ( + "testing" + + in "github.com/TBD54566975/ftl/integration" +) + +func TestFTLMap(t *testing.T) { + in.Run(t, "", + in.CopyModule("mapper"), + in.Build("mapper"), + in.ExecModuleTest("mapper"), + ) +} diff --git a/go-runtime/ftl/map.go b/go-runtime/ftl/map.go index 18baf19ba3..4e296b676c 100644 --- a/go-runtime/ftl/map.go +++ b/go-runtime/ftl/map.go @@ -2,6 +2,9 @@ package ftl import ( "context" + "fmt" + + "github.com/TBD54566975/ftl/go-runtime/internal" ) type MapHandle[T, U any] struct { @@ -10,14 +13,20 @@ type MapHandle[T, U any] struct { } func (mh *MapHandle[T, U]) Get(ctx context.Context) U { - t, err := mh.fn(ctx, mh.getter.Get(ctx)) - if err != nil { - panic(err) + out := internal.FromContext(ctx).CallMap(ctx, mh, func(ctx context.Context) (any, error) { + return mh.fn(ctx, mh.getter.Get(ctx)) + }) + u, ok := out.(U) + if !ok { + panic(fmt.Sprintf("output object %v is not compatible with expected type %T", out, *new(U))) } - return t + return u } // Map an FTL resource type to a new type. func Map[T, U any](getter Handle[T], fn func(context.Context, T) (U, error)) MapHandle[T, U] { - return MapHandle[T, U]{fn: fn, getter: getter} + return MapHandle[T, U]{ + fn: fn, + getter: getter, + } } diff --git a/go-runtime/ftl/map_test.go b/go-runtime/ftl/map_test.go index 436a661ec8..14c3aba2c8 100644 --- a/go-runtime/ftl/map_test.go +++ b/go-runtime/ftl/map_test.go @@ -3,8 +3,10 @@ package ftl import ( "context" "fmt" + "strconv" "testing" + "github.com/TBD54566975/ftl/go-runtime/internal" "github.com/alecthomas/assert/v2" ) @@ -13,7 +15,7 @@ type intHandle int func (s intHandle) Get(ctx context.Context) int { return int(s) } func TestMapPanic(t *testing.T) { - ctx := context.Background() + ctx := internal.WithContext(context.Background(), internal.New()) n := intHandle(1) once := Map(n, func(ctx context.Context, n int) (string, error) { return "", fmt.Errorf("test error %d", n) @@ -22,3 +24,12 @@ func TestMapPanic(t *testing.T) { once.Get(ctx) }) } + +func TestMapGet(t *testing.T) { + ctx := internal.WithContext(context.Background(), internal.New()) + n := intHandle(1) + once := Map(n, func(ctx context.Context, n int) (string, error) { + return strconv.Itoa(n), nil + }) + assert.Equal(t, once.Get(ctx), "1") +} diff --git a/go-runtime/ftl/testdata/go/mapper/ftl.toml b/go-runtime/ftl/testdata/go/mapper/ftl.toml new file mode 100644 index 0000000000..8276a86fa2 --- /dev/null +++ b/go-runtime/ftl/testdata/go/mapper/ftl.toml @@ -0,0 +1,2 @@ +module = "mapper" +language = "go" diff --git a/go-runtime/ftl/testdata/go/mapper/go.mod b/go-runtime/ftl/testdata/go/mapper/go.mod new file mode 100644 index 0000000000..dcebe302bb --- /dev/null +++ b/go-runtime/ftl/testdata/go/mapper/go.mod @@ -0,0 +1,45 @@ +module ftl/mapper + +go 1.22.2 + +toolchain go1.22.3 + +require github.com/TBD54566975/ftl v1.1.0 + +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/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/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/swaggest/jsonschema-go v0.3.70 // indirect + github.com/swaggest/refl v1.3.0 // indirect + github.com/zalando/go-keyring v0.2.4 // indirect + go.opentelemetry.io/otel v1.27.0 // indirect + go.opentelemetry.io/otel/metric v1.27.0 // indirect + go.opentelemetry.io/otel/trace v1.27.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect +) + +replace github.com/TBD54566975/ftl => ../../../../.. diff --git a/go-runtime/ftl/testdata/go/mapper/go.sum b/go-runtime/ftl/testdata/go/mapper/go.sum new file mode 100644 index 0000000000..d823b5af4a --- /dev/null +++ b/go-runtime/ftl/testdata/go/mapper/go.sum @@ -0,0 +1,136 @@ +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/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/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/bool64/dev v0.2.34 h1:P9n315P8LdpxusnYQ0X7MP1CZXwBK5ae5RZrd+GdSZE= +github.com/bool64/dev v0.2.34/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +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.70 h1:8Vx5nm5t/6DBFw2+WC0/Vp1ZVe9/4mpuA0tuAe0wwCI= +github.com/swaggest/jsonschema-go v0.3.70/go.mod h1:7N43/CwdaWgPUDfYV70K7Qm79tRqe/al7gLSt9YeGIE= +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.4 h1:wi2xxTqdiwMKbM6TWwi+uJCG/Tum2UV0jqaQhCa9/68= +github.com/zalando/go-keyring v0.2.4/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= +go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= +go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= +go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= +go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= +go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI= +go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A= +go.opentelemetry.io/otel/sdk/metric v1.27.0 h1:5uGNOlpXi+Hbo/DRoI31BSb1v+OGcpv2NemcCrOL8gI= +go.opentelemetry.io/otel/sdk/metric v1.27.0/go.mod h1:we7jJVrYN2kh3mVBlswtPU22K0SA+769l93J6bsyvqw= +go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= +go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +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.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +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.50.9 h1:hIWf1uz55lorXQhfoEoezdUHjxzuO6ceshET/yWjSjk= +modernc.org/libc v1.50.9/go.mod h1:15P6ublJ9FJR8YQCGy8DeQ2Uwur7iW9Hserr/T3OFZE= +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.0 h1:8YhPUs/HTnlEgErn/jSYQTwHN/ex8CjHHjg+K9iG7LM= +modernc.org/sqlite v1.30.0/go.mod h1:cgkTARJ9ugeXSNaLBPK3CqbOe7Ec7ZhWPoMFGldEYEw= +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/ftl/testdata/go/mapper/mapper.go b/go-runtime/ftl/testdata/go/mapper/mapper.go new file mode 100644 index 0000000000..55c6cd6add --- /dev/null +++ b/go-runtime/ftl/testdata/go/mapper/mapper.go @@ -0,0 +1,33 @@ +package mapper + +import ( + "context" + "fmt" + + "github.com/TBD54566975/ftl/go-runtime/ftl" // Import the FTL SDK. +) + +type Gettable struct{ s string } + +func (g Gettable) Get(ctx context.Context) string { + return g.s +} + +var gettable = Gettable{"my_string"} + +var m = ftl.Map(gettable, func(ctx context.Context, c string) (int, error) { + return len(c), nil +}) + +type EchoRequest struct { + Name ftl.Option[string] `json:"name"` +} + +type EchoResponse struct { + Message string `json:"message"` +} + +//ftl:verb +func Echo(ctx context.Context, req EchoRequest) (EchoResponse, error) { + return EchoResponse{Message: fmt.Sprintf("Hello, %s!", req.Name.Default("anonymous"))}, nil +} diff --git a/go-runtime/ftl/testdata/go/mapper/mapper_test.go b/go-runtime/ftl/testdata/go/mapper/mapper_test.go new file mode 100644 index 0000000000..09d4c04409 --- /dev/null +++ b/go-runtime/ftl/testdata/go/mapper/mapper_test.go @@ -0,0 +1,31 @@ +package mapper + +import ( + "context" + "testing" + + "github.com/TBD54566975/ftl/go-runtime/ftl/ftltest" + "github.com/alecthomas/assert/v2" +) + +func TestGet(t *testing.T) { + ctx := ftltest.Context(ftltest.WithMapsAllowed()) + got := m.Get(ctx) + assert.Equal(t, got, 9) +} + +func TestPanicsWithoutExplicitlyAllowingMaps(t *testing.T) { + ctx := ftltest.Context() + assert.Panics(t, func() { + m.Get(ctx) + }) +} + +func TestMockGet(t *testing.T) { + mockOut := 123 + ctx := ftltest.Context(ftltest.WhenMap(&m, func(ctx context.Context) (any, error) { + return mockOut, nil + })) + got := m.Get(ctx) + assert.Equal(t, got, mockOut) +} diff --git a/go-runtime/internal/api.go b/go-runtime/internal/api.go index 5707dee11e..980c926d25 100644 --- a/go-runtime/internal/api.go +++ b/go-runtime/internal/api.go @@ -17,6 +17,9 @@ type FTL interface { // PublishEvent sends an event to a pubsub topic. PublishEvent(ctx context.Context, topic string, event any) error + + // CallMap calls Get on an instance of an ftl.Map. + CallMap(ctx context.Context, mapper any, mapImpl func(context.Context) (any, error)) any } type ftlContextKey struct{} diff --git a/go-runtime/internal/impl.go b/go-runtime/internal/impl.go index 7c76fbca74..530468a7e5 100644 --- a/go-runtime/internal/impl.go +++ b/go-runtime/internal/impl.go @@ -57,3 +57,11 @@ func (r *RealFTL) PublishEvent(ctx context.Context, topic string, event any) err } return nil } + +func (r *RealFTL) CallMap(ctx context.Context, mapper any, mapImpl func(context.Context) (any, error)) any { + t, err := mapImpl(ctx) + if err != nil { + panic(err) + } + return t +}