Schemas
Schemas define data shapes for validation and pattern matching. They're used to validate responses, route data based on structure, and document expected data formats.
Basic syntax
schema SchemaName {
field: type,
optionalField: type?,
nestedField: {
subField: type
}
}
Field types
| Type | Description | Example |
|---|---|---|
string | Text value | "hello" |
number | Numeric value | 42, 3.14 |
boolean | True or false | true, false |
date | Date/datetime | "2024-01-20" |
array | Array of values | [1, 2, 3] |
object | Nested object | { a: 1 } |
any | Any type | anything |
null | Null value | null |
Optional fields
Use ? suffix for optional fields:
schema User {
id: string,
name: string,
email: string?,
phone: string?
}
Typed arrays
Wrap the element type in brackets:
schema UserList {
users: [User],
total: number
}
schema Order {
id: string,
items: [{
productId: string,
quantity: number,
price: number
}]
}
Array element types are checked recursively during schema matching and named validation.
Nested schemas
Define complex nested structures:
schema Invoice {
id: string,
customer: {
id: string,
name: string,
address: {
street: string,
city: string,
country: string
}
},
lineItems: [{
description: string,
amount: number
}],
total: number
}
Schema references
Reference other schemas:
schema Address {
street: string,
city: string,
postalCode: string,
country: string
}
schema Customer {
id: string,
name: string,
billingAddress: Address,
shippingAddress: Address?
}
Referenced schemas are checked recursively when the referenced definition is registered in the same mission.
Using schemas for validation
Validate data against schemas:
action ValidateResponse {
get "/users"
for user in response.users {
validate user as User {
assume .id is string,
assume .name is string,
assume .email is string
}
store user -> validUsers { key: .id }
}
}
Using schemas for pattern matching
Route data based on schema matches:
schema SuccessResponse {
data: any,
status: string
}
schema ErrorResponse {
error: string,
code: number
}
schema RateLimitResponse {
error: string,
retryAfter: number
}
action HandleResponse {
get "/data"
match response {
SuccessResponse -> store response.data -> data { key: .id },
RateLimitResponse -> retry { maxAttempts: 5 },
ErrorResponse -> abort "API error",
_ -> abort "Unknown response format"
}
}
Schema matching rules
Schemas match when:
- All required fields are present
- Field types match
- Optional fields, if present, match their types
schema StrictUser {
id: string, // Required
name: string, // Required
email: string? // Optional
}
// Matches: { id: "1", name: "John" }
// Matches: { id: "1", name: "John", email: "john@example.com" }
// Does NOT match: { id: 1, name: "John" } // id is number, not string
// Does NOT match: { id: "1" } // missing name
Type checking with is
Use is for inline type checking:
validate response {
assume .items is array,
assume .count is number,
assume .status is string
}
Combining schemas
Use schemas in complex match patterns:
schema PaginatedResponse {
data: array,
meta: {
page: number,
totalPages: number,
hasNext: boolean
}
}
schema EmptyResponse {
data: array,
meta: {
total: number
}
}
action FetchPaginated {
get "/items" { paginate: page(page, 100) }
match response {
PaginatedResponse where response.meta.hasNext == true -> continue,
PaginatedResponse -> store response.data -> items { key: .id },
EmptyResponse -> skip,
_ -> abort "Unexpected response"
}
}
Vague schema features
Reqon delegates schema parsing to Vague. Ranges, choices, unique fields, constraints, assumptions, invariants, generators, and OpenAPI-backed schemas are available inside missions:
import ecommerce from "./openapi.yaml"
mission Catalog {
schema Product from ecommerce.Product {
score: decimal in 0..1,
status: "active" | "paused",
constraints { score >= 0 and score <= 1 }
}
action Load { generate 20 of Product as products { seed: 42 } }
run Load
}
The older JSON-shaped [Type], inline object, number, any, and type?
forms remain supported for existing missions. New schemas should use Vague's
native grammar where possible.
Best practices
Define schemas for API responses
mission APISync {
schema UserResponse {
users: [User],
pagination: {
page: number,
total: number
}
}
schema User {
id: string,
name: string,
email: string
}
}
Use schemas for error handling
schema APIError {
error: {
message: string,
code: string
}
}
schema AuthError {
error: {
message: string,
code: string
},
code: number // HTTP status code
}
action Fetch {
get "/data"
match response {
AuthError where .code == 401 -> jump RefreshToken then retry,
APIError -> abort "API error",
_ -> continue
}
}
Document expected formats
Schemas serve as documentation:
// XeroInvoice represents an invoice from Xero API
schema XeroInvoice {
InvoiceID: string,
InvoiceNumber: string,
Type: string, // ACCREC or ACCPAY
Contact: {
ContactID: string,
Name: string
},
LineItems: [{
Description: string,
Quantity: number,
UnitAmount: number,
LineAmount: number
}],
Total: number,
Status: string // DRAFT, SUBMITTED, AUTHORISED, PAID
}
Keep schemas close to usage
Define schemas in the same mission where they're used:
mission XeroSync {
// Schema definitions at the top
schema XeroInvoice {
// ...
}
schema XeroContact {
// ...
}
// Then sources, stores, actions...
}
Or use multi-file missions to organize:
missions/xero-sync/
├── mission.vague
├── schemas/
│ ├── invoice.vague
│ └── contact.vague
└── actions/
└── fetch.vague