diff --git a/error.go b/error.go index 6a23c26a..ef54353a 100644 --- a/error.go +++ b/error.go @@ -118,6 +118,25 @@ func NewError(c Code, underlying error) *Error { return &Error{code: c, err: underlying} } +// NewWireError is similar to [NewError], but the resulting *Error returns true +// when tested with [IsWireError]. +// +// This is useful for clients trying to propagate partial failures from +// streaming RPCs. Often, these RPCs include error information in their +// response messages (for example, [gRPC server reflection] and +// OpenTelemtetry's [OTLP]). Clients propagating these errors up the stack +// should use NewWireError to clarify that the error was explicitly sent by the +// server (rather than inferred from a lower-level networking error or +// timeout). +// +// [gRPC server reflection]: https://github.com/grpc/grpc/blob/v1.49.2/src/proto/grpc/reflection/v1alpha/reflection.proto#L132-L136 +// [OTLP]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/otlp.md#partial-success +func NewWireError(c Code, underlying error) *Error { + err := NewError(c, underlying) + err.wireErr = true + return err +} + // IsWireError checks whether the error was returned by the server, as opposed // to being synthesized by the client. // diff --git a/protocol_connect.go b/protocol_connect.go index 46f2df62..c6a3582e 100644 --- a/protocol_connect.go +++ b/protocol_connect.go @@ -943,8 +943,7 @@ func (e *connectWireError) asError() *Error { if e.Code < minCode || e.Code > maxCode { e.Code = CodeUnknown } - err := NewError(e.Code, errors.New(e.Message)) - err.wireErr = true + err := NewWireError(e.Code, errors.New(e.Message)) if len(e.Details) > 0 { err.details = make([]*ErrorDetail, len(e.Details)) for i, detail := range e.Details { diff --git a/protocol_grpc.go b/protocol_grpc.go index faa99d44..8af62309 100644 --- a/protocol_grpc.go +++ b/protocol_grpc.go @@ -701,8 +701,7 @@ func grpcErrorFromTrailer(bufferPool *bufferPool, protobuf Codec, trailer http.H return errorf(CodeInternal, "gRPC protocol error: invalid error code %q", codeHeader) } message := grpcPercentDecode(bufferPool, trailer.Get(grpcHeaderMessage)) - retErr := NewError(Code(code), errors.New(message)) - retErr.wireErr = true + retErr := NewWireError(Code(code), errors.New(message)) detailsBinaryEncoded := trailer.Get(grpcHeaderDetails) if len(detailsBinaryEncoded) > 0 {