liminal-server 0.27.4

Standalone server for the liminal messaging bus
Documentation
//! ACCESS-011 R2: every place that constructs `RefusalCause::MissingGrant` or
//! `RefusalCause::HotMode` is one of the decision points the advisory mode
//! covers, so no fourth decision can be added without the mode.

use std::collections::BTreeMap;
use std::path::Path;

type TestResult = Result<(), Box<dyn std::error::Error>>;

const TOKENS: [&str; 2] = ["RefusalCause::MissingGrant", "RefusalCause::HotMode"];

/// Test modules, fixtures and integration tests are not decision points.
fn is_test_source(path: &Path) -> bool {
    let name = path
        .file_name()
        .and_then(|name| name.to_str())
        .unwrap_or("");
    name.ends_with("_tests.rs")
        || name.ends_with("_test.rs")
        || name == "fixture.rs"
        || name == "tests.rs"
        || path.components().any(|component| {
            let component = component.as_os_str().to_str().unwrap_or("");
            component == "tests" || component.ends_with("_tests")
        })
}

/// How many times `line` constructs a cause, as against comparing or
/// matching one: a token left of `=>` is a pattern, one after `==`, `!=`
/// or inside `matches!(` is a comparison, one beside `|` is a pattern.
fn constructions(line: &str) -> usize {
    let trimmed = line.trim_start();
    if trimmed.starts_with("//") || line.contains("matches!(") || line.contains("| RefusalCause") {
        return 0;
    }
    let arrow = line.find("=>");
    TOKENS
        .iter()
        .flat_map(|token| line.match_indices(token))
        .filter(|(index, token)| {
            let before = line[..*index].trim_end();
            let after = &line[index + token.len()..];
            !(before.ends_with("==")
                || before.ends_with("!=")
                || after.trim_start().starts_with('|')
                || arrow.is_some_and(|arrow| *index < arrow))
        })
        .count()
}

fn census(dir: &Path, root: &Path, sites: &mut BTreeMap<String, usize>) -> TestResult {
    for entry in std::fs::read_dir(dir)? {
        let path = entry?.path();
        if path.is_dir() {
            census(&path, root, sites)?;
            continue;
        }
        if path.extension().is_none_or(|extension| extension != "rs") || is_test_source(&path) {
            continue;
        }
        let text = std::fs::read_to_string(&path)?;
        let count = text.lines().map(constructions).sum::<usize>();
        if count > 0 {
            let relative = path.strip_prefix(root)?.display().to_string();
            sites.insert(relative, count);
        }
    }
    Ok(())
}

#[test]
fn every_missing_grant_and_hot_mode_construction_is_a_named_decision_point() -> TestResult {
    let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
    let mut sites = BTreeMap::new();
    census(&root, &root, &mut sites)?;
    let expected = BTreeMap::from([
        // (a) evaluate_grant: Held → HotMode, Refused → MissingGrant.
        ("auth_lys/lys_pass_adapter.rs".to_owned(), 2),
        // (b) the deliberate live ask: Refused → MissingGrant.
        ("auth_lys/lys_pass_adapter/deliberate.rs".to_owned(), 1),
    ]);
    assert_eq!(
        sites, expected,
        "a grant decision outside the three ACCESS-011 R2 places; route it through auth_lys::advisory"
    );
    Ok(())
}