core/internal/domain/validation.go

398 lines
7.8 KiB
Go

package domain
import (
"errors"
"fmt"
)
type ValidationError struct {
Model string
Field string
Err error
}
var _ ErrorWithParams = ValidationError{}
var _ ErrorWithPath = ValidationError{}
func (e ValidationError) Error() string {
prefix := e.Path().String()
if prefix != "" {
prefix += ": "
}
return prefix + e.Err.Error()
}
func (e ValidationError) Path() ErrorPathSegment {
return ErrorPathSegment{
Model: e.Model,
Field: e.Field,
Index: -1,
}
}
func (e ValidationError) Type() ErrorType {
var domainErr Error
if errors.As(e.Err, &domainErr) {
return domainErr.Type()
}
return ErrorTypeIncorrectInput
}
// errorcode:ignore
const errorCodeValidationFailed = "validation-failed"
func (e ValidationError) Code() string {
var domainErr Error
if errors.As(e.Err, &domainErr) {
return domainErr.Code()
}
return errorCodeValidationFailed
}
func (e ValidationError) Params() map[string]any {
var withParams ErrorWithParams
if ok := errors.As(e.Err, &withParams); !ok {
return nil
}
return withParams.Params()
}
func (e ValidationError) Unwrap() error {
return e.Err
}
type SliceElementValidationError struct {
Model string
Field string
Index int
Err error
}
var _ ErrorWithParams = SliceElementValidationError{}
var _ ErrorWithPath = SliceElementValidationError{}
func (e SliceElementValidationError) Error() string {
prefix := e.Path().String()
if prefix != "" {
prefix += ": "
}
return prefix + e.Err.Error()
}
func (e SliceElementValidationError) Path() ErrorPathSegment {
return ErrorPathSegment{
Model: e.Model,
Field: e.Field,
Index: e.Index,
}
}
func (e SliceElementValidationError) Type() ErrorType {
var domainErr Error
if errors.As(e.Err, &domainErr) {
return domainErr.Type()
}
return ErrorTypeIncorrectInput
}
// errorcode:ignore
const errorCodeSliceElementValidationFailed = "slice-element-validation-failed"
func (e SliceElementValidationError) Code() string {
var domainErr Error
if errors.As(e.Err, &domainErr) {
return domainErr.Code()
}
return errorCodeSliceElementValidationFailed
}
func (e SliceElementValidationError) Params() map[string]any {
var withParams ErrorWithParams
if ok := errors.As(e.Err, &withParams); !ok {
return nil
}
return withParams.Params()
}
func (e SliceElementValidationError) Unwrap() error {
return e.Err
}
type MinGreaterEqualError struct {
Min int
Current int
}
var _ ErrorWithParams = MinGreaterEqualError{}
func (e MinGreaterEqualError) Error() string {
return fmt.Sprintf("must be no less than %d (current: %d)", e.Min, e.Current)
}
func (e MinGreaterEqualError) Type() ErrorType {
return ErrorTypeIncorrectInput
}
const errorCodeMinGreaterEqual = "min-greater-equal"
func (e MinGreaterEqualError) Code() string {
return errorCodeMinGreaterEqual
}
func (e MinGreaterEqualError) Params() map[string]any {
return map[string]any{
"Min": e.Min,
"Current": e.Current,
}
}
type MaxLessEqualError struct {
Max int
Current int
}
var _ ErrorWithParams = MaxLessEqualError{}
func (e MaxLessEqualError) Error() string {
return fmt.Sprintf("must be no greater than %d (current: %d)", e.Max, e.Current)
}
func (e MaxLessEqualError) Type() ErrorType {
return ErrorTypeIncorrectInput
}
const errorCodeMaxLessEqual = "max-less-equal"
func (e MaxLessEqualError) Code() string {
return errorCodeMaxLessEqual
}
func (e MaxLessEqualError) Params() map[string]any {
return map[string]any{
"Max": e.Max,
"Current": e.Current,
}
}
type LenOutOfRangeError struct {
Min int
Max int
Current int
}
var _ ErrorWithParams = LenOutOfRangeError{}
func (e LenOutOfRangeError) Error() string {
return fmt.Sprintf("length must be between %d and %d (current length: %d)", e.Min, e.Max, e.Current)
}
func (e LenOutOfRangeError) Type() ErrorType {
return ErrorTypeIncorrectInput
}
const errorCodeLenOutOfRange = "length-out-of-range"
func (e LenOutOfRangeError) Code() string {
return errorCodeLenOutOfRange
}
func (e LenOutOfRangeError) Params() map[string]any {
return map[string]any{
"Min": e.Min,
"Max": e.Max,
"Current": e.Current,
}
}
type InvalidURLError struct {
URL string
}
var _ ErrorWithParams = InvalidURLError{}
func (e InvalidURLError) Error() string {
return fmt.Sprintf("%s: invalid URL", e.URL)
}
func (e InvalidURLError) Type() ErrorType {
return ErrorTypeIncorrectInput
}
const errorCodeInvalidURL = "invalid-url"
func (e InvalidURLError) Code() string {
return errorCodeInvalidURL
}
func (e InvalidURLError) Params() map[string]any {
return map[string]any{
"URL": e.URL,
}
}
type UnsupportedSortStringError struct {
Sort string
}
var _ ErrorWithParams = UnsupportedSortStringError{}
func (e UnsupportedSortStringError) Error() string {
return fmt.Sprintf("sort %s is unsupported", e.Sort)
}
func (e UnsupportedSortStringError) Type() ErrorType {
return ErrorTypeIncorrectInput
}
const errorCodeUnsupportedSortString = "unsupported-sort-string"
func (e UnsupportedSortStringError) Code() string {
return errorCodeUnsupportedSortString
}
func (e UnsupportedSortStringError) Params() map[string]any {
return map[string]any{
"Sort": e.Sort,
}
}
type SortConflictError struct {
Sort [2]string
}
var _ ErrorWithParams = SortConflictError{}
func (e SortConflictError) Error() string {
return fmt.Sprintf("sort values %s are in conflict and can't be used together", e.Sort[0]+" and "+e.Sort[1])
}
func (e SortConflictError) Type() ErrorType {
return ErrorTypeIncorrectInput
}
const errorCodeSortConflict = "sort-conflict"
func (e SortConflictError) Code() string {
return errorCodeSortConflict
}
func (e SortConflictError) Params() map[string]any {
return map[string]any{
"Sort": e.Sort,
}
}
// This error code is returned when a value can't be blank.
const errorCodeRequired = "required"
var ErrRequired error = simpleError{
msg: "can't be blank",
typ: ErrorTypeIncorrectInput,
code: errorCodeRequired,
}
// This error code is returned when a value can't be nil.
const errorCodeNil = "nil"
var ErrNil error = simpleError{
msg: "must not be nil",
typ: ErrorTypeIncorrectInput,
code: errorCodeNil,
}
// This error code is returned when a cursor can't be decoded (e.g. is malformed).
const errorCodeInvalidCursor = "invalid-cursor"
// ErrInvalidCursor is an error that is returned when a cursor can't be decoded (e.g. is malformed).
var ErrInvalidCursor error = simpleError{
msg: "invalid cursor",
typ: ErrorTypeIncorrectInput,
code: errorCodeInvalidCursor,
}
func validateSliceLen[S ~[]E, E any](s S, min, max int) error {
if l := len(s); l > max || l < min {
return LenOutOfRangeError{
Min: min,
Max: max,
Current: l,
}
}
return nil
}
func validateStringLen(s string, min, max int) error {
if l := len(s); l > max || l < min {
return LenOutOfRangeError{
Min: min,
Max: max,
Current: l,
}
}
return nil
}
const (
versionCodeMinLength = 2
versionCodeMaxLength = 2
)
func validateVersionCode(code string) error {
return validateStringLen(code, versionCodeMinLength, versionCodeMaxLength)
}
const (
serverKeyMinLength = 1
serverKeyMaxLength = 10
)
func validateServerKey(key string) error {
return validateStringLen(key, serverKeyMinLength, serverKeyMaxLength)
}
func validateIntInRange(current, min, max int) error {
if current < min {
return MinGreaterEqualError{
Min: min,
Current: current,
}
}
if current > max {
return MaxLessEqualError{
Max: max,
Current: current,
}
}
return nil
}
func validateSort[S ~[]E, E interface {
IsInConflict(s E) bool
String() string
}](sort S, min, max int) error {
if err := validateSliceLen(sort, min, max); err != nil {
return err
}
for i, s1 := range sort {
for j := i + 1; j < len(sort); j++ {
s2 := sort[j]
if s1.IsInConflict(s2) {
return SortConflictError{
Sort: [2]string{s1.String(), s2.String()},
}
}
}
}
return nil
}