Skip to content

Errors & validation

Return Err(ActionError) and the agent receives a structured error with a fixed code, not a prose string it has to parse.

ConstructorMeaning
ActionError::field(name, msg)One field is invalid
ActionError::validation([(f, e), …])Several fields at once — preferred for forms
ActionError::action_not_available()Not callable in the current state
ActionError::blocked_by_modal()A modal owns input right now
ActionError::window_not_found()Target window is gone
ActionError::action_timeout()The operation took too long
ActionError::stale_state(version)The caller's expectedVersion is out of date
ActionError::custom(code, msg)Anything else in the fixed code set

Report every problem at once

rust
// Shared by the on-screen notice and the error path, so they never disagree.
fn problems(&self) -> Vec<(&'static str, &'static str)> {
    let mut out = Vec::new();
    if self.recipient.is_empty() { out.push(("recipient", "required")); }
    if self.amount <= 0          { out.push(("amount", "must be positive")); }
    out
}

// In the handler:
let problems = this.problems();
if !problems.is_empty() {
    return Err(ActionError::validation(problems));
}

Tip

Derive both the on-screen notice block and the error from the same problems() method. The human and the agent then never see different verdicts.

You rarely construct stale_state

Purview checks expectedVersion itself, before your handler runs — see Free protocol behaviour. The constructor exists for the rare case where your own business logic detects a conflict the projection could not.