| package apierrors |
|
|
| import ( |
| "context" |
| "fmt" |
| "net/http" |
| "strings" |
|
|
| "github.com/go-chi/chi/v5/middleware" |
| ) |
|
|
| const ( |
| |
| ContentTypeKey = "Content-Type" |
| ContentTypeProblemValue = "application/problem+json" |
|
|
| |
| InternalType = "https://kongapi.info/konnect/internal" |
| InternalTitle = "Internal" |
| InternalDetail = "An internal failure occurred" |
|
|
| |
| UnavailableType = "https://kongapi.info/konnect/unavailable" |
| UnavailableTitle = "Unavailable" |
| UnavailableDetail = "The requested service is unavailable" |
|
|
| |
| NotImplementedType = "https://kongapi.info/konnect/not-implemented" |
| NotImplementedTitle = "Not Implemented" |
| NotImplementedDetail = "The requested functionality is not implemented" |
|
|
| |
| UnauthenticatedType = "https://kongapi.info/konnect/unauthenticated" |
| UnauthenticatedTitle = "Unauthenticated" |
| UnauthenticatedDetail = "A valid token is required" |
|
|
| |
| ForbiddenType = "https://kongapi.info/konnect/unauthorized" |
| ForbiddenTitle = "Forbidden" |
| ForbiddenDetail = "Permission denied" |
|
|
| |
| NotFoundType = "https://kongapi.info/konnect/not-found" |
| NotFoundTitle = "Not Found" |
| NotFoundDetail = "The requested %s was not found" |
| NotFoundDetails = "The requested %s were not found: %v" |
|
|
| |
| MethodNotAllowedType = "https://kongapi.info/konnect/method-not-allowed" |
| MethodNotAllowedTitle = "Method Not Allowed" |
| MethodNotAllowedDetail = "The requested method is not allowed" |
|
|
| |
| BadRequestType = "https://kongapi.info/konnect/bad-request" |
| BadRequestTitle = "Bad Request" |
|
|
| |
| GoneType = "https://kongapi.info/konnect/gone" |
| GoneTitle = "Gone" |
| GoneDetail = "The requested resource is no longer available" |
|
|
| |
| PreconditionFailedType = "https://kongapi.info/konnect/precondition-failed" |
| PreconditionFailedTitle = "Precondition Failed" |
|
|
| |
| RateLimitType = "https://kongapi.info/konnect/rate-limited" |
| RateLimitTitle = "Rate Limited" |
| RateLimitDetail = "Too many requests" |
|
|
| |
| ConflictType = "https://kongapi.info/konnect/resource-conflict" |
| ConflictTitle = "Conflict" |
|
|
| |
| EmptySetType = "Empty Set" |
| EmptySetCursorType = "Empty Set Cursor" |
| ) |
|
|
| |
| func NewInternalError(ctx context.Context, err error) *BaseAPIError { |
| return &BaseAPIError{ |
| Type: InternalType, |
| Status: http.StatusInternalServerError, |
| Title: InternalTitle, |
| Instance: instance(ctx), |
| Detail: InternalDetail, |
| UnderlyingError: err, |
| ctx: ctx, |
| } |
| } |
|
|
| |
| func NewServiceUnavailable(ctx context.Context, err error) *BaseAPIError { |
| return &BaseAPIError{ |
| Type: UnavailableType, |
| Status: http.StatusServiceUnavailable, |
| Title: UnavailableTitle, |
| Instance: instance(ctx), |
| Detail: UnavailableDetail, |
| UnderlyingError: err, |
| ctx: ctx, |
| } |
| } |
|
|
| |
| func NewUnauthenticatedError(ctx context.Context, err error) *BaseAPIError { |
| return &BaseAPIError{ |
| Type: UnauthenticatedType, |
| Status: http.StatusUnauthorized, |
| Title: UnauthenticatedTitle, |
| Instance: instance(ctx), |
| Detail: UnauthenticatedDetail, |
| UnderlyingError: err, |
| ctx: ctx, |
| } |
| } |
|
|
| |
| func NewForbiddenError(ctx context.Context, err error) *BaseAPIError { |
| return &BaseAPIError{ |
| Type: ForbiddenType, |
| Status: http.StatusForbidden, |
| Title: ForbiddenTitle, |
| Instance: instance(ctx), |
| Detail: ForbiddenDetail, |
| UnderlyingError: err, |
| ctx: ctx, |
| } |
| } |
|
|
| |
| func NewForbiddenErrorDetail(ctx context.Context, detailMessage string) *BaseAPIError { |
| return &BaseAPIError{ |
| Type: ForbiddenType, |
| Status: http.StatusForbidden, |
| Title: ForbiddenTitle, |
| Instance: instance(ctx), |
| Detail: MakeSentenceCase(detailMessage), |
| ctx: ctx, |
| } |
| } |
|
|
| |
| func NewNotFoundError(ctx context.Context, err error, entityType string) *BaseAPIError { |
| if entityType != "" { |
| return &BaseAPIError{ |
| Type: NotFoundType, |
| Status: http.StatusNotFound, |
| Title: NotFoundTitle, |
| Instance: instance(ctx), |
| Detail: fmt.Sprintf(NotFoundDetail, entityType), |
| UnderlyingError: err, |
| ctx: ctx, |
| } |
| } |
| return &BaseAPIError{ |
| Type: NotFoundType, |
| Status: http.StatusNotFound, |
| Title: NotFoundTitle, |
| Instance: instance(ctx), |
| UnderlyingError: err, |
| ctx: ctx, |
| } |
| } |
|
|
| |
| func NewNotFoundErrors( |
| ctx context.Context, |
| err error, |
| entityType string, |
| resources any, |
| ) *BaseAPIError { |
| return &BaseAPIError{ |
| Type: NotFoundType, |
| Status: http.StatusNotFound, |
| Title: NotFoundTitle, |
| Instance: instance(ctx), |
| Detail: fmt.Sprintf(NotFoundDetails, entityType, resources), |
| UnderlyingError: err, |
| ctx: ctx, |
| } |
| } |
|
|
| |
| func NewMethodNotAllowedError(ctx context.Context) *BaseAPIError { |
| return &BaseAPIError{ |
| Type: MethodNotAllowedType, |
| Status: http.StatusMethodNotAllowed, |
| Title: MethodNotAllowedTitle, |
| Instance: instance(ctx), |
| Detail: MethodNotAllowedDetail, |
| ctx: ctx, |
| } |
| } |
|
|
| |
| func NewBadRequestError(ctx context.Context, err error, invalidFields InvalidParameters) *BaseAPIError { |
| detail := BadRequestTitle |
| if len(invalidFields) > 0 { |
| detail = fmt.Sprintf("%s: %s", BadRequestTitle, invalidFields.String()) |
| } |
| return &BaseAPIError{ |
| Type: BadRequestType, |
| Status: http.StatusBadRequest, |
| Title: BadRequestTitle, |
| Instance: instance(ctx), |
| InvalidParameters: invalidFields, |
| UnderlyingError: err, |
| Detail: detail, |
| ctx: ctx, |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| func NewUnsupportedSortFieldError(ctx context.Context, field string, supported ...string) *BaseAPIError { |
| err := fmt.Errorf("unsupported sort field: %s", field) |
| return NewBadRequestError(ctx, err, InvalidParameters{ |
| { |
| Field: "sort", |
| Reason: fmt.Sprintf( |
| "unsupported sort field %q, supported fields: %s", |
| field, strings.Join(supported, ", "), |
| ), |
| Source: InvalidParamSourceQuery, |
| }, |
| }) |
| } |
|
|
| |
| func NewGoneError(ctx context.Context, err error) *BaseAPIError { |
| return &BaseAPIError{ |
| Type: GoneType, |
| Status: http.StatusGone, |
| Title: GoneTitle, |
| Instance: instance(ctx), |
| Detail: GoneDetail, |
| UnderlyingError: err, |
| ctx: ctx, |
| } |
| } |
|
|
| |
| func NewPreconditionFailedError(ctx context.Context, precondition string) *BaseAPIError { |
| return &BaseAPIError{ |
| Type: PreconditionFailedType, |
| Status: http.StatusPreconditionFailed, |
| Title: PreconditionFailedTitle, |
| Instance: instance(ctx), |
| Detail: MakeSentenceCase(precondition), |
| UnderlyingError: fmt.Errorf("precondition failed: %s", precondition), |
| ctx: ctx, |
| } |
| } |
|
|
| |
| func NewRateLimitError(ctx context.Context) *BaseAPIError { |
| return &BaseAPIError{ |
| Type: RateLimitType, |
| Status: http.StatusTooManyRequests, |
| Title: RateLimitTitle, |
| Instance: instance(ctx), |
| Detail: RateLimitDetail, |
| ctx: ctx, |
| } |
| } |
|
|
| func NewConflictError(ctx context.Context, err error, detail string) *BaseAPIError { |
| return &BaseAPIError{ |
| Type: ConflictType, |
| Status: http.StatusConflict, |
| Title: ConflictTitle, |
| Instance: instance(ctx), |
| Detail: detail, |
| UnderlyingError: err, |
| ctx: ctx, |
| } |
| } |
|
|
| func NewEmptySetResponse(ctx context.Context, cursorPagination bool) *BaseAPIError { |
| bae := &BaseAPIError{ |
| Status: http.StatusOK, |
| ctx: ctx, |
| } |
|
|
| if cursorPagination { |
| bae.Type = EmptySetCursorType |
| } else { |
| bae.Type = EmptySetType |
| } |
|
|
| return bae |
| } |
|
|
| func NewNotImplementedError(ctx context.Context, err error) *BaseAPIError { |
| return &BaseAPIError{ |
| Type: NotImplementedType, |
| Status: http.StatusNotImplemented, |
| Title: NotImplementedTitle, |
| Instance: instance(ctx), |
| Detail: NotImplementedDetail, |
| UnderlyingError: err, |
| ctx: ctx, |
| } |
| } |
|
|
| |
| func MakeSentenceCase(msg string) string { |
| if msg == "" { |
| return "" |
| } |
| return strings.ToUpper(msg[:1]) + msg[1:] |
| } |
|
|
| |
| |
| func instance(ctx context.Context) string { |
| reqID := middleware.GetReqID(ctx) |
| if reqID != "" { |
| return fmt.Sprintf("urn:request:%s", reqID) |
| } |
| return "" |
| } |
|
|