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
//! 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))]

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

mod action;
mod actionable;

/// Derives the `actionable::Action` trait.
#[proc_macro_error]
#[proc_macro_derive(Action)]
pub fn action_derive(input: TokenStream) -> TokenStream {
    action::derive(input)
}

/// 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 associated type named
///   `<VariantName>Handler`. For example, if the enum variant was
///   `Request::AddUser`, the associated type will be `AddUserHandler`. Each of
///   these associated types must implement the trait of the same name
///   (described in the next section).
///
/// 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};
/// #[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};
/// #[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};
/// #[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()
        }
    }
}