mirror of
https://github.com/SigNoz/signoz.git
synced 2026-06-09 10:30:27 +01:00
* fix: extended error fields * fix: remove stale comment * fix: removed retry policy enum and added example * fix: generate openapi * fix: stale comments
136 lines
4.1 KiB
Go
136 lines
4.1 KiB
Go
package render
|
|
|
|
import (
|
|
"math"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/SigNoz/signoz/pkg/errors"
|
|
jsoniter "github.com/json-iterator/go"
|
|
"github.com/tidwall/gjson"
|
|
)
|
|
|
|
const (
|
|
// Non-standard status code (originally introduced by nginx) for the case when a client closes
|
|
// the connection while the server is still processing the request.
|
|
statusClientClosedConnection = 499
|
|
)
|
|
|
|
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
|
|
|
type SuccessResponse struct {
|
|
Status string `json:"status" required:"true"`
|
|
Data interface{} `json:"data,omitempty" required:"true"`
|
|
}
|
|
|
|
type ErrorResponse struct {
|
|
Status string `json:"status" required:"true"`
|
|
Error *errors.JSON `json:"error" required:"true"`
|
|
}
|
|
|
|
func Success(rw http.ResponseWriter, httpCode int, data interface{}) {
|
|
body, err := json.Marshal(&SuccessResponse{Status: StatusSuccess.s, Data: data})
|
|
if err != nil {
|
|
Error(rw, err)
|
|
return
|
|
}
|
|
|
|
if httpCode == 0 {
|
|
httpCode = http.StatusOK
|
|
}
|
|
|
|
rw.Header().Set("Content-Type", "application/json")
|
|
|
|
rw.WriteHeader(httpCode)
|
|
_, _ = rw.Write(body)
|
|
}
|
|
|
|
func ErrorCodeFromBody(body []byte) string {
|
|
code := gjson.GetBytes(body, "error.code").String()
|
|
|
|
// This should never return empty since we only call this function on responses that were generated by us.
|
|
// If it does return empty, the codebase has failed to use render package for error responses somewhere, and we should fix that instead of trying to handle it here.
|
|
if code == "" {
|
|
return errors.CodeUnset.String()
|
|
}
|
|
|
|
return code
|
|
}
|
|
|
|
func ErrorTypeFromStatusCode(statusCode int) string {
|
|
// We are losing the exact type information here, but we can at least capture the error code and message for better observability.
|
|
// To get the exact type, we would need some changes in the render package to include the error type in the response, which we can consider in the future if there is a need for it.
|
|
switch statusCode {
|
|
case http.StatusBadRequest:
|
|
return errors.TypeInvalidInput.String()
|
|
case http.StatusNotFound:
|
|
return errors.TypeNotFound.String()
|
|
case http.StatusConflict:
|
|
return errors.TypeAlreadyExists.String()
|
|
case http.StatusUnauthorized:
|
|
return errors.TypeUnauthenticated.String()
|
|
case http.StatusNotImplemented:
|
|
return errors.TypeUnsupported.String()
|
|
case http.StatusForbidden:
|
|
return errors.TypeForbidden.String()
|
|
case statusClientClosedConnection:
|
|
return errors.TypeCanceled.String()
|
|
case http.StatusGatewayTimeout:
|
|
return errors.TypeTimeout.String()
|
|
case http.StatusUnavailableForLegalReasons:
|
|
return errors.TypeLicenseUnavailable.String()
|
|
case http.StatusTooManyRequests:
|
|
return errors.TypeTooManyRequests.String()
|
|
default:
|
|
return errors.TypeInternal.String()
|
|
}
|
|
}
|
|
|
|
func Error(rw http.ResponseWriter, cause error) {
|
|
// Derive the http code from the error type
|
|
t, _, _, _, _, _ := errors.Unwrapb(cause)
|
|
|
|
httpCode := http.StatusInternalServerError
|
|
switch t {
|
|
case errors.TypeInvalidInput:
|
|
httpCode = http.StatusBadRequest
|
|
case errors.TypeNotFound:
|
|
httpCode = http.StatusNotFound
|
|
case errors.TypeAlreadyExists:
|
|
httpCode = http.StatusConflict
|
|
case errors.TypeUnauthenticated:
|
|
httpCode = http.StatusUnauthorized
|
|
case errors.TypeUnsupported:
|
|
httpCode = http.StatusNotImplemented
|
|
case errors.TypeForbidden:
|
|
httpCode = http.StatusForbidden
|
|
case errors.TypeCanceled:
|
|
httpCode = statusClientClosedConnection
|
|
case errors.TypeTimeout:
|
|
httpCode = http.StatusGatewayTimeout
|
|
case errors.TypeFatal:
|
|
httpCode = http.StatusInternalServerError
|
|
case errors.TypeLicenseUnavailable:
|
|
httpCode = http.StatusUnavailableForLegalReasons
|
|
case errors.TypeTooManyRequests:
|
|
httpCode = http.StatusTooManyRequests
|
|
}
|
|
|
|
body, err := json.Marshal(&ErrorResponse{Status: StatusError.s, Error: errors.AsJSON(cause)})
|
|
if err != nil {
|
|
// this should never be the case
|
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Retry-After carries the explicit delay declared via
|
|
// errors.WithRetryAfter. Set it before WriteHeader so headers go on the wire.
|
|
d := errors.RetryDelayOf(cause)
|
|
if d.Seconds() > 0 {
|
|
rw.Header().Set("Retry-After", strconv.Itoa(int(math.Ceil(d.Seconds()))))
|
|
}
|
|
|
|
rw.WriteHeader(httpCode)
|
|
_, _ = rw.Write(body)
|
|
}
|