1use std::collections::{BTreeMap, BTreeSet, HashMap, VecDeque};
2use std::sync::{Arc, Mutex};
3use std::time::{Duration, Instant};
4
5use cortexkit_bus_naming::AccountNames;
6use cortexkit_bus_trait::{
7 AsyncPermissionViolation, BusError, BusResult, DeliveryToken, DurableOwner, Message,
8 RegisterUpdate, RegisterValue, Revision,
9};
10use tokio::sync::mpsc;
11
12#[derive(Debug, Clone)]
13pub struct WorkQueueConfig {
14 pub subject: String,
15 pub max_bytes: usize,
16 pub max_deliveries: u32,
17 pub max_nak_delay: Duration,
18}
19
20impl WorkQueueConfig {
21 pub fn new(subject: impl Into<String>, max_bytes: usize) -> Self {
22 Self {
23 subject: subject.into(),
24 max_bytes,
25 max_deliveries: 5,
26 max_nak_delay: Duration::from_secs(30),
27 }
28 }
29}
30
31pub const DEFAULT_STREAM_ACK_WAIT: Duration = Duration::from_secs(30);
34
35#[derive(Debug, Clone)]
36pub struct InMemoryConfig {
37 pub(crate) durables: BTreeSet<String>,
39 pub(crate) denied_subjects: BTreeSet<String>,
40 pub(crate) work_queue: Option<WorkQueueConfig>,
41 pub(crate) stream_ack_wait: Duration,
42}
43
44impl Default for InMemoryConfig {
45 fn default() -> Self {
46 Self {
47 durables: BTreeSet::new(),
48 denied_subjects: BTreeSet::new(),
49 work_queue: None,
50 stream_ack_wait: DEFAULT_STREAM_ACK_WAIT,
51 }
52 }
53}
54
55impl InMemoryConfig {
56 pub fn with_stream_ack_wait(mut self, ack_wait: Duration) -> Self {
59 self.stream_ack_wait = ack_wait;
60 self
61 }
62
63 pub fn with_durable(mut self, owner: DurableOwner<'_>) -> Self {
70 let name = durable_name(owner).expect("fixture durable owner must be a valid id token");
71 self.durables.insert(name);
72 self
73 }
74
75 pub fn deny_subject(mut self, subject: impl Into<String>) -> Self {
76 self.denied_subjects.insert(subject.into());
77 self
78 }
79
80 pub fn with_work_queue(mut self, config: WorkQueueConfig) -> Self {
81 self.work_queue = Some(config);
82 self
83 }
84}
85
86pub(crate) fn durable_name(owner: DurableOwner<'_>) -> BusResult<String> {
91 let (id, name) = match owner {
92 DurableOwner::Agent(agent_id) => (agent_id, AccountNames::consumer_name(agent_id)),
93 DurableOwner::Module(module_id) => {
94 (module_id, AccountNames::module_consumer_name(module_id))
95 }
96 };
97 name.map_err(|error| BusError::denied(id, error.to_string()))
98}
99
100#[derive(Debug, Clone, PartialEq, Eq)]
101pub enum BackendEvent {
102 Published {
103 subject: String,
104 id: String,
105 },
106 Acknowledged {
107 token: DeliveryToken,
108 },
109 NegativelyAcknowledged {
110 token: DeliveryToken,
111 },
112 Terminated {
113 token: DeliveryToken,
114 },
115 StreamTerminated {
117 stream_seq: u64,
118 reason: Option<String>,
119 },
120}
121
122#[derive(Clone)]
123pub struct InMemoryBus {
124 pub(crate) inner: Arc<Mutex<State>>,
125}
126
127impl InMemoryBus {
128 pub fn new(config: InMemoryConfig) -> Self {
129 let durable_cursors = config
130 .durables
131 .iter()
132 .cloned()
133 .map(|key| (key, DurableState::default()))
134 .collect();
135 Self {
136 inner: Arc::new(Mutex::new(State {
137 config,
138 next_stream_seq: 1,
139 stream_messages: Vec::new(),
140 durable_cursors,
141 register_revision: 0,
142 register_values: BTreeMap::new(),
143 watchers: Vec::new(),
144 queue: VecDeque::new(),
145 next_delivery_token: 1,
146 events: Vec::new(),
147 permission_violations: VecDeque::new(),
148 })),
149 }
150 }
151
152 pub fn events(&self) -> Vec<BackendEvent> {
153 self.inner
154 .lock()
155 .expect("in-memory bus poisoned")
156 .events
157 .clone()
158 }
159
160 pub fn queued_bytes(&self) -> usize {
161 self.inner
162 .lock()
163 .expect("in-memory bus poisoned")
164 .queue
165 .iter()
166 .map(|entry| entry.message.wire_size())
167 .sum()
168 }
169
170 pub fn inject_permission_violation(&self, subject: impl Into<String>) {
171 let subject = subject.into();
172 self.inner
173 .lock()
174 .expect("in-memory bus poisoned")
175 .permission_violations
176 .push_back(AsyncPermissionViolation {
177 subject,
178 reason: "fixture-configured permission violation".into(),
179 });
180 }
181}
182
183pub(crate) struct State {
184 pub config: InMemoryConfig,
185 pub next_stream_seq: u64,
186 pub stream_messages: Vec<StoredMessage>,
187 pub durable_cursors: HashMap<String, DurableState>,
188 pub register_revision: u64,
189 pub register_values: BTreeMap<String, RegisterValue>,
190 pub watchers: Vec<Watcher>,
191 pub queue: VecDeque<QueueEntry>,
192 pub next_delivery_token: u64,
193 pub events: Vec<BackendEvent>,
194 pub permission_violations: VecDeque<AsyncPermissionViolation>,
195}
196
197#[derive(Clone)]
198pub(crate) struct StoredMessage {
199 pub stream_seq: u64,
200 pub message: Message,
201}
202
203#[derive(Default)]
207pub(crate) struct DurableState {
208 pub next_index: usize,
209 pub available_at: Option<Instant>,
211 pub delivery_count: u64,
213 pub lease: Option<Lease>,
215 pub next_cursor_id: u64,
216}
217
218pub(crate) struct Lease {
222 pub cursor_id: u64,
223 pub deadline: Instant,
224}
225
226pub(crate) struct Watcher {
227 pub prefix: String,
228 pub sender: mpsc::UnboundedSender<RegisterUpdate>,
229}
230
231pub(crate) struct QueueEntry {
232 pub token: DeliveryToken,
233 pub message: Message,
234 pub delivery_count: u32,
235 pub available_at: Instant,
236 pub in_flight: bool,
237}
238
239impl State {
240 pub fn next_revision(&mut self) -> Revision {
241 self.register_revision += 1;
242 Revision(self.register_revision)
243 }
244
245 pub fn notify_watchers(&mut self, update: RegisterUpdate) {
246 let key = match &update {
247 RegisterUpdate::Put(entry) => &entry.key,
248 RegisterUpdate::Delete(tombstone) => &tombstone.key,
249 };
250 self.watchers.retain(|watcher| {
251 !key.starts_with(&watcher.prefix) || watcher.sender.send(update.clone()).is_ok()
252 });
253 }
254}