Files
acp/error.go
T
Samuel Cui ac02c71d3f feat!: stream caller-owned items and own the result queue
Reshape the public API around one push engine and restore the af05f05c
surface as a compatibility shell over it.

- NewStream takes caller-owned items (Source/Targets), reports every accepted
  item through one results callback, and Wait returns the run's terminal error.
  Run, BatchSource and the item callbacks are gone.
- The engine owns the result path: WithResultBuffer is the depth of the result
  queue, WithResultBatch is how many results one delivery carries, and
  WithResultFlushInterval is how long a successful result may wait. A result
  that carries any error is delivered immediately as its own batch.
- Restore the af05f05c API byte-compatibly in the same package: New, Copyer,
  Wait/WaitErr, AccurateJob, WildcardJob, the report events and the JSON report
  round-trip through encoding/json alone.
- One descriptor serves a whole item; the stored hash is read through the
  descriptor that reads the content and the computed hash is published through
  it before it closes.
- Make every handoff escapable by a hard stop: the read buffer, a chunk send,
  an event send and the result buffer.
- Fix the recorded sweep findings: report indent and round-trip, cmd/acp exit
  status, acp-rewrite final paths, mmap EOF/close/slice, disk-usage
  reservations, ErrTargetIO identity, the fatal-panic safety net, option
  validation, and the FreeBSD/Windows build paths.
- Cover the contract with semantic tests, add Makefile check/race/vet/cross
  targets, and document the withdrawn v0.1.0/v0.2.0 migration in README.
2026-09-19 15:18:13 +08:00

88 lines
2.1 KiB
Go

package acp
import (
"encoding/json"
"errors"
"fmt"
"reflect"
)
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
}
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. An absent or empty message leaves Err nil, which is
// what a report stores for it, 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")
}
m := new(jsonError)
if err := json.Unmarshal(buf, &m); err != nil {
return err
}
e.Src, e.Dst, e.Err = m.Src, m.Dst, nil
if m.Err != "" {
e.Err = errors.New(m.Err)
}
return nil
}