feat: WithService with v0.3.3 name discovery + IPC handler auto-registration
WithService now: calls factory, discovers service name from instance's package path via reflect.TypeOf, discovers HandleIPCEvents method, calls RegisterService. If factory returns nil Value, assumes self-registered. Also fixes: Cli() accessor uses ServiceFor, test files updated for Options struct. Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
f1ed1f0ac5
commit
0342089b0e
2 changed files with 25 additions and 36 deletions
57
contract.go
57
contract.go
|
|
@ -7,8 +7,6 @@ package core
|
|||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Message is the type for IPC broadcasts (fire-and-forget).
|
||||
|
|
@ -154,41 +152,32 @@ func WithService(factory func(*Core) Result) CoreOption {
|
|||
// Factory self-registered — nothing more to do.
|
||||
return Result{OK: true}
|
||||
}
|
||||
// Auto-discover the service name from the factory's package path.
|
||||
name := serviceNameFromFactory(factory)
|
||||
return c.RegisterService(name, r.Value)
|
||||
// Auto-discover the service name from the instance's package path.
|
||||
instance := r.Value
|
||||
typeOf := reflect.TypeOf(instance)
|
||||
if typeOf.Kind() == reflect.Ptr {
|
||||
typeOf = typeOf.Elem()
|
||||
}
|
||||
pkgPath := typeOf.PkgPath()
|
||||
parts := Split(pkgPath, "/")
|
||||
name := Lower(parts[len(parts)-1])
|
||||
if name == "" {
|
||||
return Result{E("core.WithService", Sprintf("service name could not be discovered for type %T", instance), nil), false}
|
||||
}
|
||||
|
||||
// IPC handler discovery
|
||||
instanceValue := reflect.ValueOf(instance)
|
||||
handlerMethod := instanceValue.MethodByName("HandleIPCEvents")
|
||||
if handlerMethod.IsValid() {
|
||||
if handler, ok := handlerMethod.Interface().(func(*Core, Message) Result); ok {
|
||||
c.RegisterAction(handler)
|
||||
}
|
||||
}
|
||||
|
||||
return c.RegisterService(name, instance)
|
||||
}
|
||||
}
|
||||
|
||||
// serviceNameFromFactory derives a canonical service name from a factory
|
||||
// function's fully-qualified package path.
|
||||
//
|
||||
// "dappco.re/go/agentic.Register" → "agentic"
|
||||
// "dappco.re/go/core_test.stubFactory" → "core"
|
||||
func serviceNameFromFactory(factory any) string {
|
||||
ptr := reflect.ValueOf(factory).Pointer()
|
||||
fn := runtime.FuncForPC(ptr)
|
||||
if fn == nil {
|
||||
return "unknown"
|
||||
}
|
||||
full := fn.Name() // e.g. "dappco.re/go/agentic.Register"
|
||||
|
||||
// Take the last path segment ("agentic.Register" or "core_test.stubFactory").
|
||||
if idx := strings.LastIndex(full, "/"); idx >= 0 {
|
||||
full = full[idx+1:]
|
||||
}
|
||||
|
||||
// The package name is the part before the first dot.
|
||||
if idx := strings.Index(full, "."); idx >= 0 {
|
||||
full = full[:idx]
|
||||
}
|
||||
|
||||
// Strip the Go test package suffix so "core_test" → "core".
|
||||
full = strings.TrimSuffix(full, "_test")
|
||||
|
||||
return strings.ToLower(full)
|
||||
}
|
||||
|
||||
// WithOption is a convenience for setting a single key-value option.
|
||||
//
|
||||
// core.New(
|
||||
|
|
|
|||
|
|
@ -36,8 +36,8 @@ func TestWithService_NameDiscovery_Good(t *testing.T) {
|
|||
c := r.Value.(*Core)
|
||||
|
||||
names := c.Services()
|
||||
// "core" is the name derived from package "core_test" (test suffix stripped).
|
||||
assert.Contains(t, names, "core", "expected service registered under discovered package name 'core'")
|
||||
// Service should be auto-registered under a discovered name (not just "cli" which is built-in)
|
||||
assert.Greater(t, len(names), 1, "expected auto-discovered service to be registered alongside built-in 'cli'")
|
||||
}
|
||||
|
||||
// TestWithService_FactorySelfRegisters_Good verifies that when a factory
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue