Validator Usage
How to use @grest-ts/schema in your application.
Primitive Validators
Basic type validation:
import {IsString, IsNumber, IsBoolean} from "@grest-ts/schema"
// Type guards
if (IsString.is(value)) {
// value is string
}
// Assertions (throws on failure)
IsNumber.assert(value);
// Parse with coercion (throws on failure)
const num = IsNumber.parse(value, true);
// Safe parse with error collection
const result = IsNumber.safeParse(value, true);
if (!result.success) {
console.log(result.issues.toJSON());
}Branded Validators
Semantic type validation with zero runtime overhead:
import {IsInt, IsEmail, IsUrl, IsDate, tInt, tEmail} from "@grest-ts/schema"
// Type guards
if (IsEmail.is(value)) {
// value is tEmail
}
// Parse with validation (throws on failure)
const email = IsEmail.parse(input);
const userId = IsInt.parse(input, true); // with coercion
// Safe parse with error collection
const result = IsEmail.safeParse(input);
if (!result.success) {
console.log(result.issues.toJSON());
}Available Branded Validators
| Category | Validators |
|---|---|
| Integers | IsInt, IsPosInt, IsUint |
| Sized Integers | IsInt8, IsInt16, IsInt32, IsUint8, IsUint16, IsUint32 |
| Numbers | IsNumber, IsPercentage |
| Ranges | Use .range(min, max) method on number schemas |
| Date/Time | IsDate, IsDateTime, IsTime, IsTimestamp, IsTimestampMs |
| Strings | IsEmail, IsUrl, IsIp, IsPhone, IsPassword |
| Geo | IsLatitude, IsLongitude, IsCountry, IsLanguage, IsCurrency |
Composite Validators
IsObject
Schema-based object validation:
import {IsObject, IsString, IsInt, IsEmail} from "@grest-ts/schema"
interface User {
id: tInt;
name: string;
email: tEmail;
age?: tInt;
}
const IsUser = IsObject({
id: IsInt,
name: IsString,
email: IsEmail,
age: IsInt.orUndefined
});
// Validate
if (IsUser.is(data)) {
// data is User
}
// Parse with error collection
const result = IsUser.safeParse(data, true);
if (!result.success) {
console.log(result.issues.toJSON());
}IsArray
Array validation:
import {IsArray, IsString, IsInt} from "@grest-ts/schema"
const IsStringArray = IsArray(IsString);
const IsIntArray = IsArray(IsInt);
// Nested arrays
const IsMatrix = IsArray(IsArray(IsInt));IsUnion
Union type validation:
import {IsUnion, IsString, IsNumber} from "@grest-ts/schema"
const IsStringOrNumber = IsUnion(IsString, IsNumber);
if (IsStringOrNumber.is(value)) {
// value is string | number
}When no variant matches, a plain
IsUnionreports a single generic error at the union's path — the per-variant issues are tried against throwaway lists and discarded. If you need the failing field's error preserved at its own path (e.g. to map it back onto a form input), useIsDiscriminatedinstead, which validates the selected variant directly.
IsDiscriminated
Tagged union validation:
import {IsDiscriminated, IsObject, IsString, IsInt, IsLiteral} from "@grest-ts/schema"
const IsCircle = IsObject({
type: IsLiteral("circle"),
radius: IsInt
});
const IsSquare = IsObject({
type: IsLiteral("square"),
side: IsInt
});
const IsShape = IsDiscriminated("type", {
circle: IsCircle,
square: IsSquare
});Optional and Nullable
Use .orUndefined and .orNull getters on any schema:
import {IsInt} from "@grest-ts/schema"
// Optional field (T | undefined)
const IsOptionalInt = IsInt.orUndefined;
// Nullable field (T | null)
const IsNullableInt = IsInt.orNull;
// Optional and nullable (T | null | undefined)
const IsMaybeInt = IsInt.orNull.orUndefined;Enums and Literals
import {IsEnum, IsLiteral} from "@grest-ts/schema"
// Enum validation
enum Status { Active = "active", Inactive = "inactive" }
const IsStatus = IsEnum(Status);
// Single literal
const IsActive = IsLiteral("active");
// Union of literals
const IsRole = IsLiteral("admin", "user", "guest");Error Handling
GGIssuesList
Collect all validation errors:
import {IsObject, IsString, IsInt} from "@grest-ts/schema"
const IsUser = IsObject({
name: IsString,
age: IsInt
});
const result = IsUser.safeParse({name: 123, age: "invalid"});
if (!result.success) {
// Get JSON representation with messages translated.
console.log(result.issues.toJSON());
// [
// { path: "name", code: "string.type", message: "Value must be a string" },
// { path: "age", code: "integer.type", message: "Value must be an integer" }
// ]
}Assert with Exceptions
try {
IsUser.assert(data);
// data is valid User
} catch (error) {
// GGIssuesList with validation issues
}Coercion
Parse string inputs to typed values (useful for API query/path parameters):
import {IsInt, IsBoolean} from "@grest-ts/schema"
// String to number (with coercion enabled)
const id = IsInt.parse("123", true); // returns 123
// String to boolean (with coercion enabled)
const flag = IsBoolean.parse("true", true); // returns trueCustom Branded Types
Create domain-specific branded types:
import {IsInt} from "@grest-ts/schema"
// Create branded validator
const IsUserId = IsInt.brand<"UserId">();
// Infer the type from the validator
type tUserId = typeof IsUserId.infer;
// Usage
const userId = IsUserId.parse(rawId, true); // throws on error
// userId is tUserId typeObject-Level Validation
Use .refine() to add custom checks that run after property validation:
import {IsObject, IsInt, GGIssueKey} from "@grest-ts/schema"
const ageRangeError = new GGIssueKey("age.range", "minAge must be less than or equal to maxAge");
const IsAgeRange = IsObject({
minAge: IsInt,
maxAge: IsInt
}).refine(
(value) => value.minAge <= value.maxAge,
ageRangeError
);Best Practices
Use Branded Types for Domain Modeling
// Good - semantic types
interface User {
id: tInt;
email: tEmail;
createdAt: tTimestamp;
}
// Avoid - plain primitives lose semantic meaning
interface User {
id: number;
email: string;
createdAt: number;
}Collect All Errors
// Good - collect all issues for better UX
const result = IsUser.safeParse(data, true);
if (!result.success) {
return {errors: result.issues.toJSON()};
}
// Avoid - stopping at first error
if (!IsUser.is(data)) {
return {error: "Invalid user"};
}Use Coercion for API Inputs
// Good - handle string inputs from query params
const page = IsInt.parse(req.query.page, true);
// Works with: "123" -> 123