yooso_macro/lib.rs
1//! Macros for the Yooso project.
2
3mod collection_fields;
4#[macro_use]
5mod inner_macro_meta;
6mod macro_collection;
7mod macro_database;
8mod macro_launch;
9
10use proc_macro::TokenStream;
11use syn::parse_macro_input;
12
13use crate::macro_collection::CollectionMeta;
14
15/// The [launch] attribute marks the async function that builds a `Yooso`
16/// application and turns it into the program entry point.
17///
18/// # Example
19///
20/// ```no_run
21/// use yooso::Yooso;
22///
23/// #[yooso::launch]
24/// async fn yooso() -> Yooso {
25/// Yooso::build().await
26/// }
27/// ```
28#[proc_macro_attribute]
29pub fn launch(_args: TokenStream, input: TokenStream) -> TokenStream {
30 let function = parse_macro_input!(input as syn::ItemFn);
31 macro_launch::launch(function).into()
32}
33
34/// The [database] attribute marks a struct as a database definition. The
35/// struct will be converted into a connection pool.
36///
37/// # Example
38///
39/// ```
40/// use yooso_macro::database;
41///
42/// #[database(".yooso/meta.sqlite")]
43/// struct MetaDB;
44/// ```
45#[proc_macro_attribute]
46pub fn database(args: TokenStream, input: TokenStream) -> TokenStream {
47 let name = parse_macro_input!(args as syn::LitStr);
48 let item = parse_macro_input!(input as syn::ItemStruct);
49 macro_database::database(name, item).into()
50}
51
52/// The [collection] attribute marks a struct as a table definition. The
53/// struct will be converted into an SQL-synced collection of rows. This
54/// attribute is smart enough to convert Rust types into SQL types.
55///
56/// # Example
57///
58/// ```
59/// use yooso_macro::{database,collection};
60/// use uuid::Uuid;
61///
62/// #[database(".yooso/meta.sqlite")]
63/// struct MetaDB;
64///
65/// #[collection(db = MetaDB, table = "entities")]
66/// struct EntityTable {
67/// #[primary]
68/// id: Uuid,
69/// created_at: i32,
70/// }
71/// ```
72///
73/// The example above will produce the table with the following schema in
74/// the `MetaDB` database (at path `.yooso/meta.sqlite`).
75///
76/// | CID | Name | Type | Not Null | Default | PK
77/// | --- | ------------ | ------- | -------- | ------- | ---
78/// | 0 | `id` | TEXT | YES | NULL | KEY
79/// | 1 | `created_at` | INTEGER | YES | NULL |
80///
81/// # Attributes
82///
83/// ### `#[primary]`
84///
85/// Marks the primary key column of the table. This is required for all
86/// collections and must be a single field.
87///
88/// ### `#[unique(...)]`
89///
90/// Marks a unique constraint on the table. This is equivalent to
91/// [SQL indecess](https://www.sqlitetutorial.net/sqlite-index/) and can be used to
92/// enforce an invariant on the collection.
93///
94/// ```no_run
95/// use yooso_macro::{database,collection};
96/// use uuid::Uuid;
97///
98/// #[database(".yooso/meta.sqlite")]
99/// struct MetaDB;
100///
101/// #[collection(db = MetaDB, table = "fields")]
102/// #[unique(component_id, field_name)]
103/// #[unique(component_id, position)]
104/// pub struct ComponentFieldTable {
105/// /// Snowflake value. This is the unique identifier of the field.
106/// #[primary] pub id: Uuid,
107/// /// The ID of the component that this field belongs to.
108/// pub component_id: Uuid,
109/// /// The name of the field.
110/// pub field_name: String,
111/// /// The order index of the field.
112/// pub position: i32,
113/// }
114/// ```
115///
116/// The example above will produce the table with the following schema in
117/// the `MetaDB` database (at path `.yooso/meta.sqlite`).
118///
119/// | CID | Name | Type | Not Null | Default | PK
120/// | --- | -------------- | ------- | -------- | ------- | ---
121/// | 0 | `id` | TEXT | YES | NULL | KEY
122/// | 1 | `component_id` | TEXT | YES | NULL |
123/// | 2 | `field_name` | TEXT | YES | NULL |
124/// | 3 | `position` | INTEGER | YES | NULL |
125///
126/// ### `#[default(...)]`
127///
128/// Currently unused, reserved for future use.
129#[proc_macro_attribute]
130pub fn collection(args: TokenStream, input: TokenStream) -> TokenStream {
131 let meta = parse_macro_input!(args as CollectionMeta);
132 let mut item = parse_macro_input!(input as syn::ItemStruct);
133 let unique_attributes = consume_attributes_by_name(&mut item.attrs, "unique")
134 .iter().map(|f| f.meta.clone()).collect::<Vec<_>>();
135
136 macro_collection::collection(meta, item, unique_attributes).into()
137}
138
139/// Helper method to consume attribute by name and return a vector of all
140/// attributes. For example, this consumes all `#[unique]` attributes and
141/// returns a vector of their arguments.
142pub(crate) fn consume_attributes_by_name(
143 attributes: &mut Vec<syn::Attribute>,
144 name: &str,
145) -> Vec<syn::Attribute> {
146 let mut result = Vec::new();
147 let mut i = 0;
148 while i < attributes.len() {
149 if attributes[i].path().is_ident(name) {
150 result.push(attributes.remove(i));
151 } else {
152 i += 1;
153 }
154 }
155 result
156}