feat: Options struct + Result methods + WithOption convenience

Options is now a proper struct with New(), Set(), Get(), typed accessors.
Result gains New(), Result(), Get() methods on the struct.
WithOption("key", value) convenience for core.New().

options_test.go: 22 tests passing against the new contract.
Other test files mechanically updated for compilation.

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Snider 2026-03-24 19:17:12 +00:00
parent 74f78c83a2
commit a49bc46bc7
16 changed files with 267 additions and 127 deletions

View file

@ -10,7 +10,7 @@ import (
// --- App ---
func TestApp_Good(t *testing.T) {
c := New(WithOptions(Options{{Key: "name", Value: "myapp"}})).Value.(*Core)
c := New(WithOptions(NewOptions(Option{Key: "name", Value: "myapp"}))).Value.(*Core)
assert.Equal(t, "myapp", c.App().Name)
}

8
cli.go
View file

@ -92,17 +92,17 @@ func (cl *Cli) Run(args ...string) Result {
}
// Build options from remaining args
opts := Options{}
opts := NewOptions()
for _, arg := range remaining {
key, val, valid := ParseFlag(arg)
if valid {
if Contains(arg, "=") {
opts = append(opts, Option{Key: key, Value: val})
opts.Set(key, val)
} else {
opts = append(opts, Option{Key: key, Value: true})
opts.Set(key, true)
}
} else if !IsFlag(arg) {
opts = append(opts, Option{Key: "_arg", Value: arg})
opts.Set("_arg", arg)
}
}

View file

@ -16,7 +16,7 @@ func TestCli_Good(t *testing.T) {
}
func TestCli_Banner_Good(t *testing.T) {
c := New(WithOptions(Options{{Key: "name", Value: "myapp"}})).Value.(*Core)
c := New(WithOptions(NewOptions(Option{Key: "name", Value: "myapp"}))).Value.(*Core)
assert.Equal(t, "myapp", c.Cli().Banner())
}
@ -32,7 +32,7 @@ func TestCli_Run_Good(t *testing.T) {
c.Command("hello", Command{Action: func(_ Options) Result {
executed = true
return Result{Value: "world", OK: true}
}})
}))
r := c.Cli().Run("hello")
assert.True(t, r.OK)
assert.Equal(t, "world", r.Value)
@ -45,7 +45,7 @@ func TestCli_Run_Nested_Good(t *testing.T) {
c.Command("deploy/to/homelab", Command{Action: func(_ Options) Result {
executed = true
return Result{OK: true}
}})
}))
r := c.Cli().Run("deploy", "to", "homelab")
assert.True(t, r.OK)
assert.True(t, executed)
@ -57,7 +57,7 @@ func TestCli_Run_WithFlags_Good(t *testing.T) {
c.Command("serve", Command{Action: func(opts Options) Result {
received = opts
return Result{OK: true}
}})
}))
c.Cli().Run("serve", "--port=8080", "--debug")
assert.Equal(t, "8080", received.String("port"))
assert.True(t, received.Bool("debug"))
@ -70,9 +70,9 @@ func TestCli_Run_NoCommand_Good(t *testing.T) {
}
func TestCli_PrintHelp_Good(t *testing.T) {
c := New(WithOptions(Options{{Key: "name", Value: "myapp"}})).Value.(*Core)
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 := New(WithOptions(NewOptions(Option{Key: "name", Value: "myapp"}))).Value.(*Core)
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

@ -13,13 +13,13 @@ func TestCommand_Register_Good(t *testing.T) {
c := New().Value.(*Core)
r := c.Command("deploy", Command{Action: func(_ Options) Result {
return Result{Value: "deployed", OK: true}
}})
}))
assert.True(t, r.OK)
}
func TestCommand_Get_Good(t *testing.T) {
c := New().Value.(*Core)
c.Command("deploy", Command{Action: func(_ Options) Result { return Result{OK: true} }})
c.Command("deploy", Command{Action: func(_ Options) Result { return Result{OK: true} }))
r := c.Command("deploy")
assert.True(t, r.OK)
assert.NotNil(t, r.Value)
@ -35,9 +35,9 @@ func TestCommand_Run_Good(t *testing.T) {
c := New().Value.(*Core)
c.Command("greet", Command{Action: func(opts Options) Result {
return Result{Value: Concat("hello ", opts.String("name")), OK: true}
}})
}))
cmd := c.Command("greet").Value.(*Command)
r := cmd.Run(Options{{Key: "name", Value: "world"}})
r := cmd.Run(NewOptions(Option{Key: "name", Value: "world"}))
assert.True(t, r.OK)
assert.Equal(t, "hello world", r.Value)
}
@ -46,7 +46,7 @@ func TestCommand_Run_NoAction_Good(t *testing.T) {
c := New().Value.(*Core)
c.Command("empty", Command{Description: "no action"})
cmd := c.Command("empty").Value.(*Command)
r := cmd.Run(Options{})
r := cmd.Run(NewOptions())
assert.False(t, r.OK)
}
@ -56,7 +56,7 @@ func TestCommand_Nested_Good(t *testing.T) {
c := New().Value.(*Core)
c.Command("deploy/to/homelab", Command{Action: func(_ Options) Result {
return Result{Value: "deployed to homelab", OK: true}
}})
}))
r := c.Command("deploy/to/homelab")
assert.True(t, r.OK)
@ -68,9 +68,9 @@ func TestCommand_Nested_Good(t *testing.T) {
func TestCommand_Paths_Good(t *testing.T) {
c := New().Value.(*Core)
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.Command("deploy/to/homelab", Command{Action: func(_ Options) Result { return Result{OK: true} }})
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.Command("deploy/to/homelab", Command{Action: func(_ Options) Result { return Result{OK: true} }))
paths := c.Commands()
assert.Contains(t, paths, "deploy")
@ -108,10 +108,10 @@ func TestCommand_Lifecycle_NoImpl_Good(t *testing.T) {
c := New().Value.(*Core)
c.Command("serve", Command{Action: func(_ Options) Result {
return Result{Value: "running", OK: true}
}})
}))
cmd := c.Command("serve").Value.(*Command)
r := cmd.Start(Options{})
r := cmd.Start(NewOptions())
assert.True(t, r.OK)
assert.Equal(t, "running", r.Value)
@ -158,7 +158,7 @@ func TestCommand_Lifecycle_WithImpl_Good(t *testing.T) {
c.Command("daemon", Command{Lifecycle: lc})
cmd := c.Command("daemon").Value.(*Command)
r := cmd.Start(Options{})
r := cmd.Start(NewOptions())
assert.True(t, r.OK)
assert.True(t, lc.started)
@ -178,8 +178,8 @@ func TestCommand_Lifecycle_WithImpl_Good(t *testing.T) {
func TestCommand_Duplicate_Bad(t *testing.T) {
c := New().Value.(*Core)
c.Command("deploy", Command{Action: func(_ Options) Result { return Result{OK: true} }})
r := c.Command("deploy", Command{Action: func(_ Options) Result { return Result{OK: true} }})
c.Command("deploy", Command{Action: func(_ Options) Result { return Result{OK: true} }))
r := c.Command("deploy", Command{Action: func(_ Options) Result { return Result{OK: true} }))
assert.False(t, r.OK)
}

View file

@ -120,10 +120,8 @@ func New(opts ...CoreOption) Result {
// core.WithOptions(core.Options{{Key: "name", Value: "myapp"}})
func WithOptions(opts Options) CoreOption {
return func(c *Core) Result {
cp := make(Options, len(opts))
copy(cp, opts)
c.options = &cp
if name := cp.String("name"); name != "" {
c.options = &opts
if name := opts.String("name"); name != "" {
c.app.Name = name
}
return Result{OK: true}
@ -142,6 +140,28 @@ func WithService(factory func(*Core) Result) CoreOption {
}
}
// WithOption is a convenience for setting a single key-value option.
//
// core.New(
// core.WithOption("name", "myapp"),
// core.WithOption("port", 8080),
// )
func WithOption(key string, value any) CoreOption {
return func(c *Core) Result {
if c.options == nil {
opts := NewOptions()
c.options = &opts
}
c.options.Set(key, value)
if key == "name" {
if s, ok := value.(string); ok {
c.app.Name = s
}
}
return Result{OK: true}
}
}
// WithServiceLock prevents further service registration after construction.
//
// core.New(

View file

@ -16,21 +16,21 @@ func TestNew_Good(t *testing.T) {
}
func TestNew_WithOptions_Good(t *testing.T) {
c := New(WithOptions(Options{{Key: "name", Value: "myapp"}})).Value.(*Core)
c := New(WithOptions(NewOptions(Option{Key: "name", Value: "myapp"}))).Value.(*Core)
assert.NotNil(t, c)
assert.Equal(t, "myapp", c.App().Name)
}
func TestNew_WithOptions_Bad(t *testing.T) {
// Empty options — should still create a valid Core
c := New(WithOptions(Options{})).Value.(*Core)
c := New(WithOptions(NewOptions())).Value.(*Core)
assert.NotNil(t, c)
}
func TestNew_WithService_Good(t *testing.T) {
started := false
r := New(
WithOptions(Options{{Key: "name", Value: "myapp"}}),
WithOptions(NewOptions(Option{Key: "name", Value: "myapp"})),
WithService(func(c *Core) Result {
c.Service("test", Service{
OnStart: func() Result { started = true; return Result{OK: true} },

View file

@ -28,19 +28,19 @@ func TestData_New_Good(t *testing.T) {
func TestData_New_Bad(t *testing.T) {
c := New().Value.(*Core)
r := c.Data().New(Options{{Key: "source", Value: testFS}})
r := c.Data().New(NewOptions(Option{Key: "source", Value: testFS}))
assert.False(t, r.OK)
r = c.Data().New(Options{{Key: "name", Value: "test"}})
r = c.Data().New(NewOptions(Option{Key: "name", Value: "test"}))
assert.False(t, r.OK)
r = c.Data().New(Options{{Key: "name", Value: "test"}, {Key: "source", Value: "not-an-fs"}})
r = c.Data().New(NewOptions(Option{Key: "name", Value: "test"}, Option{Key: "source", Value: "not-an-fs"}))
assert.False(t, r.OK)
}
func TestData_ReadString_Good(t *testing.T) {
c := New().Value.(*Core)
c.Data().New(Options{{Key: "name", Value: "app"}, {Key: "source", Value: testFS}, {Key: "path", Value: "testdata"}})
c.Data().New(NewOptions(Option{Key: "name", Value: "app"}, Option{Key: "source", Value: testFS}, Option{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().Value.(*Core)
c.Data().New(Options{{Key: "name", Value: "app"}, {Key: "source", Value: testFS}, {Key: "path", Value: "testdata"}})
c.Data().New(NewOptions(Option{Key: "name", Value: "app"}, Option{Key: "source", Value: testFS}, Option{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().Value.(*Core)
c.Data().New(Options{{Key: "name", Value: "brain"}, {Key: "source", Value: testFS}, {Key: "path", Value: "testdata"}})
c.Data().New(NewOptions(Option{Key: "name", Value: "brain"}, Option{Key: "source", Value: testFS}, Option{Key: "path", Value: "testdata"}))
gr := c.Data().Get("brain")
assert.True(t, gr.OK)
emb := gr.Value.(*Embed)
@ -83,21 +83,21 @@ func TestData_Get_Bad(t *testing.T) {
func TestData_Mounts_Good(t *testing.T) {
c := New().Value.(*Core)
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"}})
c.Data().New(NewOptions(Option{Key: "name", Value: "a"}, Option{Key: "source", Value: testFS}, Option{Key: "path", Value: "testdata"}))
c.Data().New(NewOptions(Option{Key: "name", Value: "b"}, Option{Key: "source", Value: testFS}, Option{Key: "path", Value: "testdata"}))
mounts := c.Data().Mounts()
assert.Len(t, mounts, 2)
}
func TestEmbed_Legacy_Good(t *testing.T) {
c := New().Value.(*Core)
c.Data().New(Options{{Key: "name", Value: "app"}, {Key: "source", Value: testFS}, {Key: "path", Value: "testdata"}})
c.Data().New(NewOptions(Option{Key: "name", Value: "app"}, Option{Key: "source", Value: testFS}, Option{Key: "path", Value: "testdata"}))
assert.NotNil(t, c.Embed())
}
func TestData_List_Good(t *testing.T) {
c := New().Value.(*Core)
c.Data().New(Options{{Key: "name", Value: "app"}, {Key: "source", Value: testFS}, {Key: "path", Value: "."}})
c.Data().New(NewOptions(Option{Key: "name", Value: "app"}, Option{Key: "source", Value: testFS}, Option{Key: "path", Value: "."}))
r := c.Data().List("app/testdata")
assert.True(t, r.OK)
}
@ -110,7 +110,7 @@ func TestData_List_Bad(t *testing.T) {
func TestData_ListNames_Good(t *testing.T) {
c := New().Value.(*Core)
c.Data().New(Options{{Key: "name", Value: "app"}, {Key: "source", Value: testFS}, {Key: "path", Value: "."}})
c.Data().New(NewOptions(Option{Key: "name", Value: "app"}, Option{Key: "source", Value: testFS}, Option{Key: "path", Value: "."}))
r := c.Data().ListNames("app/testdata")
assert.True(t, r.OK)
assert.Contains(t, r.Value.([]string), "test")
@ -118,7 +118,7 @@ func TestData_ListNames_Good(t *testing.T) {
func TestData_Extract_Good(t *testing.T) {
c := New().Value.(*Core)
c.Data().New(Options{{Key: "name", Value: "app"}, {Key: "source", Value: testFS}, {Key: "path", Value: "."}})
c.Data().New(NewOptions(Option{Key: "name", Value: "app"}, Option{Key: "source", Value: testFS}, Option{Key: "path", Value: "."}))
r := c.Data().Extract("app/testdata", t.TempDir(), nil)
assert.True(t, r.OK)
}

View file

@ -62,12 +62,10 @@ func (d *Drive) New(opts Options) Result {
d.handles = make(map[string]*DriveHandle)
}
cp := make(Options, len(opts))
copy(cp, opts)
handle := &DriveHandle{
Name: name,
Transport: transport,
Options: cp,
Options: opts,
}
d.handles[name] = handle

View file

@ -49,16 +49,16 @@ func TestDrive_Get_Bad(t *testing.T) {
func TestDrive_Has_Good(t *testing.T) {
c := New().Value.(*Core)
c.Drive().New(Options{{Key: "name", Value: "mcp"}, {Key: "transport", Value: "mcp://mcp.lthn.sh"}})
c.Drive().New(NewOptions(Option{Key: "name", Value: "mcp"}, Option{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().Value.(*Core)
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"}})
c.Drive().New(NewOptions(Option{Key: "name", Value: "api"}, Option{Key: "transport", Value: "https://api.lthn.ai"}))
c.Drive().New(NewOptions(Option{Key: "name", Value: "ssh"}, Option{Key: "transport", Value: "ssh://claude@10.69.69.165"}))
c.Drive().New(NewOptions(Option{Key: "name", Value: "mcp"}, Option{Key: "transport", Value: "mcp://mcp.lthn.sh"}))
names := c.Drive().Names()
assert.Len(t, names, 3)
assert.Contains(t, names, "api")

View file

@ -396,7 +396,7 @@ func (s *Embed) ReadDir(name string) Result {
if !r.OK {
return r
}
return Result{}.Result(fs.ReadDir(s.fsys, r.Value.(string)))
return Result{}.New(fs.ReadDir(s.fsys, r.Value.(string)))
}
// ReadFile reads the named file.

10
fs.go
View file

@ -190,7 +190,7 @@ func (m *Fs) List(p string) Result {
if !vp.OK {
return vp
}
return Result{}.Result(os.ReadDir(vp.Value.(string)))
return Result{}.New(os.ReadDir(vp.Value.(string)))
}
// Stat returns file info.
@ -199,7 +199,7 @@ func (m *Fs) Stat(p string) Result {
if !vp.OK {
return vp
}
return Result{}.Result(os.Stat(vp.Value.(string)))
return Result{}.New(os.Stat(vp.Value.(string)))
}
// Open opens the named file for reading.
@ -208,7 +208,7 @@ func (m *Fs) Open(p string) Result {
if !vp.OK {
return vp
}
return Result{}.Result(os.Open(vp.Value.(string)))
return Result{}.New(os.Open(vp.Value.(string)))
}
// Create creates or truncates the named file.
@ -221,7 +221,7 @@ func (m *Fs) Create(p string) Result {
if err := os.MkdirAll(filepath.Dir(full), 0755); err != nil {
return Result{err, false}
}
return Result{}.Result(os.Create(full))
return Result{}.New(os.Create(full))
}
// Append opens the named file for appending, creating it if it doesn't exist.
@ -234,7 +234,7 @@ func (m *Fs) Append(p string) Result {
if err := os.MkdirAll(filepath.Dir(full), 0755); err != nil {
return Result{err, false}
}
return Result{}.Result(os.OpenFile(full, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644))
return Result{}.New(os.OpenFile(full, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644))
}
// ReadStream returns a reader for the file content.

View file

@ -40,7 +40,7 @@ func TestLockEnable_Good(t *testing.T) {
func TestStartables_Good(t *testing.T) {
c := New().Value.(*Core)
c.Service("s", Service{OnStart: func() Result { return Result{OK: true} }})
c.Service("s", Service{OnStart: func() Result { return Result{OK: true} }))
r := c.Startables()
assert.True(t, r.OK)
assert.Len(t, r.Value.([]*Service), 1)
@ -48,7 +48,7 @@ func TestStartables_Good(t *testing.T) {
func TestStoppables_Good(t *testing.T) {
c := New().Value.(*Core)
c.Service("s", Service{OnStop: func() Result { return Result{OK: true} }})
c.Service("s", Service{OnStop: func() Result { return Result{OK: true} }))
r := c.Stoppables()
assert.True(t, r.OK)
assert.Len(t, r.Value.([]*Service), 1)

View file

@ -2,42 +2,24 @@
// Core primitives: Option, Options, Result.
//
// Option is a single key-value pair. Options is a collection.
// Any function that returns Result can accept Options.
// Options is the universal input type. Result is the universal output type.
// All Core operations accept Options and return Result.
//
// Create options:
//
// opts := core.Options{
// {Key: "name", Value: "brain"},
// {Key: "path", Value: "prompts"},
// }
//
// Read options:
//
// name := opts.String("name")
// port := opts.Int("port")
// ok := opts.Has("debug")
//
// Use with subsystems:
//
// c.Drive().New(core.Options{
// {Key: "name", Value: "brain"},
// {Key: "source", Value: brainFS},
// {Key: "path", Value: "prompts"},
// })
//
// Use with New:
//
// c := core.New(core.Options{
// {Key: "name", Value: "myapp"},
// })
// opts := core.NewOptions(
// core.Option{Key: "name", Value: "brain"},
// core.Option{Key: "path", Value: "prompts"},
// )
// r := c.Drive().New(opts)
// if !r.OK { log.Fatal(r.Error()) }
package core
// --- Result: Universal Output ---
// 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{{Key: "name", Value: "brain"}})
// if r.OK { use(r.Result()) }
// r := c.Data().New(opts)
// if !r.OK { core.Error("failed", "err", r.Error()) }
type Result struct {
Value any
OK bool
@ -50,21 +32,34 @@ type Result struct {
// r.Result(value) // OK = true, Value = value
// r.Result() // after set — returns the value
func (r Result) Result(args ...any) Result {
if len(args) == 0 {
if args == nil {
return r
}
return r.New(args...)
}
func (r Result) New(args ...any) Result {
if len(args) >= 1 {
r.Value = args[0]
}
if err, ok := r.Value.(error); ok {
if err != nil {
r.Value = err
r.OK = false
} else {
r.OK = true
}
}
return r
}
if len(args) == 1 {
return Result{args[0], true}
func (r Result) Get() Result {
if r.OK {
return r
}
if err, ok := args[len(args)-1].(error); ok {
if err != nil {
return Result{err, false}
}
return Result{args[0], true}
}
return Result{args[0], true}
return Result{Value: r.Value, OK: false}
}
// Option is a single key-value configuration pair.
@ -76,19 +71,51 @@ type Option struct {
Value any
}
// Options is a collection of Option items.
// The universal input type for Core operations.
// --- Options: Universal Input ---
// Options is the universal input type for Core operations.
// A structured collection of key-value pairs with typed accessors.
//
// opts := core.Options{{Key: "name", Value: "myapp"}}
// opts := core.NewOptions(
// core.Option{Key: "name", Value: "myapp"},
// core.Option{Key: "port", Value: 8080},
// )
// name := opts.String("name")
type Options []Option
type Options struct {
items []Option
}
// NewOptions creates an Options collection from key-value pairs.
//
// opts := core.NewOptions(
// core.Option{Key: "name", Value: "brain"},
// core.Option{Key: "path", Value: "prompts"},
// )
func NewOptions(items ...Option) Options {
cp := make([]Option, len(items))
copy(cp, items)
return Options{items: cp}
}
// Set adds or updates a key-value pair.
//
// opts.Set("port", 8080)
func (o *Options) Set(key string, value any) {
for i, opt := range o.items {
if opt.Key == key {
o.items[i].Value = value
return
}
}
o.items = append(o.items, Option{Key: key, Value: value})
}
// Get retrieves a value by key.
//
// r := opts.Get("name")
// if r.OK { name := r.Value.(string) }
func (o Options) Get(key string) Result {
for _, opt := range o {
for _, opt := range o.items {
if opt.Key == key {
return Result{opt.Value, true}
}
@ -138,3 +165,15 @@ func (o Options) Bool(key string) bool {
b, _ := r.Value.(bool)
return b
}
// Len returns the number of options.
func (o Options) Len() int {
return len(o.items)
}
// Items returns a copy of the underlying option slice.
func (o Options) Items() []Option {
cp := make([]Option, len(o.items))
copy(cp, o.items)
return cp
}

View file

@ -7,75 +7,121 @@ import (
"github.com/stretchr/testify/assert"
)
// --- Option / Options ---
// --- NewOptions ---
func TestNewOptions_Good(t *testing.T) {
opts := NewOptions(
Option{Key: "name", Value: "brain"},
Option{Key: "port", Value: 8080},
)
assert.Equal(t, 2, opts.Len())
}
func TestNewOptions_Empty_Good(t *testing.T) {
opts := NewOptions()
assert.Equal(t, 0, opts.Len())
assert.False(t, opts.Has("anything"))
}
// --- Options.Set ---
func TestOptions_Set_Good(t *testing.T) {
opts := NewOptions()
opts.Set("name", "brain")
assert.Equal(t, "brain", opts.String("name"))
}
func TestOptions_Set_Update_Good(t *testing.T) {
opts := NewOptions(Option{Key: "name", Value: "old"})
opts.Set("name", "new")
assert.Equal(t, "new", opts.String("name"))
assert.Equal(t, 1, opts.Len())
}
// --- Options.Get ---
func TestOptions_Get_Good(t *testing.T) {
opts := Options{
{Key: "name", Value: "brain"},
{Key: "port", Value: 8080},
}
opts := NewOptions(
Option{Key: "name", Value: "brain"},
Option{Key: "port", Value: 8080},
)
r := opts.Get("name")
assert.True(t, r.OK)
assert.Equal(t, "brain", r.Value)
}
func TestOptions_Get_Bad(t *testing.T) {
opts := Options{{Key: "name", Value: "brain"}}
opts := NewOptions(Option{Key: "name", Value: "brain"})
r := opts.Get("missing")
assert.False(t, r.OK)
assert.Nil(t, r.Value)
}
// --- Options.Has ---
func TestOptions_Has_Good(t *testing.T) {
opts := Options{{Key: "debug", Value: true}}
opts := NewOptions(Option{Key: "debug", Value: true})
assert.True(t, opts.Has("debug"))
assert.False(t, opts.Has("missing"))
}
// --- Options.String ---
func TestOptions_String_Good(t *testing.T) {
opts := Options{{Key: "name", Value: "brain"}}
opts := NewOptions(Option{Key: "name", Value: "brain"})
assert.Equal(t, "brain", opts.String("name"))
}
func TestOptions_String_Bad(t *testing.T) {
opts := Options{{Key: "port", Value: 8080}}
// Wrong type — returns empty string
opts := NewOptions(Option{Key: "port", Value: 8080})
assert.Equal(t, "", opts.String("port"))
// Missing key — returns empty string
assert.Equal(t, "", opts.String("missing"))
}
// --- Options.Int ---
func TestOptions_Int_Good(t *testing.T) {
opts := Options{{Key: "port", Value: 8080}}
opts := NewOptions(Option{Key: "port", Value: 8080})
assert.Equal(t, 8080, opts.Int("port"))
}
func TestOptions_Int_Bad(t *testing.T) {
opts := Options{{Key: "name", Value: "brain"}}
opts := NewOptions(Option{Key: "name", Value: "brain"})
assert.Equal(t, 0, opts.Int("name"))
assert.Equal(t, 0, opts.Int("missing"))
}
// --- Options.Bool ---
func TestOptions_Bool_Good(t *testing.T) {
opts := Options{{Key: "debug", Value: true}}
opts := NewOptions(Option{Key: "debug", Value: true})
assert.True(t, opts.Bool("debug"))
}
func TestOptions_Bool_Bad(t *testing.T) {
opts := Options{{Key: "name", Value: "brain"}}
opts := NewOptions(Option{Key: "name", Value: "brain"})
assert.False(t, opts.Bool("name"))
assert.False(t, opts.Bool("missing"))
}
// --- Options.Items ---
func TestOptions_Items_Good(t *testing.T) {
opts := NewOptions(Option{Key: "a", Value: 1}, Option{Key: "b", Value: 2})
items := opts.Items()
assert.Len(t, items, 2)
}
// --- Options with typed struct ---
func TestOptions_TypedStruct_Good(t *testing.T) {
// 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{{Key: "config", Value: cfg}}
opts := NewOptions(Option{Key: "config", Value: cfg})
r := opts.Get("config")
assert.True(t, r.OK)
@ -85,10 +131,47 @@ func TestOptions_TypedStruct_Good(t *testing.T) {
assert.Equal(t, "http://localhost:11434", bc.OllamaURL)
}
func TestOptions_Empty_Good(t *testing.T) {
opts := Options{}
assert.False(t, opts.Has("anything"))
assert.Equal(t, "", opts.String("anything"))
assert.Equal(t, 0, opts.Int("anything"))
assert.False(t, opts.Bool("anything"))
// --- Result ---
func TestResult_New_Good(t *testing.T) {
r := Result{}.New("value")
assert.Equal(t, "value", r.Value)
}
func TestResult_New_Error_Bad(t *testing.T) {
err := E("test", "failed", nil)
r := Result{}.New(err)
assert.False(t, r.OK)
assert.Equal(t, err, r.Value)
}
func TestResult_Result_Good(t *testing.T) {
r := Result{Value: "hello", OK: true}
assert.Equal(t, r, r.Result())
}
func TestResult_Result_WithArgs_Good(t *testing.T) {
r := Result{}.Result("value")
assert.Equal(t, "value", r.Value)
}
func TestResult_Get_Good(t *testing.T) {
r := Result{Value: "hello", OK: true}
assert.True(t, r.Get().OK)
}
func TestResult_Get_Bad(t *testing.T) {
r := Result{Value: "err", OK: false}
assert.False(t, r.Get().OK)
}
// --- WithOption ---
func TestWithOption_Good(t *testing.T) {
c := New(
WithOption("name", "myapp"),
WithOption("port", 8080),
).Value.(*Core)
assert.Equal(t, "myapp", c.App().Name)
assert.Equal(t, 8080, c.Options().Int("port"))
}

View file

@ -106,7 +106,7 @@ type ServiceFactory func() Result
// NewWithFactories creates a Runtime with the provided service factories.
func NewWithFactories(app any, factories map[string]ServiceFactory) Result {
r := New(WithOptions(Options{{Key: "name", Value: "core"}}))
r := New(WithOptions(NewOptions(Option{Key: "name", Value: "core"})))
if !r.OK {
return r
}

View file

@ -30,7 +30,7 @@ func TestService_Register_Empty_Bad(t *testing.T) {
func TestService_Get_Good(t *testing.T) {
c := New().Value.(*Core)
c.Service("brain", Service{OnStart: func() Result { return Result{OK: true} }})
c.Service("brain", Service{OnStart: func() Result { return Result{OK: true} }))
r := c.Service("brain")
assert.True(t, r.OK)
assert.NotNil(t, r.Value)