1use std::collections::HashMap;
2
3use crate::{Record, Relation};
4
5pub type ScopeFunction<T> = Box<dyn Fn(Relation<T>) -> Relation<T> + Send + Sync>;
7
8pub struct ScopeRegistry<T: Record> {
10 scopes: HashMap<String, ScopeFunction<T>>,
11}
12
13impl<T: Record> Default for ScopeRegistry<T> {
14 fn default() -> Self {
15 Self::new()
16 }
17}
18
19impl<T: Record> ScopeRegistry<T> {
20 #[must_use]
22 pub fn new() -> Self {
23 Self {
24 scopes: HashMap::new(),
25 }
26 }
27
28 pub fn add(
30 &mut self,
31 name: impl Into<String>,
32 scope: impl Fn(Relation<T>) -> Relation<T> + Send + Sync + 'static,
33 ) {
34 self.scopes.insert(name.into(), Box::new(scope));
35 }
36
37 #[must_use]
39 pub fn apply(&self, name: &str, relation: Relation<T>) -> Option<Relation<T>> {
40 self.scopes.get(name).map(|scope| scope(relation))
41 }
42
43 #[must_use]
45 pub fn names(&self) -> Vec<&String> {
46 self.scopes.keys().collect()
47 }
48}
49
50#[cfg(test)]
51mod tests {
52 use std::collections::HashMap;
53
54 use serde_json::json;
55
56 use super::ScopeRegistry;
57 use crate::{
58 OrderDirection, Relation,
59 base::test_support::{TestUser, seed_users, setup_db},
60 };
61
62 fn named_scope(
63 expected: &'static str,
64 ) -> impl Fn(Relation<TestUser>) -> Relation<TestUser> + Send + Sync + 'static {
65 move |relation| relation.r#where(HashMap::from([("name".to_owned(), json!(expected))]))
66 }
67
68 #[tokio::test]
69 async fn apply_returns_none_for_unknown_scope() {
70 let registry = ScopeRegistry::<TestUser>::new();
71
72 assert!(registry.apply("missing", Relation::new()).is_none());
73 }
74
75 #[tokio::test]
76 async fn apply_runs_registered_scope() {
77 let db = setup_db().await;
78 seed_users(&db).await;
79
80 let mut registry = ScopeRegistry::<TestUser>::new();
81 registry.add("named_bob", |relation| {
82 relation.r#where(HashMap::from([("name".to_owned(), json!("Bob"))]))
83 });
84
85 let relation = registry
86 .apply("named_bob", Relation::new())
87 .expect("scope should exist");
88 let users = relation
89 .load(&db)
90 .await
91 .expect("scoped query should succeed");
92
93 assert_eq!(users.len(), 1);
94 assert_eq!(users[0].name, "Bob");
95 }
96
97 #[tokio::test]
98 async fn scope_can_modify_relation_with_limit() {
99 let db = setup_db().await;
100 seed_users(&db).await;
101
102 let mut registry = ScopeRegistry::<TestUser>::new();
103 registry.add("first_two", |relation| relation.limit(2));
104
105 let relation = registry
106 .apply("first_two", Relation::new())
107 .expect("scope should exist");
108 let users = relation
109 .load(&db)
110 .await
111 .expect("limited query should succeed");
112
113 assert_eq!(users.len(), 2);
114 }
115
116 #[tokio::test]
117 async fn add_replaces_existing_scope_name() {
118 let db = setup_db().await;
119 seed_users(&db).await;
120
121 let mut registry = ScopeRegistry::<TestUser>::new();
122 registry.add("window", |relation| relation.limit(1));
123 registry.add("window", |relation| relation.limit(2));
124
125 let relation = registry
126 .apply("window", Relation::new())
127 .expect("scope should exist");
128 let users = relation.load(&db).await.expect("query should succeed");
129
130 assert_eq!(users.len(), 2);
131 }
132
133 #[test]
134 fn names_returns_registered_scope_names() {
135 let mut registry = ScopeRegistry::<TestUser>::new();
136 registry.add("recent", |relation| relation.limit(1));
137 registry.add("alphabetical", |relation| relation);
138
139 let mut names = registry
140 .names()
141 .into_iter()
142 .map(|name| name.as_str())
143 .collect::<Vec<_>>();
144 names.sort_unstable();
145
146 assert_eq!(names, vec!["alphabetical", "recent"]);
147 }
148
149 #[test]
150 fn default_registry_starts_without_names() {
151 let registry = ScopeRegistry::<TestUser>::default();
152
153 assert!(registry.names().is_empty());
154 }
155
156 #[tokio::test]
157 async fn scope_name_with_spaces_is_applied() {
158 let db = setup_db().await;
159 seed_users(&db).await;
160
161 let mut registry = ScopeRegistry::<TestUser>::new();
162 registry.add("named bob", named_scope("Bob"));
163
164 let users = registry
165 .apply("named bob", Relation::new())
166 .expect("scope should exist")
167 .load(&db)
168 .await
169 .expect("scoped query should succeed");
170
171 assert_eq!(users.len(), 1);
172 assert_eq!(users[0].name, "Bob");
173 }
174
175 #[tokio::test]
176 async fn scope_can_capture_external_value() {
177 let db = setup_db().await;
178 seed_users(&db).await;
179
180 let expected = "Carol".to_owned();
181 let mut registry = ScopeRegistry::<TestUser>::new();
182 registry.add("captured_name", move |relation| {
183 relation.r#where(HashMap::from([(
184 "name".to_owned(),
185 json!(expected.clone()),
186 )]))
187 });
188
189 let users = registry
190 .apply("captured_name", Relation::new())
191 .expect("scope should exist")
192 .load(&db)
193 .await
194 .expect("captured query should succeed");
195
196 assert_eq!(users.len(), 1);
197 assert_eq!(users[0].email, "carol@example.com");
198 }
199
200 #[tokio::test]
201 async fn scope_factory_with_argument_filters_expected_row() {
202 let db = setup_db().await;
203 seed_users(&db).await;
204
205 let mut registry = ScopeRegistry::<TestUser>::new();
206 registry.add("named_alice", named_scope("Alice"));
207
208 let user = registry
209 .apply("named_alice", Relation::new())
210 .expect("scope should exist")
211 .first(&db)
212 .await
213 .expect("query should succeed")
214 .expect("alice should exist");
215
216 assert_eq!(user.email, "alice@example.com");
217 }
218
219 #[tokio::test]
220 async fn scope_chaining_applies_multiple_registered_scopes() {
221 let db = setup_db().await;
222 seed_users(&db).await;
223
224 let mut registry = ScopeRegistry::<TestUser>::new();
225 registry.add("without_alice", |relation| {
226 relation.not(HashMap::from([("name".to_owned(), json!("Alice"))]))
227 });
228 registry.add("descending", |relation| {
229 relation.order("name", OrderDirection::Desc)
230 });
231
232 let relation = registry
233 .apply("without_alice", Relation::new())
234 .expect("first scope should exist");
235 let relation = registry
236 .apply("descending", relation)
237 .expect("second scope should exist");
238 let users = relation
239 .load(&db)
240 .await
241 .expect("chained scope should succeed");
242
243 let names = users.into_iter().map(|user| user.name).collect::<Vec<_>>();
244 assert_eq!(names, vec!["Carol", "Bob"]);
245 }
246
247 #[tokio::test]
248 async fn scope_can_extend_existing_relation_filters() {
249 let db = setup_db().await;
250 seed_users(&db).await;
251
252 let mut registry = ScopeRegistry::<TestUser>::new();
253 registry.add("first_match", |relation| relation.limit(1));
254
255 let base = Relation::new().r#where(HashMap::from([("name".to_owned(), json!("Carol"))]));
256 let users = registry
257 .apply("first_match", base)
258 .expect("scope should exist")
259 .load(&db)
260 .await
261 .expect("merged query should succeed");
262
263 assert_eq!(users.len(), 1);
264 assert_eq!(users[0].name, "Carol");
265 }
266
267 #[tokio::test]
268 async fn scope_can_order_results_descending() {
269 let db = setup_db().await;
270 seed_users(&db).await;
271
272 let mut registry = ScopeRegistry::<TestUser>::new();
273 registry.add("desc_by_name", |relation| {
274 relation.order("name", OrderDirection::Desc)
275 });
276
277 let first = registry
278 .apply("desc_by_name", Relation::new())
279 .expect("scope should exist")
280 .first(&db)
281 .await
282 .expect("ordered query should succeed")
283 .expect("a row should exist");
284
285 assert_eq!(first.name, "Carol");
286 }
287
288 #[tokio::test]
289 async fn scope_can_skip_rows_with_offset() {
290 let db = setup_db().await;
291 seed_users(&db).await;
292
293 let mut registry = ScopeRegistry::<TestUser>::new();
294 registry.add("skip_first", |relation| {
295 relation.order("id", OrderDirection::Asc).offset(1)
296 });
297
298 let users = registry
299 .apply("skip_first", Relation::new())
300 .expect("scope should exist")
301 .load(&db)
302 .await
303 .expect("offset query should succeed");
304
305 let names = users.into_iter().map(|user| user.name).collect::<Vec<_>>();
306 assert_eq!(names, vec!["Bob", "Carol"]);
307 }
308
309 #[tokio::test]
310 async fn scope_can_add_negated_filters() {
311 let db = setup_db().await;
312 seed_users(&db).await;
313
314 let mut registry = ScopeRegistry::<TestUser>::new();
315 registry.add("without_bob", |relation| {
316 relation.not(HashMap::from([("name".to_owned(), json!("Bob"))]))
317 });
318
319 let users = registry
320 .apply("without_bob", Relation::new())
321 .expect("scope should exist")
322 .load(&db)
323 .await
324 .expect("negated query should succeed");
325
326 let names = users.into_iter().map(|user| user.name).collect::<Vec<_>>();
327 assert_eq!(names, vec!["Alice", "Carol"]);
328 }
329
330 #[tokio::test]
331 async fn scope_merge_like_composition_preserves_all_constraints() {
332 let db = setup_db().await;
333 seed_users(&db).await;
334
335 let mut registry = ScopeRegistry::<TestUser>::new();
336 registry.add("only_carol", named_scope("Carol"));
337 registry.add("take_one", |relation| relation.limit(1));
338
339 let relation = registry
340 .apply(
341 "only_carol",
342 Relation::new().order("id", OrderDirection::Desc),
343 )
344 .expect("first scope should exist");
345 let exists = registry
346 .apply("take_one", relation)
347 .expect("second scope should exist")
348 .exists(&db)
349 .await
350 .expect("exists query should succeed");
351
352 assert!(exists);
353 }
354
355 #[test]
356 fn replacing_scope_keeps_single_registered_name() {
357 let mut registry = ScopeRegistry::<TestUser>::new();
358 registry.add("window", |relation| relation.limit(1));
359 registry.add("window", |relation| relation.limit(2));
360
361 let mut names = registry
362 .names()
363 .into_iter()
364 .map(|name| name.as_str())
365 .collect::<Vec<_>>();
366 names.sort_unstable();
367
368 assert_eq!(names, vec!["window"]);
369 }
370
371 #[tokio::test]
372 async fn scope_application_is_repeatable_without_state_leakage() {
373 let db = setup_db().await;
374 seed_users(&db).await;
375
376 let mut registry = ScopeRegistry::<TestUser>::new();
377 registry.add("without_alice", |relation| {
378 relation.not(HashMap::from([("name".to_owned(), json!("Alice"))]))
379 });
380
381 let first = registry
382 .apply("without_alice", Relation::new())
383 .expect("scope should exist")
384 .load(&db)
385 .await
386 .expect("first scoped query should succeed");
387 let second = registry
388 .apply("without_alice", Relation::new())
389 .expect("scope should exist")
390 .load(&db)
391 .await
392 .expect("second scoped query should succeed");
393
394 let first_names = first.into_iter().map(|user| user.name).collect::<Vec<_>>();
395 let second_names = second.into_iter().map(|user| user.name).collect::<Vec<_>>();
396
397 assert_eq!(first_names, vec!["Bob", "Carol"]);
398 assert_eq!(second_names, vec!["Bob", "Carol"]);
399 }
400
401 #[tokio::test]
402 async fn scope_preserves_existing_order_when_it_only_adds_filters() {
403 let db = setup_db().await;
404 seed_users(&db).await;
405
406 let mut registry = ScopeRegistry::<TestUser>::new();
407 registry.add("without_carol", |relation| {
408 relation.not(HashMap::from([("name".to_owned(), json!("Carol"))]))
409 });
410
411 let users = registry
412 .apply(
413 "without_carol",
414 Relation::new().order("name", OrderDirection::Desc),
415 )
416 .expect("scope should exist")
417 .load(&db)
418 .await
419 .expect("ordered scoped query should succeed");
420
421 let names = users.into_iter().map(|user| user.name).collect::<Vec<_>>();
422 assert_eq!(names, vec!["Bob", "Alice"]);
423 }
424}