feat(commands): add tri-state command executor contract
This commit is contained in:
parent
a299913d78
commit
0844cbbce4
2 changed files with 120 additions and 0 deletions
71
pkg/commands/executor.go
Normal file
71
pkg/commands/executor.go
Normal 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)
|
||||||
|
}
|
||||||
49
pkg/commands/executor_test.go
Normal file
49
pkg/commands/executor_test.go
Normal 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")
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue