Skip to main content

alibi_sqlx_macros/
lib.rs

1//! Proc macros for the Alibi `SQLx` integration.
2mod columns;
3mod entities;
4mod model;
5mod roots;
6
7use alibi_entity_codegen::{self as codegen, EntityRole};
8use columns::{columns, table_attribute};
9use entities::{account_impl, session_impl, user_impl, verification_impl};
10use model::model_impl;
11use proc_macro::TokenStream as ProcMacroTokenStream;
12use proc_macro2::TokenStream;
13use quote::quote;
14use roots::resolve_roots;
15use syn::{DeriveInput, parse_macro_input};
16
17fn try_generate_model(input: &DeriveInput) -> syn::Result<TokenStream> {
18    let roots = resolve_roots();
19    let table = table_attribute(input)?;
20    let fields = codegen::named_fields(input)?;
21    let columns = columns(input, fields)?;
22    model_impl(&input.ident, &table, &columns, None, &roots)
23}
24
25fn try_generate_entity(input: &DeriveInput) -> syn::Result<TokenStream> {
26    let mut roots = resolve_roots();
27    let attributes = codegen::parse_auth_attributes(input, true)?;
28    roots.id_generator = codegen::generated_id(&attributes, &roots.core);
29    let fields = codegen::named_fields(input)?;
30    codegen::validate_core_fields(input, attributes.role, fields)?;
31    let columns = columns(input, fields)?;
32    let entity_fields: Vec<codegen::EntityField> = columns
33        .iter()
34        .map(|column| codegen::EntityField {
35            ident: column.ident.clone(),
36            camel: column.camel.clone(),
37            physical: column.renamed.then(|| column.physical.clone()),
38        })
39        .collect();
40    let ident = &input.ident;
41    let table = attributes.table.clone().unwrap_or_else(|| {
42        match attributes.role {
43            EntityRole::User => "user",
44            EntityRole::Session => "session",
45            EntityRole::Account => "account",
46            EntityRole::Verification => "verification",
47        }
48        .to_owned()
49    });
50    let model = model_impl(ident, &table, &columns, Some(attributes.role), &roots)?;
51    let secondary = attributes.secondary_storage;
52    let core_root = &roots.core;
53    let role_impl = match attributes.role {
54        EntityRole::User => {
55            let auth = codegen::auth_user_impl(ident, fields, &entity_fields, secondary, core_root);
56            let model = user_impl(ident, fields, &columns, &roots)?;
57            quote! { #auth #model }
58        }
59        EntityRole::Session => {
60            let auth =
61                codegen::auth_session_impl(ident, fields, &entity_fields, secondary, core_root);
62            let model = session_impl(ident, fields, &columns, &roots)?;
63            quote! { #auth #model }
64        }
65        EntityRole::Account => {
66            let auth = codegen::auth_account_impl(ident, &entity_fields, core_root);
67            let model = account_impl(ident, fields, &columns, &roots)?;
68            quote! { #auth #model }
69        }
70        EntityRole::Verification => {
71            let auth = codegen::auth_verification_impl(ident, core_root);
72            let model = verification_impl(ident, fields, &columns, &roots)?;
73            quote! { #auth #model }
74        }
75    };
76    Ok(quote! { #model #role_impl })
77}
78
79/// Derive macro that generates `Auth*` trait impls and `Sqlx*Model` impls
80/// for a `SQLx` model.
81///
82/// # Usage
83///
84/// Annotate a struct deriving `sqlx::FromRow` with `#[derive(AuthEntity)]` and
85/// `#[auth(role = "...")]` where role is one of `user`, `session`, `account`,
86/// or `verification`. `table = "..."` names the physical table and defaults to
87/// the bundled table for the role. Physical column names follow
88/// `#[sqlx(rename = "...")]` and `#[sqlx(rename_all = "...")]`.
89/// `id_generator = "path::to::function"` selects a String ID factory; the default
90/// remains a UUID. Use `#[auth(column_type = "bpchar")]` on String fields mapped
91/// to PostgreSQL `CHAR(n)` to retain typed parameters and indexed equality.
92///
93/// ```ignore
94/// #[derive(Clone, Debug, serde::Serialize, sqlx::FromRow, AuthEntity)]
95/// #[auth(role = "user", table = "users")]
96/// pub struct User {
97///     pub id: String,
98///     // ... core fields ...
99/// }
100/// ```
101///
102/// # Extra fields
103///
104/// The struct may contain fields beyond the core set required by the auth
105/// role. They are omitted from generated inserts, so the database default
106/// applies. Every field type implements `SqlxValue`; override the inferred
107/// column category with `#[auth(column_type = "text")]`.
108#[proc_macro_derive(AuthEntity, attributes(auth))]
109pub fn derive_auth_entity(input: ProcMacroTokenStream) -> ProcMacroTokenStream {
110    let input = parse_macro_input!(input as DeriveInput);
111    try_generate_entity(&input)
112        .unwrap_or_else(syn::Error::into_compile_error)
113        .into()
114}
115
116/// Derive macro that implements `SqlxModel` for a table-backed row type.
117///
118/// `#[auth(table = "...")]` names the physical table; the `id` field is the
119/// primary key. Physical column names follow `#[sqlx(rename = "...")]` and
120/// `#[sqlx(rename_all = "...")]`, and `#[auth(column_type = "...")]`
121/// overrides a column's inferred category. The struct must also derive
122/// `sqlx::FromRow`.
123#[proc_macro_derive(SqlxModel, attributes(auth))]
124pub fn derive_sqlx_model(input: ProcMacroTokenStream) -> ProcMacroTokenStream {
125    let input = parse_macro_input!(input as DeriveInput);
126    try_generate_model(&input)
127        .unwrap_or_else(syn::Error::into_compile_error)
128        .into()
129}