rave_engine 0.8.0

A secure and efficient JSON Schema validation and Rhai script execution engine
Documentation
//! Shared utilities for distinguishing transient host I/O failures from
//! deterministic guest validation failures.
//!
//! Holochain's `must_get_*` family of host calls returns
//! [`WasmErrorInner::HostShortCircuit`] when the requested data is not yet
//! available locally (an unresolved dependency). Validation code MUST NOT
//! collapse such errors into [`ValidateCallbackResult::Invalid`], because doing
//! so warrants the author for what is really a transient sync issue.

use hdi::prelude::*;

/// Returns `true` if the error originates from a Holochain host short-circuit
/// (`WasmErrorInner::HostShortCircuit(_)`), e.g. a `must_get_*` call that
/// found no local data and needs the conductor to retry the validation.
///
/// This is the signal validation code uses to decide whether to propagate an
/// error verbatim (for retry) versus translate it into a deterministic
/// validation outcome.
pub fn is_host_short_circuit(error: &WasmError) -> bool {
    matches!(error.error, WasmErrorInner::HostShortCircuit(_))
}

/// Convert an inner-validator error into the right top-level
/// [`ValidateCallbackResult`]: a host short-circuit propagates verbatim so
/// Holochain can retry on resolved dependencies, anything else becomes an
/// `Invalid` validation outcome.
///
/// Use this at the boundary where an inner helper that returns
/// `ExternResult<()>` (or otherwise mixes host I/O with deterministic checks)
/// is called from a top-level `fn validate_*(...) -> ExternResult<ValidateCallbackResult>`.
pub fn invalid_or_host_error(error: WasmError) -> ExternResult<ValidateCallbackResult> {
    if is_host_short_circuit(&error) {
        Err(error)
    } else {
        Ok(ValidateCallbackResult::Invalid(error.to_string()))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn host_short_circuit() -> WasmError {
        WasmError {
            file: file!().to_string(),
            line: line!(),
            error: WasmErrorInner::HostShortCircuit(Vec::new()),
        }
    }

    fn guest_error(msg: &str) -> WasmError {
        WasmError {
            file: file!().to_string(),
            line: line!(),
            error: WasmErrorInner::Guest(msg.to_string()),
        }
    }

    #[test]
    fn is_host_short_circuit_true_for_host_short_circuit() {
        assert!(is_host_short_circuit(&host_short_circuit()));
    }

    #[test]
    fn is_host_short_circuit_false_for_guest_error() {
        assert!(!is_host_short_circuit(&guest_error("nope")));
    }

    #[test]
    fn is_host_short_circuit_false_for_serialize_error() {
        let err = WasmError {
            file: file!().to_string(),
            line: line!(),
            error: WasmErrorInner::Serialize(SerializedBytesError::Deserialize("x".to_string())),
        };
        assert!(!is_host_short_circuit(&err));
    }

    #[test]
    fn invalid_or_host_error_propagates_host_short_circuit() {
        let res = invalid_or_host_error(host_short_circuit());
        match res {
            Err(e) => assert!(is_host_short_circuit(&e)),
            other => panic!("expected Err(HostShortCircuit), got {:?}", other),
        }
    }

    #[test]
    fn invalid_or_host_error_collapses_guest_to_invalid() {
        let res = invalid_or_host_error(guest_error("bad data"));
        match res {
            Ok(ValidateCallbackResult::Invalid(msg)) => assert!(msg.contains("bad data")),
            other => panic!("expected Ok(Invalid), got {:?}", other),
        }
    }
}