tablo_core/resource/action/abilities.rs
1//! The policy's view of an action: matching an [`Ability`] against an action type, and the
2//! [`Abilities`](crate::policy::Abilities) builder's rules for one.
3
4use topcoat::context::Cx;
5
6use super::Action;
7use crate::{policy::Ability, resource::Resource};
8
9impl<M> Ability<'_, M> {
10 /// Whether the ability is [`RunAny`](Ability::RunAny) or [`Run`](Ability::Run) for the action
11 /// `A`.
12 ///
13 /// It compares against `A::NAME`, so renaming the action keeps the policy matching it, and a
14 /// type that is not an action of the resource does not compile. `R` is inferred when `A` is an
15 /// action of one resource.
16 ///
17 /// ```rust
18 /// # use tablo_core::{Ability, Action, NoForm, Resource};
19 /// # use topcoat::{Result, context::Cx};
20 /// # #[derive(Debug, Clone, toasty::Model, tablo_core::ModelColumns)]
21 /// # struct Post { #[key] #[auto] id: uuid::Uuid, published: bool }
22 /// # struct PostResource;
23 /// # impl Resource for PostResource {
24 /// # type Model = Post;
25 /// # type Form = NoForm<Post>;
26 /// # }
27 /// # struct Publish;
28 /// # impl Action<PostResource> for Publish {
29 /// # type Input = ();
30 /// # const NAME: &'static str = "publish";
31 /// # async fn run(_: &Cx, _: &[Post], _: (), _: &mut dyn toasty::Executor) -> Result<tablo_core::Outcome, tablo_core::WriteError<std::convert::Infallible>> { Ok(tablo_core::Outcome::done()) }
32 /// # }
33 /// # fn is_editor(_cx: &Cx) -> bool { true }
34 /// fn post_policy(cx: &Cx, ability: Ability<'_, Post>) -> bool {
35 /// match ability {
36 /// Ability::Run { record, .. } if ability.is_action::<Publish, _>() => !record.published,
37 /// Ability::ViewAny | Ability::View(_) => true,
38 /// _ => is_editor(cx),
39 /// }
40 /// }
41 /// ```
42 pub fn is_action<A, R>(self) -> bool
43 where
44 A: Action<R>,
45 R: Resource<Model = M>,
46 {
47 matches!(self, Ability::RunAny { action } | Ability::Run { action, .. } if action == A::NAME)
48 }
49}
50
51impl<M> crate::policy::Abilities<M> {
52 /// Who may run the record action `A` on a record: [`Run`](Ability::Run) with `A`'s
53 /// [`NAME`](Action::NAME). It also allows [`RunAny`](Ability::RunAny) for `A`, which
54 /// [`run_any`](Self::run_any) narrows. An action no `run` declares is refused.
55 ///
56 /// ```rust
57 /// # use tablo_core::{Abilities, Action, NoForm, Resource};
58 /// # use topcoat::{Result, context::Cx};
59 /// # #[derive(Debug, Clone, toasty::Model, tablo_core::ModelColumns)]
60 /// # struct Post { #[key] #[auto] id: uuid::Uuid, published: bool }
61 /// # struct PostResource;
62 /// # impl Resource for PostResource {
63 /// # type Model = Post;
64 /// # type Form = NoForm<Post>;
65 /// # }
66 /// # struct Publish;
67 /// # impl Action<PostResource> for Publish {
68 /// # type Input = ();
69 /// # const NAME: &'static str = "publish";
70 /// # async fn run(_: &Cx, _: &[Post], _: (), _: &mut dyn toasty::Executor) -> Result<tablo_core::Outcome, tablo_core::WriteError<std::convert::Infallible>> { Ok(tablo_core::Outcome::done()) }
71 /// # }
72 /// let policy = Abilities::<Post>::new()
73 /// .view_any(|_cx| true)
74 /// .view(|_cx, _post| true)
75 /// .run::<Publish, _>(|_cx, post| !post.published);
76 /// # let _ = policy;
77 /// ```
78 #[must_use]
79 pub fn run<A, R>(mut self, check: impl Fn(&Cx, &M) -> bool + Send + Sync + 'static) -> Self
80 where
81 A: Action<R>,
82 R: Resource<Model = M>,
83 {
84 self.set_action_record(A::NAME, Box::new(check));
85 self
86 }
87
88 /// Who may run the record action `A` at all: [`RunAny`](Ability::RunAny) with `A`'s
89 /// [`NAME`](Action::NAME). Defaults to whether [`run`](Self::run) declares `A`.
90 #[must_use]
91 pub fn run_any<A, R>(mut self, check: impl Fn(&Cx) -> bool + Send + Sync + 'static) -> Self
92 where
93 A: Action<R>,
94 R: Resource<Model = M>,
95 {
96 self.set_action_any(A::NAME, Box::new(check));
97 self
98 }
99}