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}