Skip to content

Latest commit

 

History

History
287 lines (205 loc) · 7.43 KB

File metadata and controls

287 lines (205 loc) · 7.43 KB

Condition Language

The condition language is a small expression evaluator used by RequiredIf, RequiredUnless, When, and Unless to express cross-field dependencies. It is evaluated at validation time against the current input.

Index


Literals

Kind Syntax Go type produced
String "hello" or 'hi' string
Integer 42 int
Float 3.14 float64
Boolean true / false bool

String literals use either double or single quotes. Escape sequences are not supported; use the other quote style if the value contains a quote character.

role == "admin"
label == 'it\'s'   // not valid — no escape support; use "it's" instead
label == "it's"    // correct

Field paths

Unquoted identifiers are resolved as dot-separated paths into the current input. Each segment navigates one level of nesting.

role                  // top-level field
order.status          // nested: input["order"]["status"]
user.address.city     // two levels deep
  • A path that does not exist resolves to nil.
  • nil compared to anything with == returns false; with != returns true.
  • Field names may contain letters, digits, and underscores.

Comparison operators

Operator Meaning
== equal
!= not equal
< less than
> greater than
<= less than or equal
>= greater than or equal

The right-hand side of a comparison can be a literal or another field path.

age >= 18
role == "admin"
role == expected_role      // both sides resolved as field paths
score < passing_threshold

Ordering semantics — see Type coercion.


Logical operators

Operator Meaning Precedence
! NOT (unary) highest
&& AND (binary) middle
|| OR (binary) lowest

! applies to the immediately following atom or grouped expression. && binds tighter than ||, so a || b && c is parsed as a || (b && c).

!verified
role == "admin" && plan != "free"
status == "active" || status == "pending"

! requires its operand to be boolean. Applying ! to a non-boolean field is an error.


Grouping

Parentheses override default precedence:

(status == "active" || status == "pending") && verified == true
!(role == "guest" || role == "banned")

Functions

exists(path)

Returns true if the field at path is present in the input, regardless of its value. Returns false when the field is absent.

exists(email)
exists(order.shipping_address)

Presence is distinct from truthiness — exists(field) returns true even when field is "", 0, false, or nil.

len(path)

Returns the length of the value at path:

Value type Length returned
string number of bytes
slice / array number of elements
map number of keys
absent / other 0
len(tags) > 0
len(name) >= 3 && len(name) <= 50
len(order.items) == 0

len always returns an integer, so it can be compared to integer literals with any comparison operator.

Function argument must be a field path. String literals and other expressions are not accepted as function arguments.

exists(email)          // correct
exists("email")        // error: argument must be a field path
len(tags)              // correct
len("tags")            // error: argument must be a field path

Type coercion

Numeric comparisons

When both sides of a comparison resolve to a numeric Go type (int, int8, …, uint64, float32, float64), all values are promoted to float64 and compared numerically.

age > 17          // numeric: input age=18 → 18.0 > 17.0 → true
score >= 4.5      // numeric: input score=5.0 → 5.0 >= 4.5 → true
count != 0        // numeric: input count=1 → 1.0 != 0.0 → true

String comparisons

When either side is not numeric (e.g. a string field or literal), both sides are converted to strings via fmt.Sprintf("%v", v) and compared lexicographically.

name < "z"        // lexicographic: "alice" < "z" → true
status == "active" // string equality

Boolean fields

Boolean fields returned from the input are compared directly as strings unless used with == / != against a bool literal, in which case the comparison uses the string representation ("true" / "false").

To test a boolean field, prefer:

verified == true
verified == false

Grammar reference

expr   = or
or     = and { "||" and }
and    = cmp { "&&" cmp }
cmp    = unary [ op unary ]
unary  = "!" atom | atom
atom   = "(" expr ")" | call | ident | string | int | float | bool
call   = name "(" ident ")"
op     = "==" | "!=" | "<" | ">" | "<=" | ">="
ident  = letter { letter | digit | "_" | "." }
string = '"' { char } '"' | "'" { char } "'"
int    = digit { digit }
float  = digit { digit } "." digit { digit }
bool   = "true" | "false"

Whitespace between tokens is ignored. Unknown characters are skipped silently.


Error conditions

The evaluator returns an error (surfaced as a RuleSyntaxError from schema.Validate) for:

Situation Example
Syntax error / unexpected token role == (missing right-hand side)
Unclosed parenthesis (role == "admin"
! applied to a non-boolean !role where role is a string
Unknown function missing(field)
Function argument is not a field path exists("email")
Invalid numeric literal 3.14.15

Examples

Simple field equality

validation.New().
    Field("vat_number", validation.RequiredIf(`plan == "paid"`))

Multiple conditions with AND

validation.New().
    Field("admin_code", validation.RequiredIf(`role == "admin" && exists(org_id)`))

OR with grouping

validation.New().
    Field("note", validation.RequiredIf(`(status == "active" || status == "pending") && verified == true`))

Numeric range check

validation.New().
    Field("guardian_name", validation.RequiredIf(`age < 18`))

Length-based condition

validation.New().
    Field("tag_description", validation.RequiredIf(`len(tags) > 0`))

Nested path

validation.New().
    Field("billing_zip", validation.RequiredIf(`order.type == "physical"`))

Field-to-field comparison

validation.New().
    Field("confirm_password", validation.RequiredIf(`password != confirm_password`))

Complement with RequiredUnless

validation.New().
    Field("reason", validation.RequiredUnless(`status == "approved"`))

When / Unless for conditional rule application

validation.New().
    Field("email", validation.When(`role == "admin"`, validation.Email, validation.Required))