feat(smalltrip): remove exceptions package in favor of problem
This commit is contained in:
@@ -1,913 +0,0 @@
|
||||
// Copyright 2025-present Gustavo "Guz" L. de Mello
|
||||
// Copyright 2025-present The Lored.dev Contributors
|
||||
//
|
||||
// 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 exception
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BadRequest creates a new [Exception] with the "400 Bad Request" status code,
|
||||
// a human readable message and the provided error describing what in the request
|
||||
// was wrong. The severity of this Exception by default is [WARN].
|
||||
//
|
||||
// // "The HTTP 400 Bad Request client error response status code indicates that the
|
||||
// // server would not process the request due to something the server considered to
|
||||
// // be a client error. The reason for a 400 response is typically due to malformed
|
||||
// // request syntax, invalid request message framing, or deceptive request routing.
|
||||
// //
|
||||
// // Clients that receive a 400 response should expect that repeating the request
|
||||
// // without modification will fail with the same error."
|
||||
// //
|
||||
// // - Quoted from "400 Bad Request" by Mozilla Contributors, licensed under CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400
|
||||
func BadRequest(err error, opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusBadRequest),
|
||||
WithCode("Bad Request"),
|
||||
WithMessage("The request sent is malformed, see the provided error for more information."),
|
||||
WithError(err),
|
||||
WithSeverity(WARN),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// Unathorized creates a new [Exception] with the "401 Unathorized" status code,
|
||||
// a human readable message and error. The severity of this Exception by default
|
||||
// is [WARN]. A "WWW-Authenticate" header should be sent with this exception,
|
||||
// provided via the "authenticate" parameter.
|
||||
//
|
||||
// // "The HTTP 401 Unauthorized client error response status code indicates that a
|
||||
// // request was not successful because it lacks valid authentication credentials
|
||||
// // for the requested resource. This status code is sent with an HTTP WWW-Authenticate
|
||||
// // response header that contains information on the authentication scheme the
|
||||
// // server expects the client to include to make the request successfully.
|
||||
// //
|
||||
// // A 401 Unauthorized is similar to the 403 Forbidden response, except that a 403
|
||||
// // is returned when a request contains valid credentials, but the client does not
|
||||
// // have permissions to perform a certain action."
|
||||
// //
|
||||
// // - Quoted from "401 Unathorized" by Mozilla Contributors, licensed under CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401
|
||||
func Unathorized(authenticate string, opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusUnauthorized),
|
||||
WithCode("Unathorized"),
|
||||
WithMessage("Not authorized/authenticated to be able to do this request."),
|
||||
WithError(errors.New("user agent is not authenticated to do the request")),
|
||||
WithSeverity(WARN),
|
||||
|
||||
WithHeader("WWW-Authenticate", authenticate),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// PaymentRequired creates a new [Exception] with the "402 Payment Required" status code,
|
||||
// a human readable message and error. The severity of this Exception by default
|
||||
// is [WARN].
|
||||
//
|
||||
// // "The HTTP 402 Payment Required client error response status code is a nonstandard
|
||||
// // response status code reserved for future use.
|
||||
// //
|
||||
// // This status code was created to enable digital cash or (micro) payment systems
|
||||
// // and would indicate that requested content is not available until the client makes
|
||||
// // a payment. No standard use convention exists and different systems use it in
|
||||
// // different contexts.
|
||||
// //
|
||||
// // [...]
|
||||
// //
|
||||
// // This status code is reserved but not defined. Actual implementations vary in
|
||||
// // the format and contents of the response. No browser supports a 402, and an error
|
||||
// // will be displayed as a generic 4xx status code."
|
||||
// //
|
||||
// // - Quoted from "402 Payment Required" by Mozilla Contributors, licensed under CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/402
|
||||
func PaymentRequired(opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusPaymentRequired),
|
||||
WithCode("Payment Required"),
|
||||
WithMessage("Payment is required to be able to see this page."),
|
||||
WithError(errors.New("user agent needs to have payed to see this content")),
|
||||
WithSeverity(WARN),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// Forbidden creates a new [Exception] with the "403 Forbidden" status code,
|
||||
// a human readable message and error. The severity of this Exception by default
|
||||
// is [WARN].
|
||||
//
|
||||
// // "The HTTP 403 Forbidden client error response status code indicates that the
|
||||
// // server understood the request but refused to process it. This status is similar
|
||||
// // to 401, except that for 403 Forbidden responses, authenticating or
|
||||
// // re-authenticating makes no difference. The request failure is tied to application
|
||||
// // logic, such as insufficient permissions to a resource or action.
|
||||
// //
|
||||
// // Clients that receive a 403 response should expect that repeating the request
|
||||
// // without modification will fail with the same error. Server owners may decide
|
||||
// // to send a 404 response instead of a 403 if acknowledging the existence of a
|
||||
// // resource to clients with insufficient privileges is not desired."
|
||||
// //
|
||||
// // - Quoted from "403 Forbidden" by Mozilla Contributors, licensed under CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403
|
||||
func Forbidden(opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusForbidden),
|
||||
WithCode("Forbidden"),
|
||||
WithMessage("You do not have the rights to do this request."),
|
||||
WithError(errors.New("user agent does not have the rights to do the request")),
|
||||
WithSeverity(WARN),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// NotFound creates a new [Exception] with the "404 Not Found" status code,
|
||||
// a human readable message and error. The severity of this Exception by default
|
||||
// is [WARN].
|
||||
//
|
||||
// // "The HTTP 404 Not Found client error response status code indicates that the
|
||||
// // server cannot find the requested resource. Links that lead to a 404 page are
|
||||
// // often called broken or dead links and can be subject to link rot.
|
||||
// //
|
||||
// // A 404 status code only indicates that the resource is missing without indicating
|
||||
// // if this is temporary or permanent. If a resource is permanently removed, servers
|
||||
// // should send the 410 Gone status instead.
|
||||
// //
|
||||
// // 404 errors on a website can lead to a poor user experience for your visitors,
|
||||
// // so the number of broken links (internal and external) should be minimized to
|
||||
// // prevent frustration for readers. Common causes of 404 responses are mistyped
|
||||
// // URLs or pages that are moved or deleted without redirection. For more
|
||||
// // information, see the Redirections in HTTP guide."
|
||||
// //
|
||||
// // - Quoted from "404 Not Found" by Mozilla Contributors, licensed under CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404
|
||||
func NotFound(opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusNotFound),
|
||||
WithCode("Not Found"),
|
||||
WithMessage("This content does not exists."),
|
||||
WithError(errors.New("user agent requested content that does not exists")),
|
||||
WithSeverity(WARN),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// MethodNotAllowed creates a new [Exception] with the "405 Method Not Allowed" status code,
|
||||
// a human readable message and error, and a "Allow" header with the methods provided via the
|
||||
// "allowed" parameter. The severity of this Exception by default is [WARN].
|
||||
//
|
||||
// // "The HTTP 405 Method Not Allowed client error response status code indicates
|
||||
// // that the server knows the request method, but the target resource doesn't support
|
||||
// // this method. The server must generate an Allow header in a 405 response with a
|
||||
// // list of methods that the target resource currently supports.
|
||||
// //
|
||||
// // Improper server-side permissions set on files or directories may cause a 405
|
||||
// // response when the request would otherwise be expected to succeed."
|
||||
// //
|
||||
// // - Quoted from "405 Method Not Allowed" by Mozilla Contributors, licensed under CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405
|
||||
func MethodNotAllowed(allowed []string, opts ...Option) Exception {
|
||||
a := strings.Join(allowed, ", ")
|
||||
o := []Option{
|
||||
WithStatus(http.StatusMethodNotAllowed),
|
||||
WithCode("Method Not Allowed"),
|
||||
WithMessage("The method is not allowed for this endpoints."),
|
||||
WithError(fmt.Errorf("user agent tried to use method which is not a allowed method (allowed: %s)", a)),
|
||||
WithSeverity(WARN),
|
||||
|
||||
WithHeader("Allow", a),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// NotAcceptable creates a new [Exception] with the "406 Not Acceptable" status code,
|
||||
// a human readable message and error, and a list of accepted mime types provided via
|
||||
// the "accepted" parameter. The severity of this Exception by default is [WARN].
|
||||
//
|
||||
// // "The HTTP 406 Not Acceptable client error response status code indicates that the
|
||||
// // server could not produce a response matching the list of acceptable values defined
|
||||
// // in the request's proactive content negotiation headers and that the server was
|
||||
// // unwilling to supply a default representation.
|
||||
// //
|
||||
// // [...]
|
||||
// //
|
||||
// // A server may return responses that differ from the request's accept headers.
|
||||
// // In such cases, a 200 response with a default resource that doesn't match the client's
|
||||
// // list of acceptable content negotiation values may be preferable to sending a 406 response.
|
||||
// //
|
||||
// // If a server returns a 406, the body of the message should contain the list of
|
||||
// // available representations for the resource, allowing the user to choose, although
|
||||
// // no standard way for this is defined."
|
||||
// //
|
||||
// // - Quoted from "406 Not Acceptable" by Mozilla Contributors, licensed under CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/406
|
||||
func NotAcceptable(accepted []string, opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusNotAcceptable),
|
||||
WithCode("Not Acceptable"),
|
||||
WithMessage("Unable to find any content that conforms to the request."),
|
||||
WithError(errors.New("no content conforms to the requested criteria of the user agent")),
|
||||
WithData("accepted", accepted),
|
||||
WithSeverity(WARN),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// ProxyAuthenticationRequired creates a new [Exception] with the "407 Proxy Authentication Required"
|
||||
// status code, a human readable message and error. The severity of this Exception by default is [WARN].
|
||||
// A "Proxy-Authenticate" header should be sent with this exception, provided via the
|
||||
// "authenticate" parameter.
|
||||
//
|
||||
// // "The HTTP 407 Proxy Authentication Required client error response status code indicates that
|
||||
// // the request did not succeed because it lacks valid authentication credentials for the proxy
|
||||
// // server that sits between the client and the server with access to the requested resource.
|
||||
// //
|
||||
// // This response is sent with a Proxy-Authenticate header that contains information on how to
|
||||
// // correctly authenticate requests. The client may repeat the request with a new or replaced
|
||||
// // Proxy-Authorization header field."
|
||||
// //
|
||||
// // - Quoted from "407 Proxy Authentication Required" by Mozilla Contributors, licensed under CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/407
|
||||
func ProxyAuthenticationRequired(authenticate string, opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusProxyAuthRequired),
|
||||
WithCode("Proxy Authentication Required"),
|
||||
WithMessage("Authorization/authentication via proxy is needed to access this content."),
|
||||
WithError(errors.New("user agent is missing proxy authorization/authentication")),
|
||||
WithSeverity(WARN),
|
||||
|
||||
WithHeader("Proxy-Authenticate", authenticate),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// RequestTimeout creates a new [Exception] with the "408 Request Timeout" status code, a human
|
||||
// readable message and error, with a "Connection: close" header alongside. The severity of this
|
||||
// Exception by default is [WARN].
|
||||
//
|
||||
// // "The HTTP 408 Request Timeout client error response status code indicates that the server
|
||||
// // would like to shut down this unused connection. A 408 is sent on an idle connection by some
|
||||
// // servers, even without any previous request by the client.
|
||||
// //
|
||||
// // A server should send the Connection: close header field in the response, since 408 implies
|
||||
// // that the server has decided to close the connection rather than continue waiting.
|
||||
// //
|
||||
// // This response is used much more since some browsers, like Chrome and Firefox, use HTTP
|
||||
// // pre-connection mechanisms to speed up surfing."
|
||||
// //
|
||||
// // - Quoted from "408 Request Timeout" by Mozilla Contributors, licensed under CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408
|
||||
func RequestTimeout(opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusRequestTimeout),
|
||||
WithCode("Request Timeout"),
|
||||
WithMessage("This request was shut down."),
|
||||
WithError(errors.New("request timed out")),
|
||||
WithSeverity(WARN),
|
||||
|
||||
WithHeader("Connection", "close"),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// Conflict creates a new [Exception] with the "409 Conflict" status code, a human
|
||||
// readable message and error. The severity of this Exception by default is [WARN].
|
||||
//
|
||||
// // "The HTTP 409 Conflict client error response status code indicates a request
|
||||
// // conflict with the current state of the target resource.
|
||||
// //
|
||||
// // In WebDAV remote web authoring, 409 conflict responses are errors sent to the
|
||||
// // client so that a user might be able to resolve a conflict and resubmit the
|
||||
// // request. [...] Additionally, you may get a 409 response when uploading a file
|
||||
// // that is older than the existing one on the server, resulting in a version
|
||||
// // control conflict.
|
||||
// //
|
||||
// // In other systems, 409 responses may be used for implementation-specific purposes,
|
||||
// // such as to indicate that the server has received multiple requests to update
|
||||
// // the same resource."
|
||||
// //
|
||||
// // - Quoted from "409 Conflict" by Mozilla Contributors, licensed under CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409
|
||||
func Conflict(opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusConflict),
|
||||
WithCode("Conflict"),
|
||||
WithMessage("Request conflicts with the current state of the server."),
|
||||
WithError(errors.New("user agent sent a request which conflicts with the current state")),
|
||||
WithSeverity(WARN),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// Gone creates a new [Exception] with the "410 Gone" status code, a human
|
||||
// readable message and error. The severity of this Exception by default is [WARN].
|
||||
//
|
||||
// // "The HTTP 410 Gone client error response status code indicates that the target
|
||||
// // resource is no longer available at the origin server and that this condition
|
||||
// // is likely to be permanent. A 410 response is cacheable by default.
|
||||
// //
|
||||
// // Clients should not repeat requests for resources that return a 410 response,
|
||||
// // and website owners should remove or replace links that return this code.
|
||||
// // If server owners don't know whether this condition is temporary or permanent, Exception
|
||||
// // a 404 status code should be used instead."
|
||||
// //
|
||||
// // - Quoted from "410 Gone" by Mozilla Contributors, licensed under CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/410
|
||||
func Gone(opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusGone),
|
||||
WithCode("Gone"),
|
||||
WithMessage("The requested content has been permanently deleted."),
|
||||
WithError(errors.New("user agent has requested content that has been permanently deleted")),
|
||||
WithSeverity(WARN),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// LengthRequired creates a new [Exception] with the "411 Length Required" status
|
||||
// code, a human readable message and error. The severity of this Exception by
|
||||
// default is [WARN].
|
||||
//
|
||||
// // "The HTTP 411 Length Required client error response status code indicates that
|
||||
// // the server refused to accept the request without a defined Content-Length header."
|
||||
// //
|
||||
// // - Quoted from "411 Length Required" by Mozilla Contributors, licensed under CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/411
|
||||
func LengthRequired(opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusLengthRequired),
|
||||
WithCode("Length Required"),
|
||||
WithMessage(`The request does not contain a "Content-Length" header, which is required.`),
|
||||
WithError(errors.New(`user agent has requested without required "Content-Length" header`)),
|
||||
WithSeverity(WARN),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// PreconditionFailed creates a new [Exception] with the "412 Precondition Failed"
|
||||
// status code, a human readable message and error. The severity of this Exception
|
||||
// by default is [WARN].
|
||||
//
|
||||
// // "The HTTP 412 Precondition Failed client error response status code indicates
|
||||
// // that access to the target resource was denied. This happens with conditional
|
||||
// // requests on methods other than GET or HEAD when the condition defined by the
|
||||
// // If-Unmodified-Since or If-Match headers is not fulfilled. In that case, the
|
||||
// // request (usually an upload or a modification of a resource) cannot be made
|
||||
// // and this error response is sent back."
|
||||
// //
|
||||
// // - Quoted from "412 Precondition Failed" by Mozilla Contributors, licensed
|
||||
// // under CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412
|
||||
func PreconditionFailed(err error, opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusPreconditionFailed),
|
||||
WithCode("Precondition Failed"),
|
||||
WithMessage("The request does passes required preconditions."),
|
||||
WithError(err),
|
||||
WithSeverity(WARN),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// ContentTooLarge creates a new [Exception] with the "413 Content Too Large"
|
||||
// status code, a human readable message and error. The severity of this
|
||||
// Exception by default is [WARN].
|
||||
//
|
||||
// // "The HTTP 413 Content Too Large client error response status code indicates
|
||||
// // that the request entity was larger than limits defined by server. The server
|
||||
// // might close the connection or return a Retry-After header field."
|
||||
// //
|
||||
// // - Quoted from "413 Content Too Large" by Mozilla Contributors, licensed
|
||||
// // under CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/413
|
||||
func ContentTooLarge(opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusRequestEntityTooLarge),
|
||||
WithCode("Content Too Large"),
|
||||
WithMessage("The request body is larger than expected."),
|
||||
WithError(errors.New("user agent sent request body that is larger than expected")),
|
||||
WithSeverity(WARN),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// URITooLong creates a new [Exception] with the "414 URI Too Long"
|
||||
// status code, a human readable message and error. The severity of this
|
||||
// Exception by default is [WARN].
|
||||
//
|
||||
// // "The HTTP 414 URI Too Long client error response status code indicates
|
||||
// // that a URI requested by the client was longer than the server is willing
|
||||
// // to interpret.
|
||||
// //
|
||||
// // There are a few rare conditions when this error might occur:
|
||||
// //
|
||||
// // - a client has improperly converted a POST request to a GET request with
|
||||
// // long query information,
|
||||
// //
|
||||
// // - a client has descended into a loop of redirection (for example, a
|
||||
// // redirected URI prefix that points to a suffix of itself), or
|
||||
// //
|
||||
// // - the server is under attack by a client attempting to exploit potential
|
||||
// // security holes."
|
||||
// //
|
||||
// // - Quoted from "414 URI Too Long" by Mozilla Contributors, licensed under
|
||||
// // CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/414
|
||||
func URITooLong(opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusRequestURITooLong),
|
||||
WithCode("URI Too Long"),
|
||||
WithMessage("The request has a URI longer than expected."),
|
||||
WithError(errors.New("user agent sent request with URI longer than expected")),
|
||||
WithSeverity(WARN),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// UnsupportedMediaType creates a new [Exception] with the "415 Unsupported Media Type"
|
||||
// status code, a human readable message and error. The severity of this Exception by
|
||||
// default is [WARN].
|
||||
//
|
||||
// // "The HTTP 415 Unsupported Media Type client error response status code indicates
|
||||
// // that the server refused to accept the request because the message content format
|
||||
// // is not supported.
|
||||
// //
|
||||
// // The format problem might be due to the request's indicated Content-Type or
|
||||
// // Content-Encoding, or as a result of processing the request message content. Some
|
||||
// // servers may be strict about the expected Content-Type of requests. For example,
|
||||
// // sending UTF8 instead of UTF-8 to specify the UTF-8 charset may cause the server
|
||||
// // to consider the media type invalid."
|
||||
// //
|
||||
// // - Quoted from "415 Unsupported Media Type" by Mozilla Contributors, licensed
|
||||
// // under CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/415
|
||||
func UnsupportedMediaType(opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusUnsupportedMediaType),
|
||||
WithCode("Unsupported Media Type"),
|
||||
WithMessage("The request unsupported media type."),
|
||||
WithError(errors.New("user agent sent request with unsupported media type")),
|
||||
WithSeverity(WARN),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// RangeNotSatisfiable creates a new [Exception] with the "416 Range Not Satisfiable"
|
||||
// status code, a human readable message and error. The severity of this Exception by
|
||||
// default is [WARN]. A "Content-Range" header is sent with the provided number of
|
||||
// bytes via the "contentRange" parameter.
|
||||
//
|
||||
// // "The HTTP 416 Range Not Satisfiable client error response status code indicates
|
||||
// // that a server could not serve the requested ranges. The most likely reason for
|
||||
// // this response is that the document doesn't contain such ranges, or that the
|
||||
// // Range header value, though syntactically correct, doesn't make sense.
|
||||
// //
|
||||
// // The 416 response message should contain a Content-Range indicating an unsatisfied
|
||||
// // range (that is a '*') followed by a '/' and the current length of the resource,
|
||||
// // e.g., Content-Range: bytes */12777
|
||||
// //
|
||||
// // When encountering this error, browsers typically either abort the operation
|
||||
// // (for example, a download will be considered non-resumable) or request the whole
|
||||
// // document again without ranges."
|
||||
// //
|
||||
// // - Quoted from "416 Range Not Satisfiable" by Mozilla Contributors, licensed
|
||||
// // under CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/416
|
||||
func RangeNotSatisfiable(contentRange int, opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusUnsupportedMediaType),
|
||||
WithCode("Range Not Satisfiable"),
|
||||
WithMessage(`Request's "Range" header cannot be satified.`),
|
||||
WithError(errors.New(`user agent sent request with unsitisfiable "Range" header`)),
|
||||
WithSeverity(WARN),
|
||||
|
||||
WithHeader("Content-Range", fmt.Sprintf("bytes */%d", contentRange)),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// ExpectationFailed creates a new [Exception] with the "417 Expectation Failed"
|
||||
// status code, a human readable message and error. The severity of this Exception
|
||||
// by default is [WARN].
|
||||
//
|
||||
// // "The HTTP 417 Expectation Failed client error response status code indicates
|
||||
// // that the expectation given in the request's Expect header could not be met.
|
||||
// // After receiving a 417 response, a client should repeat the request without an
|
||||
// // Expect request header, including the file in the request body without waiting
|
||||
// // for a 100 response. See the Expect header documentation for more details."
|
||||
// //
|
||||
// // - Quoted from "417 Expectation Failed" by Mozilla Contributors, licensed under
|
||||
// // CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/417
|
||||
func ExpectationFailed(opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusExpectationFailed),
|
||||
WithCode("Exception Failed"),
|
||||
WithMessage(`Request's "Expect" header cannot be met.`),
|
||||
WithError(errors.New(`user agent sent request with unmetable "Expect" header`)),
|
||||
WithSeverity(WARN),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// ImATeapot creates a new [Exception] with the "418 I'm a teapot" status code,
|
||||
// a human readable message and error. The severity of this Exception by default
|
||||
// is [WARN].
|
||||
//
|
||||
// // "The HTTP 418 I'm a teapot status response code indicates that the server
|
||||
// // refuses to brew coffee because it is, permanently, a teapot. A combined
|
||||
// // coffee/tea pot that is temporarily out of coffee should instead return 503.
|
||||
// // This error is a reference to Hyper Text Coffee Pot Control Protocol defined
|
||||
// // in April Fools' jokes in 1998 and 2014.
|
||||
// //
|
||||
// // Some websites use this response for requests they do not wish to handle,
|
||||
// // such as automated queries."
|
||||
// //
|
||||
// // - Quoted from "418 I'm a teapot" by Mozilla Contributors, licensed under
|
||||
// // CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/418
|
||||
func ImATeapot(opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusTeapot),
|
||||
WithCode("I'm a teapot"),
|
||||
WithMessage("The request to brew coffee with a teapot is impossible."),
|
||||
WithError(errors.New("user agent tried to brew coffee with a teapot, an impossible request")),
|
||||
WithSeverity(WARN),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// MisdirectedRequest creates a new [Exception] with the "421 Misdirected Request"
|
||||
// status code, a human readable message and error. The severity of this Exception
|
||||
// by default is [WARN].
|
||||
//
|
||||
// // "The HTTP 421 Misdirected Request client error response status code indicates
|
||||
// // that the request was directed to a server that is not able to produce a response.
|
||||
// // This can be sent by a server that is not configured to produce responses for the
|
||||
// // combination of scheme and authority that are included in the request URI.
|
||||
// //
|
||||
// // Clients may retry the request over a different connection."
|
||||
// //
|
||||
// // - Quoted from "421 Misdirected Request" by Mozilla Contributors, licensed under
|
||||
// // CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/421
|
||||
func MisdirectedRequest(opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusMisdirectedRequest),
|
||||
WithCode("Misdirected Request"),
|
||||
WithMessage("The request was directed to a location unable to produce a response."),
|
||||
WithError(errors.New("user agent requested on a location that is unable to produce a response.")),
|
||||
WithSeverity(WARN),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// UnprocessableContent creates a new [Exception] with the "422 Unprocessable Content"
|
||||
// status code, a human readable message and error. The severity of this Exception
|
||||
// by default is [WARN].
|
||||
//
|
||||
// // "The HTTP 422 Unprocessable Content client error response status code indicates
|
||||
// // that the server understood the content type of the request content, and the syntax
|
||||
// // of the request content was correct, but it was unable to process the contained
|
||||
// // instructions.
|
||||
// //
|
||||
// // Clients that receive a 422 response should expect that repeating the request
|
||||
// // without modification will fail with the same error."
|
||||
// //
|
||||
// // - Quoted from "422 Unprocessable Content" by Mozilla Contributors, licensed
|
||||
// // under CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422
|
||||
func UnprocessableContent(opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusUnprocessableEntity),
|
||||
WithCode("Unprocessable Content"),
|
||||
WithMessage("Unable to follow request due to semantic errors."),
|
||||
WithError(errors.New("user agent sent request containing semantic errors.")),
|
||||
WithSeverity(WARN),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// Locked creates a new [Exception] with the "423 Locked" status code, a human
|
||||
// readable message and error. The severity of this Exception by default is [WARN].
|
||||
//
|
||||
// // "The HTTP 423 Locked client error response status code indicates that a
|
||||
// // resource is locked, meaning it can't be accessed. Its response body should
|
||||
// // contain information in WebDAV's XML format.
|
||||
// //
|
||||
// // NOTE: The ability to lock a resource to prevent conflicts is specific to
|
||||
// // some WebDAV servers. Browsers accessing web pages will never encounter
|
||||
// // this status code; in the erroneous cases it happens, they will handle
|
||||
// // it as a generic 400 status code."
|
||||
// //
|
||||
// // - Quoted from "423 Locked" by Mozilla Contributors, licensed under CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/423
|
||||
func Locked(opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusUnprocessableEntity),
|
||||
WithCode("Locked"),
|
||||
WithMessage("This resource is locked."),
|
||||
WithError(errors.New("user agent requested a locked resource")),
|
||||
WithSeverity(WARN),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// FailedDependency creates a new [Exception] with the "424 Failed Dependency"
|
||||
// status code, a human readable message and error. The severity of this
|
||||
// Exception by default is [WARN].
|
||||
//
|
||||
// // "The HTTP 424 Failed Dependency client error response status code
|
||||
// // indicates that the method could not be performed on the resource
|
||||
// // because the requested action depended on another action, and that
|
||||
// // action failed.
|
||||
// //
|
||||
// // Regular web servers typically do not return this status code, but
|
||||
// // some protocols like WebDAV can return it. For example, in WebDAV,
|
||||
// // if a PROPPATCH request was issued, and one command fails then
|
||||
// // automatically every other command will also fail with
|
||||
// // 424 Failed Dependency."
|
||||
// //
|
||||
// // - Quoted from "424 Failed Dependency" by Mozilla Contributors,
|
||||
// // licensed under CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/424
|
||||
func FailedDependency(opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusFailedDependency),
|
||||
WithCode("Failed Dependency"),
|
||||
WithMessage("Cannot respond due to failure of a previous request."),
|
||||
WithError(errors.New("request cannot be due to failure of a previous request")),
|
||||
WithSeverity(WARN),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// TooEarly creates a new [Exception] with the "425 Too Early" status code,
|
||||
// a human readable message and error. The severity of this Exception by
|
||||
// default is [WARN].
|
||||
//
|
||||
// // "LIMITED AVAILABILITY
|
||||
// //
|
||||
// // The HTTP 425 Too Early client error response status code indicates
|
||||
// // that the server was unwilling to risk processing a request that might
|
||||
// // be replayed to avoid potential replay attacks.
|
||||
// //
|
||||
// // If a client has interacted with a server recently, early data
|
||||
// // (also known as zero round-trip time (0-RTT) data) allows the client
|
||||
// // to send data to a server in the first round trip of a connection,
|
||||
// // without waiting for the TLS handshake to complete. A client that
|
||||
// // sends a request in early data does not need to include the
|
||||
// // Early-Data header."
|
||||
// //
|
||||
// // - Quoted from "425 Too Early" by Mozilla Contributors, licensed under
|
||||
// // CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/425
|
||||
func TooEarly(opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusTooEarly),
|
||||
WithCode("Too Early"),
|
||||
WithMessage("Unwilling to process the request to avoid replay attacks."),
|
||||
WithError(errors.New("request was rejected to avoid replay attacks")),
|
||||
WithSeverity(WARN),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// UpgradeRequired creates a new [Exception] with the "426 Upgrade Required"
|
||||
// status code, a human readable message and error. The severity of this
|
||||
// Exception by default is [WARN]. A "Upgrade" header is sent with the value
|
||||
// provided by the "upgrade" parameter.
|
||||
//
|
||||
// // "The HTTP 426 Upgrade Required client error response status code
|
||||
// // indicates that the server refused to perform the request using the
|
||||
// // current protocol but might be willing to do so after the client
|
||||
// // upgrades to a different protocol.
|
||||
// //
|
||||
// // The server sends an Upgrade header with this response to indicate
|
||||
// // the required protocol(s)."
|
||||
// //
|
||||
// // - Quoted from "426 Upgrade Required" by Mozilla Contributors,
|
||||
// // licensed under CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/426
|
||||
func UpgradeRequired(upgrade string, opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusUpgradeRequired),
|
||||
WithCode("Upgrade Required"),
|
||||
WithMessage("An upgrade to the protocol is required to do this request."),
|
||||
WithError(fmt.Errorf("user agent needs to upgrade to protocol %q", upgrade)),
|
||||
WithHeader("Upgrade", upgrade),
|
||||
WithSeverity(WARN),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// PreconditionRequired creates a new [Exception] with the "428 Precondition Required"
|
||||
// status code, a human readable message and error. The severity of this Exception
|
||||
// by default is [WARN].
|
||||
//
|
||||
// // "The HTTP 428 Precondition Required client error response status code indicates
|
||||
// // that the server requires the request to be conditional.
|
||||
// //
|
||||
// // Typically, a 428 response means that a required precondition header such as
|
||||
// // If-Match is missing. When a precondition header does not match the server-side
|
||||
// // state, the response should be 412 Precondition Failed."
|
||||
// //
|
||||
// // - Quoted from "428 Precondition Required" by Mozilla Contributors,
|
||||
// // licensed under CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/426
|
||||
func PreconditionRequired(opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusPreconditionRequired),
|
||||
WithCode("Precondition Required"),
|
||||
WithMessage("The request needs to be conditional."),
|
||||
WithError(errors.New("user agent sent request that is not conditional")),
|
||||
WithSeverity(WARN),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// TooManyRequests creates a new [Exception] with the "429 Too Many Requests"
|
||||
// status code, a human readable message and error. The severity of this
|
||||
// Exception by default is [WARN].
|
||||
//
|
||||
// To provide a "Retry-After" header, use the [WithHeader] option function.
|
||||
//
|
||||
// // "The HTTP 429 Too Many Requests client error response status code
|
||||
// // indicates the client has sent too many requests in a given amount
|
||||
// // of time. This mechanism of asking the client to slow down the rate
|
||||
// // of requests is commonly called "rate limiting".
|
||||
// //
|
||||
// // A Retry-After header may be included to this response to indicate
|
||||
// // how long a client should wait before making the request again."
|
||||
// //
|
||||
// // - Quoted from "429 Too Many Requests" by Mozilla Contributors,
|
||||
// // licensed under CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429
|
||||
func TooManyRequests(opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusTooManyRequests),
|
||||
WithCode("Too Many Requests"),
|
||||
WithMessage("Too many requests were sent in the span of a short time."),
|
||||
WithError(errors.New("user agent sent too many requests")),
|
||||
WithSeverity(WARN),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// RequestHeaderFieldsTooLarge creates a new [Exception] with the
|
||||
// "431 Request Header Fields Too Large" status code, a human readable
|
||||
// message and error. The severity of this Exception by default is [WARN].
|
||||
//
|
||||
// // "The HTTP 431 Request Header Fields Too Large client error response
|
||||
// // status code indicates that the server refuses to process the request
|
||||
// // because the request's HTTP headers are too long. The request may
|
||||
// // be resubmitted after reducing the size of the request headers.
|
||||
// //
|
||||
// // 431 can be used when the total size of request headers is too large
|
||||
// // or when a single header field is too large. To help clients running
|
||||
// // into this error, indicate which of the two is the problem in the
|
||||
// // response body and, ideally, say which headers are too large. This
|
||||
// // lets people attempt to fix the problem, such as by clearing cookies.
|
||||
// //
|
||||
// // Servers will often produce this status if:
|
||||
// // - The Referer URL is too long
|
||||
// // - There are too many Cookies in the request"
|
||||
// //
|
||||
// // - Quoted from "431 Request Header Fields Too Large" by Mozilla Contributors,
|
||||
// // licensed under CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/431
|
||||
func RequestHeaderFieldsTooLarge(opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusRequestHeaderFieldsTooLarge),
|
||||
WithCode("Request Header Fields Too Large"),
|
||||
WithMessage("Headers fields are larger than expected."),
|
||||
WithError(errors.New("user agent sent header fields larger than expected")),
|
||||
WithSeverity(WARN),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// UnavailableForLegalReasons creates a new [Exception] with the
|
||||
// "451 Unavailable For Legal Reasons" status code, a human readable
|
||||
// message and error. The severity of this Exception by default is [WARN].
|
||||
//
|
||||
// // "The HTTP 451 Unavailable For Legal Reasons client error response
|
||||
// // status code indicates that the user requested a resource that is
|
||||
// // not available due to legal reasons, such as a web page for which
|
||||
// // a legal action has been issued."
|
||||
// //
|
||||
// // - Quoted from "451 Unavailable For Legal Reasons" by Mozilla Contributors,
|
||||
// // licensed under CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/451
|
||||
func UnavailableForLegalReasons(opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusUnavailableForLegalReasons),
|
||||
WithCode("Unavailable For Legal Reasons"),
|
||||
WithMessage("Content cannot be legally be provided."),
|
||||
WithError(errors.New("user agent requested content that cannot be legally provided")),
|
||||
WithSeverity(WARN),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
@@ -1,382 +0,0 @@
|
||||
// Copyright 2025-present Gustavo "Guz" L. de Mello
|
||||
// Copyright 2025-present The Lored.dev Contributors
|
||||
//
|
||||
// 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 exception
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// InternalServerError creates a new [Exception] with the "500 Internal Server Error"
|
||||
// status code, a human readable message and the provided error describing what in
|
||||
// the request was wrong. The severity of this Exception by default is [ERROR].
|
||||
//
|
||||
// An error should be provided to add context to the exception.
|
||||
//
|
||||
// // "The HTTP 500 Internal Server Error server error response status
|
||||
// // code indicates that the server encountered an unexpected condition
|
||||
// // that prevented it from fulfilling the request. This error is a generic
|
||||
// // "catch-all" response to server issues, indicating that the server
|
||||
// // cannot find a more appropriate 5XX error to respond with."
|
||||
// //
|
||||
// // - Quoted from "500 Internal Server Error" by Mozilla Contributors,
|
||||
// // licensed under CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502
|
||||
func InternalServerError(err error, opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusInternalServerError),
|
||||
WithCode("Internal Server Error"),
|
||||
WithMessage("A unexpected error occurred."),
|
||||
WithError(err),
|
||||
WithSeverity(ERROR),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// NotImplemented creates a new [Exception] with the "501 Not Implemented"
|
||||
// status code, a human readable message and the provided error describing what in
|
||||
// the request was wrong. The severity of this Exception by default is [ERROR].
|
||||
//
|
||||
// // "The HTTP 501 Not Implemented server error response status
|
||||
// // code means that the server does not support the functionality
|
||||
// // required to fulfill the request.
|
||||
// //
|
||||
// // A response with this status may also include a Retry-After header,
|
||||
// // telling the client that they can retry the request after the specified
|
||||
// // time has elapsed. A 501 response is cacheable by default unless
|
||||
// // caching headers instruct otherwise.
|
||||
// //
|
||||
// // 501 is the appropriate response when the server does not recognize
|
||||
// // the request method and is incapable of supporting it for any resource.
|
||||
// // Servers are required to support GET and HEAD, and therefore must not
|
||||
// // return 501 in response to requests with these methods. If the server
|
||||
// // does recognize the method, but intentionally does not allow it, the
|
||||
// // appropriate response is 405 Method Not Allowed."
|
||||
// //
|
||||
// // - Quoted from "501 Not Implemented" by Mozilla Contributors,
|
||||
// // licensed under CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502
|
||||
func NotImplemented(opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusNotImplemented),
|
||||
WithCode("Not Implemented"),
|
||||
WithMessage("Functionality is not supported."),
|
||||
WithError(errors.New("user agent requested functionality that is not supported")),
|
||||
WithSeverity(ERROR),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// BadGateway creates a new [Exception] with the "502 Bad Gateway"
|
||||
// status code, a human readable message and the provided error describing what in
|
||||
// the request was wrong. The severity of this Exception by default is [ERROR].
|
||||
//
|
||||
// // "The HTTP 502 Bad Gateway server error response status code
|
||||
// // indicates that a server was acting as a gateway or proxy and
|
||||
// // that it received an invalid response from the upstream server.
|
||||
// //
|
||||
// // This response is similar to a 500 Internal Server Error response
|
||||
// // in the sense that it is a generic "catch-call" for server errors.
|
||||
// // The difference is that it is specific to the point in the request
|
||||
// // chain that the error has occurred. If the origin server sends a
|
||||
// // valid HTTP error response to the gateway, the response should be
|
||||
// // passed on to the client instead of a 502 to make the failure
|
||||
// // reason transparent. If the proxy or gateway did not receive any
|
||||
// // HTTP response from the origin, it instead sends a
|
||||
// // 504 Gateway Timeout to the client."
|
||||
// //
|
||||
// // - Quoted from "502 Bad Gateway" by Mozilla Contributors, licensed
|
||||
// // under CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502
|
||||
func BadGateway(opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusBadGateway),
|
||||
WithCode("Bad Gateway"),
|
||||
WithMessage("Invalid response from upstream."),
|
||||
WithError(errors.New("upstream response is invalid")),
|
||||
WithSeverity(ERROR),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// ServiceUnavailable creates a new [Exception] with the "503 Service Unavailable"
|
||||
// status code, a human readable message and the provided error describing what in
|
||||
// the request was wrong. The severity of this Exception by default is [ERROR].
|
||||
//
|
||||
// A Retry-After header is passed with the duration provided by the "retryAfter"
|
||||
// parameter.
|
||||
//
|
||||
// // "The HTTP 503 Service Unavailable server error response status code
|
||||
// // indicates that the server is not ready to handle the request.
|
||||
// //
|
||||
// // Common causes are that a server is down for maintenance or overloaded.
|
||||
// // During maintenance, server administrators may temporarily route all traffic
|
||||
// // to a 503 page, or this may happen automatically during software updates.
|
||||
// // In overload cases, some server-side applications will reject requests with
|
||||
// // a 503 status when resource thresholds like memory, CPU, or connection pool
|
||||
// // limits are met. Dropping incoming requests creates backpressure that prevents
|
||||
// // the server's compute resources from being exhausted, avoiding more severe
|
||||
// // failures. If requests from specific clients are being restricted due to
|
||||
// // rate limiting, the appropriate response is 429 Too Many Requests.
|
||||
// //
|
||||
// // This response should be used for temporary conditions and the Retry-After
|
||||
// // HTTP header should contain the estimated time for the recovery of the
|
||||
// // service, if possible.
|
||||
// //
|
||||
// // A user-friendly page explaining the problem should be sent along with
|
||||
// // this response."
|
||||
// //
|
||||
// // - Quoted from "503 Service Unavailable" by Mozilla Contributors,
|
||||
// // licensed under CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/503
|
||||
func ServiceUnavailable(retryAfter time.Time, opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusServiceUnavailable),
|
||||
WithCode("Service Unavailable"),
|
||||
WithMessage("Not ready to handle the request."),
|
||||
WithError(errors.New("server is not ready to handle the request")),
|
||||
WithSeverity(ERROR),
|
||||
|
||||
WithHeader("Retry-After", retryAfter.Format("Mon, 02 Jan 2006 15:04:05 GMT")),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// GatewayTimeout creates a new [Exception] with the "504 Gateway Timeout"
|
||||
// status code, a human readable message and the provided error describing what in
|
||||
// the request was wrong. The severity of this Exception by default is [ERROR].
|
||||
//
|
||||
// // "The HTTP 504 Gateway Timeout server error response status code
|
||||
// // indicates that the server, while acting as a gateway or proxy,
|
||||
// // did not get a response in time from the upstream server in order
|
||||
// // to complete the request. This is similar to a 502 Bad Gateway,
|
||||
// // except that in a 504 status, the proxy or gateway did not receive
|
||||
// // any HTTP response from the origin within a certain time.
|
||||
// //
|
||||
// // There are many causes of 504 errors, and fixing such problems
|
||||
// // likely requires investigation and debugging by server administrators,
|
||||
// // or the site may work again at a later time. Exceptions are client
|
||||
// // networking errors, particularly if the service works for other
|
||||
// // visitors, and if clients use VPNs or other custom networking setups.
|
||||
// // In such cases, clients should check network settings, firewall setup,
|
||||
// // proxy settings, DNS configuration, etc."
|
||||
// //
|
||||
// // - Quoted from "504 Gateway Timeout" by Mozilla Contributors, licensed
|
||||
// // under CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504
|
||||
func GatewayTimeout(opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusGatewayTimeout),
|
||||
WithCode("Gateway Timeout"),
|
||||
WithMessage("Did not get the response from upstream in time."),
|
||||
WithError(errors.New("upstream did not respond in time")),
|
||||
WithSeverity(ERROR),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// HTTPVersionNotSupported creates a new [Exception] with the "505 HTTP Version Not Supported"
|
||||
// status code, a human readable message and the provided error describing what in
|
||||
// the request was wrong. The severity of this Exception by default is [ERROR].
|
||||
//
|
||||
// // "The HTTP 505 HTTP Version Not Supported server error response status
|
||||
// // code indicates that the HTTP version used in the request is not supported
|
||||
// // by the server.
|
||||
// //
|
||||
// // It's common to see this error when a request line is improperly formed
|
||||
// // such as GET /path to resource HTTP/1.1 or with \n terminating the request
|
||||
// // line instead of \r\n."
|
||||
// //
|
||||
// // - Quoted from "505 HTTP Version Not Supported" by Mozilla Contributors,
|
||||
// // licensed under CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/505
|
||||
func HTTPVersionNotSupported(opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusHTTPVersionNotSupported),
|
||||
WithCode("HTTP Version Not Supported"),
|
||||
WithMessage("Version of HTTP is not supported."),
|
||||
WithError(errors.New("server does not support requested HTTP version")),
|
||||
WithSeverity(ERROR),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// VariantAlsoNegotiates creates a new [Exception] with the "506 Variant Also Negotiates"
|
||||
// status code, a human readable message and the provided error describing what in
|
||||
// the request was wrong. The severity of this Exception by default is [ERROR].
|
||||
//
|
||||
// // "The HTTP 506 Variant Also Negotiates server error response status code
|
||||
// // is returned during content negotiation when there is recursive loop in
|
||||
// // the process of selecting a resource.
|
||||
// //
|
||||
// // Agent-driven content negotiation enables a client and server to
|
||||
// // collaboratively decide the best variant of a given resource when the server
|
||||
// // has multiple variants. A server sends a 506 status code due to server
|
||||
// // misconfiguration that results in circular references when creating responses."
|
||||
// //
|
||||
// // - Quoted from "506 Variant Also Negotiates" by Mozilla Contributors,
|
||||
// // licensed under CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/506
|
||||
func VariantAlsoNegotiates(opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusVariantAlsoNegotiates),
|
||||
WithCode("Variant Also Negotiates"),
|
||||
WithMessage("A recursive loop found in the process of the request."),
|
||||
WithError(errors.New("variant also negotiates, recursive loop found on request")),
|
||||
WithSeverity(ERROR),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// InsufficientStorage creates a new [Exception] with the "507 Insufficient Storage"
|
||||
// status code, a human readable message and the provided error describing what in
|
||||
// the request was wrong. The severity of this Exception by default is [ERROR].
|
||||
//
|
||||
// // "The HTTP 507 Insufficient Storage server error response status code
|
||||
// // indicates that an action could not be performed because the server does
|
||||
// // not have enough available storage to successfully complete the request.
|
||||
// //
|
||||
// // [...] Common causes of this error can be from server directories running
|
||||
// // out of available space, not enough available RAM for an operation, or
|
||||
// // internal limits reached (such as application-specific memory limits,
|
||||
// // for example). The request causing this error does not necessarily need to
|
||||
// // include content, as it may be a request that would create a resource on
|
||||
// // the server if it was successful."
|
||||
// //
|
||||
// // - Quoted from "507 Insufficient Storage" by Mozilla Contributors, licensed under
|
||||
// // CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/507
|
||||
func InsufficientStorage(opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusInsufficientStorage),
|
||||
WithCode("Insufficient Storage"),
|
||||
WithMessage("There is not enough available storage to complete the request."),
|
||||
WithError(errors.New("not enough available storage to complete request")),
|
||||
WithSeverity(ERROR),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// LoopDetected creates a new [Exception] with the "508 Loop Detected"
|
||||
// status code, a human readable message and the provided error describing what in
|
||||
// the request was wrong. The severity of this Exception by default is [ERROR].
|
||||
//
|
||||
// // "The HTTP 508 Loop Detected server error response status code indicates
|
||||
// // that the entire operation failed because it encountered an infinite loop
|
||||
// // while processing a request with Depth: infinity.
|
||||
// //
|
||||
// // The status may be given in the context of the Web Distributed Authoring
|
||||
// // and Versioning (WebDAV). It was introduced as a fallback for cases where
|
||||
// // WebDAV clients do not support 208 Already Reported responses (when requests
|
||||
// // do not explicitly include a DAV header)."
|
||||
// //
|
||||
// // - Quoted from "508 Loop Detected" by Mozilla Contributors, licensed under
|
||||
// // CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/508
|
||||
func LoopDetected(opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusLoopDetected),
|
||||
WithCode("Loop Detected"),
|
||||
WithMessage("Infinite loop found while processing the request."),
|
||||
WithError(errors.New("infinite loop found while processing request")),
|
||||
WithSeverity(ERROR),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// NotExtended creates a new [Exception] with the "510 Not Extended"
|
||||
// status code, a human readable message and the provided error describing what in
|
||||
// the request was wrong. The severity of this Exception by default is [ERROR].
|
||||
//
|
||||
// // "The HTTP 510 Not Extended server error response status code is sent
|
||||
// // when the client request declares an HTTP Extension (RFC 2774) that
|
||||
// // should be used to process the request, but the extension is not supported."
|
||||
// //
|
||||
// // - Quoted from "510 Not Extended" by Mozilla Contributors, licensed under
|
||||
// // CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/510
|
||||
func NotExtended(opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusLoopDetected),
|
||||
WithCode("Not Extended"),
|
||||
WithMessage("HTTP extension is not supported."),
|
||||
WithError(errors.New("user agent requested with a HTTP extension that is not supported")),
|
||||
WithSeverity(ERROR),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
|
||||
// NetworkAuthenticationRequired creates a new [Exception] with the "511 Network Authentication Required"
|
||||
// status code, a human readable message and the provided error describing what in
|
||||
// the request was wrong. The severity of this Exception by default is [ERROR].
|
||||
//
|
||||
// // "The HTTP 511 Network Authentication Required server error response status
|
||||
// // code indicates that the client needs to authenticate to gain network access.
|
||||
// // This status is not generated by origin servers, but by intercepting proxies
|
||||
// // that control access to a network.
|
||||
// //
|
||||
// // Network operators sometimes require some authentication, acceptance of terms,
|
||||
// // or other user interaction before granting access (for example in an internet
|
||||
// // café or at an airport). They often identify clients who have not done so using
|
||||
// // their Media Access Control (MAC) addresses."
|
||||
// //
|
||||
// // - Quoted from "511 Network Authentication Required" by Mozilla Contributors,
|
||||
// // licensed under CC-BY-SA 2.5.
|
||||
// //
|
||||
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/511
|
||||
func NetworkAuthenticationRequired(opts ...Option) Exception {
|
||||
o := []Option{
|
||||
WithStatus(http.StatusNetworkAuthenticationRequired),
|
||||
WithCode("Network Authentication Required"),
|
||||
WithMessage("Authentication to access network access is necessary."),
|
||||
WithError(errors.New("user agent requested without being network authenticated")),
|
||||
WithSeverity(ERROR),
|
||||
}
|
||||
o = append(o, opts...)
|
||||
|
||||
return newException(o...)
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
// Copyright 2025-present Gustavo "Guz" L. de Mello
|
||||
// Copyright 2025-present The Lored.dev Contributors
|
||||
//
|
||||
// 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 exception
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Exception struct {
|
||||
Status int `json:"status"` // HTTP Status Code
|
||||
Code string `json:"code"` // Application error code
|
||||
Message string `json:"message"` // User friendly message
|
||||
Err error `json:"error,omitempty"` // Go error
|
||||
Data map[string]any `json:"data,omitempty"` // Additional contextual data
|
||||
Severity Severity `json:"severity"` // Exception level
|
||||
|
||||
// Handler to be used. This is normally provided by a middleware via the
|
||||
// request context. Setting this field overrides any provided by the middleware
|
||||
// and can be used to add a handler when using a middleware is not possible.
|
||||
handler HandlerFunc `json:"-"`
|
||||
|
||||
headers http.Header
|
||||
}
|
||||
|
||||
var (
|
||||
_ fmt.Stringer = Exception{}
|
||||
_ error = Exception{}
|
||||
_ http.Handler = Exception{}
|
||||
)
|
||||
|
||||
func (e Exception) String() string {
|
||||
return fmt.Sprintf("%s %3d %s Exception %q", e.Severity, e.Status, e.Code, e.Message)
|
||||
}
|
||||
|
||||
func (e Exception) Error() string {
|
||||
return e.String()
|
||||
}
|
||||
|
||||
func (e Exception) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if e.handler != nil {
|
||||
e.handler(e, w, r)
|
||||
}
|
||||
|
||||
e.handler = HandlerJSON(HandlerText)
|
||||
|
||||
handler, ok := r.Context().Value(handlerFuncCtxKey).(HandlerFunc)
|
||||
if !ok {
|
||||
e.handler(e, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
handler(e, w, r)
|
||||
}
|
||||
|
||||
func newException(options ...Option) Exception {
|
||||
e := Exception{
|
||||
Status: http.StatusInternalServerError,
|
||||
Code: "Internal Server Error",
|
||||
Message: "",
|
||||
Err: nil,
|
||||
Severity: ERROR,
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
option(&e)
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
type Option = func(*Exception)
|
||||
|
||||
func WithStatus(s int) Option {
|
||||
return func(e *Exception) { e.Status = s }
|
||||
}
|
||||
|
||||
func WithCode(c string) Option {
|
||||
return func(e *Exception) { e.Code = c }
|
||||
}
|
||||
|
||||
func WithMessage(m string) Option {
|
||||
return func(e *Exception) { e.Message = m }
|
||||
}
|
||||
|
||||
func WithError(err error, errs ...error) Option {
|
||||
if len(errs) > 0 {
|
||||
es := []error{err}
|
||||
es = append(es, errs...)
|
||||
err = errors.Join(es...)
|
||||
}
|
||||
return func(e *Exception) { e.Err = err }
|
||||
}
|
||||
|
||||
func WithSeverity(s Severity) Option {
|
||||
return func(e *Exception) { e.Severity = s }
|
||||
}
|
||||
|
||||
func WithData(key string, v any) Option {
|
||||
return func(e *Exception) {
|
||||
if e.Data == nil {
|
||||
e.Data = make(map[string]any)
|
||||
}
|
||||
e.Data[key] = v
|
||||
}
|
||||
}
|
||||
|
||||
func WithHeader(header string, v string) Option {
|
||||
return func(e *Exception) {
|
||||
if e.headers == nil {
|
||||
e.headers = http.Header{}
|
||||
}
|
||||
e.headers.Add(header, v)
|
||||
}
|
||||
}
|
||||
|
||||
func WithoutHeader(header string) Option {
|
||||
return func(e *Exception) {
|
||||
if e.headers == nil {
|
||||
e.headers = http.Header{}
|
||||
}
|
||||
e.headers.Del(header)
|
||||
}
|
||||
}
|
||||
|
||||
func WithHandler(h HandlerFunc) Option {
|
||||
return func(e *Exception) { e.handler = h }
|
||||
}
|
||||
@@ -1,315 +0,0 @@
|
||||
// Copyright 2025-present Gustavo "Guz" L. de Mello
|
||||
// Copyright 2025-present The Lored.dev Contributors
|
||||
//
|
||||
// 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 exception
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"forge.capytal.company/loreddev/x/smalltrip/middleware"
|
||||
)
|
||||
|
||||
func Middleware(options ...MiddlewareOption) middleware.Middleware {
|
||||
opts := middlewareOpts{
|
||||
templates: make(map[int]*template.Template),
|
||||
handlers: make(map[string]HandlerFunc),
|
||||
defaultHandler: HandlerJSON(HandlerText),
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
option(&opts)
|
||||
}
|
||||
|
||||
if _, ok := opts.templates[0]; !ok {
|
||||
opts.templates[0] = defaultTemplate
|
||||
}
|
||||
|
||||
if _, ok := opts.handlers["application/json"]; !ok {
|
||||
opts.handlers["application/json"] = HandlerJSON(HandlerText)
|
||||
}
|
||||
if _, ok := opts.handlers["text/html"]; !ok {
|
||||
opts.handlers["text/html"] = HandlerTemplates(opts.templates, opts.defaultHandler)
|
||||
}
|
||||
if _, ok := opts.handlers["application/xhtml+xml"]; !ok {
|
||||
opts.handlers["application/xhtml+xml"] = HandlerTemplates(opts.templates, opts.defaultHandler)
|
||||
}
|
||||
if _, ok := opts.handlers["application/xml"]; !ok {
|
||||
opts.handlers["application/xml"] = HandlerTemplates(opts.templates, opts.defaultHandler)
|
||||
}
|
||||
|
||||
return NewMiddleware(func(e Exception, w http.ResponseWriter, r *http.Request) {
|
||||
for k, v := range opts.handlers {
|
||||
if strings.Contains(r.Header.Get("Accept"), k) {
|
||||
v(e, w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
opts.defaultHandler(e, w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func PanicMiddleware() middleware.Middleware {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err := fmt.Errorf("panic recovered: %+v", r)
|
||||
InternalServerError(err, WithSeverity(FATAL)).ServeHTTP(w, req)
|
||||
}
|
||||
}()
|
||||
next.ServeHTTP(w, req)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var defaultTemplate = template.Must(template.New("xx-small-trip-default-Exception-template").Parse(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Smalltrip Exception</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Exception:</p>
|
||||
<ul>
|
||||
<li>Status: {{ .Status }}</li>
|
||||
<li>Code: {{ .Code }}</li>
|
||||
<li>Message: {{ .Message }}</li>
|
||||
<li>Err: {{ .Err }}</li>
|
||||
<li>Severity: {{ .Severity }}</li>
|
||||
{{if .Data -}}
|
||||
<li>Data:
|
||||
<ul>
|
||||
{{range $k, $v := .Data -}}
|
||||
<li>{{$k | printf "%s"}}: {{$v | printf "%+v"}}</li>
|
||||
{{- end}}
|
||||
</ul>
|
||||
</li>
|
||||
{{- end}}
|
||||
</ul>
|
||||
</body>
|
||||
<html>
|
||||
`))
|
||||
|
||||
type MiddlewareOption = func(*middlewareOpts)
|
||||
|
||||
func MiddlewareTemplate(t *template.Template, statusCode ...int) MiddlewareOption {
|
||||
return func(mo *middlewareOpts) {
|
||||
if len(statusCode) > 0 {
|
||||
mo.templates[statusCode[0]] = t
|
||||
} else {
|
||||
mo.templates[0] = t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func MiddlewareHandler(h HandlerFunc, mimeType ...string) MiddlewareOption {
|
||||
return func(mo *middlewareOpts) {
|
||||
if len(mimeType) > 0 {
|
||||
mo.handlers[mimeType[0]] = h
|
||||
} else {
|
||||
mo.defaultHandler = h
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type middlewareOpts struct {
|
||||
templates map[int]*template.Template
|
||||
handlers map[string]HandlerFunc
|
||||
defaultHandler HandlerFunc
|
||||
}
|
||||
|
||||
func NewMiddleware(handler HandlerFunc) middleware.Middleware {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
r = r.WithContext(context.WithValue(r.Context(), handlerFuncCtxKey, handler))
|
||||
|
||||
mw := &middlewareReponseWriter{w, false}
|
||||
|
||||
next.ServeHTTP(mw, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type middlewareReponseWriter struct {
|
||||
http.ResponseWriter
|
||||
done bool
|
||||
}
|
||||
|
||||
func (w *middlewareReponseWriter) WriteHeader(s int) {
|
||||
if !w.done {
|
||||
w.ResponseWriter.WriteHeader(s)
|
||||
}
|
||||
w.done = true
|
||||
}
|
||||
|
||||
const handlerFuncCtxKey = "xx-smalltrip-Exception-handler-func"
|
||||
|
||||
type HandlerFunc = func(e Exception, w http.ResponseWriter, r *http.Request)
|
||||
|
||||
func HandlerTemplates(ts map[int]*template.Template, fallback HandlerFunc) HandlerFunc {
|
||||
return func(e Exception, w http.ResponseWriter, r *http.Request) {
|
||||
if len(ts) == 0 {
|
||||
fallback(e, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
t, ok := ts[e.Status]
|
||||
if ok {
|
||||
HandlerTemplate(t, fallback)(e, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Loops over ordered list and gets the last one that is small or equal
|
||||
// the current Status. For example, if the current Exception has Status 404,
|
||||
// and we provide a map like:
|
||||
//
|
||||
// map[int]*template.Template{
|
||||
// 100: Template100,
|
||||
// 200: Template200,
|
||||
// 300: Template300,
|
||||
// 400: Template400,
|
||||
// 500: Template500,
|
||||
// }
|
||||
//
|
||||
// It will be converted to a ordered list of keys: 100, 200, 300, 400, 500.
|
||||
// This loops iterates on all keys until a value bigger than the current Status
|
||||
// is found (in this example, 500), then it uses the previous (in this example 400).
|
||||
//
|
||||
// So the 404 Exception will be rendered using the Template400.
|
||||
|
||||
keys := make([]int, len(ts), len(ts))
|
||||
|
||||
var i int
|
||||
for k := range ts {
|
||||
keys[i] = k
|
||||
i++
|
||||
}
|
||||
|
||||
slices.Sort(keys)
|
||||
|
||||
key := keys[0]
|
||||
for _, k := range keys {
|
||||
if k > e.Status {
|
||||
break
|
||||
}
|
||||
key = k
|
||||
}
|
||||
|
||||
t, ok = ts[key]
|
||||
if ok {
|
||||
HandlerTemplate(t, fallback)(e, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
fallback(e, w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func HandlerTemplate(t *template.Template, fallback HandlerFunc) HandlerFunc {
|
||||
return func(e Exception, w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
for k := range e.headers {
|
||||
w.Header().Set(k, e.headers.Get(k))
|
||||
}
|
||||
|
||||
w.WriteHeader(e.Status)
|
||||
|
||||
err := t.Execute(w, e)
|
||||
if err != nil {
|
||||
e.Err = errors.Join(fmt.Errorf("executing Exception template: %s", e.Error()), e.Err)
|
||||
|
||||
fallback(e, w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func HandlerJSON(fallback HandlerFunc) HandlerFunc {
|
||||
return func(e Exception, w http.ResponseWriter, r *http.Request) {
|
||||
j, err := json.Marshal(e)
|
||||
if err != nil {
|
||||
e.Err = errors.Join(fmt.Errorf("marshalling Exception struct: %s", e.Error()), e.Err)
|
||||
|
||||
fallback(e, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
for k := range e.headers {
|
||||
w.Header().Set(k, e.headers.Get(k))
|
||||
}
|
||||
|
||||
w.WriteHeader(e.Status)
|
||||
|
||||
_, err = w.Write(j)
|
||||
if err != nil {
|
||||
e.Err = errors.Join(fmt.Errorf("writing JSON response to body: %s", e.Error()), e.Err)
|
||||
|
||||
HandlerText(e, w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var _ HandlerFunc = HandlerJSON(HandlerText)
|
||||
|
||||
func HandlerText(e Exception, w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
for k := range e.headers {
|
||||
w.Header().Set(k, e.headers.Get(k))
|
||||
}
|
||||
|
||||
w.WriteHeader(e.Status)
|
||||
|
||||
_, err := w.Write([]byte(fmt.Sprintf(
|
||||
"Status: %3d\n"+
|
||||
"Code: %s"+
|
||||
"Message: %s\n"+
|
||||
"Err: %s\n"+
|
||||
"Severity: %s\n\n"+
|
||||
"%+v\n\n"+
|
||||
"%#v",
|
||||
e.Status,
|
||||
e.Code,
|
||||
e.Message,
|
||||
e.Err,
|
||||
e.Severity,
|
||||
|
||||
e, e,
|
||||
)))
|
||||
if err != nil {
|
||||
_, _ = w.Write([]byte(fmt.Sprintf(
|
||||
"Ok, what should we do at this point? You fucked up so bad that this message " +
|
||||
"shouldn't even be able to be sent in the first place. If you are a normal user I'm " +
|
||||
"so sorry for you to be reading this. If you're a developer, go fix your ResponseWriter " +
|
||||
"implementation, because this should never happen in any normal codebase. " +
|
||||
"I hope for the life of anyone you love you don't use this message in some " +
|
||||
"error checking or any sort of API-contract, because there will be no more hope " +
|
||||
"for you or your project. May God or any other or any other divinity that you may " +
|
||||
"or may not believe be with you when trying to fix this mistake, you will need it.",
|
||||
// If someone use this as part of the API-contract I'll not even be surprised.
|
||||
// So any change to this message is still considered a breaking change.
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
var _ HandlerFunc = HandlerText
|
||||
@@ -1,89 +0,0 @@
|
||||
// Copyright 2025-present Gustavo "Guz" L. de Mello
|
||||
// Copyright 2025-present The Lored.dev Contributors
|
||||
//
|
||||
// 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 exception
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
type Severity int
|
||||
|
||||
var (
|
||||
_ fmt.Stringer = (Severity)(0)
|
||||
_ encoding.TextMarshaler = (Severity)(0)
|
||||
_ encoding.TextUnmarshaler = (*Severity)(nil)
|
||||
)
|
||||
|
||||
const (
|
||||
DEBUG Severity = Severity(slog.LevelDebug)
|
||||
INFO Severity = Severity(slog.LevelInfo)
|
||||
WARN Severity = Severity(slog.LevelWarn)
|
||||
ERROR Severity = Severity(slog.LevelError)
|
||||
FATAL Severity = Severity(slog.LevelError * 2)
|
||||
|
||||
stringDEBUG = "DEBUG"
|
||||
stringINFO = "INFO"
|
||||
stringWARN = "WARN"
|
||||
stringERROR = "ERROR"
|
||||
stringFATAL = "FATAL"
|
||||
|
||||
stringUNDEFINED = "UNDEFINED"
|
||||
)
|
||||
|
||||
func (s Severity) String() string {
|
||||
switch s {
|
||||
case DEBUG:
|
||||
return stringDEBUG
|
||||
case INFO:
|
||||
return stringINFO
|
||||
case WARN:
|
||||
return stringWARN
|
||||
case ERROR:
|
||||
return stringERROR
|
||||
case FATAL:
|
||||
return stringFATAL
|
||||
default:
|
||||
return fmt.Sprintf(stringUNDEFINED)
|
||||
}
|
||||
}
|
||||
|
||||
func (s Severity) MarshalText() ([]byte, error) {
|
||||
str := s.String()
|
||||
if str == stringUNDEFINED {
|
||||
return nil, fmt.Errorf("severity of value %q does not exists", s)
|
||||
}
|
||||
return []byte(str), nil
|
||||
}
|
||||
|
||||
func (s *Severity) UnmarshalText(text []byte) error {
|
||||
switch string(text) {
|
||||
case stringDEBUG:
|
||||
*s = DEBUG
|
||||
case stringINFO:
|
||||
*s = INFO
|
||||
case stringWARN:
|
||||
*s = WARN
|
||||
case stringERROR:
|
||||
*s = ERROR
|
||||
case stringFATAL:
|
||||
*s = FATAL
|
||||
default:
|
||||
return fmt.Errorf("severity level %d does not exists", s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user