cfgmatic_source/lib.rs
1//! # cfgmatic-source
2//!
3//! Configuration sources (file, env, remote) for cfgmatic framework.
4//!
5//! This crate provides a unified interface for loading configuration
6//! from various sources: files, environment variables, and remote URLs.
7//!
8//! ## Architecture
9//!
10//! ```mermaid
11//! graph TB
12//! subgraph "Public API"
13//! LIB[lib.rs Facade]
14//! PRELUDE[prelude]
15//! FUNC[Convenience Functions]
16//! end
17//!
18//! subgraph "Config Layer"
19//! OPT[LoaderOptions]
20//! CTX[LoadContext]
21//! end
22//!
23//! subgraph "Application Layer"
24//! LOADER[Loader]
25//! DETECTOR[FormatDetector]
26//! end
27//!
28//! subgraph "Infrastructure Layer"
29//! FS[FileSource]
30//! ES[EnvSource]
31//! MS[MemorySource]
32//! CS[CompositeSource]
33//! REG[SourceRegistry]
34//! end
35//!
36//! subgraph "Domain Layer"
37//! SRC[Source Trait]
38//! KIND[SourceKind]
39//! META[SourceMetadata]
40//! FMT[Format]
41//! RAW[RawContent]
42//! PARSED[ParsedContent]
43//! ERR[SourceError]
44//! end
45//!
46//! LIB --> PRELUDE
47//! LIB --> FUNC
48//! LIB --> OPT
49//! LIB --> LOADER
50//! LIB --> SRC
51//!
52//! FUNC --> LOADER
53//! LOADER --> REG
54//! REG --> FS
55//! REG --> ES
56//! REG --> MS
57//! REG --> CS
58//!
59//! FS --> SRC
60//! ES --> SRC
61//! MS --> SRC
62//! CS --> SRC
63//!
64//! SRC --> RAW
65//! RAW --> FMT
66//! FMT --> PARSED
67//! DETECTOR --> FMT
68//!
69//! OPT --> CTX
70//! CTX --> LOADER
71//! ```
72//!
73//! ## Layer Dependencies
74//!
75//! The architecture follows the Onion (Layered) pattern with dependencies
76//! pointing inward:
77//!
78//! ```text
79//! +------------------------------------------+
80//! | lib.rs (Facade) |
81//! +------------------------------------------+
82//! |
83//! +------------------------------------------+
84//! | Config Layer |
85//! | Options, Context, Builders |
86//! +------------------------------------------+
87//! |
88//! +------------------------------------------+
89//! | Infrastructure Layer |
90//! | FileSource, EnvSource, MemorySource, |
91//! | CompositeSource, Registry |
92//! +------------------------------------------+
93//! |
94//! +------------------------------------------+
95//! | Application Layer |
96//! | Loader, FormatDetector |
97//! +------------------------------------------+
98//! |
99//! +------------------------------------------+
100//! | Domain Layer |
101//! | Source, SourceKind, Format, |
102//! | RawContent, ParsedContent, Error |
103//! +------------------------------------------+
104//! ```
105//!
106//! ## Feature Flags
107//!
108//! | Feature | Description | Default |
109//! |-----------|------------------------------------|---------|
110//! | `file` | File-based sources | Yes |
111//! | `env` | Environment variable sources | No |
112//! | `remote` | Remote HTTP/HTTPS sources | No |
113//! | `toml` | TOML format support | Yes |
114//! | `json` | JSON format support | Yes |
115//! | `yaml` | YAML format support | No |
116//! | `async` | Async source loading | No |
117//! | `watch` | File watching support | No |
118//!
119//! ## Quick Start
120//!
121//! ### Load from a file
122//!
123//! ```rust,ignore
124//! use cfgmatic_source::prelude::*;
125//!
126//! let content = FileSource::new("config.toml")
127//! .load()?
128//! .parse_as::<MyConfig>()?;
129//! ```
130//!
131//! ### Load from environment
132//!
133//! ```rust,ignore
134//! use cfgmatic_source::prelude::*;
135//!
136//! let content = EnvSource::new("APP")
137//! .load()?
138//! .parse_as::<MyConfig>()?;
139//! ```
140//!
141//! ### Combine multiple sources
142//!
143//! ```rust,ignore
144//! use cfgmatic_source::prelude::*;
145//!
146//! let source = CompositeSource::new()
147//! .with_source(FileSource::new("config.toml"), SourcePriority::High)
148//! .with_source(EnvSource::new("APP"), SourcePriority::Low);
149//!
150//! let content = source.load()?.parse_as::<MyConfig>()?;
151//! ```
152//!
153//! ### Use convenience functions
154//!
155//! ```rust,ignore
156//! use cfgmatic_source::{load, load_from_file, load_from_env};
157//!
158//! // Auto-detect source and load
159//! let config: MyConfig = load()?;
160//!
161//! // Load from specific file
162//! let config: MyConfig = load_from_file("config.toml")?;
163//!
164//! // Load from environment
165//! let config: MyConfig = load_from_env("APP")?;
166//! ```
167
168#![warn(missing_docs)]
169// Allow some pedantic lints that are too noisy
170#![allow(clippy::missing_const_for_fn)]
171#![allow(clippy::doc_markdown)]
172#![allow(clippy::needless_pass_by_value)]
173#![allow(clippy::option_if_let_else)]
174#![allow(clippy::use_self)]
175#![allow(clippy::derivable_impls)]
176#![allow(clippy::trivially_copy_pass_by_ref)]
177#![allow(clippy::redundant_closure_for_method_calls)]
178#![allow(clippy::unused_self)]
179#![allow(clippy::cast_precision_loss)]
180#![allow(clippy::unnecessary_lazy_evaluations)]
181#![allow(clippy::uninlined_format_args)]
182#![allow(clippy::single_match_else)]
183#![allow(clippy::should_implement_trait)]
184#![allow(clippy::needless_continue)]
185#![allow(clippy::missing_fields_in_debug)]
186#![allow(clippy::struct_excessive_bools)]
187#![allow(clippy::cast_possible_truncation)]
188#![allow(clippy::map_unwrap_or)]
189#![allow(clippy::collapsible_if)]
190
191pub mod constants;
192pub mod domain;
193pub mod infrastructure;
194
195// Placeholder modules for future implementation
196/// Config layer - options and context.
197pub mod config;
198
199/// Application layer - services.
200pub mod application;
201
202// Re-exports from domain layer
203pub use domain::{
204 Format, ParsedContent, RawContent, Result, Source, SourceError, SourceKind, SourceMetadata,
205};
206
207// Re-exports from infrastructure layer
208pub use infrastructure::{
209 CompositeSource, CompositeSourceBuilder, MemorySource, MemorySourceBuilder, SourcePriority,
210};
211
212#[cfg(feature = "file")]
213pub use infrastructure::{FileSource, FileSourceBuilder};
214
215#[cfg(feature = "env")]
216pub use infrastructure::{EnvConfig, EnvSource, EnvSourceBuilder};
217
218// Re-exports from config layer
219pub use config::{
220 LoadOptions, LoadOptionsBuilder, MergeStrategy, SourceConfig, SourceConfigBuilder,
221};
222
223// Re-exports from application layer
224pub use application::{
225 LoadResult, Loader, LoaderBuilder, SourceCoordinator, SourceCoordinatorBuilder,
226};
227
228// Re-exports from constants
229pub use constants::{
230 DEFAULT_APP_NAME, DEFAULT_CONFIG_BASE_NAME, DEFAULT_DEBOUNCE_MS, DEFAULT_ENV_PREFIX,
231 DEFAULT_EXTENSIONS, DEFAULT_REMOTE_TIMEOUT_SECS, ENV_KEY_SEPARATOR, JSON_POINTER_ROOT,
232 MAX_REMOTE_RETRIES, MAX_SEARCH_DEPTH,
233};
234
235pub mod prelude {
236 //! Common imports for cfgmatic-source.
237 //!
238 //! This module re-exports the most commonly used types and traits
239 //! for convenience.
240
241 // Domain layer
242 pub use crate::domain::{
243 Format, ParsedContent, RawContent, Result, Source, SourceError, SourceKind, SourceMetadata,
244 };
245
246 // Infrastructure layer
247 pub use crate::infrastructure::{
248 CompositeSource, CompositeSourceBuilder, MemorySource, MemorySourceBuilder, SourcePriority,
249 };
250
251 #[cfg(feature = "file")]
252 pub use crate::infrastructure::{FileSource, FileSourceBuilder};
253
254 #[cfg(feature = "env")]
255 pub use crate::infrastructure::{EnvConfig, EnvSource, EnvSourceBuilder};
256
257 // Config layer
258 pub use crate::config::{
259 LoadOptions, LoadOptionsBuilder, MergeStrategy, SourceConfig, SourceConfigBuilder,
260 };
261
262 // Application layer
263 pub use crate::application::{
264 LoadResult, Loader, LoaderBuilder, SourceCoordinator, SourceCoordinatorBuilder,
265 };
266
267 // Constants
268 pub use crate::constants::{
269 DEFAULT_APP_NAME, DEFAULT_CONFIG_BASE_NAME, DEFAULT_DEBOUNCE_MS, DEFAULT_ENV_PREFIX,
270 DEFAULT_EXTENSIONS, DEFAULT_REMOTE_TIMEOUT_SECS, ENV_KEY_SEPARATOR, JSON_POINTER_ROOT,
271 MAX_REMOTE_RETRIES, MAX_SEARCH_DEPTH,
272 };
273}
274
275// =============================================================================
276// Convenience Functions
277// =============================================================================
278
279/// Load configuration from auto-detected source.
280///
281/// This function attempts to load configuration from the following sources
282/// in order of priority:
283///
284/// 1. Environment variables (if `env` feature is enabled)
285/// 2. Configuration file in current directory (if `file` feature is enabled)
286///
287/// # Errors
288///
289/// Returns [`SourceError`] if no configuration source can be found or
290/// if loading fails.
291///
292/// # Example
293///
294/// ```rust,ignore
295/// use cfgmatic_source::load;
296/// use serde::Deserialize;
297///
298/// #[derive(Deserialize)]
299/// struct Config {
300/// name: String,
301/// port: u16,
302/// }
303///
304/// let config: Config = load()?;
305/// ```
306#[cfg(feature = "file")]
307pub fn load<T>() -> crate::domain::Result<T>
308where
309 T: serde::de::DeserializeOwned,
310{
311 // Try to find and load config file
312 let source = FileSource::new("config.toml");
313 let raw = source.load_raw()?;
314 let content = raw
315 .as_str()
316 .map_err(|e| SourceError::serialization(&e.to_string()))?;
317 let format = source.detect_format().unwrap_or(Format::Toml);
318 format.parse_as(content.as_ref())
319}
320
321/// Load configuration from a specific file.
322///
323/// # Errors
324///
325/// Returns [`SourceError`] if the file cannot be read or parsed.
326///
327/// # Example
328///
329/// ```rust,ignore
330/// use cfgmatic_source::load_from_file;
331///
332/// let config: MyConfig = load_from_file("config.toml")?;
333/// ```
334#[cfg(feature = "file")]
335pub fn load_from_file<T, P>(path: P) -> crate::domain::Result<T>
336where
337 T: serde::de::DeserializeOwned,
338 P: AsRef<std::path::Path> + Into<std::path::PathBuf>,
339{
340 let source = FileSource::new(path);
341 let raw = source.load_raw()?;
342 let content = raw
343 .as_str()
344 .map_err(|e| SourceError::serialization(&e.to_string()))?;
345 let format = source.detect_format().unwrap_or(Format::Toml);
346 format.parse_as(content.as_ref())
347}
348
349/// Load configuration from environment variables.
350///
351/// # Errors
352///
353/// Returns [`SourceError`] if environment variables cannot be read or parsed.
354///
355/// # Example
356///
357/// ```rust,ignore
358/// use cfgmatic_source::load_from_env;
359///
360/// // Reads APP_NAME, APP_PORT, etc.
361/// let config: MyConfig = load_from_env("APP")?;
362/// ```
363#[cfg(feature = "env")]
364pub fn load_from_env<T>(prefix: &str) -> crate::domain::Result<T>
365where
366 T: serde::de::DeserializeOwned,
367{
368 let source = EnvSource::with_prefix(prefix);
369 let raw = source.load_raw()?;
370 let content = raw
371 .as_str()
372 .map_err(|e| SourceError::serialization(&e.to_string()))?;
373 // EnvSource always produces JSON
374 Format::Json.parse_as(content.as_ref())
375}
376
377/// Load configuration from memory (useful for testing).
378///
379/// # Errors
380///
381/// Returns [`SourceError`] if the content cannot be parsed.
382///
383/// # Example
384///
385/// ```rust,ignore
386/// use cfgmatic_source::load_from_memory;
387///
388/// let toml_content = r#"
389/// name = "test"
390/// port = 8080
391/// "#;
392///
393/// let config: MyConfig = load_from_memory(toml_content, Format::Toml)?;
394/// ```
395pub fn load_from_memory<T>(content: &str, format: Format) -> crate::domain::Result<T>
396where
397 T: serde::de::DeserializeOwned,
398{
399 format.parse_as(content)
400}