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}