diff --git a/conjure-go-contract/errors/http.go b/conjure-go-contract/errors/http.go index 2d34bb4f..5ae7d986 100644 --- a/conjure-go-contract/errors/http.go +++ b/conjure-go-contract/errors/http.go @@ -15,10 +15,12 @@ package errors import ( + "context" "encoding/json" "net/http" "github.com/palantir/conjure-go-runtime/v2/conjure-go-contract/codecs" + "github.com/palantir/witchcraft-go-logging/wlog/svclog/svc1log" ) // WriteErrorResponse writes error to the response writer. @@ -52,3 +54,35 @@ func WriteErrorResponse(w http.ResponseWriter, e Error) { w.WriteHeader(e.Code().StatusCode()) _, _ = w.Write(marshaledError) // There is nothing we can do on write failure. } + +func WriteErrorResponseWithContext(ctx context.Context, w http.ResponseWriter, e Error) { + var marshaledError []byte + var err error + + // First try to marshal with custom handling (if present) + if marshaler, ok := e.(json.Marshaler); ok { + marshaledError, err = codecs.JSON.Marshal(marshaler) + } + // If we fail, use best-effort conversion to SerializableError. + if marshaledError == nil || err != nil { + params, err := codecs.JSON.Marshal(mergeParams(e)) // on failure, params will be nil + if err != nil { + params = nil + } + // This should never fail, since all fields other than params are primitives + // and we fall back to empty params if they fail above. Nothing we can do otherwise. + marshaledError, _ = codecs.JSON.Marshal(SerializableError{ + ErrorCode: e.Code(), + ErrorName: e.Name(), + ErrorInstanceID: e.InstanceID(), + Parameters: params, + }) + } + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(e.Code().StatusCode()) + _, rwWriteErr := w.Write(marshaledError) // There is nothing we can do on write failure. + if rwWriteErr != nil { + svc1log.FromContext(ctx).Error("Failed to write response", svc1log.Stacktrace(rwWriteErr)) + } +} diff --git a/conjure-go-server/httpserver/handlers.go b/conjure-go-server/httpserver/handlers.go index 760f82f7..613bbb3a 100644 --- a/conjure-go-server/httpserver/handlers.go +++ b/conjure-go-server/httpserver/handlers.go @@ -66,7 +66,7 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch e := cause.(type) { case errors.Error: // if error is a conjure error, use WriteErrorResponse utility - errors.WriteErrorResponse(w, e) + errors.WriteErrorResponseWithContext(r.Context(), w, e) case json.Marshaler: // else if error is a json marshaler, write as json WriteJSONResponse(w, e, status)