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

nordisk/edda

nornir-build-thing-skew/src/lib.rs

//! **What a reader DID with a recipe, in a shape two readers can be compared in.**
//!
//! See `tests/recipe_skew.rs` for the guard itself and the measurement it was
//! written from.

use std::collections::BTreeSet;

/// One reader's honest reading of one recipe.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Reading {
    /// `CARGO_PKG_VERSION` of the reader.
    pub reader: &'static str,
    /// Every `build.*` key path the reader ignored. Empty is the law.
    pub dropped: Vec<String>,
    /// The refusal, when the reader could not load the file at all.
    pub refused: Option<String>,
}

impl Reading {
    /// Did this reader read every declared byte of the recipe?
    pub fn is_faithful(&self) -> bool {
        self.refused.is_none() && self.dropped.is_empty()
    }

    /// One line, for a failure message that has to name what happened.
    pub fn line(&self) -> String {
        match (&self.refused, self.dropped.is_empty()) {
            (Some(e), _) => format!(
                "{}: REFUSED — {}",
                self.reader,
                e.lines().next().unwrap_or("").trim()
            ),
            (None, true) => format!("{}: read it in full", self.reader),
            (None, false) => format!(
                "{}: SILENTLY DROPPED {}\n        {}",
                self.reader,
                self.dropped.len(),
                self.dropped.join("\n        ")
            ),
        }
    }
}

/// The reading, generated once per linked crate copy.
///
/// A macro and not a trait because the two crates are two unrelated types with
/// the same shape — there is no trait either of them implements, and there could
/// not be: the older one was published before the newer one existed.
#[macro_export]
macro_rules! reader {
    ($fn_name:ident, $krate:ident, $version:expr) => {
        /// Read `text` with this crate copy and report what it did.
        pub fn $fn_name(text: &str) -> $crate::Reading {
            let mut seen = ::std::collections::BTreeSet::new();
            // toml >= 0.9: building the deserializer parses; a parse error is
            // this reader's error like any other.
            let r: Result<$krate::variant::BuildConfig, _> = ::toml::Deserializer::parse(text)
                .and_then(|de| {
                    ::serde_ignored::deserialize(de, |p| {
                        seen.insert(p.to_string());
                    })
                });
            match r {
                Ok(_) => $crate::Reading {
                    reader: $version,
                    dropped: $crate::recipe_paths(seen),
                    refused: None,
                },
                Err(e) => $crate::Reading {
                    reader: $version,
                    dropped: Vec::new(),
                    refused: Some(e.to_string()),
                },
            }
        }
    };
}

/// Only the `[build]` subtree is the recipe: `BuildConfig` also parses a
/// `nornir.toml` full of other root tables, and those are not this format's
/// business.
pub fn recipe_paths(seen: BTreeSet<String>) -> Vec<String> {
    seen.into_iter()
        .filter(|p| p == "build" || p.starts_with("build."))
        .collect()
}