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 sim_kernel::{
    CapabilityName, Cx, Expr, Ref, Result, Symbol, Value, browse_internal_capability,
    catalog::registry_catalog_view, force_list_to_vec, registry_catalog_read_capability,
};

use super::registry::registry_catalog_subject;
use super::schema::{FacetBuilder, RedactionBuilder};

const SCHEMA_FACET: (&str, &str) = ("browse", "schema");
const EXAMPLES_FACET: (&str, &str) = ("browse", "examples");
const OPERATIONS_FACET: (&str, &str) = ("browse", "operations");

pub(super) fn facets_from_card_spine(
    cx: &mut Cx,
    subject: &Ref,
    spine: &[(Symbol, Value)],
    provenance: Option<Value>,
) -> Result<Vec<Value>> {
    let schema = schema_facet(cx, spine, provenance)?;
    let examples = examples_facet(cx, spine)?;
    let operations = operations_facet(cx, spine)?;
    let mut facets = vec![schema, examples, operations];
    if has_registry_catalog_facet(subject) {
        facets.push(registry_catalog_facet(cx)?);
    }
    Ok(facets)
}

fn schema_facet(
    cx: &mut Cx,
    spine: &[(Symbol, Value)],
    provenance: Option<Value>,
) -> Result<Value> {
    let evidence = optional_list_items(cx, provenance.clone())?;
    let payload = cx.factory().table(vec![
        (field_symbol("args"), required_field(spine, "args")?.clone()),
        (
            field_symbol("result"),
            required_field(spine, "result")?.clone(),
        ),
        (
            field_symbol("shape-known"),
            required_field(spine, "shape-known")?.clone(),
        ),
        (
            field_symbol("evidence"),
            provenance.unwrap_or(cx.factory().list(Vec::new())?),
        ),
    ])?;
    public_facet(
        cx,
        Symbol::qualified(SCHEMA_FACET.0, SCHEMA_FACET.1),
        Symbol::new("schema"),
        payload,
        evidence,
    )
}

fn examples_facet(cx: &mut Cx, spine: &[(Symbol, Value)]) -> Result<Value> {
    let examples = example_refs(cx, required_field(spine, "tests")?.clone())?;
    let payload = cx.factory().list(examples)?;
    public_facet(
        cx,
        Symbol::qualified(EXAMPLES_FACET.0, EXAMPLES_FACET.1),
        Symbol::new("examples"),
        payload,
        Vec::new(),
    )
}

fn operations_facet(cx: &mut Cx, spine: &[(Symbol, Value)]) -> Result<Value> {
    let payload = operation_payload(cx, spine)?;
    private_facet(
        cx,
        Symbol::qualified(OPERATIONS_FACET.0, OPERATIONS_FACET.1),
        Symbol::new("operations"),
        payload,
        vec![browse_internal_capability()],
        Vec::new(),
    )
}

fn registry_catalog_facet(cx: &mut Cx) -> Result<Value> {
    let requires = vec![registry_catalog_read_capability()];
    let value = if cx.capabilities().contains(&requires[0]) {
        registry_catalog_view(cx)?
    } else {
        cx.factory().nil()?
    };
    private_facet(
        cx,
        registry_catalog_subject(),
        Symbol::new("catalog"),
        value,
        requires,
        Vec::new(),
    )
}

fn has_registry_catalog_facet(subject: &Ref) -> bool {
    matches!(
        subject,
        Ref::Symbol(symbol)
            if symbol == &registry_catalog_subject()
                || symbol == &Symbol::qualified("browse", "catalog")
    )
}

fn public_facet(
    cx: &mut Cx,
    name: Symbol,
    kind: Symbol,
    value: Value,
    evidence: Vec<Value>,
) -> Result<Value> {
    facet(cx, name, kind, value, Vec::new(), "public", evidence)
}

fn private_facet(
    cx: &mut Cx,
    name: Symbol,
    kind: Symbol,
    value: Value,
    requires: Vec<CapabilityName>,
    evidence: Vec<Value>,
) -> Result<Value> {
    gated_facet(cx, name, kind, value, requires, "private", evidence)
}

fn gated_facet(
    cx: &mut Cx,
    name: Symbol,
    kind: Symbol,
    value: Value,
    requires: Vec<CapabilityName>,
    visibility: &'static str,
    evidence: Vec<Value>,
) -> Result<Value> {
    if requires
        .iter()
        .all(|capability| cx.capabilities().contains(capability))
    {
        return facet(cx, name, kind, value, requires, visibility, evidence);
    }
    let redacted = redaction(cx, &requires)?;
    facet(cx, name, kind, redacted, requires, visibility, evidence)
}

fn facet(
    cx: &mut Cx,
    name: Symbol,
    kind: Symbol,
    value: Value,
    requires: Vec<CapabilityName>,
    visibility: &'static str,
    evidence: Vec<Value>,
) -> Result<Value> {
    let mut builder = FacetBuilder::new(name);
    builder.kind = kind;
    builder.shape = Symbol::qualified("core", "Any");
    builder.value = Some(value);
    builder.requires = capability_values(cx, &requires)?;
    builder.visibility = Symbol::new(visibility);
    builder.evidence = evidence;
    builder.build(cx)
}

fn redaction(cx: &mut Cx, requires: &[CapabilityName]) -> Result<Value> {
    let mut builder = RedactionBuilder::unavailable();
    builder.reason = Symbol::new("capability-required");
    builder.requires = capability_values(cx, requires)?;
    builder.summary = format!(
        "requires {}",
        requires
            .iter()
            .map(CapabilityName::as_str)
            .collect::<Vec<_>>()
            .join(", ")
    );
    builder.build(cx)
}

fn operation_payload(cx: &mut Cx, spine: &[(Symbol, Value)]) -> Result<Value> {
    let args = required_field(spine, "args")?.clone();
    let result = required_field(spine, "result")?.clone();
    let requires = required_field(spine, "requires")?.clone();
    let ops = list_items(cx, required_field(spine, "ops")?.clone())?;
    let entries = ops
        .into_iter()
        .map(|op| operation_value(cx, op, args.clone(), result.clone(), requires.clone()))
        .collect::<Result<Vec<_>>>()?;
    cx.factory().list(entries)
}

fn operation_value(
    cx: &mut Cx,
    key: Value,
    args: Value,
    result: Value,
    requires: Value,
) -> Result<Value> {
    cx.factory().table(vec![
        (field_symbol("key"), key),
        (field_symbol("args"), args),
        (field_symbol("result"), result),
        (field_symbol("effects"), cx.factory().list(Vec::new())?),
        (field_symbol("requires"), requires),
    ])
}

fn example_refs(cx: &mut Cx, tests: Value) -> Result<Vec<Value>> {
    list_items(cx, tests)?
        .into_iter()
        .filter_map(|test| match test.object().as_expr(cx) {
            Ok(Expr::Map(entries)) if bool_field(&entries, "example") == Some(true) => {
                Some(name_value(cx, &entries))
            }
            Ok(_) => None,
            Err(err) => Some(Err(err)),
        })
        .collect()
}

fn name_value(cx: &mut Cx, entries: &[(Expr, Expr)]) -> Result<Value> {
    match expr_field(entries, "name") {
        Some(Expr::Symbol(symbol)) => cx.factory().symbol(symbol.clone()),
        _ => cx.factory().nil(),
    }
}

fn optional_list_items(cx: &mut Cx, value: Option<Value>) -> Result<Vec<Value>> {
    match value {
        Some(value) => list_items(cx, value),
        None => Ok(Vec::new()),
    }
}

fn list_items(cx: &mut Cx, value: Value) -> Result<Vec<Value>> {
    let Some(list) = value.object().as_list() else {
        return Ok(Vec::new());
    };
    force_list_to_vec(cx, list, "browse facet list")
}

fn capability_values(cx: &mut Cx, capabilities: &[CapabilityName]) -> Result<Vec<Value>> {
    capabilities
        .iter()
        .map(|capability| cx.factory().symbol(capability.as_symbol()))
        .collect()
}

fn required_field<'a>(entries: &'a [(Symbol, Value)], name: &str) -> Result<&'a Value> {
    find_field(entries, name)
        .ok_or_else(|| sim_kernel::Error::HostError(format!("missing Card fixed field {name}")))
}

use super::fields::{
    expr_entry_field as expr_field, key as field_symbol, value_field as find_field,
};

fn bool_field(entries: &[(Expr, Expr)], name: &str) -> Option<bool> {
    match expr_field(entries, name) {
        Some(Expr::Bool(value)) => Some(*value),
        _ => None,
    }
}