From 9d365a959369eea4394c0dc7b78f3afb0d2598ac Mon Sep 17 00:00:00 2001 From: Kristofers Solo Date: Tue, 15 Jul 2025 19:00:50 +0300 Subject: [PATCH] docs: update libdocs --- filecaster-derive/src/lib.rs | 107 ++++++++++++++++++-------------- filecaster/src/lib.rs | 114 +++++++++++++++++++++++++++++++++++ 2 files changed, 177 insertions(+), 44 deletions(-) diff --git a/filecaster-derive/src/lib.rs b/filecaster-derive/src/lib.rs index d2ded52..d072173 100644 --- a/filecaster-derive/src/lib.rs +++ b/filecaster-derive/src/lib.rs @@ -1,39 +1,42 @@ -//! # filecaster +//! # filecaster-derive //! -//! `filecaster` is a small `proc-macro` crate that provides a derive‐macro -//! `#[derive(FromFile)]` to make it trivial to load partial configurations -//! from files, merge them with defaults, and get a fully‐populated struct. +//! `filecaster-derive` is the procedural macro crate for `filecaster`. It provides the +//! `#[derive(FromFile)]` macro, which automates the process of loading partial +//! configurations from files, merging them with default values, and constructing +//! fully-populated Rust structs. +//! +//! This crate significantly simplifies configuration management by generating +//! the necessary boilerplate code for the `FromFile` trait (defined in the +//! `filecaster` crate). //! //! ## What it does //! //! For any struct with named fields, `#[derive(FromFile)]` generates: //! -//! 1. A companion `NameFile` struct in which each field is wrapped -//! in `Option<...>`. -//! 2. A constructor `YourStruct::from_file(file: Option) -> YourStruct` -//! that takes your partially‐filled file struct, fills in `None` fields -//! with either: -//! - an expression you supply via `#[from_file(default = ...)]`, or -//! - `Default::default()` (requires `T: Default`) -//! 3. An implementation of `From> for YourStruct`. +//! 1. A companion "shadow" struct (e.g., `YourStructFile` for `YourStruct`) +//! where each field is wrapped in `Option`. This shadow struct is +//! designed for deserialization from configuration files (e.g., JSON, TOML, YAML). +//! 2. An implementation of the `FromFile` trait for your original struct. This +//! includes the `from_file` method, which takes an `Option` +//! and constructs your final `YourStruct`. It intelligently fills in `None` +//! fields with either: +//! - An expression you supply via `#[from_file(default = ...)]`. +//! - `Default::default()` (if no `default` attribute is provided, requiring `T: Default`). //! -//! Because each field in the file‐struct is optional, you can deserialize -//! e.g. JSON, YAML or TOML into it via Serde, then call `.from_file(...)` -//! to get your final struct. -//! -//! ## Optional per‐field defaults +//! ## Optional per-field defaults //! //! Use a `#[from_file(default = )]` attribute on any field to override //! the fallback value. You may supply any expression valid in that struct’s -//! context. If you omit it, the macro will require `T: Default` and call -//! `unwrap_or_default()`. +//! context. If you omit it, the macro will require the field's type to implement +//! `Default` and will call `Default::default()`. //! -//! Example: +//! ## Example //! //! ```rust //! use filecaster::FromFile; +//! use serde::{Deserialize, Serialize}; //! -//! #[derive(Debug, Clone, FromFile)] +//! #[derive(Debug, Clone, PartialEq, FromFile, Serialize, Deserialize)] //! struct AppConfig { //! /// If the user does not specify a host, use `"127.0.0.1"`. //! #[from_file(default = "127.0.0.1")] @@ -43,38 +46,54 @@ //! #[from_file(default = 4)] //! workers: usize, //! -//! /// If not set, use `false`. -//! auto_reload: bool, // requires `bool: Default` +//! /// If not set, use `false`. Requires `bool: Default`. +//! auto_reload: bool, //! } //! -//! let file_content = r#" -//! { -//! "host": "localhost" -//! } -//! "#; +//! fn main() { +//! // Simulate file content (e.g., from a JSON file) +//! let file_content = r#"{ +//! "host": "localhost", +//! "workers": 8 +//! }"#; //! -//! let config_from_file = serde_json::from_str::(file_content).unwrap(); -//! // After deserializing the partial config from disk (e.g. with Serde): -//! let cfg = AppConfig::from_file(Some(config_from_file)); -//! println!("{cfg:#?}"); +//! // The `AppConfigFile` struct is automatically generated by `#[derive(FromFile)]`. +//! // It has all fields as `Option`. +//! let partial_config: AppConfigFile = serde_json::from_str(file_content).unwrap(); +//! +//! // Use the generated `from_file` method to get the final config. +//! // Default values are applied for missing fields. +//! let config = AppConfig::from_file(Some(partial_config)); +//! +//! assert_eq!(config.host, "localhost"); +//! assert_eq!(config.workers, 8); +//! assert_eq!(config.auto_reload, false); // `Default::default()` for bool is `false` +//! +//! println!("Final Config: {:#?}", config); +//! +//! // Example with no file content (all defaults) +//! let default_config = AppConfig::from_file(None); +//! assert_eq!(default_config.host, "127.0.0.1"); +//! assert_eq!(default_config.workers, 4); +//! assert_eq!(default_config.auto_reload, false); +//! } //! ``` //! //! ## Feature flags //! -//! - `merge` -//! If you enable the `merge` feature, the generated `File` struct will -//! also derive `merge::Merge`, and you can layer multiple partial files -//! together before calling `.from_file(...)`. Any field‐level merge strategy -//! annotations (`#[merge(...)]`) are applied automatically. +//! - `serde`: Enables `serde` serialization/deserialization support for the +//! generated shadow structs. This is typically required to deserialize +//! your configuration from file formats like JSON, TOML, or YAML. +//! - `merge`: If enabled, the generated shadow struct will also derive +//! `merge::Merge`. This allows you to layer multiple partial configuration +//! files together before calling `.from_file(...)`. Any field-level +//! `#[merge(...)]` attributes will be respected. //! //! ## Limitations -//! -//! - Only works on structs with _named_ fields (no tuple‐structs or enums). -//! - All fields without a `#[from_file(default = ...)]` must implement `Default`. -//! -//! ## License -//! -//! MIT OR Apache-2.0 +//! +//! - Only works on structs with _named_ fields (no tuple structs or enums). +//! - All fields without a `#[from_file(default = ...)]` attribute must +//! implement the `Default` trait. mod from_file; diff --git a/filecaster/src/lib.rs b/filecaster/src/lib.rs index d110217..89d4ec5 100644 --- a/filecaster/src/lib.rs +++ b/filecaster/src/lib.rs @@ -1,10 +1,124 @@ +//! # filecaster +//! +//! `filecaster` provides the core `FromFile` trait, which is used in conjunction with the +//! `filecaster-derive` crate to enable automatic deserialization and merging of +//! configuration from various file formats into Rust structs. +//! +//! This crate defines the fundamental interface for types that can be constructed +//! from an optional "shadow" representation, typically deserialized from a file. +//! The `filecaster-derive` crate provides a procedural macro to automatically +//! implement this trait for your structs, handling default values and merging logic. +//! +//! ## How it works +//! +//! The `FromFile` trait defines how a final configuration struct (`Self`) can be +//! constructed from an optional intermediate "shadow" struct (`Self::Shadow`). +//! The `filecaster-derive` macro generates this `Shadow` struct and the +//! `from_file` implementation for your configuration types. +//! +//! When you derive `FromFile` for a struct, `filecaster-derive` creates a +//! corresponding `YourStructFile` (the `Shadow` type) where all fields are +//! wrapped in `Option`. This `YourStructFile` can then be deserialized +//! from a file (e.g., JSON, TOML, YAML) using `serde`. +//! +//! The `from_file` method then takes this `Option` and +//! constructs your final `YourStruct`, applying default values for any fields +//! that were `None` in the `YourStructFile`. +//! +//! ## Example +//! +//! While the `FromFile` trait is implemented via the `filecaster-derive` macro, +//! here's a conceptual example of how it's used: +//! +//! ```rust,ignore +//! use filecaster::FromFile; +//! use serde::{Deserialize, Serialize}; +//! +//! // This struct would typically have `#[derive(FromFile)]` +//! // from the `filecaster-derive` crate. +//! #[derive(Debug, Default, PartialEq, Serialize, Deserialize)] +//! struct AppConfig { +//! host: String, +//! port: u16, +//! enabled: bool, +//! } +//! +//! // The `Shadow` type is automatically generated by `filecaster-derive` +//! // and would look something like this: +//! #[derive(Debug, Default, PartialEq, Serialize, Deserialize)] +//! struct AppConfigFile { +//! host: Option, +//! port: Option, +//! enabled: Option, +//! } +//! +//! // The `FromFile` implementation is also automatically generated. +//! // For demonstration, here's a simplified manual implementation: +//! impl FromFile for AppConfig { +//! type Shadow = AppConfigFile; +//! +//! fn from_file(file: Option) -> Self { +//! let file = file.unwrap_or_default(); +//! AppConfig { +//! host: file.host.unwrap_or_else(|| "127.0.0.1".to_string()), +//! port: file.port.unwrap_or(8080), +//! enabled: file.enabled.unwrap_or(true), +//! } +//! } +//! } +//! +//! fn main() { +//! // Simulate deserializing from a file +//! let file_content = r#"{ "host": "localhost", "port": 3000 }"#; +//! let partial_config: AppConfigFile = serde_json::from_str(file_content).unwrap(); +//! +//! // Construct the final config using the FromFile trait +//! let config = AppConfig::from_file(Some(partial_config)); +//! +//! assert_eq!(config.host, "localhost"); +//! assert_eq!(config.port, 3000); +//! assert_eq!(config.enabled, true); // Default value applied +//! +//! println!("Final Config: {:?}", config); +//! } +//! ``` +//! +//! ## Feature flags +//! +//! - `derive`: Enables the `filecaster-derive` crate, allowing you to use `#[derive(FromFile)]`. +//! - `serde`: Enables `serde` serialization/deserialization support for the `FromFile` trait. +//! - `merge`: Enables `merge` crate support, allowing for merging multiple partial configurations. + pub use filecaster_derive::FromFile; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// Marker for types that can be built from an [`Option`] produced by the macro. +/// +/// The `FromFile` trait is the core interface for `filecaster`. It defines how a +/// final configuration struct (`Self`) can be constructed from an optional +/// intermediate "shadow" struct (`Self::Shadow`). +/// +/// The `Self::Shadow` associated type represents the intermediate structure +/// that is typically deserialized from a configuration file. All fields in +/// `Self::Shadow` are usually `Option`, allowing for partial configurations. +/// +/// The `from_file` method takes an `Option` and is responsible +/// for producing a fully-populated `Self` instance. This involves applying +/// default values for any fields that were `None` in the `Shadow` instance. +/// +/// This trait is primarily designed to be implemented automatically via the +/// `#[derive(FromFile)]` procedural macro provided by the `filecaster-derive` crate. pub trait FromFile: Sized { + /// The intermediate "shadow" type that is typically deserialized from a file. + /// + /// This type usually mirrors the main struct but with all fields wrapped in `Option`. type Shadow: Default; + /// Constructs the final struct from an optional shadow representation. + /// + /// If `file` is `None`, a default `Shadow` instance should be used. + /// The implementation should then populate `Self` by taking values from + /// `file` where present, and applying defaults otherwise. fn from_file(file: Option) -> Self; }