Skip to main content

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}