Skip to main content

derive_defs/
lib.rs

1//! # derive-defs
2//!
3//! Library for generating derive preset macros from TOML configuration.
4//!
5//! This crate provides functionality to parse TOML configuration files
6//! and generate proc-macro code for derive attribute presets.
7//!
8//! ## Overview
9//!
10//! `derive-defs` allows you to define named sets of derive attributes
11//! through a declarative TOML configuration. This is useful when you have
12//! common derive patterns that you use repeatedly across your codebase.
13//!
14//! ## Quick Start
15//!
16//! 1. Create a `derive_defs.toml` configuration file:
17//!
18//! ```toml
19//! [defs.serialization]
20//! traits = ["Clone", "Serialize", "Deserialize"]
21//! attrs = ['#[serde(rename_all = "camelCase")]']
22//!
23//! [defs.model]
24//! traits = ["Debug", "Clone", "PartialEq"]
25//! ```
26//!
27//! 2. Add to your `build.rs`:
28//!
29//! ```rust,no_run
30//! derive_defs::generate("derive_defs.toml")
31//!     .expect("Failed to generate derive defs");
32//! ```
33//!
34//! 3. Use the generated macros in your code:
35//!
36//! ```rust,ignore
37//! use my_crate_macros::*;
38//!
39//! #[serialization]
40//! struct User {
41//!     name: String,
42//!     age: u32,
43//! }
44//! ```
45//!
46//! ## Features
47//!
48//! - **Declarative Configuration**: Define derive presets in TOML, not code
49//! - **Inheritance**: Bundles can extend other bundles via `extends`
50//! - **Cross-file Imports**: Split configuration across multiple files
51//! - **Runtime Modification**: Use `omit`/`add` to modify bundles at use site
52//!
53//! ## TOML Configuration
54//!
55//! ### Basic Syntax
56//!
57//! ```toml
58//! [defs.<name>]
59//! traits = ["Trait1", "Trait2"]  # List of derive traits
60//! attrs = ["#[attr]"]             # Additional attributes
61//! extends = "<parent>"            # Inheritance (optional)
62//! ```
63//!
64//! ### Inheritance
65//!
66//! ```toml
67//! [defs.base]
68//! traits = ["Debug", "Clone"]
69//!
70//! [defs.value_object]
71//! extends = "base"
72//! traits = ["PartialEq", "Eq", "Hash"]
73//! # Result: Debug, Clone, PartialEq, Eq, Hash
74//! ```
75//!
76//! ### Cross-file Imports
77//!
78//! ```toml
79//! [includes]
80//! common = "shared/common_defs.toml"
81//!
82//! [defs.api_response]
83//! extends = "common.serialization"
84//! traits = ["Default"]
85//! ```
86//!
87//! ## API Usage
88//!
89//! ### Attribute Modifiers
90//!
91//! ```rust,ignore
92//! // Basic usage
93//! #[serialization]
94//! struct User { ... }
95//!
96//! // Exclude Clone from the bundle
97//! #[serialization(omit(Clone))]
98//! struct Session { ... }
99//!
100//! // Add Default and Hash to the bundle
101//! #[serialization(add(Default, Hash))]
102//! struct Config { ... }
103//!
104//! // Exclude serde attributes
105//! #[serialization(omit_attrs(serde))]
106//! struct Internal { ... }
107//!
108//! // Combination
109//! #[serialization(omit(Clone), add(Copy))]
110//! struct Flags { ... }
111//! ```
112//!
113//! ## Error Handling
114//!
115//! The crate provides clear error messages at `build.rs` time:
116//!
117//! - Circular inheritance detection
118//! - Undefined parent references
119//! - Missing include files
120//! - Duplicate definition names
121
122#![cfg_attr(docsrs, feature(doc_cfg))]
123#![doc(
124    html_logo_url = "https://raw.githubusercontent.com/opentc/rust-derive-defs/main/assets/logo.png"
125)]
126#![doc(
127    html_favicon_url = "https://raw.githubusercontent.com/opentc/rust-derive-defs/main/assets/favicon.ico"
128)]
129
130use std::path::Path;
131
132pub mod codegen;
133pub mod includes;
134pub mod parser;
135pub mod resolver;
136pub mod validation;
137
138/// Errors that can occur during code generation.
139#[derive(Debug, thiserror::Error)]
140#[non_exhaustive]
141pub enum Error {
142    /// Failed to read the configuration file.
143    #[error("failed to read config file: {0}")]
144    ConfigRead(#[source] std::io::Error),
145
146    /// Failed to parse TOML.
147    #[error("failed to parse TOML: {0}")]
148    TomlParse(#[source] toml::de::Error),
149
150    /// Validation error.
151    #[error("validation error: {0}")]
152    Validation(String),
153
154    /// Failed to write generated code.
155    #[error("failed to write generated code: {0}")]
156    CodegenWrite(#[source] std::io::Error),
157
158    /// Circular dependency detected.
159    #[error("circular extends detected: {0}")]
160    CircularDependency(String),
161
162    /// Reference to undefined definition.
163    #[error("def \"{def}\" extends \"{parent}\" which is not defined")]
164    UndefinedParent {
165        /// The name of the definition with the invalid extends.
166        def: String,
167        /// The name of the undefined parent.
168        parent: String,
169    },
170
171    /// Include file not found.
172    #[error("include file \"{path}\" not found (resolved to {resolved})")]
173    IncludeNotFound {
174        /// The path as specified in the config.
175        path: String,
176        /// The resolved absolute path.
177        resolved: String,
178    },
179}
180
181/// Result type alias for this crate.
182pub type Result<T> = std::result::Result<T, Error>;
183
184/// Generate proc-macro code from a TOML configuration file.
185///
186/// This function parses the TOML configuration at `path` and generates
187/// proc-macro code in the `OUT_DIR` directory. The generated code should
188/// be included in your proc-macro crate.
189///
190/// # Errors
191///
192/// Returns an error if:
193/// - The configuration file cannot be read
194/// - The TOML is invalid
195/// - There are validation errors (circular dependencies, undefined parents, etc.)
196/// - The output file cannot be written
197///
198/// # Example
199///
200/// ```no_run
201/// derive_defs::generate("derive_defs.toml")
202///     .expect("Failed to generate derive defs");
203/// ```
204///
205/// # Panics
206///
207/// This function panics if the `OUT_DIR` environment variable is not set.
208/// This should only happen if called outside of a build script context.
209pub fn generate<P: AsRef<Path>>(path: P) -> Result<()> {
210    let config = parser::parse_file(&path)?;
211    let config = includes::resolve_includes(config, &path)?;
212    let resolved = resolver::resolve(&config)?;
213    codegen::generate(resolved)
214}
215
216/// Generate proc-macro code from a TOML configuration file with custom output path.
217///
218/// Similar to [`generate`], but allows specifying a custom output path instead
219/// of using `OUT_DIR`.
220///
221/// # Errors
222///
223/// Same as [`generate`].
224///
225/// # Example
226///
227/// ```no_run
228/// derive_defs::generate_to("derive_defs.toml", "/tmp/output.rs")
229///     .expect("Failed to generate derive defs");
230/// ```
231pub fn generate_to<P: AsRef<Path>, O: AsRef<Path>>(path: P, output: O) -> Result<()> {
232    let config = parser::parse_file(&path)?;
233    let config = includes::resolve_includes(config, &path)?;
234    let resolved = resolver::resolve(&config)?;
235    codegen::generate_to(resolved, output)
236}
237
238/// Generate proc-macro code from an already resolved configuration.
239///
240/// This is useful when you want to programmatically build a configuration
241/// and generate code without reading from a file.
242///
243/// # Errors
244///
245/// Returns an error if writing the output file fails.
246///
247/// # Example
248///
249/// ```no_run
250/// use derive_defs::resolver::{ResolvedConfig, ResolvedDef};
251/// use std::collections::HashMap;
252///
253/// let mut defs = HashMap::new();
254/// defs.insert("model".to_string(), ResolvedDef {
255///     name: "model".to_string(),
256///     traits: vec!["Debug".to_string(), "Clone".to_string()],
257///     attrs: vec![],
258/// });
259///
260/// let config = ResolvedConfig { defs };
261/// derive_defs::generate_from_resolved(config, "/tmp/output.rs").unwrap();
262/// ```
263pub fn generate_from_resolved<O: AsRef<Path>>(
264    resolved: resolver::ResolvedConfig,
265    output: O,
266) -> Result<()> {
267    codegen::generate_to(resolved, output)
268}
269
270/// Common imports for convenience.
271pub mod prelude {
272    pub use crate::{Error, Result, generate, generate_to};
273}