feat(cli): JSON output envelope with stable error codes
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
// Package cli implements command dispatch and the agent JSON envelope.
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
CodeConfig = "config"
|
||||
CodeDB = "db"
|
||||
CodeNetwork = "network"
|
||||
CodeAuth = "auth"
|
||||
CodePolicy = "policy"
|
||||
CodeNotFound = "not_found"
|
||||
CodeUsage = "usage"
|
||||
)
|
||||
|
||||
// Envelope is the single JSON object every agent command emits.
|
||||
type Envelope struct {
|
||||
Error bool `json:"error"`
|
||||
ErrorDetail map[string]any `json:"error_detail"`
|
||||
Data any `json:"data"`
|
||||
}
|
||||
|
||||
func Success(data any) Envelope {
|
||||
if data == nil {
|
||||
data = map[string]any{}
|
||||
}
|
||||
return Envelope{Error: false, ErrorDetail: map[string]any{}, Data: data}
|
||||
}
|
||||
|
||||
func Failure(code, message string) Envelope {
|
||||
return Envelope{
|
||||
Error: true,
|
||||
ErrorDetail: map[string]any{"code": code, "message": message},
|
||||
Data: map[string]any{},
|
||||
}
|
||||
}
|
||||
|
||||
func (e Envelope) Write(w io.Writer) error {
|
||||
b, err := json.Marshal(e)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b = append(b, '\n')
|
||||
_, err = w.Write(b)
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSuccessEnvelope(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
if err := Success(map[string]any{"count": 2}).Write(&buf); err != nil {
|
||||
t.Fatalf("write: %v", err)
|
||||
}
|
||||
var got map[string]any
|
||||
if err := json.Unmarshal(buf.Bytes(), &got); err != nil {
|
||||
t.Fatalf("unmarshal: %v", err)
|
||||
}
|
||||
if got["error"] != false {
|
||||
t.Fatalf("error should be false: %v", got["error"])
|
||||
}
|
||||
if _, ok := got["data"]; !ok {
|
||||
t.Fatal("data key missing")
|
||||
}
|
||||
if ed, ok := got["error_detail"].(map[string]any); !ok || len(ed) != 0 {
|
||||
t.Fatalf("error_detail should be empty object: %v", got["error_detail"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestFailureEnvelope(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
_ = Failure(CodeNotFound, "uid 7 not found").Write(&buf)
|
||||
var got struct {
|
||||
Error bool `json:"error"`
|
||||
ErrorDetail struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
} `json:"error_detail"`
|
||||
}
|
||||
_ = json.Unmarshal(buf.Bytes(), &got)
|
||||
if !got.Error || got.ErrorDetail.Code != "not_found" {
|
||||
t.Fatalf("bad failure envelope: %+v", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user