Skip to content

Commit

Permalink
Merge pull request #82 from fakefloordiv/dev
Browse files Browse the repository at this point in the history
Extended status errors, updates in routing resources for more convinience
  • Loading branch information
flrdv authored Mar 25, 2023
2 parents 80e5379 + 6747c69 commit 35716d5
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 57 deletions.
36 changes: 11 additions & 25 deletions http/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,33 +167,19 @@ func (r Response) WithAttachment(reader io.Reader, size int) Response {
return r
}

// WithError tries to set a corresponding status code and response body equal to error text
// if error is known to server, otherwise setting status code to status.InternalServerError
// without setting a response body to the error text, because this possibly may reveal some
// sensitive internal infrastructure details
// WithError checks, whether the passed error is a HTTPError instance. In this case,
// setting response code and body to HTTPError.Code and HTTPError.Message respectively.
// If the check failed, simply setting the code to status.InternalServerError. Error
// message won't be included in the response, as this possibly can spoil project internals,
// creating security breaches
func (r Response) WithError(err error) Response {
resp := r.WithBody(err.Error())

switch err {
case status.ErrBadRequest:
return resp.WithCode(status.BadRequest)
case status.ErrNotFound:
return resp.WithCode(status.NotFound)
case status.ErrMethodNotAllowed:
return resp.WithCode(status.MethodNotAllowed)
case status.ErrTooLarge, status.ErrURITooLong:
return resp.WithCode(status.RequestEntityTooLarge)
case status.ErrHeaderFieldsTooLarge:
return resp.WithCode(status.RequestHeaderFieldsTooLarge)
case status.ErrUnsupportedProtocol:
return resp.WithCode(status.NotImplemented)
case status.ErrUnsupportedEncoding:
return resp.WithCode(status.NotAcceptable)
case status.ErrConnectionTimeout:
return resp.WithCode(status.RequestTimeout)
default:
return r.WithCode(status.InternalServerError)
if http, ok := err.(status.HTTPError); ok {
return r.
WithCode(http.Code).
WithBody(http.Message)
}

return r.WithCode(status.InternalServerError)
}

// Headers returns an underlying response headers
Expand Down
25 changes: 25 additions & 0 deletions http/response_bench_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package http

import (
"errors"
"github.com/indigo-web/indigo/http/status"
"testing"
)

func BenchmarkResponse_WithError(b *testing.B) {
resp := NewResponse()
knownErr := status.ErrBadRequest
unknownErr := errors.New("some crap happened, unable to recover")

b.Run("KnownError", func(b *testing.B) {
for i := 0; i < b.N; i++ {
resp.WithError(knownErr)
}
})

b.Run("UnknownError", func(b *testing.B) {
for i := 0; i < b.N; i++ {
resp.WithError(unknownErr)
}
})
}
1 change: 1 addition & 0 deletions http/status/codes.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ const (
PreconditionRequired Code = 428 // RFC 6585, 3
TooManyRequests Code = 429 // RFC 6585, 4
RequestHeaderFieldsTooLarge Code = 431 // RFC 6585, 5
CloseConnection Code = 439
UnavailableForLegalReasons Code = 451 // RFC 7725, 3

InternalServerError Code = 500 // RFC 9110, 15.6.1
Expand Down
88 changes: 68 additions & 20 deletions http/status/errors.go
Original file line number Diff line number Diff line change
@@ -1,27 +1,75 @@
package status

import (
"errors"
)
type HTTPError struct {
Code Code
Message string
}

var (
ErrBadRequest = errors.New("bad request")
ErrMethodNotImplemented = errors.New("request method is not supported")
ErrTooLarge = errors.New("too large")
ErrHeaderFieldsTooLarge = errors.New("header fields too large")
ErrURITooLong = errors.New("request URI too long")
ErrURIDecoding = errors.New("invalid URI encoding")
ErrBadQuery = errors.New("bad URL query")
ErrUnsupportedProtocol = errors.New("protocol is not supported")
ErrUnsupportedEncoding = errors.New("content encoding is not supported")
ErrTooManyHeaders = errors.New("too many headers")
func newErr(code Code, message string) HTTPError {
return HTTPError{
Code: code,
Message: message,
}
}

ErrConnectionTimeout = errors.New("connection timed out")
ErrCloseConnection = errors.New("internal error as a signal")
func (h HTTPError) Error() string {
return h.Message
}

ErrNotFound = errors.New("not found")
ErrMethodNotAllowed = errors.New("method is not allowed")
ErrInternalServerError = errors.New("internal server error")
var (
ErrConnectionTimeout = newErr(RequestTimeout, "connection timed out")
ErrCloseConnection = newErr(CloseConnection, "internal error as a signal")
ErrShutdown = newErr(CloseConnection, "graceful shutdown")

ErrShutdown = errors.New("graceful shutdown")
ErrBadRequest = newErr(BadRequest, "bad request")
ErrNotFound = newErr(NotFound, "not found")
ErrInternalServerError = newErr(InternalServerError, "internal server error")
// ErrMethodNotImplemented is actually uses the same error code as just ErrNotImplemented, but used
// to explain the problem more preciously
ErrMethodNotImplemented = newErr(NotImplemented, "request method is not supported")
ErrMethodNotAllowed = newErr(MethodNotAllowed, "MethodNotAllowed")
ErrTooLarge = newErr(RequestEntityTooLarge, "too large")
ErrHeaderFieldsTooLarge = newErr(RequestHeaderFieldsTooLarge, "header fields too large")
ErrURITooLong = newErr(RequestURITooLong, "request URI too long")
ErrURIDecoding = newErr(BadRequest, "invalid URI encoding")
ErrBadQuery = newErr(BadRequest, "bad URL query")
ErrUnsupportedProtocol = newErr(HTTPVersionNotSupported, "protocol is not supported")
ErrUnsupportedEncoding = newErr(NotAcceptable, "content encoding is not supported")
ErrTooManyHeaders = newErr(RequestHeaderFieldsTooLarge, "too many headers")
ErrUnauthorized = newErr(Unauthorized, "unauthorized")
ErrPaymentRequired = newErr(PaymentRequired, "payment required")
ErrForbidden = newErr(Forbidden, "forbidden")
ErrNotAcceptable = newErr(NotAcceptable, "not acceptable")
ErrProxyAuthRequired = newErr(ProxyAuthRequired, "proxy auth required")
ErrRequestTimeout = newErr(RequestTimeout, "request timeout")
ErrConflict = newErr(Conflict, "conflict")
ErrGone = newErr(Gone, "gone")
ErrLengthRequired = newErr(LengthRequired, "length required")
ErrPreconditionFailed = newErr(PreconditionFailed, "precondition failed")
ErrRequestEntityTooLarge = newErr(RequestEntityTooLarge, "request entity too large")
ErrRequestURITooLong = newErr(RequestURITooLong, "request URI too long")
ErrUnsupportedMediaType = newErr(UnsupportedMediaType, "unsupported media type")
ErrRequestedRangeNotSatisfiable = newErr(RequestedRangeNotSatisfiable, "requested range not satisfiable")
ErrExpectationFailed = newErr(ExpectationFailed, "expectation failed")
ErrTeapot = newErr(Teapot, "i'm a teapot")
ErrMisdirectedRequest = newErr(MisdirectedRequest, "misdirected request")
ErrUnprocessableEntity = newErr(UnprocessableEntity, "unprocessable entity")
ErrLocked = newErr(Locked, "locked")
ErrFailedDependency = newErr(FailedDependency, "failed dependency")
ErrTooEarly = newErr(TooEarly, "too early")
ErrUpgradeRequired = newErr(UpgradeRequired, "upgrade required")
ErrPreconditionRequired = newErr(PreconditionRequired, "precondition required")
ErrTooManyRequests = newErr(TooManyRequests, "too many requests")
ErrRequestHeaderFieldsTooLarge = newErr(RequestHeaderFieldsTooLarge, "request header fields too large")
ErrUnavailableForLegalReasons = newErr(UnavailableForLegalReasons, "unavailable for legal reasons")
ErrNotImplemented = newErr(NotImplemented, "not implemented")
ErrBadGateway = newErr(BadGateway, "bad gateway")
ErrServiceUnavailable = newErr(ServiceUnavailable, "service unavailable")
ErrGatewayTimeout = newErr(GatewayTimeout, "GatewayTimeout")
ErrHTTPVersionNotSupported = newErr(HTTPVersionNotSupported, "HTTP version not supported")
ErrVariantAlsoNegotiates = newErr(VariantAlsoNegotiates, "variant also negotiates")
ErrInsufficientStorage = newErr(InsufficientStorage, "insufficient storage")
ErrLoopDetected = newErr(LoopDetected, "loop detected")
ErrNotExtended = newErr(NotExtended, "not extended")
ErrNetworkAuthenticationRequired = newErr(NetworkAuthenticationRequired, "network authentication required")
)
33 changes: 22 additions & 11 deletions router/inbuilt/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,56 +23,67 @@ func (r *Router) Resource(path string) Resource {

// Use applies middlewares to the resource, wrapping all the already registered
// and registered in future handlers
func (r Resource) Use(middlewares ...types.Middleware) {
func (r Resource) Use(middlewares ...types.Middleware) Resource {
r.group.Use(middlewares...)
return r
}

// Route is a shortcut to group.Route, providing the extra empty path to the call
func (r Resource) Route(method methods.Method, fun types.HandlerFunc, mwares ...types.Middleware) {
func (r Resource) Route(method methods.Method, fun types.HandlerFunc, mwares ...types.Middleware) Resource {
r.group.Route(method, "", fun, mwares...)
return r
}

// Get is a shortcut for registering GET-requests
func (r Resource) Get(handler types.HandlerFunc, mwares ...types.Middleware) {
func (r Resource) Get(handler types.HandlerFunc, mwares ...types.Middleware) Resource {
r.group.Get("", handler, mwares...)
return r
}

// Head is a shortcut for registering HEAD-requests
func (r Resource) Head(handler types.HandlerFunc, mwares ...types.Middleware) {
func (r Resource) Head(handler types.HandlerFunc, mwares ...types.Middleware) Resource {
r.group.Head("", handler, mwares...)
return r
}

// Post is a shortcut for registering POST-requests
func (r Resource) Post(handler types.HandlerFunc, mwares ...types.Middleware) {
func (r Resource) Post(handler types.HandlerFunc, mwares ...types.Middleware) Resource {
r.group.Post("", handler, mwares...)
return r
}

// Put is a shortcut for registering PUT-requests
func (r Resource) Put(handler types.HandlerFunc, mwares ...types.Middleware) {
func (r Resource) Put(handler types.HandlerFunc, mwares ...types.Middleware) Resource {
r.group.Put("", handler, mwares...)
return r
}

// Delete is a shortcut for registering DELETE-requests
func (r Resource) Delete(handler types.HandlerFunc, mwares ...types.Middleware) {
func (r Resource) Delete(handler types.HandlerFunc, mwares ...types.Middleware) Resource {
r.group.Delete("", handler, mwares...)
return r
}

// Connect is a shortcut for registering CONNECT-requests
func (r Resource) Connect(handler types.HandlerFunc, mwares ...types.Middleware) {
func (r Resource) Connect(handler types.HandlerFunc, mwares ...types.Middleware) Resource {
r.group.Connect("", handler, mwares...)
return r
}

// Options is a shortcut for registering OPTIONS-requests
func (r Resource) Options(handler types.HandlerFunc, mwares ...types.Middleware) {
func (r Resource) Options(handler types.HandlerFunc, mwares ...types.Middleware) Resource {
r.group.Options("", handler, mwares...)
return r
}

// Trace is a shortcut for registering TRACE-requests
func (r Resource) Trace(handler types.HandlerFunc, mwares ...types.Middleware) {
func (r Resource) Trace(handler types.HandlerFunc, mwares ...types.Middleware) Resource {
r.group.Trace("", handler, mwares...)
return r
}

// Patch is a shortcut for registering PATCH-requests
func (r Resource) Patch(handler types.HandlerFunc, mwares ...types.Middleware) {
func (r Resource) Patch(handler types.HandlerFunc, mwares ...types.Middleware) Resource {
r.group.Patch("", handler, mwares...)
return r
}
2 changes: 1 addition & 1 deletion version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.6.0
0.6.1

0 comments on commit 35716d5

Please sign in to comment.