cot_macros/
lib.rs

1mod admin;
2mod api_response_enum;
3mod cache;
4mod dbtest;
5mod form;
6mod from_request;
7mod main_fn;
8mod model;
9mod query;
10mod select_as_form_field;
11mod select_choice;
12
13use darling::Error;
14use darling::ast::NestedMeta;
15use proc_macro::TokenStream;
16use proc_macro_crate::crate_name;
17use quote::quote;
18use syn::{DeriveInput, ItemFn, parse_macro_input};
19
20use crate::admin::impl_admin_model_for_struct;
21use crate::api_response_enum::{impl_api_operation_response_for_enum, impl_into_response_for_enum};
22use crate::dbtest::fn_to_dbtest;
23use crate::form::impl_form_for_struct;
24use crate::from_request::impl_from_request_head_for_struct;
25use crate::main_fn::{fn_to_cot_e2e_test, fn_to_cot_main, fn_to_cot_test};
26use crate::model::impl_model_for_struct;
27use crate::query::{Query, query_to_tokens};
28use crate::select_as_form_field::impl_select_as_form_field_for_enum;
29use crate::select_choice::impl_select_choice_for_enum;
30
31#[proc_macro_derive(Form, attributes(form))]
32pub fn derive_form(input: TokenStream) -> TokenStream {
33    let ast = parse_macro_input!(input as DeriveInput);
34    let token_stream = impl_form_for_struct(&ast);
35    token_stream.into()
36}
37
38#[proc_macro_derive(AdminModel)]
39pub fn derive_admin_model(input: TokenStream) -> TokenStream {
40    let ast = parse_macro_input!(input as DeriveInput);
41    let token_stream = impl_admin_model_for_struct(&ast);
42    token_stream.into()
43}
44
45/// Implement the [`Model`] trait for a struct.
46///
47/// This macro will generate an implementation of the [`Model`] trait for the
48/// given named struct. Note that all the fields of the struct **must**
49/// implement the [`DatabaseField`] trait.
50///
51/// # Model types
52///
53/// The model type can be specified using the `model_type` parameter. The model
54/// type can be one of the following:
55///
56/// * `application` (default): The model represents an actual table in a
57///   normally running instance of the application.
58/// ```
59/// use cot::db::model;
60///
61/// #[model(model_type = "application")]
62/// // This is equivalent to:
63/// // #[model]
64/// struct User {
65///     #[model(primary_key)]
66///     id: i32,
67///     username: String,
68/// }
69/// ```
70/// * `migration`: The model represents a table that is used for migrations. The
71///   model name must be prefixed with an underscore. You shouldn't ever need to
72///   use this type; the migration engine will generate the migration model
73///   types for you.
74///
75///   Migration models have two major uses. The first is so that the migration
76///   engine uses knows what was the state of model at the time the last
77///   migration was generated. This allows the engine to automatically detect
78///   the changes and generate the necessary migration code. The second use is
79///   to allow custom code in the migrations: you might want the migration to
80///   fill in some data, for instance. You can't use the actual model for this
81///   because the model might have changed since the migration was generated.
82///   You can, however, use the migration model, which will always represent
83///   the state of the model at the time the migration runs.
84/// ```
85/// // In a migration file
86/// use cot::db::model;
87///
88/// #[model(model_type = "migration")]
89/// struct _User {
90///     #[model(primary_key)]
91///     id: i32,
92///     username: String,
93/// }
94/// ```
95/// * `internal`: The model represents a table that is used internally by Cot
96///   (e.g. the `cot__migrations` table, storing which migrations have been
97///   applied). They are ignored by the migration generator and should never be
98///   used outside Cot code.
99/// ```
100/// use cot::db::model;
101///
102/// #[model(model_type = "internal")]
103/// struct CotMigrations {
104///     #[model(primary_key)]
105///     id: i32,
106///     app: String,
107///     name: String,
108/// }
109/// ```
110///
111/// [`Model`]: trait.Model.html
112/// [`DatabaseField`]: trait.DatabaseField.html
113#[proc_macro_attribute]
114pub fn model(args: TokenStream, input: TokenStream) -> TokenStream {
115    let attr_args = match NestedMeta::parse_meta_list(args.into()) {
116        Ok(v) => v,
117        Err(e) => {
118            return TokenStream::from(Error::from(e).write_errors());
119        }
120    };
121    let mut ast = parse_macro_input!(input as DeriveInput);
122    let token_stream = impl_model_for_struct(&attr_args, &mut ast);
123    token_stream.into()
124}
125
126// Simple derive macro that doesn't do anything useful but defines the `model`
127// helper attribute.
128//
129// It's used internally by the `model` macro to avoid having to remove the
130// `#[model]` attributes from the struct fields. Typically, the Rust compiler
131// complains when there are unused attributes, so we define this macro to avoid
132// that. This way, we don't have to remove the `#[model]` attributes from the
133// struct fields, while other derive macros (such as `AdminModel`) can still use
134// them.
135#[proc_macro_derive(ModelHelper, attributes(model))]
136pub fn derive_model_helper(_item: TokenStream) -> TokenStream {
137    TokenStream::new()
138}
139
140#[proc_macro]
141pub fn query(input: TokenStream) -> TokenStream {
142    let query_input = parse_macro_input!(input as Query);
143    query_to_tokens(query_input).into()
144}
145
146#[proc_macro_attribute]
147pub fn dbtest(_args: TokenStream, input: TokenStream) -> TokenStream {
148    let fn_input = parse_macro_input!(input as ItemFn);
149    fn_to_dbtest(fn_input)
150        .unwrap_or_else(syn::Error::into_compile_error)
151        .into()
152}
153
154#[proc_macro_attribute]
155pub fn main(_args: TokenStream, input: TokenStream) -> TokenStream {
156    let fn_input = parse_macro_input!(input as ItemFn);
157    fn_to_cot_main(fn_input)
158        .unwrap_or_else(syn::Error::into_compile_error)
159        .into()
160}
161
162#[proc_macro_attribute]
163pub fn cachetest(_args: TokenStream, input: TokenStream) -> TokenStream {
164    let fn_input = parse_macro_input!(input as ItemFn);
165    cache::fn_to_cache_test(&fn_input).into()
166}
167
168/// An attribute macro that defines an `async` test function for a Cot-powered
169/// app.
170///
171/// This is pretty much an equivalent to `#[tokio::test]` provided so that you
172/// don't have to declare `tokio` as a dependency in your tests.
173///
174/// # Examples
175///
176/// ```no_run
177/// use cot::test::TestDatabase;
178///
179/// #[cot::test]
180/// async fn test_db() {
181///     let db = TestDatabase::new_sqlite().await.unwrap();
182///     // do something with the database
183///     db.cleanup().await.unwrap();
184/// }
185/// ```
186#[proc_macro_attribute]
187pub fn test(_args: TokenStream, input: TokenStream) -> TokenStream {
188    let fn_input = parse_macro_input!(input as ItemFn);
189    fn_to_cot_test(&fn_input).into()
190}
191
192#[proc_macro_attribute]
193pub fn e2e_test(_args: TokenStream, input: TokenStream) -> TokenStream {
194    let fn_input = parse_macro_input!(input as ItemFn);
195    fn_to_cot_e2e_test(&fn_input).into()
196}
197
198pub(crate) fn cot_ident() -> proc_macro2::TokenStream {
199    let cot_crate = crate_name("cot").expect("cot is not present in `Cargo.toml`");
200    match cot_crate {
201        proc_macro_crate::FoundCrate::Itself => {
202            quote! { ::cot }
203        }
204        proc_macro_crate::FoundCrate::Name(name) => {
205            let ident = syn::Ident::new(&name, proc_macro2::Span::call_site());
206            quote! { ::#ident }
207        }
208    }
209}
210
211#[proc_macro_derive(FromRequestHead)]
212pub fn derive_from_request_head(input: TokenStream) -> TokenStream {
213    let ast = parse_macro_input!(input as DeriveInput);
214    let token_stream = impl_from_request_head_for_struct(&ast);
215    token_stream.into()
216}
217
218#[proc_macro_derive(SelectChoice, attributes(select_choice))]
219pub fn derive_select_choice(input: TokenStream) -> TokenStream {
220    let ast = syn::parse_macro_input!(input as DeriveInput);
221    let token_stream = impl_select_choice_for_enum(&ast);
222    token_stream.into()
223}
224
225#[proc_macro_derive(SelectAsFormField)]
226pub fn derive_select_as_form_field(input: TokenStream) -> TokenStream {
227    let ast = syn::parse_macro_input!(input as DeriveInput);
228    let token_stream = impl_select_as_form_field_for_enum(&ast);
229    token_stream.into()
230}
231
232#[proc_macro_derive(IntoResponse)]
233pub fn derive_into_response(input: TokenStream) -> TokenStream {
234    let ast = parse_macro_input!(input as DeriveInput);
235    impl_into_response_for_enum(&ast).into()
236}
237
238#[proc_macro_derive(ApiOperationResponse)]
239pub fn derive_api_operation_response(input: TokenStream) -> TokenStream {
240    let ast = parse_macro_input!(input as DeriveInput);
241    impl_api_operation_response_for_enum(&ast).into()
242}
243
244/// The `Template` derive macro and its `template()` attribute.
245///
246/// Please see our [template guide](https://cot.rs/guide/latest/templates/) and [askama's book](
247/// https://askama.readthedocs.io/en/stable/creating_templates.html) for more information.
248#[proc_macro_derive(Template, attributes(template))]
249pub fn derive_template(input: TokenStream) -> TokenStream {
250    askama_derive::derive_template(input.into(), import_askama).into()
251}
252
253/// A macro attribute to write custom filters for askama templates.
254///
255/// Please see [askama's book](https://askama.readthedocs.io/en/stable/filters.html#custom-filters)
256/// for more information.
257#[proc_macro_attribute]
258pub fn filter_fn(attr: TokenStream, item: TokenStream) -> TokenStream {
259    askama_derive::derive_filter_fn(attr.into(), item.into(), import_askama).into()
260}
261
262fn import_askama() -> proc_macro2::TokenStream {
263    let cot = cot_ident();
264    quote!(use #cot::__private::askama;)
265}