File size: 5,427 Bytes
04f1444 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | package apierrors
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"github.com/openmeterio/openmeter/api/v3/render"
)
// BaseAPIError is the schema for all API apierrors.
type BaseAPIError struct {
// A unique identifier for this error. When dereferenced it must provide
// human-readable documentation for the problem. The URL must follow
// (#122 - Resource Names) and must not contain URI fragments type.
Type string `json:"type"`
// The HTTP status code of the error. Useful when passing the response body
// to child properties in a frontend UI. Must be returned as an integer.
Status int `json:"status"`
// A short, human-readable summary of the problem. It should not change
// between occurrences of a problem, except for localization. Should be
// provided as "Sentence case" for direct use in the UI.
Title string `json:"title"`
// Used to return the correlation ID back to the user, in the format
// {product}:trace:<trace_id>. This helps us find the relevant logs when a
// customer reports an issue.
Instance string `json:"instance"`
// A human-readable explanation specific to this occurrence of the problem.
// This field may contain request/entity data to help the user understand
// what went wrong. Enclose variable values in square brackets. Should be
// provided as "Sentence case" for direct use in the UI.
Detail string `json:"detail"`
// Used to indicate which fields have invalid values when validated. Both a
// human-readable value (reason) and a type that can be used for localized
// results (rule) are provided.
InvalidParameters InvalidParameters `json:"invalid_parameters,omitempty"`
// UnderlyingError is the underlying error stack to be logged.
// NOTE: this should not be returned to callers.
UnderlyingError error `json:"-"`
// The context used to extract a logger.
ctx context.Context
}
// InvalidParameters is a collection of fields that failed input validation.
type InvalidParameters []InvalidParameter
type InvalidParameterSource uint8
const (
InvalidParamSourcePath InvalidParameterSource = iota + 1
InvalidParamSourceQuery
InvalidParamSourceBody
InvalidParamSourceHeader
)
func (i InvalidParameterSource) String() string {
switch i {
case InvalidParamSourceBody:
return "body"
case InvalidParamSourcePath:
return "path"
case InvalidParamSourceHeader:
return "header"
case InvalidParamSourceQuery:
return "query"
}
return ""
}
func ToInvalid(s string) InvalidParameterSource {
switch s {
case "query":
return InvalidParamSourceQuery
case "path":
return InvalidParamSourcePath
case "body":
return InvalidParamSourceBody
case "header":
return InvalidParamSourceHeader
}
return InvalidParameterSource(0)
}
func (i InvalidParameterSource) MarshalJSON() ([]byte, error) {
return json.Marshal(i.String())
}
func (i *InvalidParameterSource) UnmarshalJSON(data []byte) error {
var source string
if err := json.Unmarshal(data, &source); err != nil {
return err
}
*i = ToInvalid(source)
return nil
}
// InvalidParameter is a single field that failed input validation.
type InvalidParameter struct {
// Field concerned by the error.
Field string `json:"field"`
// Rule represents the rule that has triggered the error.
Rule string `json:"rule,omitempty"`
// Reason describes why the error has been triggered.
Reason string `json:"reason"`
// Source describes where the error has been triggered: body, header, path.
Source InvalidParameterSource `json:"source"`
// Choices represents the available choices for value in a case of an enum.
Choices []string `json:"choices,omitempty"`
// Minimum is an optional field for setting the minimum required value for
// an attribute.
Minimum *int `json:"minimum,omitempty"`
// Maximum is an optional field for setting the maximum required value for
// an attribute.
Maximum *int `json:"maximum,omitempty"`
// Dependents is an optional field for when the rule "dependent_fields" is
// applied.
Dependents []string `json:"dependents,omitempty"`
}
// Stringer method for a collection of InvalidParameter entities.
func (ips InvalidParameters) String() string {
out := new(strings.Builder)
for i, param := range ips {
out.WriteString(param.Field)
if param.Rule != "" {
_, _ = fmt.Fprintf(out, " [%s]", param.Rule)
}
_, _ = fmt.Fprintf(out, ": %s", param.Reason)
if i != len(ips)-1 {
_, _ = fmt.Fprintf(out, ", ")
}
}
return out.String()
}
// Error satisfies the error interface.
func (bae *BaseAPIError) Error() string {
switch {
case bae.InvalidParameters != nil:
return fmt.Sprintf("%s: %s", bae.UnderlyingError, bae.InvalidParameters.String())
case bae.Detail != "" && bae.UnderlyingError != nil:
return fmt.Sprintf("%s: %s", bae.Detail, bae.UnderlyingError)
case bae.Detail != "":
return bae.Detail
}
if bae.UnderlyingError != nil {
return bae.UnderlyingError.Error()
}
return bae.Title
}
// Unwrap returns the underlying error
func (bae *BaseAPIError) Unwrap() error {
return bae.UnderlyingError
}
// Context is the context that created the error
func (bae *BaseAPIError) Context() context.Context {
return bae.ctx
}
// HandleAPIError is a helper function that accepts an error
func (bae *BaseAPIError) HandleAPIError(
w http.ResponseWriter,
r *http.Request,
) {
_ = render.RenderJSON(w, bae, render.WithContentType(ContentTypeProblemValue), render.WithStatus(bae.Status))
}
|