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}