ironstate 0.1.3

Verified state machines for humans and AI agents
Documentation
//! Versioned restore: decode a persisted machine and migrate it forward.

use crate::error::RestoreError;

/// A pure, testable upgrade from an older schema to a newer one.
///
/// Versioned restore walks a chain of these — oldest first — to bring a stored
/// value up to the current type. Each step is an ordinary function, so it can
/// be unit-tested in isolation.
pub trait MigrateFrom<Old>: Sized {
    /// Convert a value of the older type into this one.
    fn migrate(old: Old) -> Self;
}

/// A machine whose persisted form carries a schema version.
///
/// Generated by `#[derive(StateMachine)]`. With `version`/`history` declared,
/// `restore_versioned` decodes the stored payload as the type recorded for its
/// version and runs the `MigrateFrom` chain up to the current schema.
pub trait Versioned: Sized {
    /// The current schema version. `1` when no version was declared.
    const VERSION: u32;

    /// Decode a `{version, payload}` envelope and migrate it to the current
    /// schema.
    fn restore_versioned(bytes: &[u8]) -> Result<Self, RestoreError>;
}

/// Runtime helpers invoked by generated `restore_versioned` bodies.
///
/// Kept in one place so every derived implementation parses envelopes and maps
/// decode failures to the same typed errors.
#[cfg(feature = "restore")]
pub mod rt {
    use crate::error::RestoreError;
    use serde::Deserialize;
    use serde_json::Value;

    #[derive(Deserialize)]
    struct Envelope {
        version: u32,
        payload: Value,
    }

    /// Split a stored envelope into its version tag and undecoded payload.
    pub fn parse_envelope(bytes: &[u8]) -> Result<(u32, Value), RestoreError> {
        let env: Envelope = serde_json::from_slice(bytes).map_err(|e| RestoreError::Decode {
            version: 0,
            source: Box::new(e),
        })?;
        Ok((env.version, env.payload))
    }

    /// Decode a payload as the type recorded for `version`.
    pub fn decode<T>(version: u32, payload: Value) -> Result<T, RestoreError>
    where
        T: for<'de> Deserialize<'de>,
    {
        serde_json::from_value(payload).map_err(|e| RestoreError::Decode {
            version,
            source: Box::new(e),
        })
    }

    /// Build the "stored version is from a newer binary" error.
    pub fn newer_than_binary(found: u32, supports: u32) -> RestoreError {
        RestoreError::NewerThanBinary { found, supports }
    }

    /// Build the "version is not in this type's history" error.
    pub fn unknown_version(found: u32) -> RestoreError {
        RestoreError::UnknownVersion { found }
    }
}