ron2_derive/
lib.rs

1//! RON derive macros for serialization, deserialization, and schema generation.
2//!
3//! This crate provides derive macros:
4//!
5//! - `#[derive(Ron)]` - Implements ToRon, FromRon, and RonSchema (all three)
6//! - `#[derive(ToRon)]` - Serialize to RON without serde
7//! - `#[derive(FromRon)]` - Deserialize from RON without serde
8//! - `#[derive(RonSchema)]` - Generate RON schema definitions
9//!
10//! All derives share the `#[ron(...)]` attribute namespace.
11//!
12//! # Example
13//!
14//! ```ignore
15//! use ron2_derive::Ron;
16//!
17//! #[derive(Debug, PartialEq, Ron)]
18//! /// Application configuration
19//! struct AppConfig {
20//!     /// Server port
21//!     port: u16,
22//!     /// Optional hostname
23//!     #[ron(default)]
24//!     host: Option<String>,
25//! }
26//! ```
27//!
28//! # Container Attributes
29//!
30//! - `#[ron(rename = "Name")]` - Rename the type in RON output
31//! - `#[ron(rename_all = "camelCase")]` - Rename all fields (camelCase, snake_case, PascalCase, etc.)
32//! - `#[ron(deny_unknown_fields)]` - Error on unknown fields during deserialization
33//! - `#[ron(transparent)]` - Serialize/deserialize as the single inner field
34//!
35//! # Field Attributes
36//!
37//! - `#[ron(rename = "name")]` - Rename this field
38//! - `#[ron(skip)]` - Skip this field entirely
39//! - `#[ron(skip_serializing)]` - Skip during serialization only
40//! - `#[ron(skip_deserializing)]` - Skip during deserialization (use default)
41//! - `#[ron(default)]` - Use `Default::default()` if missing
42//! - `#[ron(default = "path::to::fn")]` - Use custom default function
43//! - `#[ron(flatten)]` - Flatten nested struct fields into parent
44//! - `#[ron(skip_serializing_if = "path::to::fn")]` - Skip if predicate returns true
45//! - `#[ron(explicit)]` - Require explicit `Some(...)` or `None` for Option fields
46//! - `#[ron(opt)]` - Use default if missing, skip serialization if equals default
47//!
48//! # Variant Attributes
49//!
50//! - `#[ron(rename = "Name")]` - Rename this variant
51//! - `#[ron(skip)]` - Skip this variant
52//!
53//! # Extension Behavior
54//!
55//! ## Implicit Some (Default)
56//!
57//! `Option<T>` fields accept bare values without `Some(...)`:
58//!
59//! ```ignore
60//! #[derive(FromRon)]
61//! struct Config {
62//!     name: Option<String>,
63//! }
64//! // Accepts: (name: "Alice") or (name: Some("Alice")) or (name: None)
65//! ```
66//!
67//! ## Explicit Option (`#[ron(explicit)]`)
68//!
69//! Require `Some(...)` or `None` syntax for disambiguation:
70//!
71//! ```ignore
72//! #[derive(FromRon)]
73//! struct Config {
74//!     #[ron(explicit)]
75//!     value: Option<Option<bool>>,
76//! }
77//! // Requires: (value: Some(Some(true))) or (value: Some(None)) or (value: None)
78//! ```
79//!
80//! ## Transparent Newtypes (`#[ron(transparent)]`)
81//!
82//! Single-field structs serialize as their inner type:
83//!
84//! ```ignore
85//! #[derive(FromRon, ToRon)]
86//! #[ron(transparent)]
87//! struct UserId(u64);
88//!
89//! // Serializes as: 42
90//! // Not as: UserId(42)
91//! ```
92//!
93mod attr;
94mod de;
95mod field_util;
96mod schema_codegen;
97mod ser;
98mod type_mapper;
99mod util;
100
101use attr::ContainerAttrs;
102use proc_macro::TokenStream;
103use quote::quote;
104use syn::parse_macro_input;
105
106/// Derive macro for generating RON schema definitions.
107///
108/// # Attributes
109///
110/// ## Container attributes
111///
112/// - `#[ron(rename = "Name")]` - Rename the type
113/// - `#[ron(rename_all = "camelCase")]` - Rename all fields
114///
115/// ## Field attributes
116///
117/// - `#[ron(default)]` - Mark field as optional (has a default value)
118/// - `#[ron(flatten)]` - Flatten nested struct fields into the parent
119/// - `#[ron(skip)]` - Skip this field in the schema
120/// - `#[ron(rename = "name")]` - Rename this field
121///
122/// # Schema Output
123///
124/// Schemas are written at runtime by calling `write_schemas()` on the type:
125///
126/// ```ignore
127/// use ron2::schema::RonSchema;
128///
129/// // Write to a specific directory
130/// Config::write_schemas(Some("./schemas"))?;
131///
132/// // Or use the default XDG location (~/.local/share/ron-schemas/)
133/// Config::write_schemas(None)?;
134/// ```
135///
136/// Schema files are named by their fully-qualified type path:
137/// `my_crate::config::Config` → `my_crate/config/Config.schema.ron`
138#[proc_macro_derive(RonSchema, attributes(ron))]
139pub fn derive_ron_schema(input: TokenStream) -> TokenStream {
140    let input = parse_macro_input!(input as syn::DeriveInput);
141
142    // Extract container attributes
143    let container_attrs = match ContainerAttrs::from_ast(&input.attrs) {
144        Ok(attrs) => attrs,
145        Err(err) => return err.to_compile_error().into(),
146    };
147
148    // // Compile-time schema generation
149    // let schema_dir = std::env::var("RON_SCHEMA_DIR").ok();
150    // let schema_global = std::env::var("RON_SCHEMA_GLOBAL")
151    //     .map(|v| v == "1")
152    //     .unwrap_or(false);
153
154    // if schema_dir.is_some() || schema_global {
155    //     if let Some(schema) = schema_build::build_schema(&input, &container_attrs) {
156    //         // Ignore errors during compile-time schema writing - don't fail the build
157    //         let _ = schema_build::write_schema_at_compile_time(
158    //             &input.ident,
159    //             &schema,
160    //             schema_dir.as_deref(),
161    //         );
162    //     }
163    // }
164
165    // Code generation
166    match schema_codegen::impl_ron_schema(&input, &container_attrs) {
167        Ok(tokens) => tokens.into(),
168        Err(err) => err.to_compile_error().into(),
169    }
170}
171
172/// Derive macro for serializing to RON without serde.
173///
174/// # Example
175///
176/// ```ignore
177/// use ron2_derive::ToRon;
178/// use ron2::ToRon;
179///
180/// #[derive(ToRon)]
181/// struct Point {
182///     x: f32,
183///     y: f32,
184/// }
185///
186/// let point = Point { x: 1.0, y: 2.0 };
187/// let ron = point.to_ron().unwrap();
188/// ```
189#[proc_macro_derive(ToRon, attributes(ron))]
190pub fn derive_to_ron(input: TokenStream) -> TokenStream {
191    let input = parse_macro_input!(input as syn::DeriveInput);
192
193    match ser::derive_to_ron(&input) {
194        Ok(tokens) => tokens.into(),
195        Err(err) => err.to_compile_error().into(),
196    }
197}
198
199/// Derive macro for deserializing from RON without serde.
200///
201/// # Example
202///
203/// ```ignore
204/// use ron2_derive::FromRon;
205/// use ron2::FromRon;
206///
207/// #[derive(FromRon)]
208/// struct Point {
209///     x: f32,
210///     y: f32,
211/// }
212///
213/// let point: Point = Point::from_ron("(x: 1.0, y: 2.0)").unwrap();
214/// ```
215#[proc_macro_derive(FromRon, attributes(ron))]
216pub fn derive_from_ron(input: TokenStream) -> TokenStream {
217    let input = parse_macro_input!(input as syn::DeriveInput);
218
219    match de::derive_from_ron(&input) {
220        Ok(tokens) => tokens.into(),
221        Err(err) => err.to_compile_error().into(),
222    }
223}
224
225/// Derive macro that implements ToRon, FromRon, and RonSchema.
226///
227/// This is a convenience macro that combines all three derives.
228///
229/// # Example
230///
231/// ```ignore
232/// use ron2_derive::Ron;
233///
234/// #[derive(Debug, PartialEq, Ron)]
235/// struct Point {
236///     x: f32,
237///     y: f32,
238/// }
239///
240/// let point = Point { x: 1.0, y: 2.0 };
241/// let ron = point.to_ron().unwrap();
242/// let parsed: Point = Point::from_ron(&ron).unwrap();
243/// let schema = Point::schema();
244/// ```
245#[proc_macro_derive(Ron, attributes(ron))]
246pub fn derive_ron(input: TokenStream) -> TokenStream {
247    let input = parse_macro_input!(input as syn::DeriveInput);
248
249    // Extract container attributes
250    let container_attrs = match ContainerAttrs::from_ast(&input.attrs) {
251        Ok(attrs) => attrs,
252        Err(err) => return err.to_compile_error().into(),
253    };
254
255    // // Compile-time schema generation
256    // let schema_dir = std::env::var("RON_SCHEMA_DIR").ok();
257    // let schema_global = std::env::var("RON_SCHEMA_GLOBAL")
258    //     .map(|v| v == "1")
259    //     .unwrap_or(false);
260
261    // if schema_dir.is_some() || schema_global {
262    //     if let Some(schema) = schema_build::build_schema(&input, &container_attrs) {
263    //         let _ = schema_build::write_schema_at_compile_time(
264    //             &input.ident,
265    //             &schema,
266    //             schema_dir.as_deref(),
267    //         );
268    //     }
269    // }
270
271    // Combine all three derives
272    let schema_result = schema_codegen::impl_ron_schema(&input, &container_attrs);
273    let to_ron_result = ser::derive_to_ron(&input);
274    let from_ron_result = de::derive_from_ron(&input);
275
276    match (schema_result, to_ron_result, from_ron_result) {
277        (Ok(schema), Ok(to_ron), Ok(from_ron)) => {
278            let combined = quote! {
279                #schema
280                #to_ron
281                #from_ron
282            };
283            combined.into()
284        }
285        (Err(e), _, _) => e.to_compile_error().into(),
286        (_, Err(e), _) => e.to_compile_error().into(),
287        (_, _, Err(e)) => e.to_compile_error().into(),
288    }
289}