trellis-core 0.1.2

Deterministic reactive resource graph core types.
Documentation
use std::collections::BTreeSet;

use trellis_core::{
    AuditEvent, CollectionDiffKind, DependencyList, Graph, OutputFrameKindTrace,
    ResourceCommandKind, ResourceKey, ResourcePlan, ResourceTransitionPolicy, StagedInputOutcome,
};

#[derive(Clone, Debug, Eq, PartialEq)]
enum Command {
    Open(String),
}

fn members(values: &[&str]) -> BTreeSet<String> {
    values.iter().map(|value| (*value).to_owned()).collect()
}

fn key(value: &str) -> ResourceKey {
    ResourceKey::new(format!("resource:{value}"))
}

#[test]
fn transaction_trace_records_stable_structural_facts() {
    let mut graph = Graph::<Command, String>::new_with_command_type();
    let mut tx = graph.begin_transaction().unwrap();
    let scope = tx.create_scope("scope").unwrap();
    let source = tx.input::<BTreeSet<String>>("source").unwrap();
    let mode = tx.input::<String>("mode").unwrap();
    tx.set_input(mode, "initial".to_owned()).unwrap();
    tx.set_input(source, members(&["b", "a"])).unwrap();
    let count = tx
        .derived(
            "count",
            DependencyList::new([source.id()]).unwrap(),
            move |ctx| Ok(ctx.input(source)?.len()),
        )
        .unwrap();
    let collection = tx
        .set_collection(
            "demand",
            DependencyList::new([source.id()]).unwrap(),
            move |ctx| Ok(ctx.input(source)?.clone()),
        )
        .unwrap();
    tx.set_resource_planner(collection, scope, move |ctx| {
        let mut plan = ResourcePlan::new();
        for added in &ctx.diff().added {
            plan.open(
                key(&added.value),
                ctx.scope(),
                Command::Open(added.value.clone()),
            );
        }
        Ok(plan)
    })
    .unwrap();
    let first_output = tx
        .materialized_output(
            "first",
            scope,
            DependencyList::new([collection.id(), mode.id()]).unwrap(),
            move |ctx| {
                Ok(format!(
                    "{}:{}",
                    ctx.input(mode)?,
                    ctx.set_collection(collection)?.len()
                ))
            },
        )
        .unwrap();
    let second_output = tx
        .materialized_output(
            "second",
            scope,
            DependencyList::new([count.id()]).unwrap(),
            move |ctx| Ok(format!("count:{}", ctx.derived(count)?)),
        )
        .unwrap();
    let result = tx.commit().unwrap();
    drop(tx);

    let trace = result.trace();
    assert_eq!(
        trace
            .staged_input_changes
            .iter()
            .map(|change| (change.node, change.outcome))
            .collect::<Vec<_>>(),
        vec![
            (source.id(), StagedInputOutcome::Changed),
            (mode.id(), StagedInputOutcome::Changed),
        ]
    );
    assert_eq!(
        trace.dirty_roots,
        vec![source.id(), mode.id(), count.id(), collection.id()]
    );
    assert_eq!(trace.recomputed_derived_nodes, vec![count.id()]);
    assert_eq!(trace.changed_derived_nodes, vec![count.id()]);
    assert_eq!(trace.recomputed_collection_nodes, vec![collection.id()]);
    assert_eq!(trace.changed_collection_nodes, vec![collection.id()]);

    assert_eq!(trace.collection_diffs.len(), 1);
    assert_eq!(trace.collection_diffs[0].node, collection.id());
    assert_eq!(trace.collection_diffs[0].kind, CollectionDiffKind::Set);
    assert_eq!(trace.collection_diffs[0].added, 2);
    assert_eq!(trace.collection_diffs[0].removed, 0);
    assert_eq!(trace.collection_diffs[0].unchanged, 0);

    assert_eq!(
        trace
            .resource_commands
            .iter()
            .map(|command| (&command.key, command.kind, command.transition))
            .collect::<Vec<_>>(),
        vec![
            (
                &key("a"),
                ResourceCommandKind::Open,
                ResourceTransitionPolicy::Open
            ),
            (
                &key("b"),
                ResourceCommandKind::Open,
                ResourceTransitionPolicy::Open
            ),
        ]
    );
    assert_eq!(
        trace
            .output_frames
            .iter()
            .map(|frame| (frame.output_key, frame.kind))
            .collect::<Vec<_>>(),
        vec![
            (first_output.key(), OutputFrameKindTrace::Baseline),
            (second_output.key(), OutputFrameKindTrace::Baseline),
        ]
    );
    assert_eq!(trace.scope_events.len(), 1);
    assert_eq!(
        trace
            .audit_log
            .iter()
            .filter_map(|entry| match entry.event {
                AuditEvent::InputChanged(node) => Some(node),
                _ => None,
            })
            .collect::<Vec<_>>(),
        vec![source.id(), mode.id()]
    );
}