filecaster 0.2.3

Procedural macro to derive configuration from files, with optional merging capabilities.
Documentation
//! # 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<T>`. This `YourStructFile` can then be deserialized
//! from a file (e.g., JSON, TOML, YAML) using `serde`.
//!
//! The `from_file` method then takes this `Option<YourStructFile>` 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,
//!     auto_reload: 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<String>,
//!     port: Option<u16>,
//!     auto_reload: Option<bool>,
//! }
//!
//! // 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::Shadow>) -> 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),
//!             auto_reload: file.auto_reload.unwrap_or(true),
//!         }
//!     }
//! }
//!
//! fn example() {
//!     // 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.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.port, 8080);
//!     assert_eq!(default_config.auto_reload, false);
//! }
//! ```
//!
//! ## 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<Shadow>`] 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<T>`, allowing for partial configurations.
///
/// The `from_file` method takes an `Option<Self::Shadow>` 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<T>`.
    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::Shadow>) -> Self;
}

#[cfg(not(feature = "serde"))]
impl<T> FromFile for T
where
    T: Default,
{
    type Shadow = T;
    fn from_file(file: Option<Self>) -> Self {
        file.unwrap_or_default()
    }
}

#[cfg(feature = "serde")]
impl<T> FromFile for T
where
    T: Default + Serialize + for<'de> Deserialize<'de>,
{
    type Shadow = T;
    fn from_file(file: Option<Self>) -> Self {
        file.unwrap_or_default()
    }
}