Skip to content

Commit

Permalink
Add lt, lte, gt and gte rules
Browse files Browse the repository at this point in the history
  • Loading branch information
mjarkk committed Nov 13, 2024
1 parent 55404c4 commit 9b71ee1
Show file tree
Hide file tree
Showing 7 changed files with 346 additions and 128 deletions.
24 changes: 24 additions & 0 deletions RULES.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,18 @@ The field under validation must not be empty when it is present.

This validator is almost equal to the `required` validator except that it allows nil values.

### `gt:field`

The field under validation must be greater than the given field.
The two fields must be of the same type.
Strings, numerics, arrays, and files are evaluated using the same conventions as the size rule.

### `gte:field`

The field under validation must be greater than or equal to the given field or value.
The two fields must be of the same type.
Strings, numerics, arrays, and files are evaluated using the same conventions as the size rule.

### `hex_color`

The field under validation must contain a valid color value in [hexadecimal](https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color) format.
Expand All @@ -211,6 +223,18 @@ The field under validation must be an IPv6 address.

The field under validation must be a valid JSON `string` or `[]byte`.

### `lt:field`

The field under validation must be less than the given field.
The two fields must be of the same type.
Strings, numerics, arrays, and files are evaluated using the same conventions as the size rule.

### `lte:field`

The field under validation must be less than or equal to the given field.
The two fields must be of the same type.
Strings, numerics, arrays, and files are evaluated using the same conventions as the size rule.

### `lowercase`

The field under validation must be lowercase.
Expand Down
44 changes: 44 additions & 0 deletions needle.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,16 @@ func (n *Needle) IsNumeric() bool {
}
}

// IsBool returns true if the type kind is a list
func (n *Needle) IsList() bool {
switch n.Kind() {
case reflect.Slice, reflect.Array:
return true
default:
return false
}
}

// HasLen returns true if the type kind supports the reflect.Len method
func (n *Needle) HasLen() bool {
switch n.Kind() {
Expand Down Expand Up @@ -265,3 +275,37 @@ func (n *Needle) Date() (time.Time, ConvertStatus) {
return time.Time{}, InvalidType
}
}

// Float64 tries to convert the number to a float64
func (n *Needle) Float64() (float64, bool) {
if !n.HasValue() {
return 0, false
}

switch n.Kind() {
case reflect.Float32, reflect.Float64:
return n.Value.Float(), true
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return float64(n.Value.Int()), true
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return float64(n.Value.Uint()), true
}

return 0, false
}

// Int64 tries to convert the number to a int64
func (n *Needle) Int64() (int64, bool) {
if !n.HasValue() {
return 0, false
}

switch n.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return n.Value.Int(), true
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return int64(n.Value.Uint()), true
}

return 0, false
}
214 changes: 182 additions & 32 deletions rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,8 @@ func init() {
// File

RegisterValidator("filled", Filled)

// Greater Than
// Greater Than Or Equal

RegisterValidator("gt", Gt)
RegisterValidator("gte", Gte)
RegisterValidator("hex_color", HexColor)

// Image (File)
Expand All @@ -91,10 +89,8 @@ func init() {
RegisterValidator("ipv4", IPV4)
RegisterValidator("ipv6", IPV6)
RegisterValidator("json", JSON)

// Less Than
// Less Than Or Equal

RegisterValidator("lt", Lt)
RegisterValidator("lte", Lte)
RegisterValidator("lowercase", Lowercase)
// Unsupported: List
RegisterValidator("mac_address", MacAddress)
Expand Down Expand Up @@ -205,18 +201,18 @@ func init() {
"extensions": BasicMessageResolver("The :attribute field must have one of the following extensions: :args."),
// "file": BasicMessageResolver("The :attribute field must be a file."),
"filled": BasicMessageResolver("The :attribute field must have a value."),
// "gt": MessageHintResolver{Hints: map[string]string{
// "array": "The :attribute field must have more than :value items.",
// "file": "The :attribute field must be greater than :value kilobytes.",
// "numeric": "The :attribute field must be greater than :value.",
// "string": "The :attribute field must be greater than :value characters.",
// }},
// "gte": MessageHintResolver{Hints: map[string]string{
// "array": "The :attribute field must have :value items or more.",
// "file": "The :attribute field must be greater than or equal to :value kilobytes.",
// "numeric": "The :attribute field must be greater than or equal to :value.",
// "string": "The :attribute field must be greater than or equal to :value characters.",
// }},
"gt": MessageHintResolver{Hints: map[string]string{
"array": "The :attribute field must have more than :value items.",
"file": "The :attribute field must be greater than :value kilobytes.",
"numeric": "The :attribute field must be greater than :value.",
"string": "The :attribute field must be greater than :value characters.",
}},
"gte": MessageHintResolver{Hints: map[string]string{
"array": "The :attribute field must have :value items or more.",
"file": "The :attribute field must be greater than or equal to :value kilobytes.",
"numeric": "The :attribute field must be greater than or equal to :value.",
"string": "The :attribute field must be greater than or equal to :value characters.",
}},
"hex_color": BasicMessageResolver("The :attribute field must be a valid hexadecimal color."),
// "image": BasicMessageResolver("The :attribute field must be an image."),
"in": BasicMessageResolver("The selected :attribute is invalid."),
Expand All @@ -228,18 +224,18 @@ func init() {
"json": BasicMessageResolver("The :attribute field must be a valid JSON string."),
// "list": BasicMessageResolver("The :attribute field must be a list."),
"lowercase": BasicMessageResolver("The :attribute field must be lowercase."),
// "lt": MessageHintResolver{Hints: map[string]string{
// "array": "The :attribute field must have less than :value items.",
// "file": "The :attribute field must be less than :value kilobytes.",
// "numeric": "The :attribute field must be less than :value.",
// "string": "The :attribute field must be less than :value characters.",
// }},
// "lte": MessageHintResolver{Hints: map[string]string{
// "array": "The :attribute field must not have more than :value items.",
// "file": "The :attribute field must be less than or equal to :value kilobytes.",
// "numeric": "The :attribute field must be less than or equal to :value.",
// "string": "The :attribute field must be less than or equal to :value characters.",
// }},
"lt": MessageHintResolver{Hints: map[string]string{
"array": "The :attribute field must have less than :value items.",
"file": "The :attribute field must be less than :value kilobytes.",
"numeric": "The :attribute field must be less than :value.",
"string": "The :attribute field must be less than :value characters.",
}},
"lte": MessageHintResolver{Hints: map[string]string{
"array": "The :attribute field must not have more than :value items.",
"file": "The :attribute field must be less than or equal to :value kilobytes.",
"numeric": "The :attribute field must be less than or equal to :value.",
"string": "The :attribute field must be less than or equal to :value characters.",
}},
"mac_address": BasicMessageResolver("The :attribute field must be a valid MAC address."),
"max": MessageHintResolver{
Fallback: "The :attribute field must not be greater than :arg.",
Expand Down Expand Up @@ -1715,3 +1711,157 @@ func Confirmed(ctx *ValidatorCtx) (string, bool) {

return "", true
}

type SizeCompareStatus uint8

const (
SizeCompareStatusEq SizeCompareStatus = iota
SizeCompareStatusLt
SizeCompareStatusGt
)

func compareFieldsBase(ctx *ValidatorCtx) (SizeCompareStatus, ConvertStatus) {
if len(ctx.Args) == 0 {
return 0, Invalid
}

ctx.UnwrapPointer()

if !ctx.HasValue() {
return 0, ValueNil
}

other := ctx.Field(ctx.Args[0])
other.UnwrapPointer()

if !other.HasValue() {
return 0, Invalid
}

if ctx.IsNumeric() || other.IsNumeric() {
if !ctx.IsNumeric() || !other.IsNumeric() {
return 0, InvalidType
}

if ctx.IsFloat() || other.IsFloat() {
aValue, aOk := ctx.Float64()
bValue, bOk := other.Float64()
if !aOk || !bOk {
return 0, InvalidType
}

if aValue == bValue {
return SizeCompareStatusEq, ConverstionOk
}
if aValue < bValue {
return SizeCompareStatusLt, ConverstionOk
}
return SizeCompareStatusGt, ConverstionOk
}

if ctx.IsUint() && other.IsUint() {
aValue := ctx.Value.Uint()
bValue := other.Value.Uint()

if aValue == bValue {
return SizeCompareStatusEq, ConverstionOk
}
if aValue < bValue {
return SizeCompareStatusLt, ConverstionOk
}
return SizeCompareStatusGt, ConverstionOk
}

aValue, aOk := ctx.Int64()
bValue, bOk := other.Int64()
if !aOk || !bOk {
return 0, InvalidType
}

if aValue == bValue {
return SizeCompareStatusEq, ConverstionOk
}
if aValue < bValue {
return SizeCompareStatusLt, ConverstionOk
}
return SizeCompareStatusGt, ConverstionOk
}

compareLen := false
if ctx.IsList() || other.IsList() {
if !ctx.IsList() || !other.IsList() {
return 0, InvalidType
}
compareLen = true
}

if ctx.Kind() == reflect.String && other.Kind() == reflect.String {
compareLen = true
}

if compareLen {
aLen := ctx.Value.Len()
bLen := other.Value.Len()
if aLen == bLen {
return SizeCompareStatusEq, ConverstionOk
}
if aLen < bLen {
return SizeCompareStatusLt, ConverstionOk
}
return SizeCompareStatusGt, ConverstionOk
}

return 0, InvalidType
}

func Gt(ctx *ValidatorCtx) (string, bool) {
sizeStatus, status := compareFieldsBase(ctx)
if !status.Oke() {
return status.Response()
}

if sizeStatus != SizeCompareStatusGt {
return "lt", false
}

return "", true
}

func Gte(ctx *ValidatorCtx) (string, bool) {
sizeStatus, status := compareFieldsBase(ctx)
if !status.Oke() {
return status.Response()
}

if sizeStatus == SizeCompareStatusLt {
return "lt", false
}

return "", true
}

func Lt(ctx *ValidatorCtx) (string, bool) {
sizeStatus, status := compareFieldsBase(ctx)
if !status.Oke() {
return status.Response()
}

if sizeStatus != SizeCompareStatusLt {
return "gt", false
}

return "", true
}

func Lte(ctx *ValidatorCtx) (string, bool) {
sizeStatus, status := compareFieldsBase(ctx)
if !status.Oke() {
return status.Response()
}

if sizeStatus == SizeCompareStatusGt {
return "gt", false
}

return "", true
}
Loading

0 comments on commit 9b71ee1

Please sign in to comment.