diff --git a/example/rest-petstore/app/app.go b/example/rest-petstore/app/app.go index f4daf75..b94a923 100644 --- a/example/rest-petstore/app/app.go +++ b/example/rest-petstore/app/app.go @@ -11,7 +11,6 @@ import ( "github.com/z5labs/humus/example/internal/petstore" "github.com/z5labs/humus/example/rest-petstore/endpoint" - "github.com/z5labs/humus" "github.com/z5labs/humus/rest" ) @@ -19,7 +18,7 @@ type Config struct { rest.Config `config:",squash"` } -func Init(ctx context.Context, cfg Config) (humus.App, error) { +func Init(ctx context.Context, cfg Config) (*rest.App, error) { store := petstore.NewInMemory() app := rest.New( diff --git a/example/rest-petstore/main.go b/example/rest-petstore/main.go index 66cee75..2a033f3 100644 --- a/example/rest-petstore/main.go +++ b/example/rest-petstore/main.go @@ -10,13 +10,12 @@ import ( _ "embed" "github.com/z5labs/humus/example/rest-petstore/app" - - "github.com/z5labs/humus" + "github.com/z5labs/humus/rest" ) //go:embed config.yaml var configBytes []byte func main() { - humus.Run(bytes.NewReader(configBytes), app.Init) + rest.Run(bytes.NewReader(configBytes), app.Init) } diff --git a/humus.go b/humus.go index d6c9b58..915bc77 100644 --- a/humus.go +++ b/humus.go @@ -7,18 +7,13 @@ package humus import ( - "bytes" "context" _ "embed" "errors" - "io" "log/slog" "os" "time" - "github.com/z5labs/humus/internal" - "github.com/z5labs/humus/internal/global" - "github.com/z5labs/bedrock" "github.com/z5labs/bedrock/pkg/app" "github.com/z5labs/bedrock/pkg/appbuilder" @@ -43,11 +38,7 @@ import ( ) //go:embed default_config.yaml -var configBytes []byte - -func init() { - global.RegisterConfigSource(internal.ConfigSource(bytes.NewReader(configBytes))) -} +var DefaultConfig []byte type OTelConfig struct { ServiceName string `config:"service_name"` @@ -89,12 +80,9 @@ func Logger(name string) *slog.Logger { type App bedrock.App // Run -func Run[T any](r io.Reader, build func(context.Context, T) (App, error)) { - cfgSrcs := global.ConfigSources - cfgSrcs = append(cfgSrcs, internal.ConfigSource(r)) - +func Run[T any](build func(context.Context, T) (App, error), srcs ...config.Source) error { runner := runner{ - srcs: cfgSrcs, + srcs: srcs, detectResource: detectResource, newTraceExporter: func(ctx context.Context, oc OTelConfig) (sdktrace.SpanExporter, error) { return otlptracegrpc.New( @@ -123,21 +111,7 @@ func Run[T any](r io.Reader, build func(context.Context, T) (App, error)) { }, } - err := run(runner, build) - if err == nil { - return - } - - // there's a chance Run failed on config parsing/unmarshalling - // thus the logging config is most likely unusable and we should - // instead create our own logger here for logging this error - // there's a chance Run failed on config parsing/unmarshalling - // thus the logging config is most likely unusable and we should - // instead create our own logger here for logging this error - fallbackLogger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ - AddSource: true, - })) - fallbackLogger.Error("failed while running application", slog.String("error", err.Error())) + return run(runner, build) } type runner struct { diff --git a/humus_test.go b/humus_test.go index a75fe51..e18949b 100644 --- a/humus_test.go +++ b/humus_test.go @@ -10,14 +10,12 @@ import ( "context" "encoding/json" "errors" - "os" - "path/filepath" "strings" "testing" - "time" "github.com/stretchr/testify/assert" "github.com/z5labs/bedrock/pkg/config" + "github.com/z5labs/humus/internal" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/stdout/stdoutlog" "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" @@ -41,79 +39,25 @@ func (f appFunc) Run(ctx context.Context) error { } func TestRun(t *testing.T) { - t.Run("will log a record to stdout", func(t *testing.T) { + t.Run("will return an error", func(t *testing.T) { t.Run("if no otlp target is set", func(t *testing.T) { - filename := filepath.Join(t.TempDir(), "log.json") - f, err := os.Create(filename) - if !assert.Nil(t, err) { - return - } - - done := make(chan struct{}) - go func() { - defer close(done) - - stdout := os.Stdout - defer func() { - os.Stdout = stdout - }() - - os.Stdout = f - - app := appFunc(func(ctx context.Context) error { - log := Logger("app") - log.InfoContext(ctx, "hello") - return nil - }) + app := appFunc(func(ctx context.Context) error { + log := Logger("app") + log.InfoContext(ctx, "hello") + return nil + }) - Run(strings.NewReader(""), func(ctx context.Context, cfg Config) (App, error) { + err := Run( + func(ctx context.Context, cfg Config) (App, error) { return app, nil - }) - }() - - select { - case <-time.After(30 * time.Second): - t.Fail() - return - case <-done: - } - - err = f.Close() - if !assert.Nil(t, err) { - return - } - - f, err = os.Open(filename) - if !assert.Nil(t, err) { - return - } - defer f.Close() - - type log struct { - Body struct { - Value string `json:"Value"` - } `json:"Body"` - Scope struct { - Name string `json:"Name"` - } `json:"Scope"` - } - - var l log - dec := json.NewDecoder(f) - err = dec.Decode(&l) + }, + internal.ConfigSource(strings.NewReader("")), + ) if !assert.Nil(t, err) { return } - if !assert.Equal(t, "app", l.Scope.Name) { - return - } - if !assert.Equal(t, "hello", l.Body.Value) { - return - } }) - }) - t.Run("will return an error", func(t *testing.T) { t.Run("if it fails to read one of the config sources", func(t *testing.T) { build := func(ctx context.Context, cfg Config) (App, error) { return nil, nil diff --git a/internal/global/config.go b/internal/global/config.go deleted file mode 100644 index bffe9bc..0000000 --- a/internal/global/config.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) 2024 Z5Labs and Contributors -// -// This software is released under the MIT License. -// https://opensource.org/licenses/MIT - -package global - -import "github.com/z5labs/bedrock/pkg/config" - -var ConfigSources []config.Source - -func RegisterConfigSource(src config.Source) { - ConfigSources = append(ConfigSources, src) -} diff --git a/rest/rest.go b/rest/rest.go index ef5c1ab..60d9533 100644 --- a/rest/rest.go +++ b/rest/rest.go @@ -12,12 +12,14 @@ import ( _ "embed" "errors" "fmt" + "io" + "log/slog" "net" "net/http" + "os" "github.com/z5labs/humus" "github.com/z5labs/humus/internal" - "github.com/z5labs/humus/internal/global" "github.com/z5labs/bedrock" "github.com/z5labs/bedrock/pkg/app" @@ -27,11 +29,7 @@ import ( ) //go:embed default_config.yaml -var configBytes []byte - -func init() { - global.RegisterConfigSource(internal.ConfigSource(bytes.NewReader(configBytes))) -} +var DefaultConfig []byte // Config type Config struct { @@ -47,6 +45,32 @@ type Config struct { } `config:"openapi"` } +// Run +func Run[T any](r io.Reader, f func(context.Context, T) (*App, error)) { + err := humus.Run( + func(ctx context.Context, cfg T) (humus.App, error) { + return f(ctx, cfg) + }, + internal.ConfigSource(bytes.NewReader(humus.DefaultConfig)), + internal.ConfigSource(bytes.NewReader(DefaultConfig)), + internal.ConfigSource(r), + ) + if err == nil { + return + } + + // there's a chance Run failed on config parsing/unmarshalling + // thus the logging config is most likely unusable and we should + // instead create our own logger here for logging this error + // there's a chance Run failed on config parsing/unmarshalling + // thus the logging config is most likely unusable and we should + // instead create our own logger here for logging this error + fallbackLogger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ + AddSource: true, + })) + fallbackLogger.Error("failed while running application", slog.String("error", err.Error())) +} + // Option type Option func(*App)