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}