atmosphere_macros/
lib.rs

1//! # Macros for Atmosphere
2//!
3//! This crate provides a set of procedural macros to simplify and automate various tasks in
4//! atmosphere. These macros enhance the developer experience by reducing boilerplate,
5//! ensuring consistency, and integrating seamlessly with the framework's functionalities.
6//!
7//! This crate includes macros for deriving schema information from structs, handling table-related
8//! attributes, and managing hooks within the framework. The macros are designed to be intuitive
9//! and align with the framework's conventions, making them a powerful tool in the application
10//! development process.
11
12#![cfg(any(feature = "postgres", feature = "mysql", feature = "sqlite"))]
13
14use proc_macro::TokenStream;
15use quote::{ToTokens, quote};
16use syn::{ItemStruct, parse_macro_input};
17
18mod derive;
19mod hooks;
20mod schema;
21
22use schema::table::Table;
23
24/// An attribute macro that stores metadata about the sql table and derives needed traits.
25///
26/// Keys:
27///
28/// - `schema` - sets schema name.
29/// - `name` - sets table name.
30///
31/// Usage:
32///
33/// ```ignore
34/// # use atmosphere::prelude::*;
35/// #[table(schema = "public", name = "user")]
36/// # struct User {
37/// #     #[sql(pk)]
38/// #     id: i32,
39/// #     #[sql(unique)]
40/// #     username: String,
41/// # }
42/// ```
43#[proc_macro_attribute]
44pub fn table(table_args: TokenStream, input: TokenStream) -> TokenStream {
45    let mut model = parse_macro_input!(input as ItemStruct);
46
47    for ref mut field in model.fields.iter_mut() {
48        let attribute = field
49            .attrs
50            .iter()
51            .find(|a| a.path().is_ident(schema::column::attribute::PATH));
52
53        let Some(attribute) = attribute else {
54            continue;
55        };
56
57        let attribute: schema::column::attribute::Attribute = attribute.parse_args().unwrap();
58
59        if let Some(rename) = attribute.renamed {
60            struct Extract {
61                rename: syn::Attribute,
62            }
63
64            impl syn::parse::Parse for Extract {
65                fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
66                    Ok(Self {
67                        rename: input
68                            .call(syn::Attribute::parse_outer)?
69                            .into_iter()
70                            .next()
71                            .unwrap(),
72                    })
73                }
74            }
75
76            let Extract { rename } =
77                syn::parse_str(&format!("#[sqlx(rename = \"{rename}\")]")).unwrap();
78
79            field.attrs.push(rename);
80        }
81    }
82
83    let table = match Table::parse_struct(&model, table_args) {
84        Ok(table) => table,
85        Err(error) => return error.into_compile_error().into(),
86    };
87
88    for field in model.fields.iter_mut() {
89        field.attrs.retain(|attr| !attr.path().is_ident("sql"));
90    }
91
92    let model = model.to_token_stream();
93    let derives = derive::all(&table);
94
95    quote! {
96        #[derive(::atmosphere::sqlx::FromRow)]
97        #model
98
99        #derives
100    }
101    .into()
102}
103
104/// An attribute macro for registering on a table. Must be used with `#[table]` macro.
105///
106/// Takes as argument a type which implements `Hook<Self>` for the entity type.
107///
108/// Usage:
109///
110/// ```ignore
111/// # use atmosphere::prelude::*;
112/// # use atmosphere::hooks::*;
113/// #[table(schema = "public", name = "user")]
114/// #[hooks(MyHook)]
115/// struct User {
116///     #[sql(pk)]
117///     id: i32,
118///     #[sql(unique)]
119///     username: String,
120/// }
121///
122/// struct MyHook;
123///
124/// impl Hook<User> for MyHook {
125///     fn stage(&self) -> HookStage {
126///         todo!()
127///     }
128/// }
129/// ```
130#[proc_macro_attribute]
131pub fn hooks(attr: TokenStream, input: TokenStream) -> TokenStream {
132    let model = parse_macro_input!(input as ItemStruct);
133    let _ = parse_macro_input!(attr as hooks::Hooks);
134    quote! { #model }.into()
135}