1
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

            
13
use proc_macro::TokenStream;
14
use proc_macro_error::{abort, emit_error, proc_macro_error};
15
use syn::{parse::Parse, parse_macro_input, DeriveInput};
16

            
17
mod action;
18
mod actionable;
19
mod 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))]
30
4
pub fn action_derive(input: TokenStream) -> TokenStream {
31
4
    let input = parse_macro_input!(input as DeriveInput);
32
4
    match action::derive(&input) {
33
4
        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))]
200
4
pub fn actionable_derive(input: TokenStream) -> TokenStream {
201
4
    let input = parse_macro_input!(input as DeriveInput);
202
4
    match actionable::derive(&input) {
203
4
        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))]
226
1
pub fn dispatcher_derive(input: TokenStream) -> TokenStream {
227
1
    let input = parse_macro_input!(input as DeriveInput);
228
1
    match dispatcher::derive(&input, false) {
229
1
        Ok(tokens) => tokens.into(),
230
        Err(err) => {
231
            emit_error!(input.ident, err.to_string());
232
            TokenStream::default()
233
        }
234
    }
235
}
236

            
237
/// Derives the `AsyncDispatcher` trait.
238
///
239
/// This trait requires the `input` parameter to be specified. The full list of
240
/// parameters that can be customized are:
241
///
242
/// * `input` Type: `#[dispatcher(input = "EnumName")]`. The enum name here
243
///   needs to have had [`Actionable`](actionable_derive) derived on it.
244
/// * Crate name override: `#[actionable(actionable = "someothername")]`. If you
245
///   find yourself needing to import `actionable` as another name, this setting
246
///   will replace all mentions of `actionable` with the identifier specified.
247
///
248
/// The `input` type must be in scope, as do the derived traits generated by
249
/// deriving `Actionable`.
250
#[proc_macro_error]
251
#[proc_macro_derive(AsyncDispatcher, attributes(dispatcher))]
252
3
pub fn async_dispatcher_derive(input: TokenStream) -> TokenStream {
253
3
    let input = parse_macro_input!(input as DeriveInput);
254
3
    match dispatcher::derive(&input, true) {
255
3
        Ok(tokens) => tokens.into(),
256
        Err(err) => {
257
            emit_error!(input.ident, err.to_string());
258
            TokenStream::default()
259
        }
260
    }
261
}
262

            
263
8
fn actionable(actionable: Option<syn::Path>, span: proc_macro2::Span) -> syn::Path {
264
8
    actionable.unwrap_or_else(|| {
265
4
        let mut segments = syn::punctuated::Punctuated::new();
266
4
        segments.push_value(syn::PathSegment {
267
4
            ident: syn::Ident::new("actionable", span),
268
4
            arguments: syn::PathArguments::None,
269
4
        });
270
4
        syn::Path {
271
4
            leading_colon: None,
272
4
            segments,
273
4
        }
274
8
    })
275
8
}
276

            
277
#[derive(Debug, thiserror::Error)]
278
pub(crate) enum Error {
279
    #[error("darling error: {0}")]
280
    Darling(#[from] darling::Error),
281
    #[error("syn error: {0}")]
282
    Syn(#[from] syn::Error),
283
}
284

            
285
#[derive(Debug, Clone)]
286
enum ActionableArg {
287
    Actionable(syn::Path),
288
    Async,
289
}
290

            
291
impl Parse for ActionableArg {
292
7
    fn parse(input: &'_ syn::parse::ParseBuffer<'_>) -> syn::Result<Self> {
293
7
        let ident: syn::Ident = input.parse()?;
294
7
        if ident == "actionable" {
295
4
            let _: syn::Token![=] = input.parse()?;
296
4
            Ok(Self::Actionable(input.parse()?))
297
3
        } else if ident == "async" {
298
3
            Ok(Self::Async)
299
        } else {
300
            abort!(ident, "expected `actionable` or `async`")
301
        }
302
7
    }
303
}
304

            
305
6
#[derive(Clone, Debug, Default)]
306
struct ActionableArgs {
307
    actionable: Option<syn::Path>,
308
    asynchronous: bool,
309
}
310

            
311
impl Parse for ActionableArgs {
312
5
    fn parse(input: &'_ syn::parse::ParseBuffer<'_>) -> syn::Result<Self> {
313
5
        let content;
314
5
        let _ = syn::parenthesized!(content in input);
315
5
        let args: syn::punctuated::Punctuated<ActionableArg, syn::Token![,]> =
316
5
            content.parse_terminated(ActionableArg::parse)?;
317

            
318
5
        let mut result = Self::default();
319
12
        for arg in args {
320
7
            match arg {
321
4
                ActionableArg::Actionable(path) => result.actionable = Some(path),
322
3
                ActionableArg::Async => result.asynchronous = true,
323
            }
324
        }
325

            
326
5
        Ok(result)
327
5
    }
328
}