1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
//! Macros for the `actionable` API framework.

#![forbid(unsafe_code)]
#![warn(
    clippy::cargo,
    missing_docs,
    clippy::nursery,
    clippy::pedantic,
    future_incompatible,
    rust_2018_idioms
)]
#![cfg_attr(doc, deny(rustdoc::all))]

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

mod action;
mod actionable;
mod dispatcher;

/// Derives the `actionable::Action` trait.
///
/// This trait can be customizd using the `action` attribute in these ways:
///
/// * Crate name override: `#[action(actionable = "someothername")]`. If you
///   find yourself needing to import `actionable` as another name, this setting
///   will replace all mentions of `actionable` with the identifier specified.
#[proc_macro_error]
#[proc_macro_derive(Action, attributes(action))]
pub fn action_derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    match action::derive(&input) {
        Ok(tokens) => tokens.into(),
        Err(err) => {
            emit_error!(input.ident, err.to_string());
            TokenStream::default()
        }
    }
}

/// Derives a set of traits that can be used to implement a permissions-driven
/// API. There are options that can be customized with the `#[actionable]`
/// attribute at the enum level:
///
/// * Crate name override: `#[actionable(actionable = "someothername")]`. If you
///   find yourself needing to import `actionable` as another name, this setting
///   will replace all mentions of `actionable` with the identifier specified.
///
/// ## The Dispatcher Trait
///
/// The first trait that is generated is named `<EnumName>Dispatcher`. For
/// example, if the enum's name is `Request`, the generated trait name will be
/// `RequestDispatcher`. This trait has no methods for you to implement. It
/// defines several associated types:
///
/// * `Output`: The `Ok` side of the `Result`.
/// * `Error`: The `Err` side of the `Result`. Must implement
///   `From<actionable::PermissionDenied>`.
/// * For each variant in the enum, another Trait named `<VariantName>Handler`.
///   For example, if the enum variant was `Request::AddUser`, the trait will be
///   `AddUserHandler`. Each of these traits must be implemented by any type
///   implementing `<EnumName>Dispatcher`.
///
/// The dispatcher trait has a method available for you to use to dispatch
/// requests: `async fn dispatch(&self, permissions: &Permissions, request:
/// <EnumName>) -> Result<Self::Output, Self::Error>`.
///
/// ## The Handler Traits
///
/// For each variant in the enum, a trait will be generated named
/// `<VariantName>Handler`. Using the same example above, the enum variant
/// `Request::AddUser` would generate the trait `AddUserHandler`. These traits
/// are implemented using the
/// [`async-trait`](https://crates.io/crate/async-trait) trait.
///
/// Each variant must have a protection method assigned using the
/// `#[actionable]` attribute. There are three protection methods:
///
/// ### No Protection: `#[actionable(protection = "none")]`
///
/// A handler with no protection has one method:
///
/// ```rust
/// # type Output = ();
/// # type Error = ();
/// # use actionable::{Permissions, async_trait};
/// #[async_trait]
/// trait Handler {
///     type Dispatcher;
///     async fn handle(
///         dispatcher: &Self::Dispatcher,
///         permissions: &Permissions,
///         /* each field on this variant is passed
///         as a parameter to this method */
///     ) -> Result<Output, Error>;
/// }
/// ```
///
/// Actionable does not do any checks before invoking this handler.
///
/// ### Simple Protection: `#[actionable(protection = "simple")]`
///
/// A handler with simple protection exposes methods and types to allow
/// specifying an `actionable::ResourceName` and an `Action` for this handler:
///
/// ```rust
/// # type Output = ();
/// # type Error = ();
/// # use actionable::{Permissions, ResourceName, async_trait};
/// #[async_trait]
/// trait Handler {
///     type Dispatcher;
///     type Action;
///
///     fn resource_name<'a>(
///         dispatcher: &Self::Dispatcher,
///         /* each field on this variant is passed
///         by reference as a parameter to this method */
///     ) -> ResourceName<'a>;
///
///     fn action() -> Self::Action;
///
///     async fn handle_protected(
///         dispatcher: &Self::Dispatcher,
///         permissions: &Permissions,
///         /* each field on this variant is passed
///         as a parameter to this method */
///     ) -> Result<Output, Error>;
/// }
/// ```
///
/// When the handler is invoked, it first checks `permissions` to ensure that
/// `action()` is allowed to be performed on `resource_name()`. If it is not
/// allowed, an `actionable::PermissionDenied` error will be returned. If it is
/// allowed, `handle_protected()` will be executed.
///
/// ### Custom Protection: `#[actionable(protection = "custom")]`
///
/// A handler with custom protection has two methods, one to verify permissions
/// and one to execute the protected code:
///
/// ```rust
/// # type Output = ();
/// # type Error = ();
/// # use actionable::{Permissions, async_trait};
/// #[async_trait]
/// trait Handler {
///     type Dispatcher;
///     async fn verify_permissions(
///         dispatcher: &Self::Dispatcher,
///         permissions: &Permissions,
///         /* each field on this variant is passed
///         by refrence as a parameter to this method */
///     ) -> Result<(), Error>;
///
///     async fn handle_protected(
///         dispatcher: &Self::Dispatcher,
///         permissions: &Permissions,
///         /* each field on this variant is passed as a parameter
///         to this method */
///     ) -> Result<Output, Error>;
/// }
/// ```
///
/// Actionable will first call `verify_permissions()`. If you return `Ok(())`,
/// your `handle_protected()` method is invoked.
///
/// ## Why should you use the built-in protection modes?
///
/// Actionable attempts to make permission handling easy to understand and
/// implement while making it difficult to forget implementing permission
/// handling. This is only effective if you use the protection levels.
///
/// Because Actionable includes `permissions` in every call to
/// `handle[_protected]()`, technically you could use a protection level of
/// `none` and implement permission handling within the `handle()` function.
/// While it would work, you shouldn't do this.
///
/// Actionable encourages placing information about permission handling in the
/// definition of the enum. By using `simple` and `custom` protection
/// strategies, consumers of your API will be able to see at the enum level what
/// APIs check permissions. When trying to understand what permissions are being
/// used, this is critical.
///
/// By placing your permission handling code in locations that follow a
/// repeatable patern, you're helping anyone who is reading the code separate
/// what logic is related to permission handling and what logic is related to
/// the API implementation.
///
/// ## What protection mode should you use?
///
/// * If your handler is operating on a single resource and performing a single
///   action, use the `simple` protection mode.
/// * If your handler needs to check permissions but it's more complicated than
///   the first scenario, use the `custom` protection mode.
/// * If you aren't enforcing permissions inside of this handler, use the `none`
///   protection mode.
#[proc_macro_error]
#[proc_macro_derive(Actionable, attributes(actionable))]
pub fn actionable_derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    match actionable::derive(&input) {
        Ok(tokens) => tokens.into(),
        Err(err) => {
            emit_error!(input.ident, err.to_string());
            TokenStream::default()
        }
    }
}

/// Derives the `Dispatcher` trait.
///
/// This trait requires the `input` parameter to be specified. The full list of
/// parameters that can be customized are:
///
/// * `input` Type: `#[dispatcher(input = "EnumName")]`. The enum name here
///   needs to have had [`Actionable`](actionable_derive) derived on it.
/// * Crate name override: `#[actionable(actionable = "someothername")]`. If you
///   find yourself needing to import `actionable` as another name, this setting
///   will replace all mentions of `actionable` with the identifier specified.
///
/// The `input` type must be in scope, as do the derived traits generated by
/// deriving `Actionable`.
#[proc_macro_error]
#[proc_macro_derive(Dispatcher, attributes(dispatcher))]
pub fn dispatcher_derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    match dispatcher::derive(&input) {
        Ok(tokens) => tokens.into(),
        Err(err) => {
            emit_error!(input.ident, err.to_string());
            TokenStream::default()
        }
    }
}

fn actionable(actionable: Option<syn::Path>, span: proc_macro2::Span) -> syn::Path {
    actionable.unwrap_or_else(|| {
        let mut segments = syn::punctuated::Punctuated::new();
        segments.push_value(syn::PathSegment {
            ident: syn::Ident::new("actionable", span),
            arguments: syn::PathArguments::None,
        });
        syn::Path {
            leading_colon: None,
            segments,
        }
    })
}

#[derive(Debug, thiserror::Error)]
pub(crate) enum Error {
    #[error("darling error: {0}")]
    Darling(#[from] darling::Error),
    #[error("syn error: {0}")]
    Syn(#[from] syn::Error),
}

#[derive(Debug, Clone)]
struct Actionable(syn::Path);

impl Parse for Actionable {
    fn parse(input: &'_ syn::parse::ParseBuffer<'_>) -> syn::Result<Self> {
        let actionable: syn::Ident = input.parse()?;
        if actionable != "actionable" {
            abort!(
                actionable,
                "the only parameter action() accepts is `actionable`"
            )
        }
        let _: syn::Token![=] = input.parse()?;
        let path: syn::Path = input.parse()?;
        Ok(Self(path))
    }
}

#[derive(Debug)]
struct ActionableArgs(Option<Actionable>);

impl Parse for ActionableArgs {
    fn parse(input: &'_ syn::parse::ParseBuffer<'_>) -> syn::Result<Self> {
        let content;
        let _ = syn::parenthesized!(content in input);
        let mut content: syn::punctuated::Punctuated<Actionable, syn::Token![,]> =
            content.parse_terminated(Actionable::parse)?;
        if let Some(actionable) = content.pop() {
            if !content.is_empty() {
                abort!(
                    content.first().unwrap().0.segments.first().unwrap().ident,
                    "Only one parameter, `actionable` is allowed"
                );
            }
            Ok(Self(Some(actionable.into_value())))
        } else {
            Ok(Self(None))
        }
    }
}