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