typesec_macro/lib.rs
1//! # typesec-macro
2//!
3//! Procedural macros for the typesec ecosystem.
4//!
5//! ## `#[derive(TypesecRole)]`
6//!
7//! Derive the [`Role`][typesec_core::role::Role] trait for a struct, pulling
8//! permissions and resource patterns from the `#[role(...)]` attribute:
9//!
10//! ```rust,ignore
11//! use typesec_macro::TypesecRole;
12//!
13//! #[derive(TypesecRole)]
14//! #[role(permissions = "read,write", resources = "code/*,infra/*")]
15//! pub struct Engineer;
16//! ```
17//!
18//! Expands to:
19//!
20//! ```rust,ignore
21//! impl typesec_core::role::Role for Engineer {
22//! fn name() -> &'static str { "engineer" }
23//! fn permission_names() -> &'static [&'static str] { &["read", "write"] }
24//! fn resource_patterns() -> &'static [&'static str] { &["code/*", "infra/*"] }
25//! }
26//! ```
27//!
28//! ## `policy!` macro
29//!
30//! Inline role definitions without a YAML file:
31//!
32//! ```rust,ignore
33//! use typesec_macro::policy;
34//!
35//! policy! {
36//! role Analyst {
37//! can [read, read_sensitive] on ["reports/*", "metrics/*"];
38//! }
39//! role LeadAnalyst extends Analyst {
40//! can [write] on ["reports/drafts/*"];
41//! }
42//! }
43//! ```
44//!
45//! The macro internals are split across [`shared`] (permission validation and
46//! name casing), [`role_derive`] (the derive expansion), and [`policy_dsl`] (the
47//! `policy!` parser and codegen).
48
49use proc_macro::TokenStream;
50use syn::{DeriveInput, parse_macro_input};
51
52mod policy_dsl;
53mod role_derive;
54mod shared;
55
56/// Derive the `typesec_core::role::Role` trait.
57///
58/// Requires a `#[role(permissions = "...", resources = "...")]` attribute.
59#[proc_macro_derive(TypesecRole, attributes(role))]
60pub fn derive_typesec_role(input: TokenStream) -> TokenStream {
61 let input = parse_macro_input!(input as DeriveInput);
62 match role_derive::derive_typesec_role_impl(input) {
63 Ok(ts) => ts.into(),
64 Err(e) => e.to_compile_error().into(),
65 }
66}
67
68/// Inline policy macro.
69///
70/// ```rust,ignore
71/// policy! {
72/// role Analyst {
73/// can [read, read_sensitive] on ["reports/*"];
74/// }
75/// role Engineer extends Analyst {
76/// can [write, execute] on ["code/*"];
77/// }
78/// }
79/// ```
80///
81/// Expands each `role X { ... }` block to a struct + `Role` impl.
82#[proc_macro]
83pub fn policy(input: TokenStream) -> TokenStream {
84 match policy_dsl::policy_impl(input.into()) {
85 Ok(ts) => ts.into(),
86 Err(e) => e.to_compile_error().into(),
87 }
88}
89
90#[cfg(test)]
91mod tests;