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 }