Skip to main content

prefer/
lib.rs

1//! # prefer
2//!
3//! A lightweight library for managing application configurations with support for multiple file formats.
4//!
5//! `prefer` helps you manage application configurations while providing users the flexibility
6//! of using whatever configuration format fits their needs.
7//!
8//! ## no_std Support
9//!
10//! This crate supports `no_std` environments with `alloc`. The core types (`ConfigValue`, `FromValue`,
11//! `ValueVisitor`) work without std. Enable the `std` feature for file I/O, async loading, and format parsers.
12//!
13//! ## Features
14//!
15//! - **no_std compatible**: Core types work with just `alloc`
16//! - **Format-agnostic**: Supports JSON, JSON5, YAML, TOML, INI, and XML (with `std`)
17//! - **Automatic discovery**: Searches standard system paths for configuration files (with `std`)
18//! - **Async by design**: Non-blocking operations for file I/O (with `std`)
19//! - **File watching**: Monitor configuration files for changes (with `std`)
20//! - **Dot-notation access**: Access nested values with `"auth.username"`
21//! - **No serde required**: Uses a lightweight `FromValue` trait instead
22//!
23//! ## Examples
24//!
25//! ```no_run
26//! # #[cfg(feature = "std")]
27//! # {
28//! use prefer::load;
29//!
30//! #[tokio::main]
31//! async fn main() -> prefer::Result<()> {
32//!     // Load configuration from any supported format
33//!     let config = load("settings").await?;
34//!
35//!     // Access values using dot notation
36//!     let username: String = config.get("auth.username")?;
37//!     println!("Username: {}", username);
38//!
39//!     Ok(())
40//! }
41//! # }
42//! ```
43
44#![cfg_attr(not(feature = "std"), no_std)]
45
46#[cfg(not(feature = "std"))]
47extern crate alloc;
48
49#[cfg(feature = "std")]
50pub mod builder;
51#[cfg(feature = "std")]
52pub mod config;
53#[cfg(feature = "std")]
54pub mod discovery;
55pub mod error;
56#[cfg(feature = "std")]
57pub mod events;
58#[cfg(feature = "std")]
59pub mod formatter;
60#[cfg(feature = "std")]
61pub mod loader;
62#[cfg(feature = "std")]
63pub mod registry;
64#[cfg(feature = "std")]
65pub mod source;
66pub mod value;
67pub mod visitor;
68#[cfg(feature = "std")]
69pub mod watch;
70
71// Core types (always available)
72pub use error::{Error, Result};
73pub use value::{ConfigValue, FromValue};
74pub use visitor::{SeqAccess, ValueVisitor};
75
76// std-dependent types
77#[cfg(feature = "std")]
78pub use builder::ConfigBuilder;
79#[cfg(feature = "std")]
80pub use config::Config;
81#[cfg(feature = "std")]
82#[allow(deprecated)]
83pub use source::{EnvSource, FileSource, LayeredSource, MemorySource, Source};
84
85// Re-export the derive macro when the feature is enabled
86#[cfg(feature = "derive")]
87pub use prefer_derive::FromValue;
88
89/// Load a configuration by identifier.
90///
91/// Routes through the plugin registry: finds a loader that can handle the
92/// identifier, loads the raw content, finds a formatter that can parse
93/// the content, and returns the parsed `Config`.
94///
95/// For bare names (e.g., `"myapp"`), the built-in `FileLoader` searches
96/// standard system paths. For scheme-based identifiers (e.g.,
97/// `"postgres://..."`), a `DbLoader` must be registered via inventory.
98///
99/// # Examples
100///
101/// ```no_run
102/// # #[cfg(feature = "std")]
103/// # {
104/// use prefer::load;
105///
106/// #[tokio::main]
107/// async fn main() -> prefer::Result<()> {
108///     let config = load("myapp").await?;
109///     let value: String = config.get("some.key")?;
110///     Ok(())
111/// }
112/// # }
113/// ```
114#[cfg(feature = "std")]
115pub async fn load(identifier: &str) -> Result<Config> {
116    let loader =
117        registry::find_loader(identifier).ok_or(Error::NoLoaderFound(identifier.to_string()))?;
118
119    let formatters = registry::collect_formatters();
120
121    let result = loader.load(identifier, &formatters).await?;
122
123    Ok(Config::with_metadata(
124        result.data,
125        result.source,
126        loader.name().to_string(),
127    ))
128}
129
130/// Watch a configuration source for changes.
131///
132/// Routes through the plugin registry to find a loader that supports
133/// watching, then returns a receiver that yields new `Config` instances
134/// when the source changes.
135///
136/// # Examples
137///
138/// ```no_run
139/// # #[cfg(feature = "std")]
140/// # {
141/// use prefer::watch;
142///
143/// #[tokio::main]
144/// async fn main() -> prefer::Result<()> {
145///     let mut receiver = watch("myapp").await?;
146///
147///     while let Some(config) = receiver.recv().await {
148///         println!("Configuration updated!");
149///         let value: String = config.get("some.key")?;
150///     }
151///
152///     Ok(())
153/// }
154/// # }
155/// ```
156#[cfg(feature = "std")]
157pub async fn watch(identifier: &str) -> Result<tokio::sync::mpsc::Receiver<Config>> {
158    let loader =
159        registry::find_loader(identifier).ok_or(Error::NoLoaderFound(identifier.to_string()))?;
160
161    loader
162        .watch(identifier)
163        .await?
164        .ok_or(Error::WatchNotSupported(identifier.to_string()))
165}