filecaster/lib.rs
1//! # filecaster
2//!
3//! `filecaster` is a small `proc-macro` crate that provides a derive‐macro
4//! `#[derive(FromFile)]` to make it trivial to load partial configurations
5//! from files, merge them with defaults, and get a fully‐populated struct.
6//!
7//! ## What it does
8//!
9//! For any struct with named fields, `#[derive(FromFile)]` generates:
10//!
11//! 1. A companion `<YourStruct>NameFile` struct in which each field is wrapped
12//! in `Option<...>`.
13//! 2. A constructor `YourStruct::from_file(file: Option<YourStructFile>) -> YourStruct`
14//! that takes your partially‐filled file struct, fills in `None` fields
15//! with either:
16//! - an expression you supply via `#[from_file(default = ...)]`, or
17//! - `Default::default()` (requires `T: Default`)
18//! 3. An implementation of `From<Option<YourStructFile>> for YourStruct`.
19//!
20//! Because each field in the file‐struct is optional, you can deserialize
21//! e.g. JSON, YAML or TOML into it via Serde, then call `.from_file(...)`
22//! to get your final struct.
23//!
24//! ## Optional per‐field defaults
25//!
26//! Use a `#[from_file(default = <expr>)]` attribute on any field to override
27//! the fallback value. You may supply any expression valid in that struct’s
28//! context. If you omit it, the macro will require `T: Default` and call
29//! `unwrap_or_default()`.
30//!
31//! Example:
32//!
33//! ```rust
34//! use filecaster::FromFile;
35//!
36//! #[derive(Debug, Clone, FromFile)]
37//! struct AppConfig {
38//! /// If the user does not specify a host, use `"127.0.0.1"`.
39//! #[from_file(default = "127.0.0.1")]
40//! host: String,
41//!
42//! /// Number of worker threads; defaults to `4`.
43//! #[from_file(default = 4)]
44//! workers: usize,
45//!
46//! /// If not set, use `false`.
47//! auto_reload: bool, // requires `bool: Default`
48//! }
49//!
50//! let file_content = r#"
51//! {
52//! "host": "localhost"
53//! }
54//! "#;
55//!
56//! let config_from_file = serde_json::from_str::<AppConfigFile>(file_content).unwrap();
57//! // After deserializing the partial config from disk (e.g. with Serde):
58//! let cfg = AppConfig::from_file(Some(config_from_file));
59//! println!("{cfg:#?}");
60//! ```
61//!
62//! ## Feature flags
63//!
64//! - `merge`
65//! If you enable the `merge` feature, the generated `<Name>File` struct will
66//! also derive `merge::Merge`, and you can layer multiple partial files
67//! together before calling `.from_file(...)`. Any field‐level merge strategy
68//! annotations (`#[merge(...)]`) are applied automatically.
69//!
70//! ## Limitations
71//!
72//! - Only works on structs with _named_ fields (no tuple‐structs or enums).
73//! - All fields without a `#[from_file(default = ...)]` must implement `Default`.
74//!
75//! ## License
76//!
77//! MIT OR Apache-2.0
78
79mod from_file;
80
81pub(crate) use from_file::impl_from_file;
82use proc_macro::TokenStream;
83use proc_macro_error2::proc_macro_error;
84use syn::{DeriveInput, parse_macro_input};
85
86/// Implements the `FromFile` derive macro.
87///
88/// This macro processes the `#[from_file]` attribute on structs to generate
89/// code for loading data from files.
90#[proc_macro_error]
91#[proc_macro_derive(FromFile, attributes(from_file))]
92pub fn derive_from_file(input: TokenStream) -> TokenStream {
93 let inp = parse_macro_input!(input as DeriveInput);
94 impl_from_file(&inp)
95 .unwrap_or_else(|e| e.to_compile_error())
96 .into()
97}