1use topcoat::context::Cx;
5
6use super::{Ability, Policy};
7
8type RequestCheck = Box<dyn Fn(&Cx) -> bool + Send + Sync>;
10
11type RecordCheck<M> = Box<dyn Fn(&Cx, &M) -> bool + Send + Sync>;
13
14struct Pair<M> {
17 any: Option<RequestCheck>,
18 record: Option<RecordCheck<M>>,
19}
20
21impl<M> Default for Pair<M> {
22 fn default() -> Self {
23 Self {
24 any: None,
25 record: None,
26 }
27 }
28}
29
30impl<M> Pair<M> {
31 fn any_allows(&self, cx: &Cx) -> bool {
34 match &self.any {
35 Some(check) => check(cx),
36 None => self.record.is_some(),
37 }
38 }
39
40 fn record_allows(&self, cx: &Cx, record: &M) -> bool {
41 self.record.as_ref().is_some_and(|check| check(cx, record))
42 }
43}
44
45pub struct Abilities<M> {
82 view_any: Option<RequestCheck>,
83 view: Option<RecordCheck<M>>,
84 create: Option<RequestCheck>,
85 update: Option<RecordCheck<M>>,
86 delete: Pair<M>,
87 restore: Pair<M>,
88 force_delete: Pair<M>,
89 actions: Vec<(&'static str, Pair<M>)>,
91 header_actions: Vec<(&'static str, RequestCheck)>,
93}
94
95impl<M> Default for Abilities<M> {
96 fn default() -> Self {
97 Self {
98 view_any: None,
99 view: None,
100 create: None,
101 update: None,
102 delete: Pair::default(),
103 restore: Pair::default(),
104 force_delete: Pair::default(),
105 actions: Vec::new(),
106 header_actions: Vec::new(),
107 }
108 }
109}
110
111impl<M> Abilities<M> {
112 pub fn new() -> Self {
114 Self::default()
115 }
116
117 #[must_use]
122 pub fn view_any(mut self, check: impl Fn(&Cx) -> bool + Send + Sync + 'static) -> Self {
123 self.view_any = Some(Box::new(check));
124 self
125 }
126
127 #[must_use]
131 pub fn view(mut self, check: impl Fn(&Cx, &M) -> bool + Send + Sync + 'static) -> Self {
132 self.view = Some(Box::new(check));
133 self
134 }
135
136 #[must_use]
138 pub fn create(mut self, check: impl Fn(&Cx) -> bool + Send + Sync + 'static) -> Self {
139 self.create = Some(Box::new(check));
140 self
141 }
142
143 #[must_use]
145 pub fn update(mut self, check: impl Fn(&Cx, &M) -> bool + Send + Sync + 'static) -> Self {
146 self.update = Some(Box::new(check));
147 self
148 }
149
150 #[must_use]
153 pub fn delete_any(mut self, check: impl Fn(&Cx) -> bool + Send + Sync + 'static) -> Self {
154 self.delete.any = Some(Box::new(check));
155 self
156 }
157
158 #[must_use]
160 pub fn delete(mut self, check: impl Fn(&Cx, &M) -> bool + Send + Sync + 'static) -> Self {
161 self.delete.record = Some(Box::new(check));
162 self
163 }
164
165 #[must_use]
168 pub fn restore_any(mut self, check: impl Fn(&Cx) -> bool + Send + Sync + 'static) -> Self {
169 self.restore.any = Some(Box::new(check));
170 self
171 }
172
173 #[must_use]
175 pub fn restore(mut self, check: impl Fn(&Cx, &M) -> bool + Send + Sync + 'static) -> Self {
176 self.restore.record = Some(Box::new(check));
177 self
178 }
179
180 #[must_use]
184 pub fn force_delete_any(mut self, check: impl Fn(&Cx) -> bool + Send + Sync + 'static) -> Self {
185 self.force_delete.any = Some(Box::new(check));
186 self
187 }
188
189 #[must_use]
192 pub fn force_delete(mut self, check: impl Fn(&Cx, &M) -> bool + Send + Sync + 'static) -> Self {
193 self.force_delete.record = Some(Box::new(check));
194 self
195 }
196
197 fn action_mut(&mut self, name: &'static str) -> &mut Pair<M> {
199 let index = match self.actions.iter().position(|(action, _)| *action == name) {
200 Some(index) => index,
201 None => {
202 self.actions.push((name, Pair::default()));
203 self.actions.len() - 1
204 }
205 };
206 &mut self.actions[index].1
207 }
208
209 pub(crate) fn set_action_any(&mut self, name: &'static str, check: RequestCheck) {
211 self.action_mut(name).any = Some(check);
212 }
213
214 pub(crate) fn set_action_record(&mut self, name: &'static str, check: RecordCheck<M>) {
216 self.action_mut(name).record = Some(check);
217 }
218
219 pub(crate) fn set_header_action(&mut self, name: &'static str, check: RequestCheck) {
221 self.header_actions.retain(|(action, _)| *action != name);
222 self.header_actions.push((name, check));
223 }
224
225 fn action(&self, name: &str) -> Option<&Pair<M>> {
226 self.actions
227 .iter()
228 .find(|(action, _)| *action == name)
229 .map(|(_, pair)| pair)
230 }
231}
232
233impl<M> std::fmt::Debug for Abilities<M> {
234 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
236 let declared = [
237 ("view_any", self.view_any.is_some()),
238 ("view", self.view.is_some()),
239 ("create", self.create.is_some()),
240 ("update", self.update.is_some()),
241 ("delete_any", self.delete.any.is_some()),
242 ("delete", self.delete.record.is_some()),
243 ("restore_any", self.restore.any.is_some()),
244 ("restore", self.restore.record.is_some()),
245 ("force_delete_any", self.force_delete.any.is_some()),
246 ("force_delete", self.force_delete.record.is_some()),
247 ];
248 f.debug_struct("Abilities")
249 .field(
250 "declared",
251 &declared
252 .iter()
253 .filter_map(|(name, set)| set.then_some(*name))
254 .collect::<Vec<_>>(),
255 )
256 .field(
257 "actions",
258 &self
259 .actions
260 .iter()
261 .map(|(name, _)| name)
262 .collect::<Vec<_>>(),
263 )
264 .field(
265 "header_actions",
266 &self
267 .header_actions
268 .iter()
269 .map(|(name, _)| name)
270 .collect::<Vec<_>>(),
271 )
272 .finish()
273 }
274}
275
276impl<M: 'static> Policy<M> for Abilities<M> {
277 fn allows(&self, cx: &Cx, ability: Ability<'_, M>) -> bool {
278 match ability {
280 Ability::ViewAny => self.view_any.as_ref().is_some_and(|check| check(cx)),
281 Ability::View(record) => self.view.as_ref().is_some_and(|check| check(cx, record)),
282 Ability::Create => self.create.as_ref().is_some_and(|check| check(cx)),
283 Ability::Update(record) => self.update.as_ref().is_some_and(|check| check(cx, record)),
284 Ability::DeleteAny => self.delete.any_allows(cx),
285 Ability::Delete(record) => self.delete.record_allows(cx, record),
286 Ability::RestoreAny => self.restore.any_allows(cx),
287 Ability::Restore(record) => self.restore.record_allows(cx, record),
288 Ability::ForceDeleteAny => self.force_delete.any_allows(cx),
289 Ability::ForceDelete(record) => self.force_delete.record_allows(cx, record),
290 Ability::RunAny { action } => self.action(action).is_some_and(|p| p.any_allows(cx)),
291 Ability::Run { action, record } => self
292 .action(action)
293 .is_some_and(|pair| pair.record_allows(cx, record)),
294 Ability::RunHeader { action } => self
295 .header_actions
296 .iter()
297 .find(|(name, _)| *name == action)
298 .is_some_and(|(_, check)| check(cx)),
299 }
300 }
301}