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 a description of the error that occurred.
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 function that performs classification.
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 the most generic available classifier.
func NewTopLevelGenericErrWrapper(err error) *ErrWrapper {
	return NewErrWrapper(ClassifyGenericError, TopLevelOperation, err)
}
