Validator¶
Vanguard.Util.Validator builds composable runtime validators. It is designed for untrusted network payloads and application data that must be checked before use.
Require¶
A validator returns:
or:
Callbacks must explicitly return true to pass.
Safe Invocation¶
Validator.validate catches validator errors and converts them into false, message.
Composite validators also safely invoke their child validators.
Calling a primitive/custom validator directly does not add a new outer pcall; use validate when arbitrary custom validators may throw.
any¶
Always returns true, including for nil.
Use sparingly at network boundaries.
type¶
Uses Lua type and reports expected/actual types.
Common names: nil, boolean, number, string, function, thread, table, and userdata.
robloxType¶
Uses Roblox typeof for values such as Vector3, CFrame, Color3, Instance, and EnumItem.
string¶
Options:
MinLength: minimum byte length;MaxLength: maximum byte length;Pattern: Lua pattern that must match.
Length uses Lua #, which counts bytes rather than Unicode graphemes. Apply domain-specific Unicode rules separately when user-facing text requires them.
Invalid Lua patterns throw during validation; network guards convert the throw to GUARD_ERROR. Test patterns when constructing schemas.
number¶
NaN and positive/negative infinity are rejected by default.
Allow non-finite values only explicitly:
Network gameplay values should almost always remain finite.
integer¶
Runs number checks and then requires value % 1 == 0.
boolean¶
Equivalent to Validator.type("boolean").
literal¶
Requires equality with the provided value. Tables compare by identity.
optional and nullable¶
Passes nil; otherwise runs the child validator.
Validator.nullable is an alias.
oneOf¶
local identifier = Validator.oneOf(
Validator.string({ MinLength = 1 }),
Validator.integer({ Min = 1 })
)
Passes when any child passes. Failure reports a generic “did not match any allowed shape†message rather than every child reason.
array¶
local itemIds = Validator.array(
Validator.string({ MinLength = 1, MaxLength = 64 }),
{
MinLength = 1,
MaxLength = 20,
}
)
Requires:
- a table;
- sequential integer keys from 1 through length;
- no string or out-of-range keys;
- no gaps;
- every item to pass the child validator;
- optional length bounds.
This rejects mixed array/map payloads.
map¶
local quantities = Validator.map(
Validator.string({ MinLength = 1, MaxLength = 64 }),
Validator.integer({ Min = 0, Max = 999 })
)
Validates every key and value in a table. Map does not enforce a maximum entry count; add a custom wrapper when untrusted maps need size limits.
shape¶
local item = Validator.shape({
Id = Validator.string({ MinLength = 1, MaxLength = 64 }),
Count = Validator.integer({ Min = 0, Max = 999 }),
Equipped = Validator.optional(Validator.boolean()),
})
By default, unknown keys are rejected. Missing fields are passed as nil to their validators, so wrap optional fields with Validator.optional.
Allow extra fields explicitly:
Strict shapes are recommended for network payloads.
instance¶
Checks:
- value has Roblox type
Instance; - optional
IsA(className)condition; - optional equality/descendancy under
ancestor.
Do not accept arbitrary client-supplied Instances merely because they pass a class check. Verify that the Instance represents an action the Player may perform.
custom¶
local evenNumber = Validator.custom(function(value)
return type(value) == "number" and value % 2 == 0
end, "value must be even")
The callback receives the value and may return true or false, message. The explicit callback message wins, then the default message, then the generated path message.
tuple¶
local purchasePayload = Validator.tuple(
Validator.string({ MinLength = 1, MaxLength = 64 }),
Validator.integer({ Min = 1, Max = 10 }),
Validator.optional(Validator.string({ MaxLength = 32 }))
)
Tuple validates function arguments rather than one value.
Behavior:
- each configured validator receives its positional argument;
- missing positions receive nil;
- optional validators allow missing arguments;
- extra arguments beyond the configured validator count reject;
- errors identify
argument N.
Use tuple directly as a network rule:
Nested Schema Example¶
local loadout = Validator.shape({
Name = Validator.string({ MinLength = 1, MaxLength = 32 }),
Slots = Validator.array(
Validator.shape({
ItemId = Validator.string({ MinLength = 1, MaxLength = 64 }),
Skin = Validator.optional(Validator.string({ MaxLength = 64 })),
}),
{ MaxLength = 10 }
),
})
Errors include nested paths such as value.Slots[2].ItemId.
Security Guidance¶
- Bound every untrusted string and collection.
- Prefer strict shapes.
- Reject unexpected tuple arguments.
- Keep numbers finite and range-limited.
- Validate data shape before authentication/verification does expensive work.
- Validation does not prove ownership, permission, freshness, or authenticity.
- Re-check authoritative state in verification and mutation logic.