Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/nornir

src/facet_react.rs

//! **nornir's REACTION to a facett UI error code** (facett error-codes, Phase 2).
//!
//! facett panes raise a stable, unique UI error code — `facet-<component>-<n>`
//! ([`facett_core::errcode`]) — carrying BOTH the human message and the code, so a
//! consumer reacts to the CODE, not a matched string. This module is nornir's half.
//!
//! ## How this COMPOSES with `nornir-<subsystem>-<n>`
//! nornir already owns a frozen registry of its OWN application error codes
//! ([`crate::errcode`], `nornir-<subsystem>-<n>`). The two vocabularies **compose,
//! they never collide**:
//!
//! * a **facett UI** failure surfaced through a nornir mount keeps facett's frozen
//!   code plus nornir's mount prefix — `nornir/viz:facet-graph3d-1`;
//! * nornir's OWN backend failure keeps `nornir-<subsystem>-<n>`.
//!
//! nornir's [`crate::errcode::parse`] already tolerates a consumer mount prefix, so
//! the two live side by side in one message/state channel without ambiguity.
//!
//! ## The mounts
//! nornir embeds facett components in its **viz** surface (`nornir/viz`): the 🌐
//! `graph3d` node cloud and the 🚇 `graphview` metro board, driven by
//! [`crate::autonom::facett_probe`]. Both are declared in facett's shared registry,
//! so the reaction obligations below are generated, not hand-authored.

use facett_core::errcode::{
    ConsumerMount, FacetError, FacetRecorder, Reaction, matrix_for_app, reaction_for,
};

/// nornir's application key in the shared reaction matrix.
pub const APP: &str = "nornir";

/// The **viz** mount — nornir's embedded facett surface (the 🌐 graph3d cloud + the
/// 🚇 graphview metro board). `site` is the ANTI-FICTION anchor: the guard test
/// asserts this file exists and really mounts the components.
pub const VIZ: ConsumerMount = ConsumerMount {
    app: APP,
    surface: "viz",
    site: "src/autonom/facett_probe.rs",
};

/// Every nornir mount.
pub const MOUNTS: &[ConsumerMount] = &[VIZ];

/// The reaction-matrix rows nornir is contracted to satisfy — sliced from facett's
/// shared table, so declaring a mount in the registry creates the obligation here.
pub fn matrix() -> Vec<(String, &'static str, &'static str, Reaction)> {
    matrix_for_app(APP)
}

/// **React to a facett UI error raised at a nornir mount.** Namespaces the code with
/// the mount, records the MOUNTED code into `rec` (which nornir folds into its
/// `state_json`), and returns the canonical [`Reaction`] for the caller to act on.
/// An unregistered code is [`Reaction::Diagnose`] — never a silent pass.
pub fn react(rec: &mut FacetRecorder, mount: ConsumerMount, err: &FacetError) -> Reaction {
    rec.record(mount, err)
}

/// Inspect a driven facett component's `state_json` for a raised error code and, if
/// present, react to it. This is the seam the viz probe uses: a facett pane publishes
/// `state_json.error_code` when it is in its error state (that is the whole point of
/// the scheme), so nornir reads the CODE out of the data instead of scanning pixels
/// or matching a rendered string.
///
/// Returns the reaction taken, or `None` when the pane published no error code.
pub fn react_to_state(
    rec: &mut FacetRecorder,
    mount: ConsumerMount,
    state: &serde_json::Value,
    message_hint: &str,
) -> Option<Reaction> {
    let code = state.get("error_code")?.as_str()?;
    let entry = facett_core::errcode::lookup(code);
    let msg = entry
        .map(|e| e.message.to_string())
        .unwrap_or_else(|| message_hint.to_string());
    let (component, n) = entry.map(|e| (e.component, e.n)).unwrap_or(("unknown", 0));
    // Rebuild the typed error from the published code (the pane already formatted the
    // human half; the CODE is what nornir branches on).
    let err = FacetError::new(component, n, leak(code), msg);
    Some(react(rec, mount, &err))
}

/// Codes arriving as runtime strings must become `&'static str` to ride in a
/// [`FacetError`]. Bounded: only ever the registry's codes (or a genuinely unknown
/// one, which is itself a fault worth surfacing).
fn leak(code: &str) -> &'static str {
    facett_core::errcode::lookup(code)
        .map(|e| e.code)
        .unwrap_or_else(|| Box::leak(code.to_string().into_boxed_str()))
}

/// The observable Phase-2 surface for nornir's `state_json` — the matrix nornir
/// implements plus whatever has actually been recorded.
pub fn state_json(rec: &FacetRecorder) -> serde_json::Value {
    let rows: Vec<serde_json::Value> = matrix()
        .into_iter()
        .map(|(mounted, base, mount, reaction)| {
            serde_json::json!({
                "mounted_code": mounted,
                "code": base,
                "mount": mount,
                "reaction": reaction.token(),
            })
        })
        .collect();
    serde_json::json!({
        "scheme": "nornir/<surface>:facet-<component>-<n>",
        "app_codes": "nornir-<subsystem>-<n> (see crate::errcode) — composes, never collides",
        "pairs": rows.len(),
        "mounts": MOUNTS.iter().map(|m| m.prefix()).collect::<Vec<_>>(),
        "matrix": rows,
        "recorded": rec.state_json(),
    })
}

/// Resolve the canonical reaction for a (possibly mounted) facett code.
pub fn reaction_of(code: &str) -> Option<Reaction> {
    reaction_for(code)
}

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

    #[test]
    fn mount_prefix_is_stable() {
        assert_eq!(VIZ.prefix(), "nornir/viz");
        assert_eq!(VIZ.app, APP);
    }

    #[test]
    fn reacting_namespaces_the_code_and_returns_the_canonical_reaction() {
        let mut rec = FacetRecorder::new();
        let err = facett_core::facet_err!(graph3d, 1, "no nodes to render");
        assert_eq!(react(&mut rec, VIZ, &err), Reaction::EmptyState);
        assert!(
            rec.saw("nornir/viz:facet-graph3d-1"),
            "the MOUNTED code is recorded"
        );
        // The base facett code is untouched — it is facett's, frozen.
        assert_eq!(rec.entries()[0].code, "facet-graph3d-1");
    }

    /// The facett code and nornir's OWN app code COMPOSE — both parse, neither
    /// shadows the other.
    #[test]
    fn facett_codes_compose_with_nornirs_own_app_codes() {
        // A nornir app code still parses out of a message that also carries a mount.
        let app_msg = crate::errcode::WAREHOUSE_OPEN.tag("/warehouse");
        assert_eq!(
            crate::errcode::parse(&app_msg),
            Some(&crate::errcode::WAREHOUSE_OPEN)
        );
        // A facett mounted code is NOT a nornir app code, and vice-versa.
        assert!(crate::errcode::by_code("nornir/viz:facet-graph3d-1").is_none());
        assert!(facett_core::errcode::lookup("nornir-warehouse-1").is_none());
        // Both namespaces resolve in their own registry.
        assert!(facett_core::errcode::lookup("facet-graph3d-1").is_some());
        assert!(crate::errcode::by_code(crate::errcode::WAREHOUSE_OPEN.code).is_some());
    }

    /// The state seam: a pane that published `error_code` gets reacted to, one that
    /// did not is left alone.
    #[test]
    fn react_to_state_reads_the_code_out_of_the_panes_data() {
        let mut rec = FacetRecorder::new();
        let healthy = serde_json::json!({ "nodes": 12, "error_code": serde_json::Value::Null });
        assert_eq!(
            react_to_state(&mut rec, VIZ, &healthy, ""),
            None,
            "healthy pane: no reaction"
        );
        let broken = serde_json::json!({ "nodes": 0, "error_code": "facet-graph3d-1" });
        assert_eq!(
            react_to_state(&mut rec, VIZ, &broken, ""),
            Some(Reaction::EmptyState),
            "the pane's published CODE drives the reaction"
        );
        assert!(rec.saw("nornir/viz:facet-graph3d-1"));
    }
}