Skip to main content

simple_zanzibar/
store.rs

1//! Defines the storage abstraction for relation tuples.
2
3use std::collections::HashSet;
4
5use crate::model::{Object, Relation, RelationTuple, User};
6
7/// A trait for abstracting the storage and retrieval of `RelationTuple`s.
8/// This allows the core logic to be decoupled from the specific storage backend.
9pub trait TupleStore: Send + Sync {
10    /// Reads tuples from the store, with optional filtering.
11    ///
12    /// # Arguments
13    ///
14    /// * `object` - The object to filter by.
15    /// * `relation` - An optional relation to filter by.
16    /// * `user` - An optional user to filter by.
17    ///
18    /// # Returns
19    ///
20    /// A vector of matching `RelationTuple`s.
21    fn read_tuples(
22        &self,
23        object: &Object,
24        relation: Option<&Relation>,
25        user: Option<&User>,
26    ) -> Vec<RelationTuple>;
27
28    /// Writes a single tuple to the store.
29    ///
30    /// # Returns
31    ///
32    /// `Ok(())` if the write was successful, or an error string if it failed.
33    ///
34    /// # Errors
35    ///
36    /// Returns an error string when the tuple cannot be written, such as attempting to create a
37    /// duplicate tuple in stores that enforce uniqueness.
38    fn write_tuple(&mut self, tuple: RelationTuple) -> Result<(), String>;
39
40    /// Deletes a single tuple from the store.
41    ///
42    /// # Returns
43    ///
44    /// `Ok(())` if the delete was successful, or an error string if it failed.
45    ///
46    /// # Errors
47    ///
48    /// Returns an error string when the tuple cannot be deleted, such as when it does not exist.
49    fn delete_tuple(&mut self, tuple: &RelationTuple) -> Result<(), String>;
50
51    /// Returns all tuples currently stored.
52    ///
53    /// This compatibility method lets the v2 indexed store rebuild from legacy state during the
54    /// migration period.
55    fn all_tuples(&self) -> Vec<RelationTuple>;
56
57    /// Replaces all tuples currently stored.
58    ///
59    /// This compatibility method lets the service apply validated batch mutations atomically
60    /// across the legacy tuple store and the indexed relationship store during the migration
61    /// period.
62    fn replace_all(&mut self, tuples: Vec<RelationTuple>);
63}
64
65/// A simple, in-memory implementation of the `TupleStore` trait using a `HashSet`.
66#[derive(Debug, Default)]
67pub struct InMemoryTupleStore {
68    store: HashSet<RelationTuple>,
69}
70
71impl TupleStore for InMemoryTupleStore {
72    fn read_tuples(
73        &self,
74        object: &Object,
75        relation: Option<&Relation>,
76        user: Option<&User>,
77    ) -> Vec<RelationTuple> {
78        self.store
79            .iter()
80            .filter(|t| {
81                t.object == *object
82                    && relation.is_none_or(|r| t.relation == *r)
83                    && user.is_none_or(|u| t.user == *u)
84            })
85            .cloned()
86            .collect()
87    }
88
89    fn write_tuple(&mut self, tuple: RelationTuple) -> Result<(), String> {
90        if self.store.insert(tuple) {
91            Ok(())
92        } else {
93            Err("Tuple already exists".to_string())
94        }
95    }
96
97    fn delete_tuple(&mut self, tuple: &RelationTuple) -> Result<(), String> {
98        if self.store.remove(tuple) {
99            Ok(())
100        } else {
101            Err("Tuple not found".to_string())
102        }
103    }
104
105    fn all_tuples(&self) -> Vec<RelationTuple> {
106        self.store.iter().cloned().collect()
107    }
108
109    fn replace_all(&mut self, tuples: Vec<RelationTuple>) {
110        self.store = tuples.into_iter().collect();
111    }
112}