diff --git a/validation/length.go b/validation/length.go new file mode 100644 index 0000000..d54bd0d --- /dev/null +++ b/validation/length.go @@ -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 +} diff --git a/validation/range.go b/validation/range.go new file mode 100644 index 0000000..0bbdb65 --- /dev/null +++ b/validation/range.go @@ -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 +} diff --git a/validation/regex.go b/validation/regex.go new file mode 100644 index 0000000..5dc6f8e --- /dev/null +++ b/validation/regex.go @@ -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 +}