filecaster_derive/
lib.rs

1//! # filecaster-derive
2//!
3//! `filecaster-derive` is the procedural macro crate for `filecaster`. It provides the
4//! `#[derive(FromFile)]` macro, which automates the process of loading partial
5//! configurations from files, merging them with default values, and constructing
6//! fully-populated Rust structs.
7//!
8//! This crate significantly simplifies configuration management by generating
9//! the necessary boilerplate code for the `FromFile` trait (defined in the
10//! `filecaster` crate).
11//!
12//! ## What it does
13//!
14//! For any struct with named fields, `#[derive(FromFile)]` generates:
15//!
16//! 1.  A companion "shadow" struct (e.g., `YourStructFile` for `YourStruct`)
17//!     where each field is wrapped in `Option<T>`. This shadow struct is
18//!     designed for deserialization from configuration files (e.g., JSON, TOML, YAML).
19//! 2.  An implementation of the `FromFile` trait for your original struct. This
20//!     includes the `from_file` method, which takes an `Option<YourStructFile>`
21//!     and constructs your final `YourStruct`. It intelligently fills in `None`
22//!     fields with either:
23//!     -   An expression you supply via `#[from_file(default = ...)]`.
24//!     -   `Default::default()` (if no `default` attribute is provided, requiring `T: Default`).
25//!
26//! ## Optional per-field defaults
27//!
28//! Use a `#[from_file(default = <expr>)]` attribute on any field to override
29//! the fallback value. You may supply any expression valid in that struct’s
30//! context. If you omit it, the macro will require the field's type to implement
31//! `Default` and will call `Default::default()`.
32//!
33//! ## Example
34//!
35//! ```rust
36//! use filecaster::FromFile;
37//!
38//! #[derive(Debug, Clone, PartialEq, FromFile)]
39//! struct AppConfig {
40//!     /// If the user does not specify a host, use `"127.0.0.1"`.
41//!     #[from_file(default = "127.0.0.1")]
42//!     host: String,
43//!
44//!     /// Port number; defaults to `8080`.
45//!     #[from_file(default = 8080)]
46//!     port: u16,
47//!
48//!     /// If not set, use `false`. Requires `bool: Default`.
49//!     auto_reload: bool,
50//! }
51//!
52//! fn example() {
53//!     // Simulate file content (e.g., from a JSON file)
54//!     let file_content = r#"{ "host": "localhost", "port": 3000 }"#;
55//!
56//!     // The `AppConfigFile` struct is automatically generated by `#[derive(FromFile)]`.
57//!     // It has all fields as `Option<T>`.
58//!     let partial_config: AppConfigFile = serde_json::from_str(file_content).unwrap();
59//!     let partial_config2 = partial_config.clone();
60//!
61//!     // Use the generated `from_file` method to get the final config.
62//!     // Default values are applied for missing fields.
63//!     let config = AppConfig::from_file(Some(partial_config));
64//!     // or
65//!     let config: AppConfig = partial_config2.into();
66//!
67//!     assert_eq!(config.host, "localhost");
68//!     assert_eq!(config.port, 3000);
69//!     assert_eq!(config.auto_reload, false); // `Default::default()` for bool is `false`
70//!
71//!     println!("Final Config: {:#?}", config);
72//!
73//!     // Example with no file content (all defaults)
74//!     let default_config = AppConfig::from_file(None);
75//!     assert_eq!(default_config.host, "127.0.0.1");
76//!     assert_eq!(default_config.port, 8080);
77//!     assert_eq!(default_config.auto_reload, false);
78//! }
79//! ```
80//!
81//! ## Feature flags
82//!
83//! -   `serde`: Enables `serde` serialization/deserialization support for the
84//!     generated shadow structs. This is typically required to deserialize
85//!     your configuration from file formats like JSON, TOML, or YAML.
86//! -   `merge`: If enabled, the generated shadow struct will also derive
87//!     `merge::Merge`. This allows you to layer multiple partial configuration
88//!     files together before calling `.from_file(...)`. Any field-level
89//!     `#[merge(...)]` attributes will be respected.
90//!
91//! ## Limitations
92//!
93//! -   Only works on structs with _named_ fields (no tuple structs or enums).
94//! -   All fields without a `#[from_file(default = ...)]` attribute must
95//!     implement the `Default` trait.
96
97mod from_file;
98
99pub(crate) use from_file::impl_from_file;
100use proc_macro::TokenStream;
101use proc_macro_error2::proc_macro_error;
102use syn::{DeriveInput, parse_macro_input};
103
104/// Implements the [`FromFile`] trait.
105///
106/// This macro processes the `#[from_file]` attribute on structs to generate
107/// code for loading data from files.
108#[proc_macro_error]
109#[proc_macro_derive(FromFile, attributes(from_file))]
110pub fn derive_from_file(input: TokenStream) -> TokenStream {
111    let inp = parse_macro_input!(input as DeriveInput);
112    impl_from_file(&inp)
113        .unwrap_or_else(|e| e.to_compile_error())
114        .into()
115}