feat: add constraints (length, range, regex)
This commit is contained in:
parent
b7c2ddeebf
commit
f2abb43261
3 changed files with 261 additions and 0 deletions
95
validation/length.go
Normal file
95
validation/length.go
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
package validation
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
type Length struct {
|
||||
Min *int
|
||||
Max *int
|
||||
MinMessage string
|
||||
MaxMessage string
|
||||
ExactMessage string
|
||||
TypeErrorMessage string
|
||||
}
|
||||
|
||||
func NewLength() Length {
|
||||
return Length{
|
||||
MinMessage: "This value is too short (min: {{ min }}).",
|
||||
MaxMessage: "This value is too long (max: {{ max }}).",
|
||||
ExactMessage: "This value is not valid (expected: {{ min }}).",
|
||||
TypeErrorMessage: "This value can not be processed.",
|
||||
}
|
||||
}
|
||||
|
||||
func (c Length) WithMin(v int) Length {
|
||||
c.Min = &v
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c Length) WithMax(v int) Length {
|
||||
c.Max = &v
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c Length) WithExact(v int) Length {
|
||||
c.Min = &v
|
||||
c.Max = &v
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c Length) Validate(data any) []Error {
|
||||
if c.Min == nil && c.Max == nil {
|
||||
return []Error{}
|
||||
}
|
||||
|
||||
errors := []Error{}
|
||||
|
||||
t := reflect.TypeOf(data)
|
||||
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
var size *int
|
||||
|
||||
switch t.Kind() {
|
||||
case reflect.Array:
|
||||
case reflect.Slice:
|
||||
s := reflect.ValueOf(data).Len()
|
||||
size = &s
|
||||
case reflect.String:
|
||||
s := len(data.(string))
|
||||
size = &s
|
||||
|
||||
default:
|
||||
errors = append(errors, Error(c.TypeErrorMessage))
|
||||
}
|
||||
|
||||
if size != nil {
|
||||
if c.Max != nil && c.Min != nil {
|
||||
if *c.Max == *c.Min && *size != *c.Max {
|
||||
errors = append(errors, Error(c.BuildMessage(c.ExactMessage)))
|
||||
}
|
||||
} else if c.Min != nil && *size < *c.Min {
|
||||
errors = append(errors, Error(c.BuildMessage(c.MinMessage)))
|
||||
} else if c.Max != nil && *size > *c.Max {
|
||||
errors = append(errors, Error(c.BuildMessage(c.MaxMessage)))
|
||||
}
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
func (c *Length) BuildMessage(message string) string {
|
||||
message = strings.ReplaceAll(message, "{{ min }}", cast.ToString(c.Min))
|
||||
message = strings.ReplaceAll(message, "{{ max }}", cast.ToString(c.Max))
|
||||
|
||||
return message
|
||||
}
|
||||
102
validation/range.go
Normal file
102
validation/range.go
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
package validation
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
type Range struct {
|
||||
Min *float64
|
||||
Max *float64
|
||||
MinMessage string
|
||||
MaxMessage string
|
||||
RangeMessage string
|
||||
TypeErrorMessage string
|
||||
}
|
||||
|
||||
func NewRange() Range {
|
||||
return Range{
|
||||
MinMessage: "This value must be greater than or equal to {{ min }}.",
|
||||
MaxMessage: "This value must be less than or equal to {{ max }}.",
|
||||
RangeMessage: "This value should be between {{ min }} and {{ max }}.",
|
||||
TypeErrorMessage: "This value can not be processed.",
|
||||
}
|
||||
}
|
||||
|
||||
func (c Range) WithMin(v float64) Range {
|
||||
c.Min = &v
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c Range) WithMax(v float64) Range {
|
||||
c.Max = &v
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c Range) WithRange(vMin, vMax float64) Range {
|
||||
c.Min = &vMin
|
||||
c.Max = &vMax
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c Range) Validate(data any) []Error {
|
||||
if c.Min == nil && c.Max == nil {
|
||||
return []Error{}
|
||||
}
|
||||
|
||||
errors := []Error{}
|
||||
|
||||
t := reflect.TypeOf(data)
|
||||
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
switch t.Kind() {
|
||||
case reflect.Float32:
|
||||
case reflect.Float64:
|
||||
case reflect.Int:
|
||||
case reflect.Int16:
|
||||
case reflect.Int32:
|
||||
case reflect.Int64:
|
||||
case reflect.Int8:
|
||||
case reflect.Uint:
|
||||
case reflect.Uint16:
|
||||
case reflect.Uint32:
|
||||
case reflect.Uint64:
|
||||
case reflect.Uint8:
|
||||
case reflect.String:
|
||||
isValidMin := c.Min == nil || *c.Min <= cast.ToFloat64(data.(string))
|
||||
isValidMax := c.Max == nil || *c.Max >= cast.ToFloat64(data.(string))
|
||||
|
||||
if !isValidMin || !isValidMax {
|
||||
errors = append(errors, Error(c.BuildMessage()))
|
||||
}
|
||||
default:
|
||||
errors = append(errors, Error(c.TypeErrorMessage))
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
func (c *Range) BuildMessage() string {
|
||||
var message string
|
||||
|
||||
if c.Min != nil && c.Max == nil {
|
||||
message = c.MinMessage
|
||||
} else if c.Max != nil && c.Min == nil {
|
||||
message = c.MaxMessage
|
||||
} else {
|
||||
message = c.RangeMessage
|
||||
}
|
||||
|
||||
message = strings.ReplaceAll(message, "{{ min }}", cast.ToString(c.Min))
|
||||
message = strings.ReplaceAll(message, "{{ max }}", cast.ToString(c.Max))
|
||||
|
||||
return message
|
||||
}
|
||||
64
validation/regex.go
Normal file
64
validation/regex.go
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
package validation
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type Regex struct {
|
||||
Message string
|
||||
TypeErrorMessage string
|
||||
Match bool
|
||||
Expression string
|
||||
}
|
||||
|
||||
func NewRegex(expr string) Regex {
|
||||
return Regex{
|
||||
Message: "This value is not valid.",
|
||||
TypeErrorMessage: "This value can not be processed.",
|
||||
Match: true,
|
||||
Expression: expr,
|
||||
}
|
||||
}
|
||||
|
||||
func (c Regex) MustMatch() Regex {
|
||||
c.Match = true
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c Regex) MustNotMatch() Regex {
|
||||
c.Match = false
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c Regex) Validate(data any) []Error {
|
||||
errors := []Error{}
|
||||
notBlank := NotBlank{}
|
||||
nbErrs := notBlank.Validate(data)
|
||||
|
||||
if len(nbErrs) > 0 {
|
||||
return errors
|
||||
}
|
||||
|
||||
t := reflect.TypeOf(data)
|
||||
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
switch t.Kind() {
|
||||
case reflect.String:
|
||||
matched, _ := regexp.MatchString(c.Expression, data.(string))
|
||||
|
||||
if !matched && c.Match || matched && !c.Match {
|
||||
errors = append(errors, Error(c.Message))
|
||||
}
|
||||
|
||||
default:
|
||||
errors = append(errors, Error(c.TypeErrorMessage))
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue