ooni-probe-cli/internal/netxlite/errwrapper.go

103 lines
3.3 KiB
Go
Raw Normal View History

package netxlite
import "encoding/json"
// ErrWrapper is our error wrapper for Go errors. The key objective of
// this structure is to properly set Failure, which is also returned by
// the Error() method, to be one of the OONI failure strings.
//
// OONI failure strings are defined in the github.com/ooni/spec repo
// at https://github.com/ooni/spec/blob/master/data-formats/df-007-errors.md.
type ErrWrapper struct {
// Failure is the OONI failure string. The failure strings are
// loosely backward compatible with Measurement Kit.
//
// This is either one of the FailureXXX strings or any other
// string like `unknown_failure: ...`. The latter represents an
// error that we have not yet mapped to a failure.
Failure string
// Operation is the operation that failed.
//
// New code will always nest ErrWrapper and you need to
// walk the chain to find what happened.
//
// The following comment describes the DEPRECATED
// legacy behavior implements by internal/engine/legacy/errorsx:
//
// If possible, the Operation string
// SHOULD be a _major_ operation. Major operations are:
//
// - ResolveOperation: resolving a domain name failed
// - ConnectOperation: connecting to an IP failed
// - TLSHandshakeOperation: TLS handshaking failed
// - HTTPRoundTripOperation: other errors during round trip
//
// Because a network connection doesn't necessarily know
// what is the current major operation we also have the
// following _minor_ operations:
//
// - CloseOperation: CLOSE failed
// - ReadOperation: READ failed
// - WriteOperation: WRITE failed
//
// If an ErrWrapper referring to a major operation is wrapping
// another ErrWrapper and such ErrWrapper already refers to
// a major operation, then the new ErrWrapper should use the
// child ErrWrapper major operation. Otherwise, it should use
// its own major operation. This way, the topmost wrapper is
// supposed to refer to the major operation that failed.
Operation string
// WrappedErr is the error that we're wrapping.
WrappedErr error
}
// Error returns the OONI failure string for this error.
func (e *ErrWrapper) Error() string {
return e.Failure
}
// Unwrap allows to access the underlying error.
func (e *ErrWrapper) Unwrap() error {
return e.WrappedErr
}
// MarshalJSON converts an ErrWrapper to a JSON value.
func (e *ErrWrapper) MarshalJSON() ([]byte, error) {
return json.Marshal(e.Failure)
}
// Classifier is the type of the function that maps a Go error
// to a OONI failure string defined at
// https://github.com/ooni/spec/blob/master/data-formats/df-007-errors.md.
type Classifier func(err error) string
// NewErrWrapper creates a new ErrWrapper using the given
// classifier, operation name, and underlying error.
//
// This function panics if classifier is nil, or operation
// is the empty string or error is nil.
func NewErrWrapper(c Classifier, op string, err error) *ErrWrapper {
if c == nil {
panic("nil classifier")
}
if op == "" {
panic("empty op")
}
if err == nil {
panic("nil err")
}
return &ErrWrapper{
Failure: c(err),
Operation: op,
WrappedErr: err,
}
}
// NewTopLevelGenericErrWrapper wraps an error occurring at top
// level using ClassifyGenericError as classifier.
func NewTopLevelGenericErrWrapper(err error) *ErrWrapper {
return NewErrWrapper(ClassifyGenericError, TopLevelOperation, err)
}