sim-nest 0.1.5

The SIM constellation umbrella facade: one crate that re-exports the runtime kernel, codecs, number domains, and libraries behind features.
Documentation
use std::sync::Arc;

use sim_kernel::{
    Args, Cx, DefaultFactory, Expr, NoopEvalPolicy, Symbol, Value, force_list_to_vec,
    registry_catalog_read_capability,
};

use crate::runtime::{
    browse::schema::{BrowseTestBuilder, CARD_V2_FIELDS, HELP_FIELDS, HelpBuilder},
    install_core_runtime,
};

use super::support::table_value;

const SCHEMA_SUBJECTS: &[(&str, &str, &str)] = &[
    ("core", "Card", "Card"),
    ("browse", "Help", "help document"),
    ("browse", "Test", "test or worked example"),
    ("browse", "Coverage", "coverage summary"),
    ("browse", "Facet", "extension payload"),
    ("browse", "Redaction", "hidden but known"),
    ("browse", "TestReport", "test run"),
];

const BROWSE_FUNCTIONS: &[(&str, &str)] = &[
    ("core", "browse"),
    ("core", "help"),
    ("core", "args"),
    ("core", "result"),
    ("core", "tests"),
    ("core", "examples"),
    ("core", "coverage"),
    ("core", "facets"),
    ("core", "help-object"),
    ("core", "browse-neighbors"),
    ("core", "browse-path"),
];

#[test]
fn browse_schema_subjects_have_cards_authored_help_and_examples() {
    let mut cx = test_cx();

    for (namespace, name, summary_fragment) in SCHEMA_SUBJECTS {
        let subject = Symbol::qualified(*namespace, *name);
        let card = browse(&mut cx, subject.clone());
        let card = expr(&mut cx, &card);
        assert_eq!(table_keys(&card), symbols(CARD_V2_FIELDS));
        assert_eq!(
            table_value(&card, &field("subject")),
            Some(&Expr::Symbol(subject.clone()))
        );
        assert!(
            shape_accepts_card(&mut cx, &card),
            "{subject} Card should satisfy core/Card"
        );

        let help = table_value(&card, &field("help")).expect("help");
        assert_eq!(table_keys(help), symbols(HELP_FIELDS));
        assert!(matches!(
            table_value(help, &field("summary")),
            Some(Expr::String(summary)) if summary.contains(summary_fragment)
        ));

        let subject_value = symbol_value(&cx, &subject);
        let examples = call(
            &mut cx,
            Symbol::qualified("core", "examples"),
            vec![subject_value],
        );
        let examples = expr(&mut cx, &examples);
        let examples = list_items(&examples);
        assert_eq!(
            examples.len(),
            1,
            "{subject} should have one schema example"
        );
        assert_eq!(
            table_value(&examples[0], &field("example")),
            Some(&Expr::Bool(true))
        );
        assert_list_contains_symbol(
            table_value(&examples[0], &field("subjects")).expect("subjects"),
            subject,
        );
    }
}

#[test]
fn browse_functions_and_projection_functions_are_browsable() {
    let mut cx = test_cx();

    for (namespace, name) in BROWSE_FUNCTIONS {
        let subject = Symbol::qualified(*namespace, *name);
        let card = browse(&mut cx, subject.clone());
        let card = expr(&mut cx, &card);

        assert_eq!(
            table_value(&card, &field("kind")),
            Some(&Expr::Symbol(Symbol::qualified("core", "function"))),
            "{subject} should be a function Card"
        );
        assert_eq!(
            table_value(&card, &field("shape-known")),
            Some(&Expr::Bool(true)),
            "{subject} should publish total call shapes"
        );

        let help = table_value(&card, &field("help")).expect("help");
        assert_eq!(
            table_value(help, &field("kind")),
            Some(&Expr::Symbol(Symbol::qualified("core", "function")))
        );
        assert!(matches!(
            table_value(help, &field("detail")),
            Some(Expr::String(detail)) if detail.contains(&subject.to_string())
        ));
    }
}

#[test]
fn registry_catalog_subject_returns_card_with_table_backed_facet() {
    let mut cx = test_cx();
    cx.grant(registry_catalog_read_capability());
    let subject = Symbol::qualified("registry", "catalog");
    let card = browse(&mut cx, subject.clone());
    let card_expr = expr(&mut cx, &card);

    assert_eq!(table_keys(&card_expr), symbols(CARD_V2_FIELDS));
    assert_eq!(
        table_value(&card_expr, &field("subject")),
        Some(&Expr::Symbol(subject.clone()))
    );
    assert!(
        shape_accepts_card(&mut cx, &card_expr),
        "{subject} Card should satisfy core/Card"
    );

    let help = table_value(&card_expr, &field("help")).expect("help");
    assert!(matches!(
        table_value(help, &field("summary")),
        Some(Expr::String(summary)) if summary.contains("registry catalog")
    ));

    let facets_value = value_field(&mut cx, &card, "facets");
    let facets = value_list(&mut cx, facets_value);
    let facet =
        facet_value_by_name(&mut cx, &facets, subject.clone()).expect("registry catalog facet");
    assert_eq!(
        value_field_expr(&mut cx, &facet, "kind"),
        Expr::Symbol(Symbol::new("catalog"))
    );
    assert_list_contains_symbol(
        &value_field_expr(&mut cx, &facet, "requires"),
        Symbol::qualified("capability", "registry.catalog.read"),
    );

    let catalog = value_field(&mut cx, &facet, "value");
    assert!(
        catalog.object().as_dir().is_some(),
        "catalog facet should expose a Dir view"
    );
    let table = catalog
        .object()
        .as_table_impl()
        .expect("catalog facet should expose a Table view");
    assert!(
        table.has(&mut cx, Symbol::new("registry")).unwrap(),
        "catalog root should expose registry namespace"
    );
    let registry_dir = table.get(&mut cx, Symbol::new("registry")).unwrap();
    let registry_table = registry_dir
        .object()
        .as_table_impl()
        .expect("registry namespace should be table-backed");
    assert!(
        registry_table.has(&mut cx, Symbol::new("exports")).unwrap(),
        "registry namespace should expose exports table"
    );
}

#[test]
fn schema_shapes_accept_complete_values_and_reject_malformed_tables() {
    let mut cx = test_cx();
    let card = browse(&mut cx, Symbol::qualified("core", "Card"));
    assert_shape_accepts(&mut cx, Symbol::qualified("core", "Card"), card);

    let help = HelpBuilder::new(symbol_value(&cx, &Symbol::qualified("core", "Card")))
        .build(&mut cx)
        .unwrap();
    assert_shape_accepts(&mut cx, Symbol::qualified("browse", "Help"), help);

    let test = BrowseTestBuilder::new(
        Symbol::qualified("browse-example", "core-card"),
        Symbol::new("core"),
    )
    .build(&mut cx)
    .unwrap();
    assert_shape_accepts(&mut cx, Symbol::qualified("browse", "Test"), test);

    for (shape, field_name) in [
        (Symbol::qualified("core", "Card"), "subject"),
        (Symbol::qualified("browse", "Help"), "subject"),
        (Symbol::qualified("browse", "Test"), "name"),
    ] {
        let malformed = cx
            .factory()
            .table(vec![(field(field_name), cx.factory().nil().unwrap())])
            .unwrap();
        assert!(
            !shape_accepts(&mut cx, shape.clone(), malformed),
            "{shape} should reject missing required fields"
        );
    }
}

fn test_cx() -> Cx {
    let mut cx = Cx::new(Arc::new(NoopEvalPolicy), Arc::new(DefaultFactory));
    install_core_runtime(&mut cx);
    cx
}

fn browse(cx: &mut Cx, subject: Symbol) -> Value {
    let subject = symbol_value(cx, &subject);
    call(cx, Symbol::qualified("core", "browse"), vec![subject])
}

fn call(cx: &mut Cx, symbol: Symbol, args: Vec<Value>) -> Value {
    cx.call_function(&symbol, Args::new(args))
        .unwrap_or_else(|err| panic!("{symbol} failed: {err}"))
}

fn symbol_value(cx: &Cx, symbol: &Symbol) -> Value {
    cx.factory().symbol(symbol.clone()).unwrap()
}

fn expr(cx: &mut Cx, value: &Value) -> Expr {
    value.object().as_expr(cx).unwrap()
}

fn table_keys(expr: &Expr) -> Vec<Symbol> {
    let Expr::Map(entries) = expr else {
        panic!("expected map");
    };
    entries
        .iter()
        .map(|(key, _)| match key {
            Expr::Symbol(symbol) => symbol.clone(),
            other => panic!("expected symbol key, got {other:?}"),
        })
        .collect()
}

fn list_items(expr: &Expr) -> &[Expr] {
    let Expr::List(items) = expr else {
        panic!("expected list");
    };
    items
}

fn symbols(fields: &[&str]) -> Vec<Symbol> {
    fields.iter().map(|field| Symbol::new(*field)).collect()
}

fn field(name: &str) -> Symbol {
    Symbol::new(name.to_owned())
}

fn assert_list_contains_symbol(expr: &Expr, expected: Symbol) {
    let Expr::List(items) = expr else {
        panic!("expected list");
    };
    assert!(
        items
            .iter()
            .any(|item| matches!(item, Expr::Symbol(symbol) if symbol == &expected))
    );
}

fn value_list(cx: &mut Cx, value: Value) -> Vec<Value> {
    let Some(list) = value.object().as_list() else {
        panic!("expected list value");
    };
    force_list_to_vec(cx, list, "browse reflection list").unwrap()
}

fn facet_value_by_name(cx: &mut Cx, facets: &[Value], name: Symbol) -> Option<Value> {
    for facet in facets {
        if value_field_expr(cx, facet, "name") == Expr::Symbol(name.clone()) {
            return Some(facet.clone());
        }
    }
    None
}

fn value_field_expr(cx: &mut Cx, value: &Value, name: &str) -> Expr {
    let field_value = value_field(cx, value, name);
    expr(cx, &field_value)
}

fn value_field(cx: &mut Cx, value: &Value, name: &str) -> Value {
    let key = field(name);
    value_entries(cx, value)
        .into_iter()
        .find_map(|(candidate, value)| (candidate == key).then_some(value))
        .unwrap_or_else(|| panic!("missing field {name}"))
}

fn value_entries(cx: &mut Cx, value: &Value) -> Vec<(Symbol, Value)> {
    if let Some(table) = value.object().as_table_impl() {
        return table.entries(cx).unwrap();
    }
    let table = value.object().as_table(cx).unwrap();
    table
        .object()
        .as_table_impl()
        .expect("expected table value")
        .entries(cx)
        .unwrap()
}

fn shape_accepts_card(cx: &mut Cx, expr: &Expr) -> bool {
    let value = cx.factory().expr(expr.clone()).unwrap();
    shape_accepts(cx, Symbol::qualified("core", "Card"), value)
}

fn assert_shape_accepts(cx: &mut Cx, shape: Symbol, value: Value) {
    assert!(
        shape_accepts(cx, shape.clone(), value),
        "{shape} should accept complete value"
    );
}

fn shape_accepts(cx: &mut Cx, shape: Symbol, value: Value) -> bool {
    let shape = cx.resolve_shape(&shape).unwrap();
    shape
        .object()
        .as_shape()
        .unwrap()
        .check_value(cx, value)
        .unwrap()
        .accepted
}