Skip to main content

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//!
42//! ## Note on `OData` Macros
43//!
44//! OData-related derives like `ODataFilterable` have been moved to `modkit-odata-macros`.
45//! Use that crate for `OData` protocol macros.
46
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///
69/// # Example
70///
71/// ```ignore
72/// #[derive(DeriveEntityModel, Scopable)]
73/// #[sea_orm(table_name = "users")]
74/// #[secure(
75///     tenant_col = "tenant_id",
76///     resource_col = "id",
77///     no_owner,
78///     no_type
79/// )]
80/// pub struct Model {
81///     #[sea_orm(primary_key)]
82///     pub id: Uuid,
83///     pub tenant_id: Uuid,
84///     pub email: String,
85/// }
86/// ```
87///
88/// # Global Entities
89///
90/// For entities that are not tenant-scoped (global lookup tables, system config, etc.),
91/// use the `unrestricted` flag:
92///
93/// ```ignore
94/// #[derive(DeriveEntityModel, Scopable)]
95/// #[sea_orm(table_name = "system_config")]
96/// #[secure(unrestricted)]
97/// pub struct Model {
98///     #[sea_orm(primary_key)]
99///     pub id: Uuid,
100///     pub key: String,
101///     pub value: String,
102/// }
103/// ```
104#[proc_macro_derive(Scopable, attributes(secure))]
105#[proc_macro_error]
106pub fn derive_scopable(input: TokenStream) -> TokenStream {
107    let input = parse_macro_input!(input as DeriveInput);
108    scopable::expand_derive_scopable(input).into()
109}