mirror of
https://github.com/pocketbase/pocketbase.git
synced 2024-12-01 02:56:49 +02:00
133 lines
3.3 KiB
Go
133 lines
3.3 KiB
Go
package apis
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
|
|
validation "github.com/go-ozzo/ozzo-validation/v4"
|
|
"github.com/pocketbase/pocketbase/tools/inflector"
|
|
)
|
|
|
|
// ApiError defines the struct for a basic api error response.
|
|
type ApiError struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
Data map[string]any `json:"data"`
|
|
|
|
// stores unformatted error data (could be an internal error, text, etc.)
|
|
rawData any
|
|
}
|
|
|
|
// Error makes it compatible with the `error` interface.
|
|
func (e *ApiError) Error() string {
|
|
return e.Message
|
|
}
|
|
|
|
// RawData returns the unformatted error data (could be an internal error, text, etc.)
|
|
func (e *ApiError) RawData() any {
|
|
return e.rawData
|
|
}
|
|
|
|
// NewNotFoundError creates and returns 404 `ApiError`.
|
|
func NewNotFoundError(message string, data any) *ApiError {
|
|
if message == "" {
|
|
message = "The requested resource wasn't found."
|
|
}
|
|
|
|
return NewApiError(http.StatusNotFound, message, data)
|
|
}
|
|
|
|
// NewBadRequestError creates and returns 400 `ApiError`.
|
|
func NewBadRequestError(message string, data any) *ApiError {
|
|
if message == "" {
|
|
message = "Something went wrong while processing your request."
|
|
}
|
|
|
|
return NewApiError(http.StatusBadRequest, message, data)
|
|
}
|
|
|
|
// NewForbiddenError creates and returns 403 `ApiError`.
|
|
func NewForbiddenError(message string, data any) *ApiError {
|
|
if message == "" {
|
|
message = "You are not allowed to perform this request."
|
|
}
|
|
|
|
return NewApiError(http.StatusForbidden, message, data)
|
|
}
|
|
|
|
// NewUnauthorizedError creates and returns 401 `ApiError`.
|
|
func NewUnauthorizedError(message string, data any) *ApiError {
|
|
if message == "" {
|
|
message = "Missing or invalid authentication token."
|
|
}
|
|
|
|
return NewApiError(http.StatusUnauthorized, message, data)
|
|
}
|
|
|
|
// NewApiError creates and returns new normalized `ApiError` instance.
|
|
func NewApiError(status int, message string, data any) *ApiError {
|
|
return &ApiError{
|
|
rawData: data,
|
|
Data: safeErrorsData(data),
|
|
Code: status,
|
|
Message: strings.TrimSpace(inflector.Sentenize(message)),
|
|
}
|
|
}
|
|
|
|
func safeErrorsData(data any) map[string]any {
|
|
switch v := data.(type) {
|
|
case validation.Errors:
|
|
return resolveSafeErrorsData[error](v)
|
|
case map[string]validation.Error:
|
|
return resolveSafeErrorsData[validation.Error](v)
|
|
case map[string]error:
|
|
return resolveSafeErrorsData[error](v)
|
|
case map[string]any:
|
|
return resolveSafeErrorsData[any](v)
|
|
default:
|
|
return map[string]any{} // not nil to ensure that is json serialized as object
|
|
}
|
|
}
|
|
|
|
func resolveSafeErrorsData[T any](data map[string]T) map[string]any {
|
|
result := map[string]any{}
|
|
|
|
for name, err := range data {
|
|
if isNestedError(err) {
|
|
result[name] = safeErrorsData(err)
|
|
continue
|
|
}
|
|
result[name] = resolveSafeErrorItem(err)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func isNestedError(err any) bool {
|
|
switch err.(type) {
|
|
case validation.Errors, map[string]validation.Error, map[string]error, map[string]any:
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// resolveSafeErrorItem extracts from each validation error its
|
|
// public safe error code and message.
|
|
func resolveSafeErrorItem(err any) map[string]string {
|
|
// default public safe error values
|
|
code := "validation_invalid_value"
|
|
msg := "Invalid value."
|
|
|
|
// only validation errors are public safe
|
|
if obj, ok := err.(validation.Error); ok {
|
|
code = obj.Code()
|
|
msg = inflector.Sentenize(obj.Error())
|
|
}
|
|
|
|
return map[string]string{
|
|
"code": code,
|
|
"message": msg,
|
|
}
|
|
}
|