diff --git a/conjure-go-client/httpclient/request_params.go b/conjure-go-client/httpclient/request_params.go index 904b4a2f..b541b1e4 100644 --- a/conjure-go-client/httpclient/request_params.go +++ b/conjure-go-client/httpclient/request_params.go @@ -23,6 +23,7 @@ import ( "time" "github.com/palantir/conjure-go-runtime/v2/conjure-go-contract/codecs" + "github.com/palantir/conjure-go-runtime/v2/conjure-go-contract/errors" werror "github.com/palantir/witchcraft-go-error" ) @@ -230,3 +231,14 @@ func WithRequestTimeout(timeout time.Duration) RequestParam { return nil }) } + +func WithRequestConjureErrorDecoder(ced errors.ConjureErrorDecoder) RequestParam { + return requestParamFunc(func(b *requestBuilder) error { + b.errorDecoderMiddleware = errorDecoderMiddleware{ + errorDecoder: restErrorDecoder{ + conjureErrorDecoder: ced, + }, + } + return nil + }) +} diff --git a/conjure-go-client/httpclient/response_error_decoder_middleware.go b/conjure-go-client/httpclient/response_error_decoder_middleware.go index 603ea36a..829e43fc 100644 --- a/conjure-go-client/httpclient/response_error_decoder_middleware.go +++ b/conjure-go-client/httpclient/response_error_decoder_middleware.go @@ -66,7 +66,9 @@ func (e errorDecoderMiddleware) RoundTrip(req *http.Request, next http.RoundTrip // If the response has a Content-Type containing 'application/json', we attempt // to unmarshal the error as a conjure error. See TestErrorDecoderMiddlewares for // example error messages and parameters. -type restErrorDecoder struct{} +type restErrorDecoder struct { + conjureErrorDecoder errors.ConjureErrorDecoder +} var _ ErrorDecoder = restErrorDecoder{} @@ -102,7 +104,13 @@ func (d restErrorDecoder) DecodeError(resp *http.Response) error { if isJSON := strings.Contains(resp.Header.Get("Content-Type"), codecs.JSON.ContentType()); !isJSON { return werror.Error(resp.Status, wSafeParams, wUnsafeParams, werror.UnsafeParam("responseBody", string(body))) } - conjureErr, jsonErr := errors.UnmarshalError(body) + var conjureErr errors.Error + var jsonErr error + if d.conjureErrorDecoder != nil { + conjureErr, jsonErr = errors.UnmarshalErrorWithDecoder(d.conjureErrorDecoder, body) + } else { + conjureErr, jsonErr = errors.UnmarshalError(body) + } if jsonErr != nil { return werror.Error(resp.Status, wSafeParams, wUnsafeParams, werror.UnsafeParam("responseBody", string(body))) } diff --git a/conjure-go-contract/errors/conjure_error_decoder.go b/conjure-go-contract/errors/conjure_error_decoder.go new file mode 100644 index 00000000..6ec98f07 --- /dev/null +++ b/conjure-go-contract/errors/conjure_error_decoder.go @@ -0,0 +1,19 @@ +// Copyright (c) 2024 Palantir Technologies. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package errors + +type ConjureErrorDecoder interface { + DecodeConjureError(name string, body []byte) (Error, error) +} diff --git a/conjure-go-contract/errors/unmarshal.go b/conjure-go-contract/errors/unmarshal.go index a0921f2c..22c4e540 100644 --- a/conjure-go-contract/errors/unmarshal.go +++ b/conjure-go-contract/errors/unmarshal.go @@ -46,3 +46,19 @@ func UnmarshalError(body []byte) (Error, error) { // Cast should never panic, as we've verified in RegisterErrorType return instance.(Error), nil } + +// UnmarshalErrorWithDecoder attempts to deserialize the message to a known implementation of Error +// using the provided ConjureErrorDecoder. +func UnmarshalErrorWithDecoder(ced ConjureErrorDecoder, body []byte) (Error, error) { + var name struct { + Name string `json:"errorName"` + } + if err := codecs.JSON.Unmarshal(body, &name); err != nil { + return nil, werror.Wrap(err, "failed to unmarshal body as conjure error") + } + cErr, err := ced.DecodeConjureError(name.Name, body) + if err != nil { + return nil, werror.Wrap(err, "failed to decode body using ConjureErrorDecoder") + } + return cErr, nil +}