tirea_state_derive/lib.rs
1//! Derive macro for tirea-state `State` trait.
2//!
3//! This crate provides the `#[derive(State)]` macro that generates:
4//! - `{Name}Ref<'a>`: Typed state reference for reading and writing
5//! - `impl State for {Name}`: Trait implementation
6//!
7//! # Usage
8//!
9//! ```ignore
10//! use tirea_state::State;
11//! use tirea_state_derive::State;
12//!
13//! #[derive(State)]
14//! struct User {
15//! name: String,
16//! age: i64,
17//! #[tirea(nested)]
18//! profile: Profile,
19//! }
20//! ```
21
22use proc_macro::TokenStream;
23use syn::{parse_macro_input, DeriveInput};
24
25mod codegen;
26mod field_kind;
27mod parse;
28
29/// Derive the `State` trait for a struct.
30///
31/// This macro generates:
32/// - A reference type `{StructName}Ref<'a>` with typed getter and setter methods
33/// - `impl State for {StructName}`
34///
35/// # Attributes
36///
37/// ## Field Attributes
38///
39/// - `#[tirea(rename = "json_name")]`: Use a different name in JSON
40/// - `#[tirea(default = "expr")]`: Default value expression if field is missing
41/// - `#[tirea(skip)]`: Exclude from state ref (field must implement `Default`)
42/// - `#[tirea(nested)]`: Treat as nested State. **Required** for struct fields
43/// that should have their own Ref type. Without this, the field is serialized as a whole value.
44/// - `#[tirea(flatten)]`: Flatten nested struct fields into parent
45///
46/// # Examples
47///
48/// ```ignore
49/// use tirea_state::{State, StateContext};
50/// use tirea_state_derive::State;
51///
52/// #[derive(State)]
53/// struct Counter {
54/// value: i64,
55/// #[tirea(rename = "display_name")]
56/// label: String,
57/// }
58///
59/// // Usage in a StateContext
60/// let counter = ctx.state::<Counter>("counters.main");
61///
62/// // Read
63/// let value = counter.value()?;
64/// let label = counter.label()?;
65///
66/// // Write (automatically collected)
67/// counter.set_value(100);
68/// counter.set_label("Updated");
69/// counter.increment_value(1);
70/// ```
71#[proc_macro_derive(State, attributes(tirea))]
72pub fn derive_state(input: TokenStream) -> TokenStream {
73 let input = parse_macro_input!(input as DeriveInput);
74
75 match codegen::expand(&input) {
76 Ok(tokens) => tokens.into(),
77 Err(err) => err.to_compile_error().into(),
78 }
79}
80
81/// Derive the `Lattice` trait for a struct with named fields.
82///
83/// Each field must implement `Lattice`. The generated `merge` delegates to
84/// `Lattice::merge` on each field independently:
85///
86/// ```ignore
87/// use tirea_state::Lattice;
88/// use tirea_state_derive::Lattice;
89///
90/// #[derive(Clone, PartialEq, Lattice)]
91/// struct Composite {
92/// counter: GCounter,
93/// flag: Flag,
94/// }
95/// ```
96///
97/// Generic type parameters automatically get a `Lattice` bound.
98/// Errors on tuple structs, unit structs, and enums.
99#[proc_macro_derive(Lattice)]
100pub fn derive_lattice(input: TokenStream) -> TokenStream {
101 let input = parse_macro_input!(input as DeriveInput);
102
103 match codegen::lattice_impl::expand(&input) {
104 Ok(tokens) => tokens.into(),
105 Err(err) => err.to_compile_error().into(),
106 }
107}