modkit_db_macros/lib.rs
1//! # modkit-db-macros
2//!
3//! Procedural macros for the `modkit-db` secure ORM layer.
4//!
5//! ## `#[derive(Scopable)]`
6//!
7//! Automatically implements `ScopableEntity` for a `SeaORM` entity based on attributes.
8//!
9//! **IMPORTANT**: All four scope dimensions must be explicitly specified. No implicit defaults.
10//!
11//! ### Example
12//!
13//! ```ignore
14//! use sea_orm::entity::prelude::*;
15//! use modkit_db::secure::Scopable;
16//!
17//! #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Scopable)]
18//! #[sea_orm(table_name = "users")]
19//! #[secure(
20//! tenant_col = "tenant_id",
21//! resource_col = "id",
22//! no_owner,
23//! no_type
24//! )]
25//! pub struct Model {
26//! #[sea_orm(primary_key)]
27//! pub id: Uuid,
28//! pub tenant_id: Uuid,
29//! pub email: String,
30//! }
31//! ```
32//!
33//! ### Attributes
34//!
35//! Each scope dimension requires exactly one declaration:
36//! - **Tenant**: `tenant_col = "column_name"` OR `no_tenant`
37//! - **Resource**: `resource_col = "column_name"` OR `no_resource`
38//! - **Owner**: `owner_col = "column_name"` OR `no_owner`
39//! - **Type**: `type_col = "column_name"` OR `no_type`
40//! - **Unrestricted**: `unrestricted` (forbids all other attributes)
41//! - **Custom PEP property**: `pep_prop(property_name = "column_name")` (repeatable)
42//!
43//! ## Note on `OData` Macros
44//!
45//! OData-related derives like `ODataFilterable` have been moved to `modkit-odata-macros`.
46//! Use that crate for `OData` protocol macros.
47#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
48
49use proc_macro::TokenStream;
50use proc_macro_error2::proc_macro_error;
51use syn::{DeriveInput, parse_macro_input};
52
53mod scopable;
54
55/// Derive macro for implementing `ScopableEntity`.
56///
57/// Place this on your `SeaORM` Model struct along with `#[secure(...)]` attributes.
58///
59/// # Attributes
60///
61/// **All four scope dimensions must be explicitly specified:**
62///
63/// - `tenant_col = "column_name"` OR `no_tenant` - Tenant isolation column
64/// - `resource_col = "column_name"` OR `no_resource` - Primary resource ID column
65/// - `owner_col = "column_name"` OR `no_owner` - Owner-based filtering column
66/// - `type_col = "column_name"` OR `no_type` - Type-based filtering column
67/// - `unrestricted` - Mark as global entity (forbids all other attributes)
68/// - `pep_prop(property_name = "column_name")` - Custom PEP property mapping (repeatable)
69///
70/// The macro auto-generates `resolve_property()` from dimension columns and `pep_prop` entries:
71/// - `tenant_col` → `"owner_tenant_id"`
72/// - `resource_col` → `"id"`
73/// - `owner_col` → `"owner_id"`
74/// - Each `pep_prop(name = "col")` → `"name"`
75///
76/// # Example
77///
78/// ```ignore
79/// #[derive(DeriveEntityModel, Scopable)]
80/// #[sea_orm(table_name = "users")]
81/// #[secure(
82/// tenant_col = "tenant_id",
83/// resource_col = "id",
84/// no_owner,
85/// no_type
86/// )]
87/// pub struct Model {
88/// #[sea_orm(primary_key)]
89/// pub id: Uuid,
90/// pub tenant_id: Uuid,
91/// pub email: String,
92/// }
93/// ```
94///
95/// # Custom PEP Properties
96///
97/// Use `pep_prop(...)` to add custom authorization property mappings beyond the
98/// standard dimension columns:
99///
100/// ```ignore
101/// #[derive(DeriveEntityModel, Scopable)]
102/// #[sea_orm(table_name = "resources")]
103/// #[secure(
104/// tenant_col = "tenant_id",
105/// resource_col = "id",
106/// no_owner,
107/// no_type,
108/// pep_prop(department_id = "department_id"),
109/// )]
110/// pub struct Model {
111/// #[sea_orm(primary_key)]
112/// pub id: Uuid,
113/// pub tenant_id: Uuid,
114/// pub department_id: Uuid,
115/// }
116/// ```
117///
118/// # Global Entities
119///
120/// For entities that are not tenant-scoped (global lookup tables, system config, etc.),
121/// use the `unrestricted` flag:
122///
123/// ```ignore
124/// #[derive(DeriveEntityModel, Scopable)]
125/// #[sea_orm(table_name = "system_config")]
126/// #[secure(unrestricted)]
127/// pub struct Model {
128/// #[sea_orm(primary_key)]
129/// pub id: Uuid,
130/// pub key: String,
131/// pub value: String,
132/// }
133/// ```
134#[proc_macro_derive(Scopable, attributes(secure))]
135#[proc_macro_error]
136pub fn derive_scopable(input: TokenStream) -> TokenStream {
137 let input = parse_macro_input!(input as DeriveInput);
138 scopable::expand_derive_scopable(input).into()
139}