Files
acp/error.go
T
Samuel Cui 4d5dbd0b8c Release ACP v0.2.3
Simplify indexed copy facts and unify staged output completion.
Fix cleanup, rewrite persistence, native paths, and terminal reporting.
Maintain compatibility, upstream notices, and automated release checks.
2026-10-05 16:29:55 +08:00

98 lines
2.5 KiB
Go

package acp
import (
"encoding/json"
"errors"
"fmt"
"reflect"
"github.com/samuelncui/acp/internal/fileio"
)
var (
_ = error(new(Error))
_ = json.Marshaler(new(Error))
_ = json.Unmarshaler(new(Error))
)
// Error is one pipeline-level failure. Its JSON form stores the message, so a report stays
// readable with encoding/json and an absent error stays absent.
type Error struct {
Src string `json:"src,omitempty"`
Dst string `json:"dst,omitempty"`
Err error `json:"error,omitempty"`
}
type jsonError struct {
Src string `json:"src,omitempty"`
Dst string `json:"dst,omitempty"`
Err string `json:"error,omitempty"`
}
// isNilError reports whether an error interface holds no error at all, including the typed
// nil a nil concrete pointer produces.
func isNilError(err error) bool {
if err == nil {
return true
}
value := reflect.ValueOf(err)
switch value.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
return value.IsNil()
}
return false
}
func (e *Error) Error() string {
if e == nil {
return "<nil>"
}
if isNilError(e.Err) {
return fmt.Sprintf("[%s => %s]", e.Src, e.Dst)
}
return fmt.Sprintf("[%s => %s]: %s", e.Src, e.Dst, e.Err)
}
// MarshalJSON encodes the message of an error. A missing error stays missing: it is omitted
// instead of being rendered from a nil value, so marshalling cannot panic on the nil errors
// UnmarshalJSON can produce.
func (e *Error) MarshalJSON() ([]byte, error) {
if e == nil {
return []byte("null"), nil
}
// Source and target paths are identities, so JSON must never normalize invalid bytes.
if err := fileio.CheckJSONPaths(e.Src, e.Dst); err != nil {
return nil, err
}
// Keep an absent failure absent instead of inventing a message.
message := ""
if !isNilError(e.Err) {
message = e.Err.Error()
}
return json.Marshal(&jsonError{Src: e.Src, Dst: e.Dst, Err: message})
}
// UnmarshalJSON decodes the object form or null. An absent or empty message leaves Err nil,
// so a round trip neither loses an error nor invents one.
func (e *Error) UnmarshalJSON(buf []byte) error {
if e == nil {
return errors.New("decode acp error failed, receiver is nil")
}
// Decode into a value so null represents an absent error without a nil pointer.
var m jsonError
if err := json.Unmarshal(buf, &m); err != nil {
return err
}
// Replace the prior error only after the document has decoded successfully.
e.Src, e.Dst, e.Err = m.Src, m.Dst, nil
if m.Err != "" {
e.Err = errors.New(m.Err)
}
return nil
}