Skip to main content

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

TypeDescriptionExample
stringText value"hello"
numberNumeric value42, 3.14
booleanTrue or falsetrue, false
dateDate/datetime"2024-01-20"
arrayArray of values[1, 2, 3]
objectNested object{ a: 1 }
anyAny typeanything
nullNull valuenull

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:

  1. All required fields are present
  2. Field types match
  3. 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
}
note

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