fix: AX audit round 5 — full naming, Result returns throughout

Renames (via GoLand refactor):
- Option.K → Key, Option.V → Value
- Err.Op → Operation, Err.Msg → Message, Err.Err → Error
- CrashSystem.OS → OperatingSystem, Arch → Architecture
- TaskID → TaskIdentifier, TaskWithID → TaskWithIdentifier
- Ipc → IPC, BaseDir → BaseDirectory
- ServiceRuntime.Opts → Options

Return type changes:
- Options.Get, Config.Get → Result (was (any, bool))
- Embed.ReadDir → Result (was ([]fs.DirEntry, error))
- Translator.Translate, I18n.Translate → Result (was string)

Rule 6:
- data.go: propagate opts.Get failure, typed error for bad fs.FS

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Snider 2026-03-20 16:32:43 +00:00
parent cf25af1a13
commit 298322ed89
24 changed files with 231 additions and 227 deletions

View file

@ -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 {

View file

@ -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{}

View file

@ -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
}

View file

@ -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 {

View file

@ -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.

View file

@ -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")

View file

@ -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.

View file

@ -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),
}

View file

@ -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.

View file

@ -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
}

View file

@ -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))

View file

@ -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 {

View file

@ -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)
}

View file

@ -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()

View file

@ -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)
}

View file

@ -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) {

View file

@ -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")
})
}

View file

@ -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)
}

View file

@ -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"))

View file

@ -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) {

View file

@ -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) {

View file

@ -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())

View file

@ -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)
}

View file

@ -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)