diff --git a/pkg/core/cli.go b/pkg/core/cli.go index b02a465..a13b263 100644 --- a/pkg/core/cli.go +++ b/pkg/core/cli.go @@ -5,7 +5,7 @@ // // Run the CLI: // -// c := core.New(core.Options{{K: "name", V: "myapp"}}) +// c := core.New(core.Options{{Key: "name", Value: "myapp"}}) // c.Command("deploy", handler) // c.Cli().Run() // @@ -84,12 +84,12 @@ func (cl *Cli) Run(args ...string) Result { key, val, valid := ParseFlag(arg) if valid { if val != "" { - opts = append(opts, Option{K: key, V: val}) + opts = append(opts, Option{Key: key, Value: val}) } else { - opts = append(opts, Option{K: key, V: true}) + opts = append(opts, Option{Key: key, Value: true}) } } else if !IsFlag(arg) { - opts = append(opts, Option{K: "_arg", V: arg}) + opts = append(opts, Option{Key: "_arg", Value: arg}) } } @@ -121,7 +121,7 @@ func (cl *Cli) PrintHelp() { if cmd.Hidden { continue } - desc := cl.core.I18n().Translate(cmd.I18nKey()) + desc := cl.core.I18n().Translate(cmd.I18nKey()).Value.(string) if desc == cmd.I18nKey() { cl.Print(" %s", path) } else { diff --git a/pkg/core/command.go b/pkg/core/command.go index 5610dd4..4b81ba8 100644 --- a/pkg/core/command.go +++ b/pkg/core/command.go @@ -43,13 +43,13 @@ type CommandLifecycle interface { // Command is the DTO for an executable operation. type Command struct { Name string - Description string // i18n key — derived from path if empty - Path string // "deploy/to/homelab" - Action CommandAction // business logic - Lifecycle CommandLifecycle // optional — provided by go-process - Flags Options // declared flags + Description string // i18n key — derived from path if empty + Path string // "deploy/to/homelab" + Action CommandAction // business logic + Lifecycle CommandLifecycle // optional — provided by go-process + Flags Options // declared flags Hidden bool - commands map[string]*Command // child commands (internal) + commands map[string]*Command // child commands (internal) mu sync.RWMutex } @@ -69,7 +69,7 @@ func (cmd *Command) I18nKey() string { // Run executes the command's action with the given options. // -// result := cmd.Run(core.Options{{K: "target", V: "homelab"}}) +// result := cmd.Run(core.Options{{Key: "target", Value: "homelab"}}) func (cmd *Command) Run(opts Options) Result { if cmd.Action == nil { return Result{} diff --git a/pkg/core/config.go b/pkg/core/config.go index 7b88dd8..ffb19b4 100644 --- a/pkg/core/config.go +++ b/pkg/core/config.go @@ -57,14 +57,17 @@ func (e *Config) Set(key string, val any) { } // Get retrieves a configuration value by key. -func (e *Config) Get(key string) (any, bool) { +func (e *Config) Get(key string) Result { e.mu.RLock() defer e.mu.RUnlock() if e.ConfigOptions == nil || e.Settings == nil { - return nil, false + return Result{} } val, ok := e.Settings[key] - return val, ok + if !ok { + return Result{} + } + return Result{val, true} } func (e *Config) String(key string) string { return ConfigGet[string](e, key) } @@ -73,12 +76,12 @@ func (e *Config) Bool(key string) bool { return ConfigGet[bool](e, key) } // ConfigGet retrieves a typed configuration value. func ConfigGet[T any](e *Config, key string) T { - val, ok := e.Get(key) - if !ok { + r := e.Get(key) + if !r.OK { var zero T return zero } - typed, _ := val.(T) + typed, _ := r.Value.(T) return typed } diff --git a/pkg/core/contract.go b/pkg/core/contract.go index 2e99060..ac4d0b8 100644 --- a/pkg/core/contract.go +++ b/pkg/core/contract.go @@ -20,8 +20,8 @@ type Task any // TaskWithID is an optional interface for tasks that need to know their assigned ID. type TaskWithID interface { Task - SetTaskID(id string) - GetTaskID() string + TaskWithIdentifier(id string) + GetTaskIdentifier() string } // QueryHandler handles Query requests. Returns Result{Value, OK}. @@ -46,22 +46,22 @@ type ActionServiceStartup struct{} type ActionServiceShutdown struct{} type ActionTaskStarted struct { - TaskID string - Task Task + TaskIdentifier string + Task Task } type ActionTaskProgress struct { - TaskID string - Task Task - Progress float64 - Message string + TaskIdentifier string + Task Task + Progress float64 + Message string } type ActionTaskCompleted struct { - TaskID string - Task Task - Result any - Error error + TaskIdentifier string + Task Task + Result any + Error error } // --- Constructor --- @@ -69,20 +69,20 @@ type ActionTaskCompleted struct { // New creates a Core instance. // // c := core.New(core.Options{ -// {K: "name", V: "myapp"}, +// {Key: "name", Value: "myapp"}, // }) func New(opts ...Options) *Core { c := &Core{ - app: &App{}, - data: &Data{}, - drive: &Drive{}, - fs: &Fs{root: "/"}, - config: &Config{ConfigOptions: &ConfigOptions{}}, - error: &ErrorPanic{}, - log: &ErrorLog{log: defaultLog}, - lock: &Lock{}, - ipc: &Ipc{}, - i18n: &I18n{}, + app: &App{}, + data: &Data{}, + drive: &Drive{}, + fs: &Fs{root: "/"}, + config: &Config{ConfigOptions: &ConfigOptions{}}, + error: &ErrorPanic{}, + log: &ErrorLog{log: defaultLog}, + lock: &Lock{}, + ipc: &Ipc{}, + i18n: &I18n{}, } if len(opts) > 0 { diff --git a/pkg/core/data.go b/pkg/core/data.go index d46c21e..1bf872c 100644 --- a/pkg/core/data.go +++ b/pkg/core/data.go @@ -7,9 +7,9 @@ // Mount a package's assets: // // c.Data().New(core.Options{ -// {K: "name", V: "brain"}, -// {K: "source", V: brainFS}, -// {K: "path", V: "prompts"}, +// {Key: "name", Value: "brain"}, +// {Key: "source", Value: brainFS}, +// {Key: "path", Value: "prompts"}, // }) // // Read from any mounted path: @@ -37,9 +37,9 @@ type Data struct { // New registers an embedded filesystem under a named prefix. // // c.Data().New(core.Options{ -// {K: "name", V: "brain"}, -// {K: "source", V: brainFS}, -// {K: "path", V: "prompts"}, +// {Key: "name", Value: "brain"}, +// {Key: "source", Value: brainFS}, +// {Key: "path", Value: "prompts"}, // }) func (d *Data) New(opts Options) Result { name := opts.String("name") @@ -47,14 +47,14 @@ func (d *Data) New(opts Options) Result { return Result{} } - source, ok := opts.Get("source") - if !ok { - return Result{} + r := opts.Get("source") + if !r.OK { + return r } - fsys, ok := source.(fs.FS) + fsys, ok := r.Value.(fs.FS) if !ok { - return Result{} + return Result{E("data.New", "source is not fs.FS", nil), false} } path := opts.String("path") @@ -69,12 +69,12 @@ func (d *Data) New(opts Options) Result { d.mounts = make(map[string]*Embed) } - r := Mount(fsys, path) - if !r.OK { - return r + mr := Mount(fsys, path) + if !mr.OK { + return mr } - emb := r.Value.(*Embed) + emb := mr.Value.(*Embed) d.mounts[name] = emb return Result{emb, true} } @@ -140,11 +140,11 @@ func (d *Data) List(path string) Result { if emb == nil { return Result{} } - entries, err := emb.ReadDir(rel) - if err != nil { - return Result{err, false} + r := emb.ReadDir(rel) + if !r.OK { + return r } - return Result{entries, true} + return Result{r.Value, true} } // ListNames returns filenames (without extensions) at a path. diff --git a/pkg/core/drive.go b/pkg/core/drive.go index 056b5b3..b851aeb 100644 --- a/pkg/core/drive.go +++ b/pkg/core/drive.go @@ -7,16 +7,16 @@ // Register a transport: // // c.Drive().New(core.Options{ -// {K: "name", V: "api"}, -// {K: "transport", V: "https://api.lthn.ai"}, +// {Key: "name", Value: "api"}, +// {Key: "transport", Value: "https://api.lthn.ai"}, // }) // c.Drive().New(core.Options{ -// {K: "name", V: "ssh"}, -// {K: "transport", V: "ssh://claude@10.69.69.165"}, +// {Key: "name", Value: "ssh"}, +// {Key: "transport", Value: "ssh://claude@10.69.69.165"}, // }) // c.Drive().New(core.Options{ -// {K: "name", V: "mcp"}, -// {K: "transport", V: "mcp://mcp.lthn.sh"}, +// {Key: "name", Value: "mcp"}, +// {Key: "transport", Value: "mcp://mcp.lthn.sh"}, // }) // // Retrieve a handle: @@ -44,8 +44,8 @@ type Drive struct { // New registers a transport handle. // // c.Drive().New(core.Options{ -// {K: "name", V: "api"}, -// {K: "transport", V: "https://api.lthn.ai"}, +// {Key: "name", Value: "api"}, +// {Key: "transport", Value: "https://api.lthn.ai"}, // }) func (d *Drive) New(opts Options) Result { name := opts.String("name") diff --git a/pkg/core/embed.go b/pkg/core/embed.go index 72d419b..f89c8da 100644 --- a/pkg/core/embed.go +++ b/pkg/core/embed.go @@ -109,10 +109,10 @@ type AssetRef struct { // ScannedPackage holds all asset references from a set of source files. type ScannedPackage struct { - PackageName string - BaseDir string - Groups []string - Assets []AssetRef + PackageName string + BaseDirectory string + Groups []string + Assets []AssetRef } // ScanAssets parses Go source files and finds asset references. @@ -131,7 +131,7 @@ func ScanAssets(filenames []string) Result { baseDir := filepath.Dir(filename) pkg, ok := packageMap[baseDir] if !ok { - pkg = &ScannedPackage{BaseDir: baseDir} + pkg = &ScannedPackage{BaseDirectory: baseDir} packageMap[baseDir] = pkg } pkg.PackageName = node.Name.Name @@ -242,7 +242,7 @@ func GeneratePack(pkg ScannedPackage) Result { return Result{err, false} } localPath := TrimPrefix(file, groupPath+"/") - relGroup, err := filepath.Rel(pkg.BaseDir, groupPath) + relGroup, err := filepath.Rel(pkg.BaseDirectory, groupPath) if err != nil { return Result{err, false} } @@ -352,8 +352,8 @@ func Mount(fsys fs.FS, basedir string) Result { s.embedFS = &efs } - if _, err := s.ReadDir("."); err != nil { - return Result{err, false} + if r := s.ReadDir("."); !r.OK { + return r } return Result{s, true} } @@ -382,8 +382,8 @@ func (s *Embed) Open(name string) Result { } // ReadDir reads the named directory. -func (s *Embed) ReadDir(name string) ([]fs.DirEntry, error) { - return fs.ReadDir(s.fsys, s.path(name)) +func (s *Embed) ReadDir(name string) Result { + return Result{}.Result(fs.ReadDir(s.fsys, s.path(name))) } // ReadFile reads the named file. diff --git a/pkg/core/error.go b/pkg/core/error.go index 4af745a..049b8bb 100644 --- a/pkg/core/error.go +++ b/pkg/core/error.go @@ -31,28 +31,28 @@ var _ ErrorSink = (*Log)(nil) // Err represents a structured error with operational context. // It implements the error interface and supports unwrapping. type Err struct { - Op string // Operation being performed (e.g., "user.Save") - Msg string // Human-readable message - Err error // Underlying error (optional) - Code string // Error code (optional, e.g., "VALIDATION_FAILED") + Operation string // Operation being performed (e.g., "user.Save") + Message string // Human-readable message + Err error // Underlying error (optional) + Code string // Error code (optional, e.g., "VALIDATION_FAILED") } // Error implements the error interface. func (e *Err) Error() string { var prefix string - if e.Op != "" { - prefix = e.Op + ": " + if e.Operation != "" { + prefix = e.Operation + ": " } if e.Err != nil { if e.Code != "" { - return Concat(prefix, e.Msg, " [", e.Code, "]: ", e.Err.Error()) + return Concat(prefix, e.Message, " [", e.Code, "]: ", e.Err.Error()) } - return Concat(prefix, e.Msg, ": ", e.Err.Error()) + return Concat(prefix, e.Message, ": ", e.Err.Error()) } if e.Code != "" { - return Concat(prefix, e.Msg, " [", e.Code, "]") + return Concat(prefix, e.Message, " [", e.Code, "]") } - return Concat(prefix, e.Msg) + return Concat(prefix, e.Message) } // Unwrap returns the underlying error for use with errors.Is and errors.As. @@ -70,7 +70,7 @@ func (e *Err) Unwrap() error { // return log.E("user.Save", "failed to save user", err) // return log.E("api.Call", "rate limited", nil) // No underlying cause func E(op, msg string, err error) error { - return &Err{Op: op, Msg: msg, Err: err} + return &Err{Operation: op, Message: msg, Err: err} } // Wrap wraps an error with operation context. @@ -87,9 +87,9 @@ func Wrap(err error, op, msg string) error { // Preserve Code from wrapped *Err var logErr *Err if As(err, &logErr) && logErr.Code != "" { - return &Err{Op: op, Msg: msg, Err: err, Code: logErr.Code} + return &Err{Operation: op, Message: msg, Err: err, Code: logErr.Code} } - return &Err{Op: op, Msg: msg, Err: err} + return &Err{Operation: op, Message: msg, Err: err} } // WrapCode wraps an error with operation context and error code. @@ -103,7 +103,7 @@ func WrapCode(err error, code, op, msg string) error { if err == nil && code == "" { return nil } - return &Err{Op: op, Msg: msg, Err: err, Code: code} + return &Err{Operation: op, Message: msg, Err: err, Code: code} } // NewCode creates an error with just code and message (no underlying error). @@ -113,7 +113,7 @@ func WrapCode(err error, code, op, msg string) error { // // var ErrNotFound = log.NewCode("NOT_FOUND", "resource not found") func NewCode(code, msg string) error { - return &Err{Msg: msg, Code: code} + return &Err{Message: msg, Code: code} } // --- Standard Library Wrappers --- @@ -150,7 +150,7 @@ func ErrorJoin(errs ...error) error { func Operation(err error) string { var e *Err if As(err, &e) { - return e.Op + return e.Operation } return "" } @@ -173,7 +173,7 @@ func ErrorMessage(err error) string { } var e *Err if As(err, &e) { - return e.Msg + return e.Message } return err.Error() } @@ -199,8 +199,8 @@ func AllOperations(err error) iter.Seq[string] { return func(yield func(string) bool) { for err != nil { if e, ok := err.(*Err); ok { - if e.Op != "" { - if !yield(e.Op) { + if e.Operation != "" { + if !yield(e.Operation) { return } } @@ -288,9 +288,9 @@ type CrashReport struct { // CrashSystem holds system information at crash time. type CrashSystem struct { - OS string `json:"os"` - Arch string `json:"arch"` - Version string `json:"go_version"` + OperatingSystem string `json:"operatingsystem"` + Architecture string `json:"architecture"` + Version string `json:"go_version"` } // ErrorPanic manages panic recovery and crash reporting. @@ -321,9 +321,9 @@ func (h *ErrorPanic) Recover() { Error: err.Error(), Stack: string(debug.Stack()), System: CrashSystem{ - OS: runtime.GOOS, - Arch: runtime.GOARCH, - Version: runtime.Version(), + OperatingSystem: runtime.GOOS, + Architecture: runtime.GOARCH, + Version: runtime.Version(), }, Meta: maps.Clone(h.meta), } diff --git a/pkg/core/i18n.go b/pkg/core/i18n.go index 590f684..415c2a3 100644 --- a/pkg/core/i18n.go +++ b/pkg/core/i18n.go @@ -14,7 +14,7 @@ import ( // Implemented by go-i18n's Srv. type Translator interface { // Translate translates a message by its ID with optional arguments. - Translate(messageID string, args ...any) string + Translate(messageID string, args ...any) Result // SetLanguage sets the active language (BCP47 tag, e.g., "en-GB", "de"). SetLanguage(lang string) error // Language returns the current language code. @@ -81,14 +81,14 @@ func (i *I18n) Translator() Translator { } // Translate translates a message. Returns the key as-is if no translator is registered. -func (i *I18n) Translate(messageID string, args ...any) string { +func (i *I18n) Translate(messageID string, args ...any) Result { i.mu.RLock() t := i.translator i.mu.RUnlock() if t != nil { return t.Translate(messageID, args...) } - return messageID + return Result{messageID, true} } // SetLanguage sets the active language. No-op if no translator is registered. diff --git a/pkg/core/options.go b/pkg/core/options.go index b1c36ee..83d3529 100644 --- a/pkg/core/options.go +++ b/pkg/core/options.go @@ -8,8 +8,8 @@ // Create options: // // opts := core.Options{ -// {K: "name", V: "brain"}, -// {K: "path", V: "prompts"}, +// {Key: "name", Value: "brain"}, +// {Key: "path", Value: "prompts"}, // } // // Read options: @@ -21,22 +21,22 @@ // Use with subsystems: // // c.Drive().New(core.Options{ -// {K: "name", V: "brain"}, -// {K: "source", V: brainFS}, -// {K: "path", V: "prompts"}, +// {Key: "name", Value: "brain"}, +// {Key: "source", Value: brainFS}, +// {Key: "path", Value: "prompts"}, // }) // // Use with New: // // c := core.New(core.Options{ -// {K: "name", V: "myapp"}, +// {Key: "name", Value: "myapp"}, // }) package core // Result is the universal return type for Core operations. // Replaces the (value, error) pattern — errors flow through Core internally. // -// r := c.Data().New(core.Options{{K: "name", V: "brain"}}) +// r := c.Data().New(core.Options{{Key: "name", Value: "brain"}}) // if r.OK { use(r.Result()) } type Result struct { Value any @@ -69,49 +69,49 @@ func (r Result) Result(args ...any) Result { // Option is a single key-value configuration pair. // -// core.Option{K: "name", V: "brain"} -// core.Option{K: "port", V: 8080} +// core.Option{Key: "name", Value: "brain"} +// core.Option{Key: "port", Value: 8080} type Option struct { - K string - V any + Key string + Value any } // Options is a collection of Option items. // The universal input type for Core operations. // -// opts := core.Options{{K: "name", V: "myapp"}} +// opts := core.Options{{Key: "name", Value: "myapp"}} // name := opts.String("name") type Options []Option // Get retrieves a value by key. // -// val, ok := opts.Get("name") -func (o Options) Get(key string) (any, bool) { +// r := opts.Get("name") +// if r.OK { name := r.Value.(string) } +func (o Options) Get(key string) Result { for _, opt := range o { - if opt.K == key { - return opt.V, true + if opt.Key == key { + return Result{opt.Value, true} } } - return nil, false + return Result{} } // Has returns true if a key exists. // // if opts.Has("debug") { ... } func (o Options) Has(key string) bool { - _, ok := o.Get(key) - return ok + return o.Get(key).OK } // String retrieves a string value, empty string if missing. // // name := opts.String("name") func (o Options) String(key string) string { - val, ok := o.Get(key) - if !ok { + r := o.Get(key) + if !r.OK { return "" } - s, _ := val.(string) + s, _ := r.Value.(string) return s } @@ -119,11 +119,11 @@ func (o Options) String(key string) string { // // port := opts.Int("port") func (o Options) Int(key string) int { - val, ok := o.Get(key) - if !ok { + r := o.Get(key) + if !r.OK { return 0 } - i, _ := val.(int) + i, _ := r.Value.(int) return i } @@ -131,10 +131,10 @@ func (o Options) Int(key string) int { // // debug := opts.Bool("debug") func (o Options) Bool(key string) bool { - val, ok := o.Get(key) - if !ok { + r := o.Get(key) + if !r.OK { return false } - b, _ := val.(bool) + b, _ := r.Value.(bool) return b } diff --git a/pkg/core/runtime.go b/pkg/core/runtime.go index adbcf38..76ae3d9 100644 --- a/pkg/core/runtime.go +++ b/pkg/core/runtime.go @@ -26,7 +26,7 @@ func NewServiceRuntime[T any](c *Core, opts T) *ServiceRuntime[T] { } func (r *ServiceRuntime[T]) Core() *Core { return r.core } -func (r *ServiceRuntime[T]) Options() T { return r.opts } +func (r *ServiceRuntime[T]) Options() T { return r.opts } func (r *ServiceRuntime[T]) Config() *Config { return r.core.Config() } // --- Lifecycle --- @@ -88,7 +88,7 @@ type ServiceFactory func() Result // NewWithFactories creates a Runtime with the provided service factories. func NewWithFactories(app any, factories map[string]ServiceFactory) Result { - c := New(Options{{K: "name", V: "core"}}) + c := New(Options{{Key: "name", Value: "core"}}) c.app.Runtime = app names := slices.Sorted(maps.Keys(factories)) diff --git a/pkg/core/task.go b/pkg/core/task.go index c984271..1dac524 100644 --- a/pkg/core/task.go +++ b/pkg/core/task.go @@ -25,9 +25,9 @@ func (c *Core) PerformAsync(t Task) Result { } taskID := Concat("task-", strconv.FormatUint(c.taskIDCounter.Add(1), 10)) if tid, ok := t.(TaskWithID); ok { - tid.SetTaskID(taskID) + tid.TaskWithIdentifier(taskID) } - c.ACTION(ActionTaskStarted{TaskID: taskID, Task: t}) + c.ACTION(ActionTaskStarted{TaskIdentifier: taskID, Task: t}) c.wg.Go(func() { r := c.PERFORM(t) var err error @@ -38,14 +38,14 @@ func (c *Core) PerformAsync(t Task) Result { err = E("core.PerformAsync", Join(" ", "no handler found for task type", reflect.TypeOf(t).String()), nil) } } - c.ACTION(ActionTaskCompleted{TaskID: taskID, Task: t, Result: r.Value, Error: err}) + c.ACTION(ActionTaskCompleted{TaskIdentifier: taskID, Task: t, Result: r.Value, Error: err}) }) return Result{taskID, true} } // Progress broadcasts a progress update for a background task. func (c *Core) Progress(taskID string, progress float64, message string, t Task) { - c.ACTION(ActionTaskProgress{TaskID: taskID, Task: t, Progress: progress, Message: message}) + c.ACTION(ActionTaskProgress{TaskIdentifier: taskID, Task: t, Progress: progress, Message: message}) } func (c *Core) Perform(t Task) Result { diff --git a/tests/app_test.go b/tests/app_test.go index 230a234..62fc909 100644 --- a/tests/app_test.go +++ b/tests/app_test.go @@ -10,7 +10,7 @@ import ( // --- App --- func TestApp_Good(t *testing.T) { - c := New(Options{{K: "name", V: "myapp"}}) + c := New(Options{{Key: "name", Value: "myapp"}}) assert.Equal(t, "myapp", c.App().Name) } diff --git a/tests/cli_test.go b/tests/cli_test.go index 497dffb..85426bb 100644 --- a/tests/cli_test.go +++ b/tests/cli_test.go @@ -15,7 +15,7 @@ func TestCli_Good(t *testing.T) { } func TestCli_Banner_Good(t *testing.T) { - c := New(Options{{K: "name", V: "myapp"}}) + c := New(Options{{Key: "name", Value: "myapp"}}) assert.Equal(t, "myapp", c.Cli().Banner()) } @@ -69,7 +69,7 @@ func TestCli_Run_NoCommand_Good(t *testing.T) { } func TestCli_PrintHelp_Good(t *testing.T) { - c := New(Options{{K: "name", V: "myapp"}}) + c := New(Options{{Key: "name", Value: "myapp"}}) c.Command("deploy", Command{Action: func(_ Options) Result { return Result{OK: true} }}) c.Command("serve", Command{Action: func(_ Options) Result { return Result{OK: true} }}) c.Cli().PrintHelp() diff --git a/tests/command_test.go b/tests/command_test.go index 8efb609..bdacd15 100644 --- a/tests/command_test.go +++ b/tests/command_test.go @@ -37,7 +37,7 @@ func TestCommand_Run_Good(t *testing.T) { return Result{Value: Concat("hello ", opts.String("name")), OK: true} }}) cmd := c.Command("greet").Value.(*Command) - r := cmd.Run(Options{{K: "name", V: "world"}}) + r := cmd.Run(Options{{Key: "name", Value: "world"}}) assert.True(t, r.OK) assert.Equal(t, "hello world", r.Value) } diff --git a/tests/config_test.go b/tests/config_test.go index 18e2613..6569748 100644 --- a/tests/config_test.go +++ b/tests/config_test.go @@ -14,16 +14,16 @@ func TestConfig_SetGet_Good(t *testing.T) { c.Config().Set("api_url", "https://api.lthn.ai") c.Config().Set("max_agents", 5) - val, ok := c.Config().Get("api_url") - assert.True(t, ok) - assert.Equal(t, "https://api.lthn.ai", val) + r := c.Config().Get("api_url") + assert.True(t, r.OK) + assert.Equal(t, "https://api.lthn.ai", r.Value) } func TestConfig_Get_Bad(t *testing.T) { c := New() - val, ok := c.Config().Get("missing") - assert.False(t, ok) - assert.Nil(t, val) + r := c.Config().Get("missing") + assert.False(t, r.OK) + assert.Nil(t, r.Value) } func TestConfig_TypedAccessors_Good(t *testing.T) { diff --git a/tests/core_test.go b/tests/core_test.go index 1a8ab17..1b99156 100644 --- a/tests/core_test.go +++ b/tests/core_test.go @@ -15,7 +15,7 @@ func TestNew_Good(t *testing.T) { } func TestNew_WithOptions_Good(t *testing.T) { - c := New(Options{{K: "name", V: "myapp"}}) + c := New(Options{{Key: "name", Value: "myapp"}}) assert.NotNil(t, c) assert.Equal(t, "myapp", c.App().Name) } @@ -45,9 +45,9 @@ func TestAccessors_Good(t *testing.T) { func TestOptions_Accessor_Good(t *testing.T) { c := New(Options{ - {K: "name", V: "testapp"}, - {K: "port", V: 8080}, - {K: "debug", V: true}, + {Key: "name", Value: "testapp"}, + {Key: "port", Value: 8080}, + {Key: "debug", Value: true}, }) opts := c.Options() assert.NotNil(t, opts) @@ -67,7 +67,7 @@ func TestOptions_Accessor_Nil(t *testing.T) { func TestCore_LogError_Good(t *testing.T) { c := New() cause := assert.AnError - r := c.LogError(cause, "test.Op", "something broke") + r := c.LogError(cause, "test.Operation", "something broke") assert.False(t, r.OK) err, ok := r.Value.(error) assert.True(t, ok) @@ -76,7 +76,7 @@ func TestCore_LogError_Good(t *testing.T) { func TestCore_LogWarn_Good(t *testing.T) { c := New() - r := c.LogWarn(assert.AnError, "test.Op", "heads up") + r := c.LogWarn(assert.AnError, "test.Operation", "heads up") assert.False(t, r.OK) _, ok := r.Value.(error) assert.True(t, ok) @@ -85,13 +85,13 @@ func TestCore_LogWarn_Good(t *testing.T) { func TestCore_Must_Ugly(t *testing.T) { c := New() assert.Panics(t, func() { - c.Must(assert.AnError, "test.Op", "fatal") + c.Must(assert.AnError, "test.Operation", "fatal") }) } func TestCore_Must_Nil_Good(t *testing.T) { c := New() assert.NotPanics(t, func() { - c.Must(nil, "test.Op", "no error") + c.Must(nil, "test.Operation", "no error") }) } diff --git a/tests/data_test.go b/tests/data_test.go index 3b1cf2b..836b386 100644 --- a/tests/data_test.go +++ b/tests/data_test.go @@ -17,9 +17,9 @@ var testFS embed.FS func TestData_New_Good(t *testing.T) { c := New() r := c.Data().New(Options{ - {K: "name", V: "test"}, - {K: "source", V: testFS}, - {K: "path", V: "testdata"}, + {Key: "name", Value: "test"}, + {Key: "source", Value: testFS}, + {Key: "path", Value: "testdata"}, }) assert.True(t, r.OK) assert.NotNil(t, r.Value) @@ -28,19 +28,19 @@ func TestData_New_Good(t *testing.T) { func TestData_New_Bad(t *testing.T) { c := New() - r := c.Data().New(Options{{K: "source", V: testFS}}) + r := c.Data().New(Options{{Key: "source", Value: testFS}}) assert.False(t, r.OK) - r = c.Data().New(Options{{K: "name", V: "test"}}) + r = c.Data().New(Options{{Key: "name", Value: "test"}}) assert.False(t, r.OK) - r = c.Data().New(Options{{K: "name", V: "test"}, {K: "source", V: "not-an-fs"}}) + r = c.Data().New(Options{{Key: "name", Value: "test"}, {Key: "source", Value: "not-an-fs"}}) assert.False(t, r.OK) } func TestData_ReadString_Good(t *testing.T) { c := New() - c.Data().New(Options{{K: "name", V: "app"}, {K: "source", V: testFS}, {K: "path", V: "testdata"}}) + c.Data().New(Options{{Key: "name", Value: "app"}, {Key: "source", Value: testFS}, {Key: "path", Value: "testdata"}}) r := c.Data().ReadString("app/test.txt") assert.True(t, r.OK) assert.Equal(t, "hello from testdata\n", r.Value.(string)) @@ -54,7 +54,7 @@ func TestData_ReadString_Bad(t *testing.T) { func TestData_ReadFile_Good(t *testing.T) { c := New() - c.Data().New(Options{{K: "name", V: "app"}, {K: "source", V: testFS}, {K: "path", V: "testdata"}}) + c.Data().New(Options{{Key: "name", Value: "app"}, {Key: "source", Value: testFS}, {Key: "path", Value: "testdata"}}) r := c.Data().ReadFile("app/test.txt") assert.True(t, r.OK) assert.Equal(t, "hello from testdata\n", string(r.Value.([]byte))) @@ -62,7 +62,7 @@ func TestData_ReadFile_Good(t *testing.T) { func TestData_Get_Good(t *testing.T) { c := New() - c.Data().New(Options{{K: "name", V: "brain"}, {K: "source", V: testFS}, {K: "path", V: "testdata"}}) + c.Data().New(Options{{Key: "name", Value: "brain"}, {Key: "source", Value: testFS}, {Key: "path", Value: "testdata"}}) emb := c.Data().Get("brain") assert.NotNil(t, emb) @@ -82,21 +82,21 @@ func TestData_Get_Bad(t *testing.T) { func TestData_Mounts_Good(t *testing.T) { c := New() - c.Data().New(Options{{K: "name", V: "a"}, {K: "source", V: testFS}, {K: "path", V: "testdata"}}) - c.Data().New(Options{{K: "name", V: "b"}, {K: "source", V: testFS}, {K: "path", V: "testdata"}}) + c.Data().New(Options{{Key: "name", Value: "a"}, {Key: "source", Value: testFS}, {Key: "path", Value: "testdata"}}) + c.Data().New(Options{{Key: "name", Value: "b"}, {Key: "source", Value: testFS}, {Key: "path", Value: "testdata"}}) mounts := c.Data().Mounts() assert.Len(t, mounts, 2) } func TestEmbed_Legacy_Good(t *testing.T) { c := New() - c.Data().New(Options{{K: "name", V: "app"}, {K: "source", V: testFS}, {K: "path", V: "testdata"}}) + c.Data().New(Options{{Key: "name", Value: "app"}, {Key: "source", Value: testFS}, {Key: "path", Value: "testdata"}}) assert.NotNil(t, c.Embed()) } func TestData_List_Good(t *testing.T) { c := New() - c.Data().New(Options{{K: "name", V: "app"}, {K: "source", V: testFS}, {K: "path", V: "."}}) + c.Data().New(Options{{Key: "name", Value: "app"}, {Key: "source", Value: testFS}, {Key: "path", Value: "."}}) r := c.Data().List("app/testdata") assert.True(t, r.OK) } @@ -109,7 +109,7 @@ func TestData_List_Bad(t *testing.T) { func TestData_ListNames_Good(t *testing.T) { c := New() - c.Data().New(Options{{K: "name", V: "app"}, {K: "source", V: testFS}, {K: "path", V: "."}}) + c.Data().New(Options{{Key: "name", Value: "app"}, {Key: "source", Value: testFS}, {Key: "path", Value: "."}}) r := c.Data().ListNames("app/testdata") assert.True(t, r.OK) assert.Contains(t, r.Value.([]string), "test") @@ -117,7 +117,7 @@ func TestData_ListNames_Good(t *testing.T) { func TestData_Extract_Good(t *testing.T) { c := New() - c.Data().New(Options{{K: "name", V: "app"}, {K: "source", V: testFS}, {K: "path", V: "."}}) + c.Data().New(Options{{Key: "name", Value: "app"}, {Key: "source", Value: testFS}, {Key: "path", Value: "."}}) r := c.Data().Extract("app/testdata", t.TempDir(), nil) assert.True(t, r.OK) } diff --git a/tests/drive_test.go b/tests/drive_test.go index 548fab0..c3f0628 100644 --- a/tests/drive_test.go +++ b/tests/drive_test.go @@ -12,8 +12,8 @@ import ( func TestDrive_New_Good(t *testing.T) { c := New() r := c.Drive().New(Options{ - {K: "name", V: "api"}, - {K: "transport", V: "https://api.lthn.ai"}, + {Key: "name", Value: "api"}, + {Key: "transport", Value: "https://api.lthn.ai"}, }) assert.True(t, r.OK) assert.Equal(t, "api", r.Value.(*DriveHandle).Name) @@ -24,7 +24,7 @@ func TestDrive_New_Bad(t *testing.T) { c := New() // Missing name r := c.Drive().New(Options{ - {K: "transport", V: "https://api.lthn.ai"}, + {Key: "transport", Value: "https://api.lthn.ai"}, }) assert.False(t, r.OK) } @@ -32,8 +32,8 @@ func TestDrive_New_Bad(t *testing.T) { func TestDrive_Get_Good(t *testing.T) { c := New() c.Drive().New(Options{ - {K: "name", V: "ssh"}, - {K: "transport", V: "ssh://claude@10.69.69.165"}, + {Key: "name", Value: "ssh"}, + {Key: "transport", Value: "ssh://claude@10.69.69.165"}, }) handle := c.Drive().Get("ssh") assert.NotNil(t, handle) @@ -48,16 +48,16 @@ func TestDrive_Get_Bad(t *testing.T) { func TestDrive_Has_Good(t *testing.T) { c := New() - c.Drive().New(Options{{K: "name", V: "mcp"}, {K: "transport", V: "mcp://mcp.lthn.sh"}}) + c.Drive().New(Options{{Key: "name", Value: "mcp"}, {Key: "transport", Value: "mcp://mcp.lthn.sh"}}) assert.True(t, c.Drive().Has("mcp")) assert.False(t, c.Drive().Has("missing")) } func TestDrive_Names_Good(t *testing.T) { c := New() - c.Drive().New(Options{{K: "name", V: "api"}, {K: "transport", V: "https://api.lthn.ai"}}) - c.Drive().New(Options{{K: "name", V: "ssh"}, {K: "transport", V: "ssh://claude@10.69.69.165"}}) - c.Drive().New(Options{{K: "name", V: "mcp"}, {K: "transport", V: "mcp://mcp.lthn.sh"}}) + c.Drive().New(Options{{Key: "name", Value: "api"}, {Key: "transport", Value: "https://api.lthn.ai"}}) + c.Drive().New(Options{{Key: "name", Value: "ssh"}, {Key: "transport", Value: "ssh://claude@10.69.69.165"}}) + c.Drive().New(Options{{Key: "name", Value: "mcp"}, {Key: "transport", Value: "mcp://mcp.lthn.sh"}}) names := c.Drive().Names() assert.Len(t, names, 3) assert.Contains(t, names, "api") @@ -68,9 +68,9 @@ func TestDrive_Names_Good(t *testing.T) { func TestDrive_OptionsPreserved_Good(t *testing.T) { c := New() c.Drive().New(Options{ - {K: "name", V: "api"}, - {K: "transport", V: "https://api.lthn.ai"}, - {K: "timeout", V: 30}, + {Key: "name", Value: "api"}, + {Key: "transport", Value: "https://api.lthn.ai"}, + {Key: "timeout", Value: 30}, }) handle := c.Drive().Get("api") assert.Equal(t, 30, handle.Options.Int("timeout")) diff --git a/tests/embed_test.go b/tests/embed_test.go index 987e48b..feb6293 100644 --- a/tests/embed_test.go +++ b/tests/embed_test.go @@ -47,9 +47,9 @@ func TestEmbed_Open_Good(t *testing.T) { func TestEmbed_ReadDir_Good(t *testing.T) { emb := Mount(testFS, "testdata").Value.(*Embed) - entries, err := emb.ReadDir(".") - assert.NoError(t, err) - assert.NotEmpty(t, entries) + r := emb.ReadDir(".") + assert.True(t, r.OK) + assert.NotEmpty(t, r.Value) } func TestEmbed_Sub_Good(t *testing.T) { diff --git a/tests/error_test.go b/tests/error_test.go index eb4f769..54b0f97 100644 --- a/tests/error_test.go +++ b/tests/error_test.go @@ -104,35 +104,35 @@ func TestFormatStackTrace_Good(t *testing.T) { func TestErrorLog_Good(t *testing.T) { c := New() cause := errors.New("boom") - r := c.Log().Error(cause, "test.Op", "something broke") + r := c.Log().Error(cause, "test.Operation", "something broke") assert.False(t, r.OK) assert.ErrorIs(t, r.Value.(error), cause) } func TestErrorLog_Nil_Good(t *testing.T) { c := New() - r := c.Log().Error(nil, "test.Op", "no error") + r := c.Log().Error(nil, "test.Operation", "no error") assert.True(t, r.OK) } func TestErrorLog_Warn_Good(t *testing.T) { c := New() cause := errors.New("warning") - r := c.Log().Warn(cause, "test.Op", "heads up") + r := c.Log().Warn(cause, "test.Operation", "heads up") assert.False(t, r.OK) } func TestErrorLog_Must_Ugly(t *testing.T) { c := New() assert.Panics(t, func() { - c.Log().Must(errors.New("fatal"), "test.Op", "must fail") + c.Log().Must(errors.New("fatal"), "test.Operation", "must fail") }) } func TestErrorLog_Must_Nil_Good(t *testing.T) { c := New() assert.NotPanics(t, func() { - c.Log().Must(nil, "test.Op", "no error") + c.Log().Must(nil, "test.Operation", "no error") }) } @@ -179,7 +179,7 @@ func TestAs_Good(t *testing.T) { err := E("op", "msg", nil) var e *Err assert.True(t, As(err, &e)) - assert.Equal(t, "op", e.Op) + assert.Equal(t, "op", e.Operation) } func TestNewError_Good(t *testing.T) { diff --git a/tests/i18n_test.go b/tests/i18n_test.go index 2df6b0b..e893122 100644 --- a/tests/i18n_test.go +++ b/tests/i18n_test.go @@ -17,9 +17,9 @@ func TestI18n_Good(t *testing.T) { func TestI18n_AddLocales_Good(t *testing.T) { c := New() r := c.Data().New(Options{ - {K: "name", V: "lang"}, - {K: "source", V: testFS}, - {K: "path", V: "testdata"}, + {Key: "name", Value: "lang"}, + {Key: "source", Value: testFS}, + {Key: "path", Value: "testdata"}, }) if r.OK { c.I18n().AddLocales(r.Value.(*Embed)) @@ -38,9 +38,10 @@ func TestI18n_Locales_Empty_Good(t *testing.T) { func TestI18n_Translate_NoTranslator_Good(t *testing.T) { c := New() - // Without a translator, T returns the key as-is - result := c.I18n().Translate("greeting.hello") - assert.Equal(t, "greeting.hello", result) + // Without a translator, Translate returns the key as-is + r := c.I18n().Translate("greeting.hello") + assert.True(t, r.OK) + assert.Equal(t, "greeting.hello", r.Value) } func TestI18n_SetLanguage_NoTranslator_Good(t *testing.T) { @@ -71,10 +72,10 @@ type mockTranslator struct { lang string } -func (m *mockTranslator) Translate(id string, args ...any) string { return "translated:" + id } -func (m *mockTranslator) SetLanguage(lang string) error { m.lang = lang; return nil } -func (m *mockTranslator) Language() string { return m.lang } -func (m *mockTranslator) AvailableLanguages() []string { return []string{"en", "de", "fr"} } +func (m *mockTranslator) Translate(id string, args ...any) Result { return Result{"translated:" + id, true} } +func (m *mockTranslator) SetLanguage(lang string) error { m.lang = lang; return nil } +func (m *mockTranslator) Language() string { return m.lang } +func (m *mockTranslator) AvailableLanguages() []string { return []string{"en", "de", "fr"} } func TestI18n_WithTranslator_Good(t *testing.T) { c := New() @@ -82,7 +83,7 @@ func TestI18n_WithTranslator_Good(t *testing.T) { c.I18n().SetTranslator(tr) assert.Equal(t, tr, c.I18n().Translator()) - assert.Equal(t, "translated:hello", c.I18n().Translate("hello")) + assert.Equal(t, "translated:hello", c.I18n().Translate("hello").Value) assert.Equal(t, "en", c.I18n().Language()) assert.Equal(t, []string{"en", "de", "fr"}, c.I18n().AvailableLanguages()) diff --git a/tests/log_test.go b/tests/log_test.go index a586185..c0dc565 100644 --- a/tests/log_test.go +++ b/tests/log_test.go @@ -110,7 +110,7 @@ func TestLogErr_Good(t *testing.T) { le := NewLogErr(l) assert.NotNil(t, le) - err := E("test.Op", "something broke", nil) + err := E("test.Operation", "something broke", nil) le.Log(err) } diff --git a/tests/options_test.go b/tests/options_test.go index c8331b5..158d496 100644 --- a/tests/options_test.go +++ b/tests/options_test.go @@ -11,34 +11,34 @@ import ( func TestOptions_Get_Good(t *testing.T) { opts := Options{ - {K: "name", V: "brain"}, - {K: "port", V: 8080}, + {Key: "name", Value: "brain"}, + {Key: "port", Value: 8080}, } - val, ok := opts.Get("name") - assert.True(t, ok) - assert.Equal(t, "brain", val) + r := opts.Get("name") + assert.True(t, r.OK) + assert.Equal(t, "brain", r.Value) } func TestOptions_Get_Bad(t *testing.T) { - opts := Options{{K: "name", V: "brain"}} - val, ok := opts.Get("missing") - assert.False(t, ok) - assert.Nil(t, val) + opts := Options{{Key: "name", Value: "brain"}} + r := opts.Get("missing") + assert.False(t, r.OK) + assert.Nil(t, r.Value) } func TestOptions_Has_Good(t *testing.T) { - opts := Options{{K: "debug", V: true}} + opts := Options{{Key: "debug", Value: true}} assert.True(t, opts.Has("debug")) assert.False(t, opts.Has("missing")) } func TestOptions_String_Good(t *testing.T) { - opts := Options{{K: "name", V: "brain"}} + opts := Options{{Key: "name", Value: "brain"}} assert.Equal(t, "brain", opts.String("name")) } func TestOptions_String_Bad(t *testing.T) { - opts := Options{{K: "port", V: 8080}} + opts := Options{{Key: "port", Value: 8080}} // Wrong type — returns empty string assert.Equal(t, "", opts.String("port")) // Missing key — returns empty string @@ -46,40 +46,40 @@ func TestOptions_String_Bad(t *testing.T) { } func TestOptions_Int_Good(t *testing.T) { - opts := Options{{K: "port", V: 8080}} + opts := Options{{Key: "port", Value: 8080}} assert.Equal(t, 8080, opts.Int("port")) } func TestOptions_Int_Bad(t *testing.T) { - opts := Options{{K: "name", V: "brain"}} + opts := Options{{Key: "name", Value: "brain"}} assert.Equal(t, 0, opts.Int("name")) assert.Equal(t, 0, opts.Int("missing")) } func TestOptions_Bool_Good(t *testing.T) { - opts := Options{{K: "debug", V: true}} + opts := Options{{Key: "debug", Value: true}} assert.True(t, opts.Bool("debug")) } func TestOptions_Bool_Bad(t *testing.T) { - opts := Options{{K: "name", V: "brain"}} + opts := Options{{Key: "name", Value: "brain"}} assert.False(t, opts.Bool("name")) assert.False(t, opts.Bool("missing")) } func TestOptions_TypedStruct_Good(t *testing.T) { - // Packages plug typed structs into Option.V + // Packages plug typed structs into Option.Value type BrainConfig struct { Name string OllamaURL string Collection string } cfg := BrainConfig{Name: "brain", OllamaURL: "http://localhost:11434", Collection: "openbrain"} - opts := Options{{K: "config", V: cfg}} + opts := Options{{Key: "config", Value: cfg}} - val, ok := opts.Get("config") - assert.True(t, ok) - bc, ok := val.(BrainConfig) + r := opts.Get("config") + assert.True(t, r.OK) + bc, ok := r.Value.(BrainConfig) assert.True(t, ok) assert.Equal(t, "brain", bc.Name) assert.Equal(t, "http://localhost:11434", bc.OllamaURL)