use std::cell::Cell;
use std::sync::Arc;
use arc_swap::ArcSwap;
use crate::domain::registry::{PrimitiveId, PrimitiveKind, Registered, Snapshot, SnapshotBuilder};
#[derive(Debug)]
pub struct Published {
snapshot: Arc<Snapshot>,
changed: Vec<PrimitiveKind>,
}
impl Published {
#[must_use]
pub const fn snapshot(&self) -> &Arc<Snapshot> {
&self.snapshot
}
#[must_use]
pub fn generation(&self) -> u64 {
self.snapshot.generation()
}
#[must_use]
pub fn changed(&self) -> &[PrimitiveKind] {
&self.changed
}
#[must_use]
pub const fn is_noop(&self) -> bool {
self.changed.is_empty()
}
}
#[derive(Debug)]
pub struct RegistryStore {
live: ArcSwap<Snapshot>,
}
impl RegistryStore {
#[must_use]
pub fn new() -> Self {
Self {
live: ArcSwap::from_pointee(Snapshot::empty()),
}
}
#[must_use]
pub fn with(snapshot: Snapshot) -> Self {
Self {
live: ArcSwap::from_pointee(snapshot),
}
}
#[must_use]
pub fn snapshot(&self) -> Arc<Snapshot> {
self.live.load_full()
}
#[must_use]
pub fn generation(&self) -> u64 {
self.live.load().generation()
}
pub fn register(&self, primitive: impl Into<Registered>) -> Published {
let primitive = Arc::new(primitive.into());
let kind = primitive.kind();
let snapshot = self.publish(move |mut next| {
next.insert_arc(Arc::clone(&primitive));
next
});
Published {
snapshot,
changed: vec![kind],
}
}
pub fn unregister(&self, kind: PrimitiveKind, id: &PrimitiveId) -> Published {
let removed = Cell::new(false);
let id = id.clone();
let snapshot = self.publish({
let removed = &removed;
move |mut next| {
removed.set(next.remove(kind, &id).is_some());
next
}
});
let changed = if removed.get() {
vec![kind]
} else {
Vec::new()
};
Published { snapshot, changed }
}
pub fn apply(&self, primitives: impl IntoIterator<Item = Registered>) -> Published {
let batch: Vec<Arc<Registered>> = primitives.into_iter().map(Arc::new).collect();
let mut changed: Vec<PrimitiveKind> = Vec::new();
for primitive in &batch {
let kind = primitive.kind();
if !changed.contains(&kind) {
changed.push(kind);
}
}
changed.sort_by_key(|kind| kind.as_str());
let snapshot = self.publish(move |mut next| {
for primitive in &batch {
next.insert_arc(Arc::clone(primitive));
}
next
});
Published { snapshot, changed }
}
fn publish(&self, apply: impl Fn(SnapshotBuilder) -> SnapshotBuilder) -> Arc<Snapshot> {
let mut current = self.live.load();
loop {
let next = Arc::new(
apply(current.to_builder()).build_at(current.generation().saturating_add(1)),
);
let previous = self.live.compare_and_swap(¤t, Arc::clone(&next));
if Arc::ptr_eq(¤t, &previous) {
return next;
}
current = previous;
}
}
}
impl Default for RegistryStore {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::RegistryStore;
use crate::domain::registry::TestError;
use crate::domain::registry::{Primitive, PrimitiveId, PrimitiveKind, Registered, Tool};
fn tool(name: &str) -> Result<Registered, TestError> {
Ok(Registered::from(Primitive::from(Tool::new(
name,
r#"{"type":"object"}"#,
)?)))
}
#[test]
fn an_empty_store_reports_itself_empty() {
let store = RegistryStore::new();
assert!(store.snapshot().is_blank());
assert_eq!(store.generation(), 0);
assert!(store.snapshot().is_empty(PrimitiveKind::Tool));
}
#[test]
fn a_replacement_is_one_publish() -> Result<(), TestError> {
let store = RegistryStore::new();
store.register(tool("a")?);
let replaced = store.register(Registered::from(Primitive::from(
Tool::new("a", r#"{"type":"object"}"#)?.with_output_schema(r#"{"type":"object"}"#)?,
)));
assert_eq!(replaced.changed(), [PrimitiveKind::Tool]);
assert_eq!(store.snapshot().len(PrimitiveKind::Tool), 1);
assert_eq!(store.generation(), 2);
Ok(())
}
#[test]
fn removing_an_absent_primitive_is_a_reported_no_op() -> Result<(), TestError> {
let store = RegistryStore::new();
store.register(tool("a")?);
let before = store.generation();
let published = store.unregister(PrimitiveKind::Tool, &PrimitiveId::from("absent"));
assert!(published.is_noop());
assert!(published.changed().is_empty());
assert_eq!(
published.generation(),
before + 1,
"a no-op still publishes, so a waiter is not left hanging"
);
assert_eq!(store.snapshot().len(PrimitiveKind::Tool), 1);
Ok(())
}
#[test]
fn removing_a_present_primitive_reports_its_kind() -> Result<(), TestError> {
let store = RegistryStore::new();
store.register(tool("a")?);
let published = store.unregister(PrimitiveKind::Tool, &PrimitiveId::from("a"));
assert_eq!(published.changed(), [PrimitiveKind::Tool]);
assert!(store.snapshot().is_empty(PrimitiveKind::Tool));
Ok(())
}
#[test]
fn an_empty_batch_changes_nothing() {
let store = RegistryStore::new();
let published = store.apply([]);
assert!(published.is_noop());
}
#[test]
fn a_publish_returns_the_snapshot_that_is_live() -> Result<(), TestError> {
let store = RegistryStore::new();
let first = store.register(tool("a")?);
assert_eq!(
first.snapshot().generation(),
store.generation(),
"the returned snapshot must be the live one"
);
assert_eq!(first.snapshot().len(PrimitiveKind::Tool), 1);
Ok(())
}
}