wasm_dbms_macros/lib.rs
1#![crate_name = "wasm_dbms_macros"]
2#![crate_type = "lib"]
3#![cfg_attr(docsrs, feature(doc_cfg))]
4#![deny(clippy::print_stdout)]
5#![deny(clippy::print_stderr)]
6
7//! Runtime-agnostic procedural macros for the wasm-dbms DBMS engine.
8//!
9//! This crate provides procedural macros to automatically implement traits
10//! required by the `wasm-dbms` engine.
11//!
12//! ## Provided Derive Macros
13//!
14//! - `Encode`: Automatically implements the `Encode` trait for structs.
15//! - `Table`: Automatically implements the `TableSchema` trait and associated types.
16//! - `DatabaseSchema`: Generates `DatabaseSchema<M>` trait dispatch and `register_tables`.
17//! - `CustomDataType`: Bridge user-defined types into the `Value` system.
18
19#![doc(html_playground_url = "https://play.rust-lang.org")]
20#![doc(
21 html_favicon_url = "https://raw.githubusercontent.com/veeso/wasm-dbms/main/assets/images/cargo/logo-128.png"
22)]
23#![doc(
24 html_logo_url = "https://raw.githubusercontent.com/veeso/wasm-dbms/main/assets/images/cargo/logo-512.png"
25)]
26
27use proc_macro::TokenStream;
28use syn::{DeriveInput, parse_macro_input};
29
30mod custom_data_type;
31mod database_schema;
32mod encode;
33mod table;
34mod utils;
35
36/// Automatically implements the `Encode` trait for a struct.
37///
38/// This derive macro generates two methods required by the `Encode` trait:
39///
40/// - `fn data_size() -> DataSize`
41/// Computes the static size of the encoded type.
42/// If all fields implement `Encode::data_size()` returning
43/// `DataSize::Fixed(n)`, then the type is also considered fixed-size.
44/// Otherwise, the type is `DataSize::Dynamic`.
45///
46/// - `fn size(&self) -> MSize`
47/// Computes the runtime-encoding size of the value by summing the
48/// sizes of all fields.
49///
50/// # What the macro generates
51///
52/// Given a struct like:
53///
54/// ```rust,ignore
55/// #[derive(Encode)]
56/// struct User {
57/// id: Uint32,
58/// name: Text,
59/// }
60/// ```
61///
62/// The macro expands into:
63///
64/// ```rust,ignore
65/// impl Encode for User {
66/// const DATA_SIZE: DataSize = DataSize::Dynamic; // or DataSize::Fixed(n) if applicable
67///
68/// fn size(&self) -> MSize {
69/// self.id.size() + self.name.size()
70/// }
71///
72/// fn encode(&'_ self) -> std::borrow::Cow<'_, [u8]> {
73/// let mut encoded = Vec::with_capacity(self.size() as usize);
74/// encoded.extend_from_slice(&self.id.encode());
75/// encoded.extend_from_slice(&self.name.encode());
76/// std::borrow::Cow::Owned(encoded)
77/// }
78///
79/// fn decode(data: std::borrow::Cow<[u8]>) -> ::wasm_dbms_api::prelude::MemoryResult<Self> {
80/// let mut offset = 0;
81/// let id = Uint32::decode(std::borrow::Borrowed(&data[offset..]))?;
82/// offset += id.size() as usize;
83/// let name = Text::decode(std::borrow::Borrowed(&data[offset..]))?;
84/// offset += name.size() as usize;
85/// Ok(Self { id, name })
86/// }
87/// }
88/// ```
89/// # Requirements
90///
91/// - Each field type must implement `Encode`.
92/// - Only works on `struct`s; enums and unions are not supported.
93/// - All field identifiers must be valid Rust identifiers (no tuple structs).
94///
95/// # Notes
96///
97/// - It is intended for internal use within the `wasm-dbms` DBMS memory
98/// system.
99///
100/// # Errors
101///
102/// The macro will fail to expand if:
103///
104/// - The struct has unnamed fields (tuple struct)
105/// - A field type does not implement `Encode`
106/// - The macro is applied to a non-struct item.
107///
108/// # Example
109///
110/// ```rust,ignore
111/// #[derive(Encode, Debug, PartialEq, Eq)]
112/// struct Position {
113/// x: Int32,
114/// y: Int32,
115/// }
116///
117/// let pos = Position { x: 10.into(), y: 20.into() };
118/// assert_eq!(Position::data_size(), DataSize::Fixed(8));
119/// assert_eq!(pos.size(), 8);
120/// let encoded = pos.encode();
121/// let decoded = Position::decode(encoded).unwrap();
122/// assert_eq!(pos, decoded);
123/// ```
124#[proc_macro_derive(Encode)]
125pub fn derive_encode(input: TokenStream) -> TokenStream {
126 let input = parse_macro_input!(input as DeriveInput);
127 self::encode::encode(input, None)
128 .expect("Failed to derive `Encode`")
129 .into()
130}
131
132/// Given a struct representing a database table, automatically implements
133/// the `TableSchema` trait with all the necessary types to work with the wasm-dbms engine.
134/// So given this struct:
135///
136/// ```rust,ignore
137/// #[derive(Table, Encode)]
138/// #[table = "posts"]
139/// struct Post {
140/// #[primary_key]
141/// id: Uint32,
142/// title: Text,
143/// content: Text,
144/// #[foreign_key(entity = "User", table = "users", column = "id")]
145/// author_id: Uint32,
146/// }
147/// ```
148///
149/// What we expect as output is:
150///
151/// - To implement the `TableSchema` trait for the struct as follows:
152///
153/// ```rust,ignore
154/// impl TableSchema for Post {
155/// type Insert = PostInsertRequest;
156/// type Record = PostRecord;
157/// type Update = PostUpdateRequest;
158/// type ForeignFetcher = PostForeignFetcher;
159///
160/// fn columns() -> &'static [ColumnDef] {
161/// &[
162/// ColumnDef {
163/// name: "id",
164/// data_type: DataTypeKind::Uint32,
165/// nullable: false,
166/// primary_key: true,
167/// foreign_key: None,
168/// },
169/// ColumnDef {
170/// name: "title",
171/// data_type: DataTypeKind::Text,
172/// nullable: false,
173/// primary_key: false,
174/// foreign_key: None,
175/// },
176/// ColumnDef {
177/// name: "content",
178/// data_type: DataTypeKind::Text,
179/// nullable: false,
180/// primary_key: false,
181/// foreign_key: None,
182/// },
183/// ColumnDef {
184/// name: "user_id",
185/// data_type: DataTypeKind::Uint32,
186/// nullable: false,
187/// primary_key: false,
188/// foreign_key: Some(ForeignKeyDef {
189/// local_column: "user_id",
190/// foreign_table: "users",
191/// foreign_column: "id",
192/// }),
193/// },
194/// ]
195/// }
196///
197/// fn table_name() -> &'static str {
198/// "posts"
199/// }
200///
201/// fn primary_key() -> &'static str {
202/// "id"
203/// }
204///
205/// fn to_values(self) -> Vec<(ColumnDef, Value)> {
206/// vec![
207/// (Self::columns()[0], Value::Uint32(self.id)),
208/// (Self::columns()[1], Value::Text(self.title)),
209/// (Self::columns()[2], Value::Text(self.content)),
210/// (Self::columns()[3], Value::Uint32(self.user_id)),
211/// ]
212/// }
213/// }
214/// ```
215///
216/// - Implement the associated `Record` type
217/// - Implement the associated `InsertRecord` type
218/// - Implement the associated `UpdateRecord` type
219/// - If has foreign keys, implement the associated `ForeignFetcher`
220///
221/// So for each struct deriving `Table`, we will generate the following type. Given `${StructName}`, we will generate:
222///
223/// - `${StructName}Record` - implementing `TableRecord`
224/// - `${StructName}InsertRequest` - implementing `InsertRecord`
225/// - `${StructName}UpdateRequest` - implementing `UpdateRecord`
226/// - `${StructName}ForeignFetcher` (only if foreign keys are present)
227///
228/// Also, we will implement the `TableSchema` trait for the struct itself and derive `Encode` for `${StructName}`.
229///
230/// ## Attributes
231///
232/// The `Table` derive macro supports the following attributes:
233///
234/// - `#[table = "table_name"]`: Specifies the name of the table in the database.
235/// - `#[alignment = N]`: (optional) Specifies the alignment for the table records. Use only if you know what you are doing.
236/// - `#[primary_key]`: Marks a field as the primary key of the table.
237/// - `#[foreign_key(entity = "EntityName", table = "table_name", column = "column_name")]`: Defines a foreign key relationship.
238/// - `#[sanitizer(SanitizerType)]`: Specifies a sanitize for the field.
239/// - `#[validate(ValidatorType)]`: Specifies a validator for the field.
240///
241#[proc_macro_derive(
242 Table,
243 attributes(
244 alignment,
245 table,
246 primary_key,
247 foreign_key,
248 sanitizer,
249 validate,
250 custom_type
251 )
252)]
253pub fn derive_table(input: TokenStream) -> TokenStream {
254 let input = parse_macro_input!(input as DeriveInput);
255 self::table::table(input)
256 .expect("failed to derive `Table`")
257 .into()
258}
259
260/// Derives the [`CustomDataType`] trait and an `impl From<T> for Value` conversion
261/// for a user-defined enum or struct.
262///
263/// The type must also derive [`Encode`] (for binary serialization) and implement
264/// [`Display`](std::fmt::Display) (for the cached display string in [`CustomValue`]).
265///
266/// # Required attribute
267///
268/// - `#[type_tag = "..."]`: A unique string identifier for this custom data type.
269///
270/// # What the macro generates
271///
272/// Given a type like:
273///
274/// ```rust,ignore
275/// #[derive(Encode, CustomDataType)]
276/// #[type_tag = "status"]
277/// enum Status { Active, Inactive }
278/// ```
279///
280/// The macro expands into:
281///
282/// ```rust,ignore
283/// impl CustomDataType for Status {
284/// const TYPE_TAG: &'static str = "status";
285/// }
286///
287/// impl From<Status> for Value {
288/// fn from(val: Status) -> Value {
289/// Value::Custom(CustomValue {
290/// type_tag: "status".to_string(),
291/// encoded: Encode::encode(&val).into_owned(),
292/// display: val.to_string(),
293/// })
294/// }
295/// }
296/// ```
297///
298/// # Note
299///
300/// The user must also provide `Display`, `Default`, and `DataType` implementations
301/// for the type. This macro only bridges the custom type to the `Value` system.
302#[proc_macro_derive(CustomDataType, attributes(type_tag))]
303pub fn derive_custom_data_type(input: TokenStream) -> TokenStream {
304 let input = parse_macro_input!(input as DeriveInput);
305 custom_data_type::custom_data_type(&input)
306 .unwrap_or_else(|e| e.to_compile_error())
307 .into()
308}
309
310/// Generates a [`DatabaseSchema`] implementation that dispatches generic
311/// DBMS operations to the correct concrete table types.
312///
313/// Given a struct annotated with `#[tables(User = "users", Post = "posts")]`,
314/// this macro produces:
315///
316/// - `impl<M: MemoryProvider> DatabaseSchema<M>` with match-arm dispatch
317/// for `select`, `insert`, `delete`, `update`, `validate_insert`,
318/// `validate_update`, and `referenced_tables`.
319/// - An inherent `register_tables` method that registers all tables in a
320/// [`DbmsContext`].
321///
322/// # Example
323///
324/// ```rust,ignore
325/// #[derive(DatabaseSchema)]
326/// #[tables(User = "users", Post = "posts")]
327/// pub struct MySchema;
328///
329/// // Register tables during initialization:
330/// MySchema::register_tables(&ctx)?;
331/// ```
332///
333/// # Requirements
334///
335/// - Each type in the `#[tables(...)]` attribute must implement
336/// [`TableSchema`].
337/// - The generated types (`UserInsertRequest`, `UserUpdateRequest`,
338/// `UserRecord`, etc.) must be in scope.
339#[proc_macro_derive(DatabaseSchema, attributes(tables))]
340pub fn derive_database_schema(input: TokenStream) -> TokenStream {
341 let input = parse_macro_input!(input as DeriveInput);
342 self::database_schema::database_schema(input)
343 .expect("failed to derive `DatabaseSchema`")
344 .into()
345}