attune-core 0.1.0

Core traits and types for attune: runtime-mutable, persisted, observable configuration.
Documentation
use crossbeam_channel::{Receiver, unbounded};
use serde::{Serialize, de::DeserializeOwned};
use std::thread;

use crate::{ChangeEvent, SettingsError, SettingsHandle, StoredValue};

type FieldGetter<T, V> = Box<dyn for<'a> Fn(&'a T) -> &'a V + Send + Sync + 'static>;
type FieldMutator<T, V> = Box<dyn Fn(&mut T, V) + Send + Sync + 'static>;

/// Read-only access to a generated settings field.
pub struct ReadOnlyField<T, V> {
    handle: SettingsHandle<T>,
    getter: FieldGetter<T, V>,
}

impl<T, V> ReadOnlyField<T, V>
where
    T: Clone + Send + Sync + 'static,
    V: Clone,
{
    /// Creates a read-only field handle.
    pub fn new(handle: SettingsHandle<T>, getter: FieldGetter<T, V>) -> Self {
        Self { handle, getter }
    }

    /// Returns the field value from the current settings snapshot.
    pub fn get(&self) -> V {
        let snapshot = self.handle.snapshot();
        (self.getter)(snapshot.as_ref()).clone()
    }
}

/// Read, write, and subscribe access to a generated persisted settings field.
pub struct PersistedField<T, V> {
    handle: SettingsHandle<T>,
    key: &'static str,
    getter: FieldGetter<T, V>,
    mutator: FieldMutator<T, V>,
}

impl<T, V> PersistedField<T, V>
where
    T: Clone + Send + Sync + 'static,
    V: Clone,
{
    /// Creates a persisted field handle.
    pub fn new(
        handle: SettingsHandle<T>,
        key: &'static str,
        getter: FieldGetter<T, V>,
        mutator: FieldMutator<T, V>,
    ) -> Self {
        Self {
            handle,
            key,
            getter,
            mutator,
        }
    }

    /// Returns the field value from the current settings snapshot.
    pub fn get(&self) -> V {
        let snapshot = self.handle.snapshot();
        (self.getter)(snapshot.as_ref()).clone()
    }
}

impl<T, V> PersistedField<T, V>
where
    T: Clone + Send + Sync + 'static,
    V: Clone + Serialize,
{
    /// Persists a new field value and updates the in-memory snapshot.
    pub fn set(&self, value: V) -> Result<(), SettingsError> {
        let old_value = Some(StoredValue::encode(&self.get())?);
        let stored = StoredValue::encode(&value)?;
        let mutator = &self.mutator;

        self.handle
            .write_field(self.key, old_value, stored, |next| {
                mutator(next, value);
            })
    }
}

impl<T, V> PersistedField<T, V>
where
    T: Clone + Send + Sync + 'static,
    V: DeserializeOwned + Send + 'static,
{
    /// Subscribes to typed changes for this field.
    ///
    /// The returned channel receives successfully decoded local and external
    /// `Set` events for this field. Deletes, deserialize failures, other keys,
    /// and values that cannot be decoded as `V` are ignored.
    pub fn on_change(&self) -> Receiver<V> {
        let source = self.handle.on_change();
        let (tx, rx) = unbounded();
        let key = self.key.to_string();

        thread::spawn(move || {
            while let Ok(event) = source.recv() {
                let ChangeEvent::Set {
                    key: event_key,
                    new_value,
                    ..
                } = event
                else {
                    continue;
                };

                if event_key != key {
                    continue;
                }

                let Ok(decoded) = new_value.decode::<V>() else {
                    continue;
                };

                if tx.send(decoded).is_err() {
                    return;
                }
            }
        });

        rx
    }
}