actionable_macros/
lib.rs

1//! Macros for the `actionable` API framework.
2
3#![forbid(unsafe_code)]
4#![warn(
5    clippy::cargo,
6    missing_docs,
7    clippy::pedantic,
8    future_incompatible,
9    rust_2018_idioms
10)]
11#![cfg_attr(doc, deny(rustdoc::all))]
12
13use proc_macro::TokenStream;
14use proc_macro_error::{abort, emit_error, proc_macro_error};
15use syn::{parse::Parse, parse_macro_input, DeriveInput};
16
17mod action;
18mod actionable;
19mod dispatcher;
20
21/// Derives the `actionable::Action` trait.
22///
23/// This trait can be customizd using the `action` attribute in these ways:
24///
25/// * Crate name override: `#[action(actionable = "someothername")]`. If you
26///   find yourself needing to import `actionable` as another name, this setting
27///   will replace all mentions of `actionable` with the identifier specified.
28#[proc_macro_error]
29#[proc_macro_derive(Action, attributes(action))]
30pub fn action_derive(input: TokenStream) -> TokenStream {
31    let input = parse_macro_input!(input as DeriveInput);
32    match action::derive(&input) {
33        Ok(tokens) => tokens.into(),
34        Err(err) => {
35            emit_error!(input.ident, err.to_string());
36            TokenStream::default()
37        }
38    }
39}
40
41/// Derives a set of traits that can be used to implement a permissions-driven
42/// API. There are options that can be customized with the `#[actionable]`
43/// attribute at the enum level:
44///
45/// * Crate name override: `#[actionable(actionable = "someothername")]`. If you
46///   find yourself needing to import `actionable` as another name, this setting
47///   will replace all mentions of `actionable` with the identifier specified.
48///
49/// ## The Dispatcher Trait
50///
51/// The first trait that is generated is named `<EnumName>Dispatcher`. For
52/// example, if the enum's name is `Request`, the generated trait name will be
53/// `RequestDispatcher`. This trait has no methods for you to implement. It
54/// defines several associated types:
55///
56/// * `Output`: The `Ok` side of the `Result`.
57/// * `Error`: The `Err` side of the `Result`. Must implement
58///   `From<actionable::PermissionDenied>`.
59/// * For each variant in the enum, another Trait named `<VariantName>Handler`.
60///   For example, if the enum variant was `Request::AddUser`, the trait will be
61///   `AddUserHandler`. Each of these traits must be implemented by any type
62///   implementing `<EnumName>Dispatcher`.
63///
64/// The dispatcher trait has a method available for you to use to dispatch
65/// requests: `async fn dispatch(&self, permissions: &Permissions, request:
66/// <EnumName>) -> Result<Self::Output, Self::Error>`.
67///
68/// ## The Handler Traits
69///
70/// For each variant in the enum, a trait will be generated named
71/// `<VariantName>Handler`. Using the same example above, the enum variant
72/// `Request::AddUser` would generate the trait `AddUserHandler`. These traits
73/// are implemented using the
74/// [`async-trait`](https://crates.io/crate/async-trait) trait.
75///
76/// Each variant must have a protection method assigned using the
77/// `#[actionable]` attribute. There are three protection methods:
78///
79/// ### No Protection: `#[actionable(protection = "none")]`
80///
81/// A handler with no protection has one method:
82///
83/// ```rust
84/// # type Output = ();
85/// # type Error = ();
86/// # use actionable::{Permissions, async_trait};
87/// #[async_trait]
88/// trait Handler {
89///     type Dispatcher;
90///     async fn handle(
91///         dispatcher: &Self::Dispatcher,
92///         permissions: &Permissions,
93///         /* each field on this variant is passed
94///         as a parameter to this method */
95///     ) -> Result<Output, Error>;
96/// }
97/// ```
98///
99/// Actionable does not do any checks before invoking this handler.
100///
101/// ### Simple Protection: `#[actionable(protection = "simple")]`
102///
103/// A handler with simple protection exposes methods and types to allow
104/// specifying an `actionable::ResourceName` and an `Action` for this handler:
105///
106/// ```rust
107/// # type Output = ();
108/// # type Error = ();
109/// # use actionable::{Permissions, ResourceName, async_trait};
110/// #[async_trait]
111/// trait Handler {
112///     type Dispatcher;
113///     type Action;
114///
115///     fn resource_name<'a>(
116///         dispatcher: &Self::Dispatcher,
117///         /* each field on this variant is passed
118///         by reference as a parameter to this method */
119///     ) -> ResourceName<'a>;
120///
121///     fn action() -> Self::Action;
122///
123///     async fn handle_protected(
124///         dispatcher: &Self::Dispatcher,
125///         permissions: &Permissions,
126///         /* each field on this variant is passed
127///         as a parameter to this method */
128///     ) -> Result<Output, Error>;
129/// }
130/// ```
131///
132/// When the handler is invoked, it first checks `permissions` to ensure that
133/// `action()` is allowed to be performed on `resource_name()`. If it is not
134/// allowed, an `actionable::PermissionDenied` error will be returned. If it is
135/// allowed, `handle_protected()` will be executed.
136///
137/// ### Custom Protection: `#[actionable(protection = "custom")]`
138///
139/// A handler with custom protection has two methods, one to verify permissions
140/// and one to execute the protected code:
141///
142/// ```rust
143/// # type Output = ();
144/// # type Error = ();
145/// # use actionable::{Permissions, async_trait};
146/// #[async_trait]
147/// trait Handler {
148///     type Dispatcher;
149///     async fn verify_permissions(
150///         dispatcher: &Self::Dispatcher,
151///         permissions: &Permissions,
152///         /* each field on this variant is passed
153///         by refrence as a parameter to this method */
154///     ) -> Result<(), Error>;
155///
156///     async fn handle_protected(
157///         dispatcher: &Self::Dispatcher,
158///         permissions: &Permissions,
159///         /* each field on this variant is passed as a parameter
160///         to this method */
161///     ) -> Result<Output, Error>;
162/// }
163/// ```
164///
165/// Actionable will first call `verify_permissions()`. If you return `Ok(())`,
166/// your `handle_protected()` method is invoked.
167///
168/// ## Why should you use the built-in protection modes?
169///
170/// Actionable attempts to make permission handling easy to understand and
171/// implement while making it difficult to forget implementing permission
172/// handling. This is only effective if you use the protection levels.
173///
174/// Because Actionable includes `permissions` in every call to
175/// `handle[_protected]()`, technically you could use a protection level of
176/// `none` and implement permission handling within the `handle()` function.
177/// While it would work, you shouldn't do this.
178///
179/// Actionable encourages placing information about permission handling in the
180/// definition of the enum. By using `simple` and `custom` protection
181/// strategies, consumers of your API will be able to see at the enum level what
182/// APIs check permissions. When trying to understand what permissions are being
183/// used, this is critical.
184///
185/// By placing your permission handling code in locations that follow a
186/// repeatable patern, you're helping anyone who is reading the code separate
187/// what logic is related to permission handling and what logic is related to
188/// the API implementation.
189///
190/// ## What protection mode should you use?
191///
192/// * If your handler is operating on a single resource and performing a single
193///   action, use the `simple` protection mode.
194/// * If your handler needs to check permissions but it's more complicated than
195///   the first scenario, use the `custom` protection mode.
196/// * If you aren't enforcing permissions inside of this handler, use the `none`
197///   protection mode.
198#[proc_macro_error]
199#[proc_macro_derive(Actionable, attributes(actionable))]
200pub fn actionable_derive(input: TokenStream) -> TokenStream {
201    let input = parse_macro_input!(input as DeriveInput);
202    match actionable::derive(&input) {
203        Ok(tokens) => tokens.into(),
204        Err(err) => {
205            emit_error!(input.ident, err.to_string());
206            TokenStream::default()
207        }
208    }
209}
210
211/// Derives the `Dispatcher` trait.
212///
213/// This trait requires the `input` parameter to be specified. The full list of
214/// parameters that can be customized are:
215///
216/// * `input` Type: `#[dispatcher(input = "EnumName")]`. The enum name here
217///   needs to have had [`Actionable`](actionable_derive) derived on it.
218/// * Crate name override: `#[actionable(actionable = "someothername")]`. If you
219///   find yourself needing to import `actionable` as another name, this setting
220///   will replace all mentions of `actionable` with the identifier specified.
221///
222/// The `input` type must be in scope, as do the derived traits generated by
223/// deriving `Actionable`.
224#[proc_macro_error]
225#[proc_macro_derive(Dispatcher, attributes(dispatcher))]
226pub fn dispatcher_derive(input: TokenStream) -> TokenStream {
227    let input = parse_macro_input!(input as DeriveInput);
228    match dispatcher::derive(&input) {
229        Ok(tokens) => tokens.into(),
230        Err(err) => {
231            emit_error!(input.ident, err.to_string());
232            TokenStream::default()
233        }
234    }
235}
236
237fn actionable(actionable: Option<syn::Path>, span: proc_macro2::Span) -> syn::Path {
238    actionable.unwrap_or_else(|| {
239        let mut segments = syn::punctuated::Punctuated::new();
240        segments.push_value(syn::PathSegment {
241            ident: syn::Ident::new("actionable", span),
242            arguments: syn::PathArguments::None,
243        });
244        syn::Path {
245            leading_colon: None,
246            segments,
247        }
248    })
249}
250
251#[derive(Debug, thiserror::Error)]
252pub(crate) enum Error {
253    #[error("darling error: {0}")]
254    Darling(#[from] darling::Error),
255    #[error("syn error: {0}")]
256    Syn(#[from] syn::Error),
257}
258
259#[derive(Debug, Clone)]
260struct Actionable(syn::Path);
261
262impl Parse for Actionable {
263    fn parse(input: &'_ syn::parse::ParseBuffer<'_>) -> syn::Result<Self> {
264        let actionable: syn::Ident = input.parse()?;
265        if actionable != "actionable" {
266            abort!(
267                actionable,
268                "the only parameter action() accepts is `actionable`"
269            )
270        }
271        let _: syn::Token![=] = input.parse()?;
272        let path: syn::Path = input.parse()?;
273        Ok(Self(path))
274    }
275}
276
277#[derive(Debug)]
278struct ActionableArgs(Option<Actionable>);
279
280impl Parse for ActionableArgs {
281    fn parse(input: &'_ syn::parse::ParseBuffer<'_>) -> syn::Result<Self> {
282        let content;
283        let _ = syn::parenthesized!(content in input);
284        let mut content: syn::punctuated::Punctuated<Actionable, syn::Token![,]> =
285            content.parse_terminated(Actionable::parse)?;
286        content.pop().map_or_else(
287            || Ok(Self(None)),
288            |actionable| {
289                if !content.is_empty() {
290                    abort!(
291                        content.first().unwrap().0.segments.first().unwrap().ident,
292                        "Only one parameter, `actionable` is allowed"
293                    );
294                }
295                Ok(Self(Some(actionable.into_value())))
296            },
297        )
298    }
299}