fix: AX audit — eloquent Result literals, renamed abbreviations, error propagation
- Result{x, true} positional literals replace verbose Result{Value: x, OK: true}
- Result{err, false} replaces bare Result{} where errors were swallowed
- ErrCode → ErrorCode, LogPan → LogPanic (no abbreviations)
- NewBuilder()/NewReader() wrappers in string.go, removed strings import from embed.go
- fmt.Errorf in log.go replaced with NewError(fmt.Sprint(...))
- 14 files, 66 audit violations resolved
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
a845866c25
commit
f8e1459bd1
14 changed files with 115 additions and 103 deletions
|
|
@ -7,7 +7,7 @@
|
|||
// Register a command:
|
||||
//
|
||||
// c.Command("deploy", func(opts core.Options) core.Result {
|
||||
// return core.Result{Value: "deployed", OK: true}
|
||||
// return core.Result{"deployed", true}
|
||||
// })
|
||||
//
|
||||
// Register a nested command:
|
||||
|
|
@ -138,11 +138,11 @@ func (c *Core) Command(path string, command ...Command) Result {
|
|||
c.commands.mu.RLock()
|
||||
cmd, ok := c.commands.commands[path]
|
||||
c.commands.mu.RUnlock()
|
||||
return Result{Value: cmd, OK: ok}
|
||||
return Result{cmd, ok}
|
||||
}
|
||||
|
||||
if path == "" {
|
||||
return Result{Value: E("core.Command", "command path cannot be empty", nil)}
|
||||
return Result{E("core.Command", "command path cannot be empty", nil), false}
|
||||
}
|
||||
|
||||
c.commands.mu.Lock()
|
||||
|
|
|
|||
|
|
@ -71,12 +71,12 @@ func (d *Data) New(opts Options) Result {
|
|||
|
||||
r := Mount(fsys, path)
|
||||
if !r.OK {
|
||||
return Result{}
|
||||
return r
|
||||
}
|
||||
|
||||
emb := r.Value.(*Embed)
|
||||
d.mounts[name] = emb
|
||||
return Result{Value: emb, OK: true}
|
||||
return Result{emb, true}
|
||||
}
|
||||
|
||||
// Get returns the Embed for a named mount point.
|
||||
|
|
@ -128,7 +128,7 @@ func (d *Data) ReadString(path string) Result {
|
|||
if !r.OK {
|
||||
return r
|
||||
}
|
||||
return Result{Value: string(r.Value.([]byte)), OK: true}
|
||||
return Result{string(r.Value.([]byte)), true}
|
||||
}
|
||||
|
||||
// List returns directory entries at a path.
|
||||
|
|
@ -142,9 +142,9 @@ func (d *Data) List(path string) Result {
|
|||
}
|
||||
entries, err := emb.ReadDir(rel)
|
||||
if err != nil {
|
||||
return Result{}
|
||||
return Result{err, false}
|
||||
}
|
||||
return Result{Value: entries, OK: true}
|
||||
return Result{entries, true}
|
||||
}
|
||||
|
||||
// ListNames returns filenames (without extensions) at a path.
|
||||
|
|
@ -165,7 +165,7 @@ func (d *Data) ListNames(path string) Result {
|
|||
}
|
||||
names = append(names, name)
|
||||
}
|
||||
return Result{Value: names, OK: true}
|
||||
return Result{names, true}
|
||||
}
|
||||
|
||||
// Extract copies a template directory to targetDir.
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ func (d *Drive) New(opts Options) Result {
|
|||
}
|
||||
|
||||
d.handles[name] = handle
|
||||
return Result{Value: handle, OK: true}
|
||||
return Result{handle, true}
|
||||
}
|
||||
|
||||
// Get returns a handle by name.
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ import (
|
|||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/template"
|
||||
)
|
||||
|
|
@ -81,9 +80,9 @@ func GetAsset(group, name string) Result {
|
|||
}
|
||||
s, err := decompress(data)
|
||||
if err != nil {
|
||||
return Result{}.Result(nil, err)
|
||||
return Result{err, false}
|
||||
}
|
||||
return Result{}.Result(s)
|
||||
return Result{s, true}
|
||||
}
|
||||
|
||||
// GetAssetBytes retrieves a packed asset as bytes.
|
||||
|
|
@ -95,7 +94,7 @@ func GetAssetBytes(group, name string) Result {
|
|||
if !r.OK {
|
||||
return r
|
||||
}
|
||||
return Result{}.Result([]byte(r.Value.(string)))
|
||||
return Result{[]byte(r.Value.(string)), true}
|
||||
}
|
||||
|
||||
// --- Build-time: AST Scanner ---
|
||||
|
|
@ -126,7 +125,7 @@ func ScanAssets(filenames []string) Result {
|
|||
fset := token.NewFileSet()
|
||||
node, err := parser.ParseFile(fset, filename, nil, parser.AllErrors)
|
||||
if err != nil {
|
||||
return Result{}.Result(nil, err)
|
||||
return Result{err, false}
|
||||
}
|
||||
|
||||
baseDir := filepath.Dir(filename)
|
||||
|
|
@ -202,7 +201,7 @@ func ScanAssets(filenames []string) Result {
|
|||
return true
|
||||
})
|
||||
if scanErr != nil {
|
||||
return Result{}.Result(nil, scanErr)
|
||||
return Result{scanErr, false}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -210,18 +209,18 @@ func ScanAssets(filenames []string) Result {
|
|||
for _, pkg := range packageMap {
|
||||
result = append(result, *pkg)
|
||||
}
|
||||
return Result{}.Result(result)
|
||||
return Result{result, true}
|
||||
}
|
||||
|
||||
// GeneratePack creates Go source code that embeds the scanned assets.
|
||||
func GeneratePack(pkg ScannedPackage) Result {
|
||||
var b strings.Builder
|
||||
b := NewBuilder()
|
||||
|
||||
b.WriteString(fmt.Sprintf("package %s\n\n", pkg.PackageName))
|
||||
b.WriteString("// Code generated by core pack. DO NOT EDIT.\n\n")
|
||||
|
||||
if len(pkg.Assets) == 0 && len(pkg.Groups) == 0 {
|
||||
return Result{}.Result(b.String())
|
||||
return Result{b.String(), true}
|
||||
}
|
||||
|
||||
b.WriteString("import \"forge.lthn.ai/core/go/pkg/core\"\n\n")
|
||||
|
|
@ -232,7 +231,7 @@ func GeneratePack(pkg ScannedPackage) Result {
|
|||
for _, groupPath := range pkg.Groups {
|
||||
files, err := getAllFiles(groupPath)
|
||||
if err != nil {
|
||||
return Result{}.Result(nil, err)
|
||||
return Result{err, false}
|
||||
}
|
||||
for _, file := range files {
|
||||
if packed[file] {
|
||||
|
|
@ -240,12 +239,12 @@ func GeneratePack(pkg ScannedPackage) Result {
|
|||
}
|
||||
data, err := compressFile(file)
|
||||
if err != nil {
|
||||
return Result{}.Result(nil, err)
|
||||
return Result{err, false}
|
||||
}
|
||||
localPath := TrimPrefix(file, groupPath+"/")
|
||||
relGroup, err := filepath.Rel(pkg.BaseDir, groupPath)
|
||||
if err != nil {
|
||||
return Result{}.Result(nil, err)
|
||||
return Result{err, false}
|
||||
}
|
||||
b.WriteString(fmt.Sprintf("\tcore.AddAsset(%q, %q, %q)\n", relGroup, localPath, data))
|
||||
packed[file] = true
|
||||
|
|
@ -259,14 +258,14 @@ func GeneratePack(pkg ScannedPackage) Result {
|
|||
}
|
||||
data, err := compressFile(asset.FullPath)
|
||||
if err != nil {
|
||||
return Result{}.Result(nil, err)
|
||||
return Result{err, false}
|
||||
}
|
||||
b.WriteString(fmt.Sprintf("\tcore.AddAsset(%q, %q, %q)\n", asset.Group, asset.Name, data))
|
||||
packed[asset.FullPath] = true
|
||||
}
|
||||
|
||||
b.WriteString("}\n")
|
||||
return Result{}.Result(b.String())
|
||||
return Result{b.String(), true}
|
||||
}
|
||||
|
||||
// --- Compression ---
|
||||
|
|
@ -302,7 +301,7 @@ func compress(input string) (string, error) {
|
|||
}
|
||||
|
||||
func decompress(input string) (string, error) {
|
||||
b64 := base64.NewDecoder(base64.StdEncoding, strings.NewReader(input))
|
||||
b64 := base64.NewDecoder(base64.StdEncoding, NewReader(input))
|
||||
gz, err := gzip.NewReader(b64)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
|
@ -354,9 +353,9 @@ func Mount(fsys fs.FS, basedir string) Result {
|
|||
}
|
||||
|
||||
if _, err := s.ReadDir("."); err != nil {
|
||||
return Result{}.Result(nil, err)
|
||||
return Result{err, false}
|
||||
}
|
||||
return Result{}.Result(s)
|
||||
return Result{s, true}
|
||||
}
|
||||
|
||||
// MountEmbed creates a scoped view of an embed.FS.
|
||||
|
|
@ -377,9 +376,9 @@ func (s *Embed) path(name string) string {
|
|||
func (s *Embed) Open(name string) Result {
|
||||
f, err := s.fsys.Open(s.path(name))
|
||||
if err != nil {
|
||||
return Result{}.Result(nil, err)
|
||||
return Result{err, false}
|
||||
}
|
||||
return Result{}.Result(f)
|
||||
return Result{f, true}
|
||||
}
|
||||
|
||||
// ReadDir reads the named directory.
|
||||
|
|
@ -394,9 +393,9 @@ func (s *Embed) ReadDir(name string) ([]fs.DirEntry, error) {
|
|||
func (s *Embed) ReadFile(name string) Result {
|
||||
data, err := fs.ReadFile(s.fsys, s.path(name))
|
||||
if err != nil {
|
||||
return Result{}.Result(nil, err)
|
||||
return Result{err, false}
|
||||
}
|
||||
return Result{}.Result(data)
|
||||
return Result{data, true}
|
||||
}
|
||||
|
||||
// ReadString reads the named file as a string.
|
||||
|
|
@ -408,7 +407,7 @@ func (s *Embed) ReadString(name string) Result {
|
|||
if !r.OK {
|
||||
return r
|
||||
}
|
||||
return Result{}.Result(string(r.Value.([]byte)))
|
||||
return Result{string(r.Value.([]byte)), true}
|
||||
}
|
||||
|
||||
// Sub returns a new Embed anchored at a subdirectory within this mount.
|
||||
|
|
@ -418,9 +417,9 @@ func (s *Embed) ReadString(name string) Result {
|
|||
func (s *Embed) Sub(subDir string) Result {
|
||||
sub, err := fs.Sub(s.fsys, s.path(subDir))
|
||||
if err != nil {
|
||||
return Result{}.Result(nil, err)
|
||||
return Result{err, false}
|
||||
}
|
||||
return Result{}.Result(&Embed{fsys: sub, basedir: "."})
|
||||
return Result{&Embed{fsys: sub, basedir: "."}, true}
|
||||
}
|
||||
|
||||
// FS returns the underlying fs.FS.
|
||||
|
|
@ -489,10 +488,10 @@ func Extract(fsys fs.FS, targetDir string, data any, opts ...ExtractOptions) Res
|
|||
// Ensure target directory exists
|
||||
targetDir, err := filepath.Abs(targetDir)
|
||||
if err != nil {
|
||||
return Result{}.Result(nil, err)
|
||||
return Result{err, false}
|
||||
}
|
||||
if err := os.MkdirAll(targetDir, 0755); err != nil {
|
||||
return Result{}.Result(nil, err)
|
||||
return Result{err, false}
|
||||
}
|
||||
|
||||
// Categorise files
|
||||
|
|
@ -523,14 +522,14 @@ func Extract(fsys fs.FS, targetDir string, data any, opts ...ExtractOptions) Res
|
|||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return Result{}.Result(nil, err)
|
||||
return Result{err, false}
|
||||
}
|
||||
|
||||
// Create directories (names may contain templates)
|
||||
for _, dir := range dirs {
|
||||
target := renderPath(filepath.Join(targetDir, dir), data)
|
||||
if err := os.MkdirAll(target, 0755); err != nil {
|
||||
return Result{}.Result(nil, err)
|
||||
return Result{err, false}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -538,7 +537,7 @@ func Extract(fsys fs.FS, targetDir string, data any, opts ...ExtractOptions) Res
|
|||
for _, path := range templateFiles {
|
||||
tmpl, err := template.ParseFS(fsys, path)
|
||||
if err != nil {
|
||||
return Result{}.Result(nil, err)
|
||||
return Result{err, false}
|
||||
}
|
||||
|
||||
targetFile := renderPath(filepath.Join(targetDir, path), data)
|
||||
|
|
@ -556,11 +555,11 @@ func Extract(fsys fs.FS, targetDir string, data any, opts ...ExtractOptions) Res
|
|||
|
||||
f, err := os.Create(targetFile)
|
||||
if err != nil {
|
||||
return Result{}.Result(nil, err)
|
||||
return Result{err, false}
|
||||
}
|
||||
if err := tmpl.Execute(f, data); err != nil {
|
||||
f.Close()
|
||||
return Result{}.Result(nil, err)
|
||||
return Result{err, false}
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
|
|
@ -574,7 +573,7 @@ func Extract(fsys fs.FS, targetDir string, data any, opts ...ExtractOptions) Res
|
|||
}
|
||||
target := renderPath(filepath.Join(targetDir, targetPath), data)
|
||||
if err := copyFile(fsys, path, target); err != nil {
|
||||
return Result{}.Result(nil, err)
|
||||
return Result{err, false}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -156,9 +156,9 @@ func Op(err error) string {
|
|||
return ""
|
||||
}
|
||||
|
||||
// ErrCode extracts the error code from an error.
|
||||
// ErrorCode extracts the error code from an error.
|
||||
// Returns empty string if the error is not an *Err or has no code.
|
||||
func ErrCode(err error) string {
|
||||
func ErrorCode(err error) string {
|
||||
var e *Err
|
||||
if As(err, &e) {
|
||||
return e.Code
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ func (m *Fs) path(p string) string {
|
|||
// validatePath ensures the path is within the sandbox, following symlinks if they exist.
|
||||
func (m *Fs) validatePath(p string) Result {
|
||||
if m.root == "/" {
|
||||
return Result{Value: m.path(p), OK: true}
|
||||
return Result{m.path(p), true}
|
||||
}
|
||||
|
||||
// Split the cleaned path into components
|
||||
|
|
@ -66,7 +66,7 @@ func (m *Fs) validatePath(p string) Result {
|
|||
current = next
|
||||
continue
|
||||
}
|
||||
return Result{}
|
||||
return Result{err, false}
|
||||
}
|
||||
|
||||
// Verify the resolved part is still within the root
|
||||
|
|
@ -79,12 +79,12 @@ func (m *Fs) validatePath(p string) Result {
|
|||
}
|
||||
Print(os.Stderr, "[%s] SECURITY sandbox escape detected root=%s path=%s attempted=%s user=%s",
|
||||
time.Now().Format(time.RFC3339), m.root, p, realNext, username)
|
||||
return Result{}
|
||||
return Result{err, false}
|
||||
}
|
||||
current = realNext
|
||||
}
|
||||
|
||||
return Result{Value: current, OK: true}
|
||||
return Result{current, true}
|
||||
}
|
||||
|
||||
// Read returns file contents as string.
|
||||
|
|
@ -93,14 +93,11 @@ func (m *Fs) Read(p string) Result {
|
|||
if !vp.OK {
|
||||
return Result{}
|
||||
}
|
||||
r := &Result{}
|
||||
data, err := os.ReadFile(vp.Value.(string))
|
||||
if err != nil {
|
||||
return Result{}
|
||||
return Result{err, false}
|
||||
}
|
||||
r.Value = string(data)
|
||||
r.OK = true
|
||||
return *r
|
||||
return Result{string(data), true}
|
||||
}
|
||||
|
||||
// Write saves content to file, creating parent directories as needed.
|
||||
|
|
@ -115,28 +112,28 @@ func (m *Fs) Write(p, content string) Result {
|
|||
func (m *Fs) WriteMode(p, content string, mode os.FileMode) Result {
|
||||
vp := m.validatePath(p)
|
||||
if !vp.OK {
|
||||
return Result{}
|
||||
return vp
|
||||
}
|
||||
full := vp.Value.(string)
|
||||
if err := os.MkdirAll(filepath.Dir(full), 0755); err != nil {
|
||||
return Result{}
|
||||
return Result{err, false}
|
||||
}
|
||||
if err := os.WriteFile(full, []byte(content), mode); err != nil {
|
||||
return Result{}
|
||||
return Result{err, false}
|
||||
}
|
||||
return Result{OK: true}
|
||||
return Result{nil, true}
|
||||
}
|
||||
|
||||
// EnsureDir creates directory if it doesn't exist.
|
||||
func (m *Fs) EnsureDir(p string) Result {
|
||||
vp := m.validatePath(p)
|
||||
if !vp.OK {
|
||||
return Result{}
|
||||
return vp
|
||||
}
|
||||
if err := os.MkdirAll(vp.Value.(string), 0755); err != nil {
|
||||
return Result{}
|
||||
return Result{err, false}
|
||||
}
|
||||
return Result{OK: true}
|
||||
return Result{nil, true}
|
||||
}
|
||||
|
||||
// IsDir returns true if path is a directory.
|
||||
|
|
@ -206,11 +203,11 @@ func (m *Fs) Open(p string) Result {
|
|||
func (m *Fs) Create(p string) Result {
|
||||
vp := m.validatePath(p)
|
||||
if !vp.OK {
|
||||
return Result{}
|
||||
return vp
|
||||
}
|
||||
full := vp.Value.(string)
|
||||
if err := os.MkdirAll(filepath.Dir(full), 0755); err != nil {
|
||||
return Result{}
|
||||
return Result{err, false}
|
||||
}
|
||||
return Result{}.Result(os.Create(full))
|
||||
}
|
||||
|
|
@ -219,11 +216,11 @@ func (m *Fs) Create(p string) Result {
|
|||
func (m *Fs) Append(p string) Result {
|
||||
vp := m.validatePath(p)
|
||||
if !vp.OK {
|
||||
return Result{}
|
||||
return vp
|
||||
}
|
||||
full := vp.Value.(string)
|
||||
if err := os.MkdirAll(filepath.Dir(full), 0755); err != nil {
|
||||
return Result{}
|
||||
return Result{err, false}
|
||||
}
|
||||
return Result{}.Result(os.OpenFile(full, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644))
|
||||
}
|
||||
|
|
@ -242,32 +239,32 @@ func (m *Fs) WriteStream(path string) Result {
|
|||
func (m *Fs) Delete(p string) Result {
|
||||
vp := m.validatePath(p)
|
||||
if !vp.OK {
|
||||
return Result{}
|
||||
return vp
|
||||
}
|
||||
full := vp.Value.(string)
|
||||
if full == "/" || full == os.Getenv("HOME") {
|
||||
return Result{}
|
||||
}
|
||||
if err := os.Remove(full); err != nil {
|
||||
return Result{}
|
||||
return Result{err, false}
|
||||
}
|
||||
return Result{OK: true}
|
||||
return Result{nil, true}
|
||||
}
|
||||
|
||||
// DeleteAll removes a file or directory recursively.
|
||||
func (m *Fs) DeleteAll(p string) Result {
|
||||
vp := m.validatePath(p)
|
||||
if !vp.OK {
|
||||
return Result{}
|
||||
return vp
|
||||
}
|
||||
full := vp.Value.(string)
|
||||
if full == "/" || full == os.Getenv("HOME") {
|
||||
return Result{}
|
||||
}
|
||||
if err := os.RemoveAll(full); err != nil {
|
||||
return Result{}
|
||||
return Result{err, false}
|
||||
}
|
||||
return Result{OK: true}
|
||||
return Result{nil, true}
|
||||
}
|
||||
|
||||
// Rename moves a file or directory.
|
||||
|
|
@ -281,7 +278,7 @@ func (m *Fs) Rename(oldPath, newPath string) Result {
|
|||
return Result{}
|
||||
}
|
||||
if err := os.Rename(oldVp.Value.(string), newVp.Value.(string)); err != nil {
|
||||
return Result{}
|
||||
return Result{err, false}
|
||||
}
|
||||
return Result{OK: true}
|
||||
return Result{nil, true}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ func (c *Core) QueryAll(q Query) Result {
|
|||
results = append(results, r.Value)
|
||||
}
|
||||
}
|
||||
return Result{Value: results, OK: true}
|
||||
return Result{results, true}
|
||||
}
|
||||
|
||||
func (c *Core) RegisterQuery(handler QueryHandler) {
|
||||
|
|
|
|||
|
|
@ -362,32 +362,32 @@ func (le *LogErr) Log(err error) {
|
|||
if err == nil {
|
||||
return
|
||||
}
|
||||
le.log.Error(ErrorMessage(err), "op", Op(err), "code", ErrCode(err), "stack", FormatStackTrace(err))
|
||||
le.log.Error(ErrorMessage(err), "op", Op(err), "code", ErrorCode(err), "stack", FormatStackTrace(err))
|
||||
}
|
||||
|
||||
// --- LogPan: Panic-Aware Logger ---
|
||||
// --- LogPanic: Panic-Aware Logger ---
|
||||
|
||||
// LogPan logs panic context without crash file management.
|
||||
// LogPanic logs panic context without crash file management.
|
||||
// Primary action: log. Secondary: recover panics.
|
||||
type LogPan struct {
|
||||
type LogPanic struct {
|
||||
log *Log
|
||||
}
|
||||
|
||||
// NewLogPan creates a LogPan bound to the given logger.
|
||||
func NewLogPan(log *Log) *LogPan {
|
||||
return &LogPan{log: log}
|
||||
// NewLogPanic creates a LogPanic bound to the given logger.
|
||||
func NewLogPanic(log *Log) *LogPanic {
|
||||
return &LogPanic{log: log}
|
||||
}
|
||||
|
||||
// Recover captures a panic and logs it. Does not write crash files.
|
||||
// Use as: defer core.NewLogPan(logger).Recover()
|
||||
func (lp *LogPan) Recover() {
|
||||
// Use as: defer core.NewLogPanic(logger).Recover()
|
||||
func (lp *LogPanic) Recover() {
|
||||
r := recover()
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
err, ok := r.(error)
|
||||
if !ok {
|
||||
err = fmt.Errorf("%v", r)
|
||||
err = NewError(fmt.Sprint("panic: ", r))
|
||||
}
|
||||
lp.log.Error("panic recovered",
|
||||
"err", err,
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ func (r *ServiceRuntime[T]) Config() *Config { return r.core.Config() }
|
|||
func (c *Core) ServiceStartup(ctx context.Context, options any) Result {
|
||||
for _, s := range c.Startables() {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return Result{Value: err}
|
||||
return Result{err, false}
|
||||
}
|
||||
r := s.OnStart()
|
||||
if !r.OK {
|
||||
|
|
@ -52,7 +52,7 @@ func (c *Core) ServiceShutdown(ctx context.Context) Result {
|
|||
c.ACTION(ActionServiceShutdown{})
|
||||
for _, s := range c.Stoppables() {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return Result{Value: err}
|
||||
return Result{err, false}
|
||||
}
|
||||
s.OnStop()
|
||||
}
|
||||
|
|
@ -64,7 +64,7 @@ func (c *Core) ServiceShutdown(ctx context.Context) Result {
|
|||
select {
|
||||
case <-done:
|
||||
case <-ctx.Done():
|
||||
return Result{Value: ctx.Err()}
|
||||
return Result{ctx.Err(), false}
|
||||
}
|
||||
return Result{OK: true}
|
||||
}
|
||||
|
|
@ -99,7 +99,7 @@ func NewWithFactories(app any, factories map[string]ServiceFactory) Result {
|
|||
c.Service(name, svc)
|
||||
}
|
||||
}
|
||||
return Result{Value: &Runtime{app: app, Core: c}, OK: true}
|
||||
return Result{&Runtime{app: app, Core: c}, true}
|
||||
}
|
||||
|
||||
// NewRuntime creates a Runtime with no custom services.
|
||||
|
|
|
|||
|
|
@ -46,21 +46,21 @@ func (c *Core) Service(name string, service ...Service) Result {
|
|||
c.Lock("srv").Mu.RLock()
|
||||
v, ok := c.services.services[name]
|
||||
c.Lock("srv").Mu.RUnlock()
|
||||
return Result{Value: v, OK: ok}
|
||||
return Result{v, ok}
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
return Result{Value: E("core.Service", "service name cannot be empty", nil)}
|
||||
return Result{E("core.Service", "service name cannot be empty", nil), false}
|
||||
}
|
||||
|
||||
c.Lock("srv").Mu.Lock()
|
||||
defer c.Lock("srv").Mu.Unlock()
|
||||
|
||||
if c.services.locked {
|
||||
return Result{Value: E("core.Service", Concat("service \"", name, "\" not permitted — registry locked"), nil)}
|
||||
return Result{E("core.Service", Concat("service \"", name, "\" not permitted — registry locked"), nil), false}
|
||||
}
|
||||
if _, exists := c.services.services[name]; exists {
|
||||
return Result{Value: E("core.Service", Join(" ", "service", name, "already registered"), nil)}
|
||||
return Result{E("core.Service", Join(" ", "service", name, "already registered"), nil), false}
|
||||
}
|
||||
|
||||
srv := &service[0]
|
||||
|
|
|
|||
|
|
@ -111,13 +111,29 @@ func RuneCount(s string) int {
|
|||
return utf8.RuneCountInString(s)
|
||||
}
|
||||
|
||||
// NewBuilder returns a new strings.Builder.
|
||||
//
|
||||
// b := core.NewBuilder()
|
||||
// b.WriteString("hello")
|
||||
// b.String() // "hello"
|
||||
func NewBuilder() *strings.Builder {
|
||||
return &strings.Builder{}
|
||||
}
|
||||
|
||||
// NewReader returns a strings.NewReader for the given string.
|
||||
//
|
||||
// r := core.NewReader("hello world")
|
||||
func NewReader(s string) *strings.Reader {
|
||||
return strings.NewReader(s)
|
||||
}
|
||||
|
||||
// Concat joins variadic string parts into one string.
|
||||
// Hook point for validation, sanitisation, and security checks.
|
||||
//
|
||||
// core.Concat("cmd.", "deploy.to.homelab", ".description")
|
||||
// core.Concat("https://", host, "/api/v1")
|
||||
func Concat(parts ...string) string {
|
||||
var b strings.Builder
|
||||
b := NewBuilder()
|
||||
for _, p := range parts {
|
||||
b.WriteString(p)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,13 +51,13 @@ func Arg(index int, args ...any) Result {
|
|||
v := args[index]
|
||||
switch v.(type) {
|
||||
case string:
|
||||
return Result{Value: ArgString(index, args...), OK: true}
|
||||
return Result{ArgString(index, args...), true}
|
||||
case int:
|
||||
return Result{Value: ArgInt(index, args...), OK: true}
|
||||
return Result{ArgInt(index, args...), true}
|
||||
case bool:
|
||||
return Result{Value: ArgBool(index, args...), OK: true}
|
||||
return Result{ArgBool(index, args...), true}
|
||||
default:
|
||||
return Result{Value: v, OK: true}
|
||||
return Result{v, true}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,13 +39,13 @@ func TestWrapCode_Good(t *testing.T) {
|
|||
cause := errors.New("invalid email")
|
||||
err := WrapCode(cause, "VALIDATION_ERROR", "user.Validate", "bad input")
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "VALIDATION_ERROR", ErrCode(err))
|
||||
assert.Equal(t, "VALIDATION_ERROR", ErrorCode(err))
|
||||
}
|
||||
|
||||
func TestNewCode_Good(t *testing.T) {
|
||||
err := NewCode("NOT_FOUND", "resource not found")
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "NOT_FOUND", ErrCode(err))
|
||||
assert.Equal(t, "NOT_FOUND", ErrorCode(err))
|
||||
}
|
||||
|
||||
// --- Error Introspection ---
|
||||
|
|
|
|||
|
|
@ -120,17 +120,17 @@ func TestLogErr_Nil_Good(t *testing.T) {
|
|||
le.Log(nil) // should not panic
|
||||
}
|
||||
|
||||
// --- LogPan ---
|
||||
// --- LogPanic ---
|
||||
|
||||
func TestLogPan_Good(t *testing.T) {
|
||||
func TestLogPanic_Good(t *testing.T) {
|
||||
l := NewLog(LogOptions{Level: LevelInfo})
|
||||
lp := NewLogPan(l)
|
||||
lp := NewLogPanic(l)
|
||||
assert.NotNil(t, lp)
|
||||
}
|
||||
|
||||
func TestLogPan_Recover_Good(t *testing.T) {
|
||||
func TestLogPanic_Recover_Good(t *testing.T) {
|
||||
l := NewLog(LogOptions{Level: LevelInfo})
|
||||
lp := NewLogPan(l)
|
||||
lp := NewLogPanic(l)
|
||||
assert.NotPanics(t, func() {
|
||||
defer lp.Recover()
|
||||
panic("caught")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue