Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: pull config from FTL runtime abstraction #1708

Merged
merged 1 commit into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions go-runtime/ftl/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"strings"

"github.com/TBD54566975/ftl/go-runtime/ftl/reflection"
"github.com/TBD54566975/ftl/internal/modulecontext"
"github.com/TBD54566975/ftl/go-runtime/internal"
)

// ConfigType is a type that can be used as a configuration value.
Expand All @@ -33,7 +33,7 @@ func (c ConfigValue[T]) GoString() string {

// Get returns the value of the configuration key from FTL.
func (c ConfigValue[T]) Get(ctx context.Context) (out T) {
err := modulecontext.FromContext(ctx).GetConfig(c.Name, &out)
err := internal.FromContext(ctx).GetConfig(ctx, c.Name, &out)
if err != nil {
panic(fmt.Errorf("failed to get %s: %w", c, err))
}
Expand Down
4 changes: 2 additions & 2 deletions go-runtime/ftl/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/alecthomas/assert/v2"

"github.com/TBD54566975/ftl/go-runtime/internal"
"github.com/TBD54566975/ftl/internal/log"
"github.com/TBD54566975/ftl/internal/modulecontext"
)
Expand All @@ -22,8 +23,7 @@ func TestConfig(t *testing.T) {
data, err := json.Marshal(C{"one", "two"})
assert.NoError(t, err)

moduleCtx := modulecontext.NewBuilder("test").AddConfigs(map[string][]byte{"test": data}).Build()
ctx = moduleCtx.ApplyToContext(ctx)
ctx = internal.WithContext(ctx, internal.New(modulecontext.NewBuilder("test").AddConfigs(map[string][]byte{"test": data}).Build()))

config := Config[C]("test")
assert.Equal(t, C{"one", "two"}, config.Get(ctx))
Expand Down
23 changes: 22 additions & 1 deletion go-runtime/ftl/ftltest/fake.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@ package ftltest

import (
"context"
"encoding/json"
"fmt"
"reflect"
"strings"

"github.com/TBD54566975/ftl/common/configuration"
"github.com/TBD54566975/ftl/go-runtime/internal"
)

type fakeFTL struct {
fsm *fakeFSMManager
mockMaps map[uintptr]mapImpl
allowMapCalls bool
configValues map[string][]byte
}

// mapImpl is a function that takes an object and returns an object of a potentially different
Expand All @@ -22,13 +25,31 @@ type mapImpl func(context.Context) (any, error)
func newFakeFTL() *fakeFTL {
return &fakeFTL{
fsm: newFakeFSMManager(),
mockMaps: make(map[uintptr]mapImpl),
mockMaps: map[uintptr]mapImpl{},
allowMapCalls: false,
configValues: map[string][]byte{},
}
}

var _ internal.FTL = &fakeFTL{}

func (f *fakeFTL) setConfig(name string, value any) error {
data, err := json.Marshal(value)
if err != nil {
return err
}
f.configValues[name] = data
return nil
}

func (f *fakeFTL) GetConfig(ctx context.Context, name string, dest any) error {
data, ok := f.configValues[name]
if !ok {
return fmt.Errorf("config value %q not found: %w", name, configuration.ErrNotFound)
}
return json.Unmarshal(data, dest)
}

func (f *fakeFTL) FSMSend(ctx context.Context, fsm string, instance string, event any) error {
return f.fsm.SendEvent(ctx, fsm, instance, event)
}
Expand Down
31 changes: 12 additions & 19 deletions go-runtime/ftl/ftltest/ftltest.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import (
)

type OptionsState struct {
configs map[string][]byte
secrets map[string][]byte
databases map[string]modulecontext.Database
mockVerbs map[schema.RefKey]modulecontext.Verb
Expand All @@ -38,7 +37,6 @@ type Option func(context.Context, *OptionsState) error
// Context suitable for use in testing FTL verbs with provided options
func Context(options ...Option) context.Context {
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),
Expand All @@ -55,7 +53,7 @@ func Context(options ...Option) context.Context {
}
}

builder := modulecontext.NewBuilder(name).AddConfigs(state.configs).AddSecrets(state.secrets).AddDatabases(state.databases)
builder := modulecontext.NewBuilder(name).AddSecrets(state.secrets).AddDatabases(state.databases)
builder = builder.UpdateForTesting(state.mockVerbs, state.allowDirectVerbBehavior, newFakeLeaseClient())
return builder.Build().ApplyToContext(ctx)
}
Expand Down Expand Up @@ -108,8 +106,12 @@ func WithProjectFiles(paths ...string) Option {
if err != nil {
return fmt.Errorf("could not read configs: %w", err)
}

fftl := internal.FromContext(ctx).(*fakeFTL) //nolint:forcetypeassert
for name, data := range configs {
state.configs[name] = data
if err := fftl.setConfig(name, json.RawMessage(data)); err != nil {
return err
}
}

sm, err := cf.NewDefaultSecretsManagerFromConfig(ctx, paths, "")
Expand Down Expand Up @@ -140,11 +142,10 @@ func WithConfig[T ftl.ConfigType](config ftl.ConfigValue[T], value T) Option {
if config.Module != reflection.Module() {
return fmt.Errorf("config %v does not match current module %s", config.Module, reflection.Module())
}
data, err := json.Marshal(value)
if err != nil {
fftl := internal.FromContext(ctx).(*fakeFTL) //nolint:forcetypeassert
if err := fftl.setConfig(config.Name, value); err != nil {
return err
}
state.configs[config.Name] = data
return nil
}
}
Expand Down Expand Up @@ -336,12 +337,8 @@ func WithCallsAllowedWithinModule() Option {
// )
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)
fftl := internal.FromContext(ctx).(*fakeFTL) //nolint:forcetypeassert
fftl.addMapMock(mapper, fake)
return nil
}
}
Expand All @@ -352,12 +349,8 @@ func WhenMap[T, U any](mapper *ftl.MapHandle[T, U], fake func(context.Context) (
// 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()
fftl := internal.FromContext(ctx).(*fakeFTL) //nolint:forcetypeassert
fftl.startAllowingMapCalls()
return nil
}
}
8 changes: 5 additions & 3 deletions go-runtime/ftl/map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@ import (
"strconv"
"testing"

"github.com/TBD54566975/ftl/go-runtime/internal"
"github.com/alecthomas/assert/v2"

"github.com/TBD54566975/ftl/go-runtime/internal"
"github.com/TBD54566975/ftl/internal/modulecontext"
)

type intHandle int

func (s intHandle) Get(ctx context.Context) int { return int(s) }

func TestMapPanic(t *testing.T) {
ctx := internal.WithContext(context.Background(), internal.New())
ctx := internal.WithContext(context.Background(), internal.New(modulecontext.Empty("test")))
n := intHandle(1)
once := Map(n, func(ctx context.Context, n int) (string, error) {
return "", fmt.Errorf("test error %d", n)
Expand All @@ -26,7 +28,7 @@ func TestMapPanic(t *testing.T) {
}

func TestMapGet(t *testing.T) {
ctx := internal.WithContext(context.Background(), internal.New())
ctx := internal.WithContext(context.Background(), internal.New(modulecontext.Empty("test")))
n := intHandle(1)
once := Map(n, func(ctx context.Context, n int) (string, error) {
return strconv.Itoa(n), nil
Expand Down
3 changes: 3 additions & 0 deletions go-runtime/internal/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ type FTL interface {

// CallMap calls Get on an instance of an ftl.Map.
CallMap(ctx context.Context, mapper any, mapImpl func(context.Context) (any, error)) any

// GetConfig unmarshals a configuration value into dest.
GetConfig(ctx context.Context, name string, dest any) error
}

type ftlContextKey struct{}
Expand Down
11 changes: 9 additions & 2 deletions go-runtime/internal/impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,24 @@ import (
"github.com/TBD54566975/ftl/backend/schema"
"github.com/TBD54566975/ftl/go-runtime/encoding"
"github.com/TBD54566975/ftl/go-runtime/ftl/reflection"
"github.com/TBD54566975/ftl/internal/modulecontext"
"github.com/TBD54566975/ftl/internal/rpc"
)

// RealFTL is the real implementation of the [internal.FTL] interface using the Controller.
type RealFTL struct{}
type RealFTL struct {
mctx modulecontext.ModuleContext
}

// New creates a new [RealFTL]
func New() *RealFTL { return &RealFTL{} }
func New(mctx modulecontext.ModuleContext) *RealFTL { return &RealFTL{mctx: mctx} }

var _ FTL = &RealFTL{}

func (r *RealFTL) GetConfig(ctx context.Context, name string, dest any) error {
return r.mctx.GetConfig(name, dest)
}

func (r *RealFTL) FSMSend(ctx context.Context, fsm, instance string, event any) error {
client := rpc.ClientFromContext[ftlv1connect.VerbServiceClient](ctx)
body, err := encoding.Marshal(event)
Expand Down
2 changes: 1 addition & 1 deletion go-runtime/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ type UserVerbConfig struct {
// This function is intended to be used by the code generator.
func NewUserVerbServer(moduleName string, handlers ...Handler) plugin.Constructor[ftlv1connect.VerbServiceHandler, UserVerbConfig] {
return func(ctx context.Context, uc UserVerbConfig) (context.Context, ftlv1connect.VerbServiceHandler, error) {
ctx = internal.WithContext(ctx, internal.New())
verbServiceClient := rpc.Dial(ftlv1connect.NewVerbServiceClient, uc.FTLEndpoint.String(), log.Error)
ctx = rpc.ContextWithClient(ctx, verbServiceClient)

Expand All @@ -48,6 +47,7 @@ func NewUserVerbServer(moduleName string, handlers ...Handler) plugin.Constructo
return nil, nil, err
}
ctx = moduleCtx.ApplyToContext(ctx)
ctx = internal.WithContext(ctx, internal.New(moduleCtx))

err = observability.Init(ctx, moduleName, "HEAD", uc.ObservabilityConfig)
if err != nil {
Expand Down
4 changes: 4 additions & 0 deletions internal/modulecontext/module_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ type Builder ModuleContext

type contextKeyModuleContext struct{}

func Empty(module string) ModuleContext {
return NewBuilder(module).Build()
}

// NewBuilder creates a new blank Builder for the given module.
func NewBuilder(module string) *Builder {
return &Builder{
Expand Down
Loading