sim-citizen 0.1.0-rc.1

Citizen support outside the SIM kernel.
Documentation
use sim_kernel::{Cx, DefaultFactory, Error, Expr, NoopEvalPolicy, ObjectEncode, Symbol};

use crate::{
    CitizenField, CitizenLib, citizen_census_markdown, example::Point, registered_citizens,
    run_registered_conformance, value_from_expr,
};

#[test]
fn point_is_registered_by_inventory() {
    let point = registered_citizens()
        .find(|info| info.symbol == "example/Point")
        .expect("point citizen should be registered");
    assert_eq!(point.version, 1);
    assert_eq!(point.arity, 2);
    assert_eq!(point.crate_name, "sim-citizen");
}

#[test]
fn point_round_trips_through_conformance() {
    let mut cx = cx();
    run_registered_conformance(&mut cx).unwrap();
}

#[test]
fn point_malformed_arity_returns_error() {
    let mut cx = cx();
    cx.load_lib(&CitizenLib::all()).unwrap();
    cx.grant(sim_kernel::read_construct_capability());

    let value = value_from_expr(&mut cx, &Expr::Symbol(Symbol::new("v1"))).unwrap();
    let err = cx
        .read_construct(&Symbol::qualified("example", "Point"), vec![value])
        .expect_err("malformed arity must fail");
    assert!(matches!(err, Error::Eval(message) if message.contains("expects 3")));
}

#[test]
fn point_wrong_capability_fails_closed() {
    let mut cx = cx();
    cx.load_lib(&CitizenLib::all()).unwrap();
    let point = Point { x: 1, y: 2 };
    let encoding = point.object_encoding(&mut cx).unwrap();
    let sim_kernel::ObjectEncoding::Constructor { class, args } = encoding else {
        panic!("point should use constructor encoding");
    };
    let values = args
        .iter()
        .map(|arg| value_from_expr(&mut cx, arg))
        .collect::<sim_kernel::Result<Vec<_>>>()
        .unwrap();

    let err = cx
        .read_construct(&class, values)
        .expect_err("read-construct must be capability-gated");
    assert!(
        matches!(err, Error::CapabilityDenied { capability } if capability == sim_kernel::read_construct_capability())
    );
}

#[test]
fn field_helpers_decode_scalar_list_and_option() {
    let mut cx = cx();
    let expr = vec![1_i64, 2, 3].encode_field();
    let value = value_from_expr(&mut cx, &expr).unwrap();
    let decoded = Vec::<i64>::decode_field_value(&mut cx, value, "numbers").unwrap();
    assert_eq!(decoded, vec![1, 2, 3]);

    let expr = Option::<String>::None.encode_field();
    let value = value_from_expr(&mut cx, &expr).unwrap();
    let decoded = Option::<String>::decode_field_value(&mut cx, value, "maybe").unwrap();
    assert_eq!(decoded, None);
}

#[test]
fn substrate_citizen_census_contains_point() {
    let generated = citizen_census_markdown();
    assert!(generated.contains("| `example/Point` | 1 | 2 | `sim-citizen` |"));
}

fn cx() -> Cx {
    Cx::new(
        std::sync::Arc::new(NoopEvalPolicy),
        std::sync::Arc::new(DefaultFactory),
    )
}