Add a shared error-code catalog so failures can be traced by stable codes.
Services can resolve legacy aliases and expose the catalog over CLI and HTTP instead of ad-hoc error strings. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+176
@@ -0,0 +1,176 @@
|
||||
// Package cli 实现 errcode 命令:list / show / search / validate / export / serve。
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"error-code-system/errcode"
|
||||
"error-code-system/server"
|
||||
)
|
||||
|
||||
// Main 执行命令行并返回进程退出码。
|
||||
func Main(args []string) int {
|
||||
return Run(args, os.Stdout, os.Stderr)
|
||||
}
|
||||
|
||||
// Run 执行命令。stdout 写结果,stderr 写用法与错误。
|
||||
func Run(args []string, stdout, stderr io.Writer) int {
|
||||
if len(args) == 0 {
|
||||
fmt.Fprintln(stderr, usage)
|
||||
return 2
|
||||
}
|
||||
switch args[0] {
|
||||
case "list":
|
||||
return cmdList(args[1:], stdout, stderr)
|
||||
case "show":
|
||||
return cmdShow(args[1:], stdout, stderr)
|
||||
case "search":
|
||||
return cmdSearch(args[1:], stdout, stderr)
|
||||
case "validate":
|
||||
return cmdValidate(args[1:], stdout, stderr)
|
||||
case "export":
|
||||
return cmdExport(args[1:], stdout, stderr)
|
||||
case "serve":
|
||||
return cmdServe(args[1:], stderr)
|
||||
case "-h", "--help", "help":
|
||||
fmt.Fprintln(stdout, usage)
|
||||
return 0
|
||||
default:
|
||||
fmt.Fprintf(stderr, "未知命令 %q\n%s\n", args[0], usage)
|
||||
return 2
|
||||
}
|
||||
}
|
||||
|
||||
const usage = `用法:
|
||||
errcode list [--domain 域] [--json]
|
||||
errcode show <码或别名>
|
||||
errcode search <关键词>
|
||||
errcode validate <码或别名>
|
||||
errcode export [--domain 域]
|
||||
errcode serve [--addr :8091]`
|
||||
|
||||
func registry() *errcode.Registry { return errcode.Default }
|
||||
|
||||
func cmdList(args []string, stdout, stderr io.Writer) int {
|
||||
fs := flag.NewFlagSet("list", flag.ContinueOnError)
|
||||
fs.SetOutput(stderr)
|
||||
domain := fs.String("domain", "", "只列出该域")
|
||||
asJSON := fs.Bool("json", false, "以 JSON 输出")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return 2
|
||||
}
|
||||
defs := registry().List(*domain)
|
||||
if *asJSON {
|
||||
enc := json.NewEncoder(stdout)
|
||||
enc.SetEscapeHTML(false)
|
||||
enc.SetIndent("", " ")
|
||||
if err := enc.Encode(defs); err != nil {
|
||||
fmt.Fprintln(stderr, err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
for _, def := range defs {
|
||||
fmt.Fprintf(stdout, "%s\t%d\t%s\t%s\n", def.Code, def.HTTP, def.Retry, def.Summary)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func cmdShow(args []string, stdout, stderr io.Writer) int {
|
||||
if len(args) != 1 {
|
||||
fmt.Fprintln(stderr, "用法: errcode show <码或别名>")
|
||||
return 2
|
||||
}
|
||||
def, ok := registry().Lookup(args[0])
|
||||
if !ok {
|
||||
fmt.Fprintf(stderr, "未登记: %s(回落 %s)\n", args[0], errcode.SysUnknownCode)
|
||||
return 1
|
||||
}
|
||||
enc := json.NewEncoder(stdout)
|
||||
enc.SetEscapeHTML(false)
|
||||
enc.SetIndent("", " ")
|
||||
if err := enc.Encode(def); err != nil {
|
||||
fmt.Fprintln(stderr, err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func cmdSearch(args []string, stdout, stderr io.Writer) int {
|
||||
if len(args) < 1 {
|
||||
fmt.Fprintln(stderr, "用法: errcode search <关键词>")
|
||||
return 2
|
||||
}
|
||||
q := strings.Join(args, " ")
|
||||
for _, def := range registry().Search(q) {
|
||||
fmt.Fprintf(stdout, "%s\t%s\n", def.Code, def.Summary)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func cmdValidate(args []string, stdout, stderr io.Writer) int {
|
||||
if len(args) != 1 {
|
||||
fmt.Fprintln(stderr, "用法: errcode validate <码或别名>")
|
||||
return 2
|
||||
}
|
||||
def, ok := registry().Lookup(args[0])
|
||||
resolved := def
|
||||
if !ok {
|
||||
resolved = registry().Resolve(args[0])
|
||||
}
|
||||
fmt.Fprintf(stdout, "input=%s known=%t resolved=%s\n", args[0], ok, resolved.Code)
|
||||
if !ok {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func cmdExport(args []string, stdout, stderr io.Writer) int {
|
||||
fs := flag.NewFlagSet("export", flag.ContinueOnError)
|
||||
fs.SetOutput(stderr)
|
||||
domain := fs.String("domain", "", "只导出该域")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return 2
|
||||
}
|
||||
var data []byte
|
||||
var err error
|
||||
if *domain == "" {
|
||||
data, err = registry().ExportJSON()
|
||||
} else {
|
||||
encBuf := &strings.Builder{}
|
||||
enc := json.NewEncoder(encBuf)
|
||||
enc.SetEscapeHTML(false)
|
||||
enc.SetIndent("", " ")
|
||||
err = enc.Encode(registry().List(*domain))
|
||||
data = []byte(encBuf.String())
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Fprintln(stderr, err)
|
||||
return 1
|
||||
}
|
||||
if _, err := stdout.Write(data); err != nil {
|
||||
fmt.Fprintln(stderr, err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func cmdServe(args []string, stderr io.Writer) int {
|
||||
fs := flag.NewFlagSet("serve", flag.ContinueOnError)
|
||||
fs.SetOutput(stderr)
|
||||
addr := fs.String("addr", server.DefaultAddr, "监听地址")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return 2
|
||||
}
|
||||
fmt.Fprintf(stderr, "errcode serve %s\n", *addr)
|
||||
if err := server.New(registry()).ListenAndServe(*addr); err != nil {
|
||||
fmt.Fprintln(stderr, err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"error-code-system/domains"
|
||||
"error-code-system/errcode"
|
||||
)
|
||||
|
||||
func TestCLIListShowValidate(t *testing.T) {
|
||||
reg := errcode.NewRegistry()
|
||||
domains.RegisterAll(reg)
|
||||
prev := errcode.Default
|
||||
errcode.Default = reg
|
||||
t.Cleanup(func() { errcode.Default = prev })
|
||||
|
||||
var out, errBuf bytes.Buffer
|
||||
if code := Run([]string{"list", "--domain", "sys"}, &out, &errBuf); code != 0 {
|
||||
t.Fatal(errBuf.String())
|
||||
}
|
||||
if !strings.Contains(out.String(), "sys.internal") || strings.Contains(out.String(), "agent.") {
|
||||
t.Fatalf("list:\n%s", out.String())
|
||||
}
|
||||
|
||||
out.Reset()
|
||||
if code := Run([]string{"show", "TIMEOUT"}, &out, &errBuf); code != 0 {
|
||||
t.Fatal(errBuf.String())
|
||||
}
|
||||
if !strings.Contains(out.String(), string(errcode.AgentTimeout)) {
|
||||
t.Fatalf("show:\n%s", out.String())
|
||||
}
|
||||
|
||||
out.Reset()
|
||||
if code := Run([]string{"validate", "nope"}, &out, &errBuf); code != 1 {
|
||||
t.Fatalf("validate exit %d %s", code, out.String())
|
||||
}
|
||||
if !strings.Contains(out.String(), "known=false") || !strings.Contains(out.String(), string(errcode.SysUnknownCode)) {
|
||||
t.Fatalf("validate:\n%s", out.String())
|
||||
}
|
||||
|
||||
out.Reset()
|
||||
if code := Run([]string{"search", "角色"}, &out, &errBuf); code != 0 {
|
||||
t.Fatal(errBuf.String())
|
||||
}
|
||||
if !strings.Contains(out.String(), string(errcode.SquadRoleNotFound)) {
|
||||
t.Fatalf("search:\n%s", out.String())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user