go/pkg/core/contract.go
Snider 173067719e fix: resolve Codex review findings — stale comments, constructor patterns
- config.go: comments updated from Cfg/NewEtc to Config/NewConfig
- service.go: comment updated from NewSrv to NewService
- embed.go: comments updated from Emb to Embed
- command.go: panic strings updated from NewSubFunction to NewChildCommandFunction
- fs.go: error ops updated from local.Delete to core.Delete
- core.go: header updated to reflect actual file contents
- contract.go: thin constructors inlined as struct literals (NewConfig, NewService, NewCoreI18n, NewBus)

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-18 09:37:34 +00:00

223 lines
5.3 KiB
Go

// SPDX-License-Identifier: EUPL-1.2
// Contracts, options, and type definitions for the Core framework.
package core
import (
"context"
"embed"
"fmt"
"io/fs"
"reflect"
"strings"
)
// Contract specifies operational guarantees for Core and its services.
type Contract struct {
DontPanic bool
DisableLogging bool
}
// Option is a function that configures the Core.
type Option func(*Core) error
// Message is the type for IPC broadcasts (fire-and-forget).
type Message any
// Query is the type for read-only IPC requests.
type Query any
// Task is the type for IPC requests that perform side effects.
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
}
// QueryHandler handles Query requests. Returns (result, handled, error).
type QueryHandler func(*Core, Query) (any, bool, error)
// TaskHandler handles Task requests. Returns (result, handled, error).
type TaskHandler func(*Core, Task) (any, bool, error)
// Startable is implemented by services that need startup initialisation.
type Startable interface {
OnStartup(ctx context.Context) error
}
// Stoppable is implemented by services that need shutdown cleanup.
type Stoppable interface {
OnShutdown(ctx context.Context) error
}
// ConfigService provides access to application configuration.
type ConfigService interface {
Get(key string, out any) error
Set(key string, v any) error
}
// --- Action Messages ---
type ActionServiceStartup struct{}
type ActionServiceShutdown struct{}
type ActionTaskStarted struct {
TaskID string
Task Task
}
type ActionTaskProgress struct {
TaskID string
Task Task
Progress float64
Message string
}
type ActionTaskCompleted struct {
TaskID string
Task Task
Result any
Error error
}
// --- Constructor ---
// New creates a Core instance with the provided options.
func New(opts ...Option) (*Core, error) {
defaultFS, _ := NewIO("/")
app := NewApp("", "", "")
c := &Core{
app: app,
fs: defaultFS,
cfg: &Config{settings: make(map[string]any), features: make(map[string]bool)},
err: &ErrPan{},
log: &ErrLog{&ErrOpts{Log: defaultLog}},
cli: NewCoreCli(app),
srv: &Service{Services: make(map[string]any)},
lock: &Lock{},
i18n: &I18n{},
}
c.ipc = &Ipc{core: c}
for _, o := range opts {
if err := o(c); err != nil {
return nil, err
}
}
c.LockApply()
return c, nil
}
// --- With* Options ---
// WithService registers a service with auto-discovered name and IPC handler.
func WithService(factory func(*Core) (any, error)) Option {
return func(c *Core) error {
serviceInstance, err := factory(c)
if err != nil {
return E("core.WithService", "failed to create service", err)
}
if serviceInstance == nil {
return E("core.WithService", "service factory returned nil instance", nil)
}
typeOfService := reflect.TypeOf(serviceInstance)
if typeOfService.Kind() == reflect.Ptr {
typeOfService = typeOfService.Elem()
}
pkgPath := typeOfService.PkgPath()
parts := strings.Split(pkgPath, "/")
name := strings.ToLower(parts[len(parts)-1])
if name == "" {
return E("core.WithService", fmt.Sprintf("service name could not be discovered for type %T (PkgPath is empty)", serviceInstance), nil)
}
instanceValue := reflect.ValueOf(serviceInstance)
handlerMethod := instanceValue.MethodByName("HandleIPCEvents")
if handlerMethod.IsValid() {
if handler, ok := handlerMethod.Interface().(func(*Core, Message) error); ok {
c.RegisterAction(handler)
} else {
return E("core.WithService", fmt.Sprintf("service %q has HandleIPCEvents but wrong signature; expected func(*Core, Message) error", name), nil)
}
}
result := c.Service(name, serviceInstance)
if err, ok := result.(error); ok {
return err
}
return nil
}
}
// WithName registers a service with an explicit name.
func WithName(name string, factory func(*Core) (any, error)) Option {
return func(c *Core) error {
serviceInstance, err := factory(c)
if err != nil {
return E("core.WithName", fmt.Sprintf("failed to create service %q", name), err)
}
result := c.Service(name, serviceInstance)
if err, ok := result.(error); ok {
return err
}
return nil
}
}
// WithApp injects the GUI runtime (e.g., Wails App).
func WithApp(runtime any) Option {
return func(c *Core) error {
c.app.Runtime = runtime
return nil
}
}
// WithAssets mounts embedded assets.
func WithAssets(efs embed.FS) Option {
return func(c *Core) error {
sub, err := Mount(efs, ".")
if err != nil {
return E("core.WithAssets", "failed to mount assets", err)
}
c.emb = sub
return nil
}
}
// WithIO sandboxes filesystem I/O to a root path.
func WithIO(root string) Option {
return func(c *Core) error {
io, err := NewIO(root)
if err != nil {
return E("core.WithIO", "failed to create IO at "+root, err)
}
c.fs = io
return nil
}
}
// WithMount mounts an fs.FS at a specific subdirectory.
func WithMount(fsys fs.FS, basedir string) Option {
return func(c *Core) error {
sub, err := Mount(fsys, basedir)
if err != nil {
return E("core.WithMount", "failed to mount "+basedir, err)
}
c.emb = sub
return nil
}
}
// WithServiceLock prevents service registration after initialisation.
func WithServiceLock() Option {
return func(c *Core) error {
c.LockEnable()
return nil
}
}