Validation
Validate steps check data constraints before processing or storing. They help ensure data quality and catch issues early.
Basic syntax
validate target {
assume constraint1,
assume constraint2
}
Simple validation
action ValidateUsers {
get "/users"
for user in response.users {
validate user {
assume .id is string,
assume length(.name) > 0,
assume .email contains "@"
}
store user -> users { key: .id }
}
}
Constraint types
Type checking
validate data {
assume .id is string,
assume .count is number,
assume .active is boolean,
assume .tags is array,
assume .metadata is object,
assume .deletedAt is null
}
Existence checks
validate data {
assume .id != null,
assume .name != null,
assume .email != null
}
String constraints
validate user {
assume length(.name) > 0,
assume length(.name) < 100,
assume .email contains "@",
assume .phone startsWith "+",
assume .country endsWith "A"
}
Numeric constraints
validate order {
assume .quantity > 0,
assume .price >= 0,
assume .discount >= 0 and .discount <= 100,
assume .total == .price * .quantity
}
Array constraints
validate response {
assume length(.items) > 0,
assume length(.items) <= 100
}
Comparison
validate event {
assume .endDate >= .startDate,
assume .createdAt <= now()
}
Complex constraints
Logical operators
validate user {
assume .status == "active" or .status == "pending",
assume .age >= 18 and .age <= 120,
assume not (.status == "banned")
}
Conditional validation
validate order {
// If discount is present, it must be valid
assume .discount == null or (.discount >= 0 and .discount <= 50),
// If status is shipped, must have tracking
assume .status != "shipped" or .trackingNumber != null
}
Validation responses
Warnings vs errors
By default, failed validations are warnings and don't stop execution:
validate user {
assume length(.name) > 0 // Warning if fails
}
// Execution continues even if validation fails
Strict validation
Combine with match for strict validation:
action StrictValidation {
get "/users"
for user in response.users {
validate user {
assume .id is string,
assume .email contains "@"
}
match user {
{ id: null } -> skip,
{ email: null } -> skip,
_ -> store user -> users { key: .id }
}
}
}
Validating nested data
validate order {
assume .id is string,
assume .customer.id is string,
assume .customer.email contains "@",
assume length(.items) > 0,
assume .items[0].quantity > 0
}
Validating arrays
action ValidateAllItems {
get "/orders"
for order in response.orders {
// Validate order-level
validate order {
assume .id is string,
assume .total > 0
}
// Validate each item
for item in order.items {
validate item {
assume .productId is string,
assume .quantity > 0,
assume .price >= 0
}
}
}
}
Custom validation messages
Use match for custom error handling:
action ValidateWithMessages {
get "/users"
for user in response.users {
match user {
{ email: null } -> {
store { userId: user.id, error: "Missing email" } -> validationErrors
skip
},
{ age: a } where a < 18 -> {
store { userId: user.id, error: "User under 18" } -> validationErrors
skip
},
_ -> continue
}
store user -> validUsers { key: .id }
}
}
Validation before store
Always validate before storing:
action SafeStore {
get "/data"
for item in response.data {
validate item {
assume .id is string,
assume .value is number
}
// Only store if valid
match item {
{ id: null } -> skip,
{ value: null } -> skip,
_ -> store item -> data { key: .id }
}
}
}
Validation schemas
Use schemas for reusable validation:
schema ValidUser {
id: string,
name: string,
email: string
}
action ValidateAgainstSchema {
get "/users"
for user in response.users {
match user {
ValidUser -> store user -> users { key: .id },
_ -> store user -> invalidUsers { key: .id }
}
}
}
Built-in validation functions
validate data {
// String functions
assume length(.name) > 0,
assume .email contains "@",
assume lowercase(.status) == "active",
// Numeric functions
assume abs(.balance) < 10000,
assume round(.price, 2) == .price,
// Array functions
assume length(.items) > 0,
assume includes(.roles, "user"),
// Date functions
assume .createdAt <= now(),
assume .expiresAt > now()
}
Complete example
mission DataValidation {
source API { auth: bearer, base: "https://api.example.com" }
store validOrders: file("valid-orders")
store invalidOrders: file("invalid-orders")
store validationErrors: file("validation-errors")
schema ValidOrder {
id: string,
customerId: string,
items: array,
total: number
}
action ValidateOrders {
get "/orders"
for order in response.orders {
// Type validation
validate order {
assume .id is string,
assume .customerId is string,
assume .items is array,
assume .total is number
}
// Business rule validation
validate order {
assume length(.items) > 0,
assume .total > 0,
assume .total == sum(.items.map(.price * .quantity)),
assume .status == "pending" or .status == "confirmed"
}
// Route based on validation
match order {
ValidOrder where .total > 0 and length(.items) > 0 -> {
store order -> validOrders { key: .id }
},
_ -> {
store {
orderId: order.id,
order: order,
reason: "Failed validation"
} -> invalidOrders { key: order.id }
}
}
}
}
run ValidateOrders
}
Best practices
Validate early
action Process {
get "/data"
// Validate immediately after fetch
validate response {
assume .data is array,
assume length(.data) > 0
}
// Then process
for item in response.data { }
}
Use specific constraints
// Good: specific constraints
validate user {
assume .email contains "@",
assume length(.email) > 5,
assume .email endsWith ".com" or .email endsWith ".org"
}
// Avoid: too loose
validate user {
assume .email is string
}
Log validation failures
action ValidateWithLogging {
for item in items {
match item {
{ id: null } -> {
store { itemId: "unknown", field: "id", error: "Missing" } -> errors
skip
},
_ -> continue
}
}
}