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, browse_internal_capability,
};

use crate::runtime::{browse::schema::FacetBuilder, install_core_runtime};

use super::support::table_value;

#[test]
fn cards_include_initial_facets_and_redact_internal_payloads() {
    let mut cx = test_cx();
    let card = browse_help_card(&mut cx);
    let card = expr(&mut cx, &card);
    let facets = list_items(table_value(&card, &field("facets")).expect("facets"));

    assert!(facet_by_name(facets, Symbol::qualified("browse", "schema")).is_some());
    assert!(facet_by_name(facets, Symbol::qualified("browse", "examples")).is_some());
    let operations =
        facet_by_name(facets, Symbol::qualified("browse", "operations")).expect("operations");

    assert_eq!(
        table_value(operations, &field("visibility")),
        Some(&Expr::Symbol(Symbol::new("private")))
    );
    assert_list_contains_symbol(
        table_value(operations, &field("requires")).expect("requires"),
        Symbol::qualified("capability", "browse.internal"),
    );

    let redaction = table_value(operations, &field("value")).expect("redaction");
    assert_eq!(
        table_value(redaction, &field("reason")),
        Some(&Expr::Symbol(Symbol::new("capability-required")))
    );
    assert_list_contains_symbol(
        table_value(redaction, &field("requires")).expect("redaction requires"),
        Symbol::qualified("capability", "browse.internal"),
    );
}

#[test]
fn browse_internal_reveals_operations_facet_payload() {
    let mut cx = test_cx();
    cx.grant(browse_internal_capability());

    let card = browse_help_card(&mut cx);
    let card = expr(&mut cx, &card);
    let facets = list_items(table_value(&card, &field("facets")).expect("facets"));
    let operations =
        facet_by_name(facets, Symbol::qualified("browse", "operations")).expect("operations");
    let payload = table_value(operations, &field("value")).expect("operations payload");
    let operation_entries = list_items(payload);

    assert!(operation_entries.iter().any(|entry| {
        table_value(entry, &field("key")) == Some(&Expr::String("core/call.v1".to_owned()))
    }));
}

#[test]
fn facet_shape_accepts_complete_facets_and_rejects_missing_fields() {
    let mut cx = test_cx();
    let valid = FacetBuilder::new(Symbol::qualified("browse", "schema"))
        .build(&mut cx)
        .unwrap();
    assert!(shape_accepts(
        &mut cx,
        Symbol::qualified("browse", "Facet"),
        valid
    ));

    let malformed = cx
        .factory()
        .table(vec![
            (
                field("name"),
                cx.factory()
                    .symbol(Symbol::qualified("browse", "schema"))
                    .unwrap(),
            ),
            (
                field("version"),
                cx.factory()
                    .number_literal(Symbol::qualified("numbers", "i64"), "1".to_owned())
                    .unwrap(),
            ),
        ])
        .unwrap();
    assert!(!shape_accepts(
        &mut cx,
        Symbol::qualified("browse", "Facet"),
        malformed
    ));
}

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

fn browse_help_card(cx: &mut Cx) -> Value {
    let subject = cx
        .factory()
        .symbol(Symbol::qualified("core", "help"))
        .unwrap();
    cx.call_function(
        &Symbol::qualified("core", "browse"),
        Args::new(vec![subject]),
    )
    .unwrap()
}

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

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

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

fn facet_by_name(facets: &[Expr], name: Symbol) -> Option<&Expr> {
    facets
        .iter()
        .find(|facet| table_value(facet, &field("name")) == Some(&Expr::Symbol(name.clone())))
}

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 field(name: &str) -> Symbol {
    Symbol::new(name.to_owned())
}