feat(commands): add tri-state command executor contract

This commit is contained in:
mingmxren 2026-03-01 14:50:34 +08:00
parent a299913d78
commit 0844cbbce4
2 changed files with 120 additions and 0 deletions

71
pkg/commands/executor.go Normal file
View file

@ -0,0 +1,71 @@
package commands
import (
"context"
"fmt"
)
type Outcome int
const (
OutcomePassthrough Outcome = iota
OutcomeHandled
OutcomeRejected
)
type ExecuteResult struct {
Outcome Outcome
Command string
Reply string
Err error
}
type Executor struct {
reg *Registry
}
func NewExecutor(reg *Registry) *Executor {
return &Executor{reg: reg}
}
func (e *Executor) Execute(ctx context.Context, req Request, _ any) ExecuteResult {
cmdName, ok := parseCommandName(req.Text)
if !ok {
return ExecuteResult{Outcome: OutcomePassthrough}
}
if e == nil || e.reg == nil {
return ExecuteResult{Outcome: OutcomePassthrough, Command: cmdName}
}
for _, def := range e.reg.ForChannel(req.Channel) {
if !matchesCommand(def, cmdName) {
continue
}
if def.Handler == nil {
return ExecuteResult{Outcome: OutcomePassthrough, Command: def.Name}
}
err := def.Handler(ctx, req)
return ExecuteResult{Outcome: OutcomeHandled, Command: def.Name, Err: err}
}
for _, def := range e.reg.defs {
if !matchesCommand(def, cmdName) {
continue
}
return ExecuteResult{
Outcome: OutcomeRejected,
Command: def.Name,
Reply: fmt.Sprintf("Command /%s is not supported on %s.", def.Name, req.Channel),
}
}
return ExecuteResult{Outcome: OutcomePassthrough, Command: cmdName}
}
func matchesCommand(def Definition, cmdName string) bool {
if def.Name == cmdName {
return true
}
return contains(def.Aliases, cmdName)
}

View file

@ -0,0 +1,49 @@
package commands
import (
"context"
"testing"
)
func TestExecutor_RegisteredButUnsupported_ReturnsRejected(t *testing.T) {
defs := []Definition{{Name: "show", Channels: []string{"telegram"}}}
ex := NewExecutor(NewRegistry(defs))
res := ex.Execute(context.Background(), Request{Channel: "whatsapp", Text: "/show"}, nil)
if res.Outcome != OutcomeRejected {
t.Fatalf("outcome=%v, want=%v", res.Outcome, OutcomeRejected)
}
}
func TestExecutor_UnknownSlashCommand_ReturnsPassthrough(t *testing.T) {
defs := []Definition{{Name: "show", Channels: []string{"telegram"}}}
ex := NewExecutor(NewRegistry(defs))
res := ex.Execute(context.Background(), Request{Channel: "telegram", Text: "/unknown"}, nil)
if res.Outcome != OutcomePassthrough {
t.Fatalf("outcome=%v, want=%v", res.Outcome, OutcomePassthrough)
}
}
func TestExecutor_SupportedCommandWithHandler_ReturnsHandled(t *testing.T) {
called := false
defs := []Definition{
{
Name: "help",
Channels: []string{"telegram"},
Handler: func(context.Context, Request) error {
called = true
return nil
},
},
}
ex := NewExecutor(NewRegistry(defs))
res := ex.Execute(context.Background(), Request{Channel: "telegram", Text: "/help@my_bot"}, nil)
if res.Outcome != OutcomeHandled {
t.Fatalf("outcome=%v, want=%v", res.Outcome, OutcomeHandled)
}
if !called {
t.Fatalf("expected handler to be called")
}
}