Skip to main content

Match (schema matching)

Match steps route data based on which schema it matches. They're used for error handling, conditional processing, and flow control.

Basic syntax​

match target {
SchemaA -> ...,
SchemaB -> ...,
_ -> ...
}

Each arm's left side is one of:

  • a schema name, which matches when the value fits that schema's shape,
  • a schema name with a guard, SchemaName where <condition>, or
  • _, the wildcard that always matches.

Arms are tried in order, and the first match wins. If nothing matches, the step throws a NoMatchError, so include a _ arm to handle the rest.

The right side of an arm is one of:

  • a flow directive (continue, skip, abort, retry, queue, jump),
  • a single step, or
  • a { ... } block of steps.

There are no object, array, literal, or binding patterns, and arms use ->, not =>.

Schema matching​

Match against defined schemas:

schema SuccessResponse {
data: array,
status: string
}

schema ErrorResponse {
error: string,
code: number
}

action HandleResponse {
get "/data"

match response {
SuccessResponse -> store response.data -> data { key: .id },
ErrorResponse -> abort "Request failed",
_ -> abort "Unknown response format"
}
}

Conditional matching​

Add a guard with where to narrow an arm further. The guard is evaluated against the matched value:

schema Order {
status: string,
total: number
}

match order {
Order where order.total > 1000 -> {
// High-value order
get "/orders/{order.id}/flag"
},
Order -> continue,
_ -> continue
}

You can guard the wildcard too, which is handy for value-based routing:

match response {
_ where response.code == 429 -> retry,
_ where not (response.error == null) -> abort "API error",
_ -> continue
}

Flow control directives​

Match arms can use these directives:

DirectiveDescription
continueProceed to the next step
skipSkip the rest of the current loop iteration
abortHalt the mission with an error
retryRetry with backoff
queueSend the value to a queue target
jumpRun another action, then optionally retry or continue

Continue​

Proceed to the next step:

match response {
SuccessResponse -> continue,
_ -> abort "No data"
}
// Next step executes

Skip​

Skip the remaining steps in the current loop iteration:

for item in items {
match item {
InactiveItem -> skip,
_ -> continue
}
// This only runs for items that aren't inactive
store item -> activeItems { key: .id }
}

Abort​

Stop mission execution. abort takes an optional string message (a literal, not an expression):

match response {
ErrorResponse -> abort "Request returned an error",
_ -> continue
}

Retry​

Retry the previous fetch. A bare retry uses the default backoff; an optional block sets the retry config:

match response {
RateLimitError -> retry {
maxAttempts: 5,
backoff: exponential,
initialDelay: 1000,
maxDelay: 60000
},
ErrorResponse -> abort "API error",
_ -> continue
}

Queue​

Send the matched value to a queue target:

match response {
ErrorResponse -> queue dlq,
_ -> continue
}

Jump​

Run another action. then retry re-runs the current step afterward; then continue moves on:

match response {
AuthError -> jump RefreshToken then retry,
_ -> continue
}

action RefreshToken {
post "/auth/refresh" {
body: { refreshToken: env("REFRESH_TOKEN") }
}
}

Matching with nested steps​

Run several steps in a match arm by wrapping them in a block:

match response {
ErrorResponse -> {
store response -> errors { key: .id }
abort "Request failed"
},
SuccessResponse -> {
for item in response.data {
store item -> processed { key: .id }
}
continue
},
_ -> continue
}

Type matching​

Use a guard with is to route on the runtime type of a value:

match value {
_ where value is string -> continue,
_ where value is number -> continue,
_ where value is array -> continue,
_ where value is null -> skip,
_ -> abort "Unexpected type"
}

Exhaustive matching​

Always include a _ arm. Without one, a value that matches no schema throws a NoMatchError:

// Good: handles everything
match response {
SuccessResponse -> continue,
ErrorResponse -> abort "Error",
_ -> abort "Unexpected format"
}

Complete example​

mission DataSync {
source API { auth: oauth2, base: "https://api.example.com" }

store data: file("data")
store errors: file("errors")
store dlq: file("dead-letter")

schema SuccessResponse {
data: array,
pagination: object?
}

schema ValidationError {
error: string,
code: number,
details: array?
}

schema RateLimitError {
error: string,
retryAfter: number
}

schema AuthError {
error: string,
code: number
}

action FetchData {
get "/data" {
paginate: offset(page, 100),
until: length(response.data) == 0
}

match response {
// Success case
SuccessResponse -> {
for item in response.data {
store item -> data { key: .id }
}
},

// Rate limited
RateLimitError -> retry {
maxAttempts: 5,
backoff: exponential,
initialDelay: 1000
},

// Auth expired
AuthError -> jump RefreshToken then retry,

// Validation error - route to the errors store
ValidationError -> {
store response -> errors { key: .id }
},

// Unknown error
_ -> queue dlq
}
}

action RefreshToken {
post "/auth/refresh" {
body: { refreshToken: env("REFRESH_TOKEN") }
}
}

run FetchData
}

For more on schemas and expression syntax, see the Vague documentation.