(Feat): Initial Commit
This commit is contained in:
16
internal/api/httperrors/auth.go
Normal file
16
internal/api/httperrors/auth.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package httperrors
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"allaboutapps.dev/aw/go-starter/internal/types"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrForbiddenUserDeactivated = NewHTTPError(http.StatusForbidden, types.PublicHTTPErrorTypeUSERDEACTIVATED, "User account is deactivated")
|
||||
ErrBadRequestInvalidPassword = NewHTTPErrorWithDetail(http.StatusBadRequest, types.PublicHTTPErrorTypeINVALIDPASSWORD, "The password provided was invalid", "Password was either too weak or did not match other criteria")
|
||||
ErrForbiddenNotLocalUser = NewHTTPError(http.StatusForbidden, types.PublicHTTPErrorTypeNOTLOCALUSER, "User account is not valid for local authentication")
|
||||
ErrNotFoundTokenNotFound = NewHTTPError(http.StatusNotFound, types.PublicHTTPErrorTypeTOKENNOTFOUND, "Provided token was not found")
|
||||
ErrConflictTokenExpired = NewHTTPError(http.StatusConflict, types.PublicHTTPErrorTypeTOKENEXPIRED, "Provided token has expired and is no longer valid")
|
||||
ErrConflictUserAlreadyExists = NewHTTPError(http.StatusConflict, types.PublicHTTPErrorTypeUSERALREADYEXISTS, "User with given username already exists")
|
||||
)
|
||||
11
internal/api/httperrors/common.go
Normal file
11
internal/api/httperrors/common.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package httperrors
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"allaboutapps.dev/aw/go-starter/internal/types"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrBadRequestZeroFileSize = NewHTTPError(http.StatusBadRequest, types.PublicHTTPErrorTypeZEROFILESIZE, "File size of 0 is not supported.")
|
||||
)
|
||||
147
internal/api/httperrors/error.go
Normal file
147
internal/api/httperrors/error.go
Normal file
@@ -0,0 +1,147 @@
|
||||
package httperrors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"allaboutapps.dev/aw/go-starter/internal/types"
|
||||
"github.com/go-openapi/swag"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// Payload in accordance with RFC 7807 (Problem Details for HTTP APIs) with the exception of the type
|
||||
// value not being represented by a URI. https://tools.ietf.org/html/rfc7807 @ 2020-04-27T15:44:37Z
|
||||
|
||||
type HTTPError struct {
|
||||
types.PublicHTTPError
|
||||
Internal error `json:"-"`
|
||||
AdditionalData map[string]interface{} `json:"-"`
|
||||
}
|
||||
|
||||
type HTTPValidationError struct {
|
||||
types.PublicHTTPValidationError
|
||||
Internal error `json:"-"`
|
||||
AdditionalData map[string]interface{} `json:"-"`
|
||||
}
|
||||
|
||||
func NewHTTPError(code int, errorType types.PublicHTTPErrorType, title string) *HTTPError {
|
||||
return &HTTPError{
|
||||
PublicHTTPError: types.PublicHTTPError{
|
||||
Code: swag.Int64(int64(code)),
|
||||
Type: errorType.Pointer(),
|
||||
Title: swag.String(title),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewHTTPErrorWithDetail(code int, errorType types.PublicHTTPErrorType, title string, detail string) *HTTPError {
|
||||
return &HTTPError{
|
||||
PublicHTTPError: types.PublicHTTPError{
|
||||
Code: swag.Int64(int64(code)),
|
||||
Type: errorType.Pointer(),
|
||||
Title: swag.String(title),
|
||||
Detail: detail,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewFromEcho(e *echo.HTTPError) *HTTPError {
|
||||
return NewHTTPError(e.Code, types.PublicHTTPErrorTypeGeneric, http.StatusText(e.Code))
|
||||
}
|
||||
|
||||
func (e *HTTPError) Error() string {
|
||||
var builder strings.Builder
|
||||
|
||||
fmt.Fprintf(&builder, "HTTPError %d (%s): %s", *e.Code, *e.Type, *e.Title)
|
||||
|
||||
if len(e.Detail) > 0 {
|
||||
fmt.Fprintf(&builder, " - %s", e.Detail)
|
||||
}
|
||||
if e.Internal != nil {
|
||||
fmt.Fprintf(&builder, ", %v", e.Internal)
|
||||
}
|
||||
if len(e.AdditionalData) > 0 {
|
||||
keys := make([]string, 0, len(e.AdditionalData))
|
||||
for k := range e.AdditionalData {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
builder.WriteString(". Additional: ")
|
||||
for i, k := range keys {
|
||||
fmt.Fprintf(&builder, "%s=%v", k, e.AdditionalData[k])
|
||||
if i < len(keys)-1 {
|
||||
builder.WriteString(", ")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func NewHTTPValidationError(code int, errorType types.PublicHTTPErrorType, title string, validationErrors []*types.HTTPValidationErrorDetail) *HTTPValidationError {
|
||||
return &HTTPValidationError{
|
||||
PublicHTTPValidationError: types.PublicHTTPValidationError{
|
||||
PublicHTTPError: types.PublicHTTPError{
|
||||
Code: swag.Int64(int64(code)),
|
||||
Type: errorType.Pointer(),
|
||||
Title: swag.String(title),
|
||||
},
|
||||
ValidationErrors: validationErrors,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewHTTPValidationErrorWithDetail(code int, errorType types.PublicHTTPErrorType, title string, validationErrors []*types.HTTPValidationErrorDetail, detail string) *HTTPValidationError {
|
||||
return &HTTPValidationError{
|
||||
PublicHTTPValidationError: types.PublicHTTPValidationError{
|
||||
PublicHTTPError: types.PublicHTTPError{
|
||||
Code: swag.Int64(int64(code)),
|
||||
Type: errorType.Pointer(),
|
||||
Title: swag.String(title),
|
||||
Detail: detail,
|
||||
},
|
||||
ValidationErrors: validationErrors,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (e *HTTPValidationError) Error() string {
|
||||
var builder strings.Builder
|
||||
|
||||
fmt.Fprintf(&builder, "HTTPValidationError %d (%s): %s", *e.Code, *e.Type, *e.Title)
|
||||
|
||||
if len(e.Detail) > 0 {
|
||||
fmt.Fprintf(&builder, " - %s", e.Detail)
|
||||
}
|
||||
if e.Internal != nil {
|
||||
fmt.Fprintf(&builder, ", %v", e.Internal)
|
||||
}
|
||||
if len(e.AdditionalData) > 0 {
|
||||
keys := make([]string, 0, len(e.AdditionalData))
|
||||
for k := range e.AdditionalData {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
builder.WriteString(". Additional: ")
|
||||
for i, k := range keys {
|
||||
fmt.Fprintf(&builder, "%s=%v", k, e.AdditionalData[k])
|
||||
if i < len(keys)-1 {
|
||||
builder.WriteString(", ")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
builder.WriteString(" - Validation: ")
|
||||
for i, ve := range e.ValidationErrors {
|
||||
fmt.Fprintf(&builder, "%s (in %s): %s", *ve.Key, *ve.In, *ve.Error)
|
||||
if i < len(e.ValidationErrors)-1 {
|
||||
builder.WriteString(", ")
|
||||
}
|
||||
}
|
||||
|
||||
return builder.String()
|
||||
}
|
||||
80
internal/api/httperrors/error_test.go
Normal file
80
internal/api/httperrors/error_test.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package httperrors_test
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"allaboutapps.dev/aw/go-starter/internal/api/httperrors"
|
||||
"allaboutapps.dev/aw/go-starter/internal/types"
|
||||
"github.com/go-openapi/swag"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestHTTPErrorSimple(t *testing.T) {
|
||||
err := httperrors.NewHTTPError(http.StatusNotFound, types.PublicHTTPErrorTypeGeneric, http.StatusText(http.StatusNotFound))
|
||||
require.Equal(t, "HTTPError 404 (generic): Not Found", err.Error())
|
||||
}
|
||||
|
||||
func TestHTTPErrorDetail(t *testing.T) {
|
||||
err := httperrors.NewHTTPErrorWithDetail(http.StatusNotFound, types.PublicHTTPErrorTypeGeneric, http.StatusText(http.StatusNotFound), "ToS violation")
|
||||
require.Equal(t, "HTTPError 404 (generic): Not Found - ToS violation", err.Error())
|
||||
}
|
||||
|
||||
func TestHTTPErrorInternalError(t *testing.T) {
|
||||
err := httperrors.NewHTTPError(http.StatusInternalServerError, types.PublicHTTPErrorTypeGeneric, http.StatusText(http.StatusInternalServerError))
|
||||
|
||||
err.Internal = sql.ErrConnDone
|
||||
|
||||
require.Equal(t, "HTTPError 500 (generic): Internal Server Error, sql: connection is already closed", err.Error())
|
||||
}
|
||||
|
||||
func TestHTTPErrorAdditionalData(t *testing.T) {
|
||||
err := httperrors.NewHTTPError(http.StatusInternalServerError, types.PublicHTTPErrorTypeGeneric, http.StatusText(http.StatusInternalServerError))
|
||||
|
||||
err.AdditionalData = map[string]interface{}{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
}
|
||||
|
||||
require.Equal(t, "HTTPError 500 (generic): Internal Server Error. Additional: key1=value1, key2=value2", err.Error())
|
||||
}
|
||||
|
||||
var valErrs = append(make([]*types.HTTPValidationErrorDetail, 0, 2), &types.HTTPValidationErrorDetail{
|
||||
Key: swag.String("test1"),
|
||||
In: swag.String("body.test1"),
|
||||
Error: swag.String("ValidationError"),
|
||||
}, &types.HTTPValidationErrorDetail{
|
||||
Key: swag.String("test2"),
|
||||
In: swag.String("body.test2"),
|
||||
Error: swag.String("Validation Error"),
|
||||
})
|
||||
|
||||
func TestHTTPValidationErrorSimple(t *testing.T) {
|
||||
err := httperrors.NewHTTPValidationError(http.StatusBadRequest, types.PublicHTTPErrorTypeGeneric, http.StatusText(http.StatusBadRequest), valErrs)
|
||||
require.Equal(t, "HTTPValidationError 400 (generic): Bad Request - Validation: test1 (in body.test1): ValidationError, test2 (in body.test2): Validation Error", err.Error())
|
||||
}
|
||||
|
||||
func TestHTTPValidationErrorDetail(t *testing.T) {
|
||||
err := httperrors.NewHTTPValidationErrorWithDetail(http.StatusBadRequest, types.PublicHTTPErrorTypeGeneric, http.StatusText(http.StatusBadRequest), valErrs, "Did API spec change?")
|
||||
require.Equal(t, "HTTPValidationError 400 (generic): Bad Request - Did API spec change? - Validation: test1 (in body.test1): ValidationError, test2 (in body.test2): Validation Error", err.Error())
|
||||
}
|
||||
|
||||
func TestHTTPValidationErrorInternalError(t *testing.T) {
|
||||
err := httperrors.NewHTTPValidationError(http.StatusBadRequest, types.PublicHTTPErrorTypeGeneric, http.StatusText(http.StatusBadRequest), valErrs)
|
||||
|
||||
err.Internal = sql.ErrConnDone
|
||||
|
||||
require.Equal(t, "HTTPValidationError 400 (generic): Bad Request, sql: connection is already closed - Validation: test1 (in body.test1): ValidationError, test2 (in body.test2): Validation Error", err.Error())
|
||||
}
|
||||
|
||||
func TestHTTPValidationErrorAdditionalData(t *testing.T) {
|
||||
err := httperrors.NewHTTPValidationError(http.StatusBadRequest, types.PublicHTTPErrorTypeGeneric, http.StatusText(http.StatusBadRequest), valErrs)
|
||||
|
||||
err.AdditionalData = map[string]interface{}{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
}
|
||||
|
||||
require.Equal(t, "HTTPValidationError 400 (generic): Bad Request. Additional: key1=value1, key2=value2 - Validation: test1 (in body.test1): ValidationError, test2 (in body.test2): Validation Error", err.Error())
|
||||
}
|
||||
12
internal/api/httperrors/push.go
Normal file
12
internal/api/httperrors/push.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package httperrors
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"allaboutapps.dev/aw/go-starter/internal/types"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrConflictPushToken = NewHTTPError(http.StatusConflict, types.PublicHTTPErrorTypePUSHTOKENALREADYEXISTS, "The given token already exists.")
|
||||
ErrNotFoundOldPushToken = NewHTTPError(http.StatusNotFound, types.PublicHTTPErrorTypeOLDPUSHTOKENNOTFOUND, "The old push token does not exists. The new token was saved.")
|
||||
)
|
||||
Reference in New Issue
Block a user