Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Validation and Invariants

Rule

Validate untrusted data at input boundaries, convert it to invariant-bearing types, and let internal code operate on those trusted values.

Why

Early boundary validation keeps checks close to parsing and prevents repeated defensive validation throughout the system.

Do

  • Validate CLI, HTTP, configuration, environment, database, message, and deserialization inputs.
  • Convert raw values into domain types as soon as practical.
  • Use fallible constructors or conversions for caller-provided values.
  • Prevent construction paths that bypass the validated domain type.
  • Use standard invariant types such as NonZero* when they fit.
  • Fail startup cleanly when configuration is invalid.
  • Re-check constraints that depend on mutable external state.
  • Follow newtype and constructor policy for representation.

Avoid

  • Do not validate the same invariant at every use site.
  • Do not pass raw primitives deep into code when a validated type exists.
  • Do not make new panic on caller input.
  • Do not treat deserialization alone as validation.
  • Do not encode every invariant in typestate when a validated value is enough.

Library vs Application

Libraries prevent invalid public construction. Applications validate process and request inputs, then pass trusted types through internal layers.

Example

#![allow(unused)]
fn main() {
pub fn create_workspace(raw_name: &str) -> Result<Workspace, WorkspaceNameError> {
    let name = WorkspaceName::try_new(raw_name)?;
    Ok(Workspace::new(name))
}

pub fn workspace_path(root: &Path, name: &WorkspaceName) -> PathBuf {
    root.join(name.as_str())
}
}

workspace_path trusts the invariant already established by WorkspaceName.

Exceptions

  • Revalidate stale caches, legacy storage, and older serialized formats.
  • Re-check authorization, uniqueness, quotas, existence, and time-dependent constraints.
  • Use typestate when invalid transition order is important enough to reject at compile time.