1mod column_enum;
2mod expand;
3mod from_row;
4mod from_row_expand;
5mod fts5;
6mod parse;
7mod paths;
8mod relational;
9mod view;
10
11use proc_macro::TokenStream;
12use syn::parse::Parser;
13use syn::{parse_macro_input, Expr, ItemStruct, Lit, Meta};
14
15#[proc_macro_attribute]
16pub fn table(attr: TokenStream, item: TokenStream) -> TokenStream {
17 let mut item_struct = parse_macro_input!(item as ItemStruct);
18
19 let (table_name, strict) = match parse_table_attrs(attr) {
20 Ok(attrs) => attrs,
21 Err(e) => return e.to_compile_error().into(),
22 };
23
24 let indexes = match parse::parse_index_attrs(&mut item_struct) {
26 Ok(idxs) => idxs,
27 Err(e) => return e.to_compile_error().into(),
28 };
29
30 let views = match parse_view_attrs(&mut item_struct) {
32 Ok(v) => v,
33 Err(e) => return e.to_compile_error().into(),
34 };
35
36 let columns = match parse::parse_struct(&item_struct) {
37 Ok(cols) => cols,
38 Err(e) => return e.to_compile_error().into(),
39 };
40
41 let input = parse::TableInput {
42 table_name,
43 strict,
44 struct_name: item_struct.ident.clone(),
45 columns,
46 indexes,
47 };
48
49 let expanded = expand::expand(&input);
50 let columns_mod = expand::expand_columns_module(&input);
51 let builder_methods = expand::expand_builder_methods(&input);
52 let vis = &item_struct.vis;
53 let view_structs: Vec<proc_macro2::TokenStream> = views
54 .iter()
55 .map(|v| view::generate_view_struct(v, &input.columns, vis))
56 .collect();
57 let clean_struct = parse::strip_column_attrs(item_struct);
58
59 let output = quote::quote! {
60 #clean_struct
61 #expanded
62 #builder_methods
63 #columns_mod
64 #(#view_structs)*
65 };
66
67 output.into()
68}
69
70#[proc_macro_attribute]
85pub fn fts5_table(attr: TokenStream, item: TokenStream) -> TokenStream {
86 match expand_fts5_table(attr, item) {
87 Ok(tokens) => tokens,
88 Err(e) => e.to_compile_error().into(),
89 }
90}
91
92fn expand_fts5_table(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
93 let item_struct = syn::parse::<ItemStruct>(item)?;
94 let parser = syn::punctuated::Punctuated::<Meta, syn::Token![,]>::parse_terminated;
95 let metas: Vec<Meta> = parser.parse(attr)?.into_iter().collect();
96 let attrs = fts5::parse_attrs(&metas)?;
97
98 for struct_attr in &item_struct.attrs {
99 if struct_attr.path().is_ident("index") || struct_attr.path().is_ident("unique_index") {
100 return Err(syn::Error::new_spanned(
101 struct_attr,
102 "virtual tables cannot declare indexes; remove it from #[fts5_table]",
103 ));
104 }
105 }
106
107 let columns = parse::parse_struct(&item_struct)?;
108 if columns.is_empty() {
109 return Err(syn::Error::new_spanned(
110 &item_struct,
111 "an fts5 table needs at least one column",
112 ));
113 }
114
115 fts5::check_columns(&columns)?;
116 let table_name = attrs.table_name()?;
117 let input = parse::TableInput {
118 table_name,
119 strict: false,
120 struct_name: item_struct.ident.clone(),
121 columns,
122 indexes: Vec::new(),
123 };
124
125 let schema_impl = fts5::expand(&attrs, &input);
126 let columns_mod = expand::expand_columns_module(&input);
127 let builder_methods = expand::expand_builder_methods(&input);
128 let clean_struct = parse::strip_column_attrs(item_struct);
129
130 Ok(
131 quote::quote! {
132 #clean_struct
133 #schema_impl
134 #builder_methods
135 #columns_mod
136 }
137 .into(),
138 )
139}
140
141fn parse_view_attrs(item: &mut ItemStruct) -> syn::Result<Vec<view::ViewInput>> {
142 let mut views = Vec::new();
143 let mut remaining_attrs = Vec::new();
144 for attr in &item.attrs {
145 if attr.path().is_ident("view") {
146 views.push(view::parse_view_attr(attr)?);
147 } else {
148 remaining_attrs.push(attr.clone());
149 }
150 }
151 item.attrs = remaining_attrs;
152 Ok(views)
153}
154
155fn parse_table_attrs(attr: TokenStream) -> syn::Result<(String, bool)> {
158 let parser = syn::punctuated::Punctuated::<Meta, syn::Token![,]>::parse_terminated;
159 let metas = parser.parse(attr)?;
160
161 let mut table_name = None;
162 let mut strict = false; for meta in &metas {
165 let Meta::NameValue(nv) = meta else {
166 return Err(syn::Error::new_spanned(
167 meta,
168 "expected name = value pairs in #[table(...)]",
169 ));
170 };
171 if nv.path.is_ident("name") {
172 let Expr::Lit(expr_lit) = &nv.value else {
173 return Err(syn::Error::new_spanned(
174 &nv.value,
175 "expected a string literal",
176 ));
177 };
178 let Lit::Str(s) = &expr_lit.lit else {
179 return Err(syn::Error::new_spanned(
180 &expr_lit.lit,
181 "expected a string literal",
182 ));
183 };
184 table_name = Some(s.value());
185 } else if nv.path.is_ident("strict") {
186 let Expr::Lit(expr_lit) = &nv.value else {
187 return Err(syn::Error::new_spanned(
188 &nv.value,
189 "expected a bool literal",
190 ));
191 };
192 let Lit::Bool(b) = &expr_lit.lit else {
193 return Err(syn::Error::new_spanned(
194 &expr_lit.lit,
195 "expected true or false",
196 ));
197 };
198 strict = b.value();
199 } else {
200 return Err(syn::Error::new_spanned(
201 &nv.path,
202 "unknown attribute, expected `name` or `strict`",
203 ));
204 }
205 }
206
207 let Some(name) = table_name else {
208 return Err(syn::Error::new(
209 proc_macro2::Span::call_site(),
210 "missing `name` in #[table(name = \"...\")]",
211 ));
212 };
213
214 Ok((name, strict))
215}
216
217#[proc_macro_derive(ColumnEnum)]
218pub fn derive_column_enum(input: TokenStream) -> TokenStream {
219 let input = parse_macro_input!(input as syn::DeriveInput);
220 match column_enum::expand_column_enum(&input) {
221 Ok(tokens) => tokens.into(),
222 Err(e) => e.to_compile_error().into(),
223 }
224}
225
226#[proc_macro_derive(FromRow, attributes(from_row))]
227pub fn derive_from_row(input: TokenStream) -> TokenStream {
228 let input = parse_macro_input!(input as syn::DeriveInput);
229 match from_row::expand_from_row(&input) {
230 Ok(tokens) => tokens.into(),
231 Err(e) => e.to_compile_error().into(),
232 }
233}
234
235#[proc_macro_derive(Relational, attributes(relational, has_many, belongs_to, many_to_many))]
236pub fn derive_relational(input: TokenStream) -> TokenStream {
237 let input = parse_macro_input!(input as syn::DeriveInput);
238 match relational::parse_relational(&input) {
239 Ok(parsed) => expand::expand_relational(&parsed).into(),
240 Err(e) => e.to_compile_error().into(),
241 }
242}