sandl 0.1.0

A framework for building parallel execution engines with dependency management, type-safe method dispatch, and event observation.
Documentation
use std::sync::{
    Arc,
    atomic::{AtomicUsize, Ordering},
};

use sandl::*;

#[test]
fn context_basic_operations() {
    let ctx = Context::new();

    ctx.set("name", Value::from("Alice"));
    assert_eq!(ctx.get("name").unwrap().as_str(), Some("Alice"));

    assert!(ctx.contains("name"));
    assert!(!ctx.contains("age"));

    let removed = ctx.remove("name");
    assert!(removed.is_some());
    assert!(!ctx.contains("name"));
}

#[test]
fn context_typed_access() {
    let ctx = Context::new();

    ctx.set_from("count", 42i64);
    let count: i64 = ctx.get_as("count").unwrap();
    assert_eq!(count, 42);

    ctx.set_from("ratio", 3.14f64);
    let ratio: f64 = ctx.get_as("ratio").unwrap();
    assert_eq!(ratio, 3.14);
}

#[test]
fn context_clone_shares_data() {
    let ctx1 = Context::new();
    ctx1.set("shared", Value::from(42));

    let ctx2 = ctx1.clone();
    assert_eq!(ctx2.get("shared").unwrap().as_i64(), Some(42));

    ctx2.set("shared", Value::from(100));
    assert_eq!(ctx1.get("shared").unwrap().as_i64(), Some(100));
}

#[test]
fn context_basic_set_get() {
    let layer1 = quick_layer!("layer1", "set", Value, |_args, ctx| {
        ctx.set("key", Value::from(42));
        Ok(value!({}))
    });

    let layer2 = quick_layer!("layer2", "get", Value, |_args, ctx| {
        let value = ctx.get("key").unwrap();
        assert_eq!(value.as_i64().unwrap(), 42);
        Ok(value!({}))
    });

    let slice = Slice::builder("test")
        .layer("layer1", |m| m.call_default("set"))
        .layer("layer2", |m| m.call_default("get"))
        .build();

    let engine = dependencies!(
        add_layers!(Engine::builder(), layer1, layer2),
        "layer2" => ["layer1"]
    )
    .add_slice(slice)
    .build()
    .unwrap();

    engine.run(RunFlags::SILENT);
}

#[test]
fn context_missing_key() {
    let layer = quick_layer!("layer", "work", Value, |_args, ctx| {
        let value = ctx.get("nonexistent");
        assert!(value.is_none());
        Ok(value!({}))
    });

    let slice = Slice::builder("test")
        .layer("layer", |m| m.call_default("work"))
        .build();

    let engine = Engine::builder()
        .add_layer(layer)
        .add_slice(slice)
        .build()
        .unwrap();

    engine.run(RunFlags::SILENT);
}

#[test]
fn context_contains() {
    let layer = quick_layer!("layer", "work", Value, |_args, ctx| {
        assert!(!ctx.contains("key"));

        ctx.set("key", Value::from(42));
        assert!(ctx.contains("key"));

        ctx.remove("key");
        assert!(!ctx.contains("key"));

        Ok(value!({}))
    });

    let slice = Slice::builder("test")
        .layer("layer", |m| m.call_default("work"))
        .build();

    let engine = Engine::builder()
        .add_layer(layer)
        .add_slice(slice)
        .build()
        .unwrap();

    engine.run(RunFlags::SILENT);
}

#[test]
fn context_overwrite() {
    let layer = quick_layer!("layer", "work", Value, |_args, ctx| {
        ctx.set("key", Value::from(10));
        assert_eq!(ctx.get("key").unwrap().as_i64().unwrap(), 10);

        ctx.set("key", Value::from(20));
        assert_eq!(ctx.get("key").unwrap().as_i64().unwrap(), 20);

        Ok(value!({}))
    });

    let slice = Slice::builder("test")
        .layer("layer", |m| m.call_default("work"))
        .build();

    let engine = Engine::builder()
        .add_layer(layer)
        .add_slice(slice)
        .build()
        .unwrap();

    engine.run(RunFlags::SILENT);
}

#[test]
fn context_type_safe_access() {
    let layer1 = quick_layer!("layer1", "set", Value, |_args, ctx| {
        ctx.set_from("int", 42i64);
        ctx.set_from("float", 3.14f64);
        ctx.set_from("string", "hello".to_string());
        ctx.set_from("bool", true);
        ctx.set_from("vec", vec![1i64, 2, 3]);
        Ok(value!({}))
    });

    let layer2 = quick_layer!("layer2", "get", Value, |_args, ctx| {
        let int_val: i64 = ctx.get_as("int").unwrap();
        let float_val: f64 = ctx.get_as("float").unwrap();
        let string_val: String = ctx.get_as("string").unwrap();
        let bool_val: bool = ctx.get_as("bool").unwrap();
        let vec_val: Vec<i64> = ctx.get_as("vec").unwrap();

        assert_eq!(int_val, 42);
        assert_eq!(float_val, 3.14);
        assert_eq!(string_val, "hello");
        assert_eq!(bool_val, true);
        assert_eq!(vec_val, vec![1, 2, 3]);

        Ok(value!({}))
    });

    let slice = Slice::builder("test")
        .layer("layer1", |m| m.call_default("set"))
        .layer("layer2", |m| m.call_default("get"))
        .build();

    let engine = dependencies!(
        add_layers!(Engine::builder(), layer1, layer2),
        "layer2" => ["layer1"]
    )
    .add_slice(slice)
    .build()
    .unwrap();

    engine.run(RunFlags::SILENT);
}

#[test]
fn context_type_conversion_error() {
    let layer = quick_layer!("layer", "work", Value, |_args, ctx| {
        ctx.set("key", Value::from("not a number"));

        let result: Result<i64> = ctx.get_as("key");
        assert!(result.is_err());

        Ok(value!({}))
    });

    let slice = Slice::builder("test")
        .layer("layer", |m| m.call_default("work"))
        .build();

    let engine = Engine::builder()
        .add_layer(layer)
        .add_slice(slice)
        .build()
        .unwrap();

    engine.run(RunFlags::SILENT);
}

#[test]
fn context_clear() {
    let layer = quick_layer!("layer", "work", Value, |_args, ctx| {
        ctx.set("a", Value::from(1));
        ctx.set("b", Value::from(2));
        ctx.set("c", Value::from(3));

        assert_eq!(ctx.len(), 3);
        assert!(!ctx.is_empty());

        ctx.clear();

        assert_eq!(ctx.len(), 0);
        assert!(ctx.is_empty());

        Ok(value!({}))
    });

    let slice = Slice::builder("test")
        .layer("layer", |m| m.call_default("work"))
        .build();

    let engine = Engine::builder()
        .add_layer(layer)
        .add_slice(slice)
        .build()
        .unwrap();

    engine.run(RunFlags::SILENT);
}

#[test]
fn context_keys() {
    let layer = quick_layer!("layer", "work", Value, |_args, ctx| {
        ctx.set("alpha", Value::from(1));
        ctx.set("beta", Value::from(2));
        ctx.set("gamma", Value::from(3));

        let keys = ctx.keys();
        assert_eq!(keys.len(), 3);
        assert!(keys.contains(&"alpha".to_string()));
        assert!(keys.contains(&"beta".to_string()));
        assert!(keys.contains(&"gamma".to_string()));

        Ok(value!({}))
    });

    let slice = Slice::builder("test")
        .layer("layer", |m| m.call_default("work"))
        .build();

    let engine = Engine::builder()
        .add_layer(layer)
        .add_slice(slice)
        .build()
        .unwrap();

    engine.run(RunFlags::SILENT);
}

#[test]
fn context_isolation_between_slices() {
    let counter = Arc::new(AtomicUsize::new(0));

    let c = counter.clone();
    let layer = quick_layer!("layer", "work", Value, move |args, ctx| {
        let id = args.get("id").unwrap().as_i64().unwrap();

        assert!(ctx.get("shared").is_none());

        ctx.set("my_id", Value::from(id));
        ctx.set("shared", Value::from(id));

        let my_id = ctx.get("my_id").unwrap().as_i64().unwrap();
        assert_eq!(my_id, id);

        c.fetch_add(1, Ordering::SeqCst);

        Ok(value!({}))
    });

    let s1 = Slice::builder("s1")
        .layer("layer", |m| m.call("work", value!({ "id": 1 })))
        .build();

    let s2 = Slice::builder("s2")
        .layer("layer", |m| m.call("work", value!({ "id": 2 })))
        .build();

    let s3 = Slice::builder("s3")
        .layer("layer", |m| m.call("work", value!({ "id": 3 })))
        .build();

    let engine = Engine::builder()
        .add_layer(layer)
        .add_slice(s1)
        .add_slice(s2)
        .add_slice(s3)
        .build()
        .unwrap();

    engine.run(RunFlags::SILENT);

    assert_eq!(counter.load(Ordering::SeqCst), 3);
}
#[test]
fn context_shared_across_layers() {
    let layer1 = Layer::builder("layer1")
        .method("set_a")
        .args::<Value>()
        .bind(|_args, ctx| {
            ctx.set("a", Value::from(10));
            Ok(value!({}))
        })
        .method("set_b")
        .args::<Value>()
        .bind(|_args, ctx| {
            ctx.set("b", Value::from(20));
            Ok(value!({}))
        })
        .build();

    let layer2 = quick_layer!("layer2", "verify", Value, |_args, ctx| {
        let a = ctx.get("a").unwrap().as_i64().unwrap();
        let b = ctx.get("b").unwrap().as_i64().unwrap();
        assert_eq!(a, 10);
        assert_eq!(b, 20);
        Ok(value!({}))
    });

    let slice = Slice::builder("test")
        .layer("layer1", |m| m.call_default("set_a").call_default("set_b"))
        .layer("layer2", |m| m.call_default("verify"))
        .build();

    let engine = dependencies!(
        add_layers!(Engine::builder(), layer1, layer2),
        "layer2" => ["layer1"]
    )
    .add_slice(slice)
    .build()
    .unwrap();

    engine.run(RunFlags::SILENT);
}