autumn_admin_plugin/
registry.rs1use std::collections::btree_map::{BTreeMap, Entry};
8
9use crate::traits::AdminModel;
10
11pub struct AdminRegistry {
16 models: BTreeMap<&'static str, Box<dyn AdminModel>>,
18}
19
20impl AdminRegistry {
21 pub(crate) fn new() -> Self {
23 Self {
24 models: BTreeMap::new(),
25 }
26 }
27
28 pub(crate) fn register<M: AdminModel>(&mut self, model: M) {
31 let slug = model.slug();
32 let name = model.display_name();
33 match self.models.entry(slug) {
34 Entry::Occupied(_) => panic!(
35 "autumn-admin: duplicate model slug '{slug}' — each model must have a unique slug",
36 ),
37 Entry::Vacant(e) => {
38 tracing::debug!(slug, name, "Registered admin model");
39 e.insert(Box::new(model));
40 }
41 }
42 }
43
44 #[must_use]
46 pub fn model_count(&self) -> usize {
47 self.models.len()
48 }
49
50 #[must_use]
52 pub fn get(&self, slug: &str) -> Option<&dyn AdminModel> {
53 self.models.get(slug).map(Box::as_ref)
54 }
55
56 pub fn iter(&self) -> impl Iterator<Item = (&'static str, &dyn AdminModel)> {
58 self.models
59 .iter()
60 .map(|(&slug, model)| (slug, model.as_ref()))
61 }
62
63 #[must_use]
65 pub fn slugs(&self) -> Vec<&'static str> {
66 self.models.keys().copied().collect()
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73 use crate::traits::*;
74 use serde_json::Value;
75
76 struct DummyModel {
77 slug: &'static str,
78 name: &'static str,
79 }
80
81 impl AdminModel for DummyModel {
82 fn slug(&self) -> &'static str {
83 self.slug
84 }
85 fn display_name(&self) -> &'static str {
86 self.name
87 }
88 fn display_name_plural(&self) -> &'static str {
89 self.name
90 }
91 fn fields(&self) -> Vec<AdminField> {
92 vec![]
93 }
94 fn list(
95 &self,
96 _pool: &diesel_async::pooled_connection::deadpool::Pool<
97 diesel_async::AsyncPgConnection,
98 >,
99 _params: ListParams,
100 ) -> AdminFuture<'_, ListResult> {
101 Box::pin(async {
102 Ok(ListResult {
103 records: vec![],
104 total: 0,
105 page: 1,
106 per_page: 25,
107 })
108 })
109 }
110 fn get(
111 &self,
112 _pool: &diesel_async::pooled_connection::deadpool::Pool<
113 diesel_async::AsyncPgConnection,
114 >,
115 _id: i64,
116 ) -> AdminFuture<'_, Option<Value>> {
117 Box::pin(async { Ok(None) })
118 }
119 fn create(
120 &self,
121 _pool: &diesel_async::pooled_connection::deadpool::Pool<
122 diesel_async::AsyncPgConnection,
123 >,
124 data: Value,
125 ) -> AdminFuture<'_, Value> {
126 Box::pin(async move { Ok(data) })
127 }
128 fn update(
129 &self,
130 _pool: &diesel_async::pooled_connection::deadpool::Pool<
131 diesel_async::AsyncPgConnection,
132 >,
133 _id: i64,
134 data: Value,
135 ) -> AdminFuture<'_, Value> {
136 Box::pin(async move { Ok(data) })
137 }
138 fn delete(
139 &self,
140 _pool: &diesel_async::pooled_connection::deadpool::Pool<
141 diesel_async::AsyncPgConnection,
142 >,
143 _id: i64,
144 ) -> AdminFuture<'_, ()> {
145 Box::pin(async { Ok(()) })
146 }
147 }
148
149 #[test]
150 fn register_and_retrieve() {
151 let mut registry = AdminRegistry::new();
152 registry.register(DummyModel {
153 slug: "projects",
154 name: "Project",
155 });
156 assert_eq!(registry.model_count(), 1);
157 assert!(registry.get("projects").is_some());
158 assert!(registry.get("nonexistent").is_none());
159 }
160
161 #[test]
162 fn iter_is_sorted() {
163 let mut registry = AdminRegistry::new();
164 registry.register(DummyModel {
165 slug: "tickets",
166 name: "Ticket",
167 });
168 registry.register(DummyModel {
169 slug: "projects",
170 name: "Project",
171 });
172 let slugs: Vec<_> = registry.iter().map(|(s, _)| s).collect();
173 assert_eq!(slugs, vec!["projects", "tickets"]);
174 }
175
176 #[test]
177 #[should_panic(expected = "duplicate model slug")]
178 fn duplicate_slug_panics() {
179 let mut registry = AdminRegistry::new();
180 registry.register(DummyModel {
181 slug: "projects",
182 name: "Project",
183 });
184 registry.register(DummyModel {
185 slug: "projects",
186 name: "Project 2",
187 });
188 }
189}