amethystate 0.23.2

A state manager for Rust applications
Documentation
use amethystate::store::builder::{Backend, StoreBuilder};
use amethystate::{ReactiveScope, amethystate};
use amethystate_core::test_utils::TempPath;
use amethystate_test_macros::backends;
use std::sync::{Arc, Mutex};

#[amethystate(prefix = "net")]
pub struct ConnectionState {
    #[amestate(default = 8080u16)]
    pub port: u16,

    #[amestate(default = "127.0.0.1".to_string())]
    pub host: String,
}

fn open(backend: Backend, tag: &str) -> anyhow::Result<(TempPath, ConnectionState)> {
    let path = TempPath::new(tag);
    let store = StoreBuilder::new(path.path()).backend(backend).build()?;
    let held = ConnectionState::new_with(&store)?;
    Ok((path, held))
}

#[backends(all)]
fn a_subscription_lasts_as_long_as_its_handle(backend: Backend) -> anyhow::Result<()> {
    let (_path, state) = open(backend, "book_subs_raii")?;
    let heard = Arc::new(Mutex::new(Vec::new()));
    let seen = Arc::clone(&heard);

    //@show subscribing, and letting the subscription go
    let sub = state.port().subscribe(move |port| {
        seen.lock().unwrap().push(*port);
    });

    state.port().set(9090)?;
    assert_eq!(*heard.lock().unwrap(), [9090]);

    drop(sub);

    state.port().set(1234)?;
    assert_eq!(*heard.lock().unwrap(), [9090]);
    //@show-end

    //@show a subscription nobody held on to
    let ignored = Arc::clone(&heard);
    let _ = state.port().subscribe(move |port| {
        ignored.lock().unwrap().push(*port);
    });

    state.port().set(4321)?;
    assert_eq!(*heard.lock().unwrap(), [9090]);
    //@show-end

    Ok(())
}

#[backends(all)]
fn a_stream_yields_every_change_to_a_loop(backend: Backend) -> anyhow::Result<()> {
    use futures::StreamExt;

    let (_path, state) = open(backend, "book_subs_stream")?;

    //@show taking the changes into a loop of your own
    let mut ports = state.port().subscription_with().stream();

    state.port().set(9090)?;
    state.port().set(1234)?;

    let mut heard = Vec::new();
    futures::executor::block_on(async {
        while let Some(port) = ports.next().await {
            heard.push(port);
            if port == 1234 {
                break;
            }
        }
    });

    assert_eq!(heard, [9090, 1234]);
    //@show-end

    Ok(())
}

#[backends(all)]
fn a_scope_holds_several_at_once(backend: Backend) -> anyhow::Result<()> {
    let (_path, state) = open(backend, "book_subs_scope")?;

    //@show keeping several subscriptions in one place
    let mut scope = ReactiveScope::new();

    state
        .port()
        .subscribe(|port| println!("port {port}"))
        .watch(&mut scope);
    state
        .host()
        .subscribe(|host| println!("host {host}"))
        .watch(&mut scope);

    scope.clear();
    //@show-end

    Ok(())
}

#[backends(all)]
fn ignoring_your_own_writes(backend: Backend) -> anyhow::Result<()> {
    let (_path, state) = open(backend, "book_subs_external")?;
    let heard = Arc::new(Mutex::new(Vec::new()));
    let seen = Arc::clone(&heard);

    //@show hearing only about somebody else's writes
    let watcher = state.port().fork();

    let _sub = state
        .port()
        .subscription_with()
        .external()
        .register(move |port| {
            seen.lock().unwrap().push(*port);
        });

    state.port().set(8080)?;
    watcher.set(9090)?;

    assert_eq!(*heard.lock().unwrap(), [9090]);
    //@show-end

    Ok(())
}

#[backends(all)]
fn a_clone_is_the_same_actor_and_a_fork_is_not(backend: Backend) -> anyhow::Result<()> {
    let (_path, state) = open(backend, "book_subs_identity")?;

    let heard = Arc::new(Mutex::new(Vec::new()));
    let seen = Arc::clone(&heard);

    //@show the same actor, and a different one
    let port = state.port();
    let same = port.clone();
    let other = port.fork();

    let _sub = port
        .subscription_with()
        .external()
        .register(move |value| seen.lock().unwrap().push(*value));

    same.set(1111)?;
    other.set(2222)?;

    assert_eq!(*heard.lock().unwrap(), [2222]);

    assert_eq!(port.instance_id(), same.instance_id());
    assert_ne!(port.instance_id(), other.instance_id());
    //@show-end

    Ok(())
}

#[backends(all)]
fn who_made_the_change(backend: Backend) -> anyhow::Result<()> {
    let (_path, state) = open(backend, "book_subs_source")?;
    let heard = Arc::new(Mutex::new(Vec::new()));
    let seen = Arc::clone(&heard);

    //@show asking who made the change
    let _sub = state
        .port()
        .subscription_with()
        .register_with_source(move |port, who| {
            seen.lock().unwrap().push((*port, who));
        });

    state.port().set(9090)?;

    let (port, who) = heard.lock().unwrap()[0];
    assert_eq!(port, 9090);
    assert_eq!(who, Some(state.port().instance_id()));
    //@show-end

    Ok(())
}