Skip to main content

agent_first_data/document/format/
mod.rs

1//! Format detection and backend selection.
2
3#[allow(unused_imports)]
4use crate::document::{DocumentError, DocumentResult, Value};
5use std::path::Path;
6
7#[cfg(feature = "dotenv")]
8pub mod dotenv;
9#[cfg(feature = "ini")]
10pub mod ini;
11// JSON is a core (non-optional) dependency of agent-first-data, so this
12// backend always compiles — unlike toml/yaml/dotenv/ini below.
13pub mod json;
14#[cfg(feature = "toml")]
15pub mod toml;
16#[cfg(feature = "yaml")]
17pub mod yaml;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum Format {
21    Json,
22    Toml,
23    Yaml,
24    Dotenv,
25    Ini,
26}
27
28impl Format {
29    /// Detect format from file extension.
30    pub fn detect(path: &Path) -> Option<Self> {
31        let file_name = path.file_name().and_then(|name| name.to_str())?;
32        let file_name_lower = file_name.to_lowercase();
33        if file_name_lower == ".env"
34            || file_name_lower.starts_with(".env.")
35            || path
36                .extension()
37                .and_then(|ext| ext.to_str())
38                .is_some_and(|ext| ext.eq_ignore_ascii_case("env"))
39        {
40            return Some(Format::Dotenv);
41        }
42
43        path.extension().and_then(|ext| ext.to_str()).and_then(|s| {
44            match s.to_lowercase().as_str() {
45                "json" => Some(Format::Json),
46                "toml" => Some(Format::Toml),
47                "yaml" | "yml" => Some(Format::Yaml),
48                "ini" => Some(Format::Ini),
49                _ => None,
50            }
51        })
52    }
53
54    /// Load a config file in the detected format.
55    pub fn load(&self, content: &str) -> DocumentResult<Value> {
56        match self {
57            Format::Json => json::load(content),
58
59            #[cfg(feature = "toml")]
60            Format::Toml => toml::load(content),
61            #[cfg(not(feature = "toml"))]
62            Format::Toml => Err(DocumentError::UnsupportedOperation {
63                format: "TOML".to_string(),
64                operation: "load".to_string(),
65                detail: "requires Cargo feature `toml`".to_string(),
66            }),
67
68            #[cfg(feature = "yaml")]
69            Format::Yaml => yaml::load(content),
70            #[cfg(not(feature = "yaml"))]
71            Format::Yaml => Err(DocumentError::UnsupportedOperation {
72                format: "YAML".to_string(),
73                operation: "load".to_string(),
74                detail: "requires Cargo feature `yaml`".to_string(),
75            }),
76
77            #[cfg(feature = "dotenv")]
78            Format::Dotenv => dotenv::load(content),
79            #[cfg(not(feature = "dotenv"))]
80            Format::Dotenv => Err(DocumentError::UnsupportedOperation {
81                format: "dotenv".to_string(),
82                operation: "load".to_string(),
83                detail: "requires Cargo feature `dotenv`".to_string(),
84            }),
85
86            #[cfg(feature = "ini")]
87            Format::Ini => ini::load(content),
88            #[cfg(not(feature = "ini"))]
89            Format::Ini => Err(DocumentError::UnsupportedOperation {
90                format: "INI".to_string(),
91                operation: "load".to_string(),
92                detail: "requires Cargo feature `ini`".to_string(),
93            }),
94        }
95    }
96
97    /// Save a config in the target format.
98    pub fn save(&self, value: &Value) -> DocumentResult<String> {
99        match self {
100            Format::Json => json::save(value),
101
102            #[cfg(feature = "toml")]
103            Format::Toml => toml::save(value),
104            #[cfg(not(feature = "toml"))]
105            Format::Toml => Err(DocumentError::UnsupportedOperation {
106                format: "TOML".to_string(),
107                operation: "save".to_string(),
108                detail: "requires Cargo feature `toml`".to_string(),
109            }),
110
111            #[cfg(feature = "yaml")]
112            Format::Yaml => yaml::save(value),
113            #[cfg(not(feature = "yaml"))]
114            Format::Yaml => Err(DocumentError::UnsupportedOperation {
115                format: "YAML".to_string(),
116                operation: "save".to_string(),
117                detail: "requires Cargo feature `yaml`".to_string(),
118            }),
119
120            #[cfg(feature = "dotenv")]
121            Format::Dotenv => dotenv::save(value),
122            #[cfg(not(feature = "dotenv"))]
123            Format::Dotenv => Err(DocumentError::UnsupportedOperation {
124                format: "dotenv".to_string(),
125                operation: "save".to_string(),
126                detail: "requires Cargo feature `dotenv`".to_string(),
127            }),
128
129            #[cfg(feature = "ini")]
130            Format::Ini => ini::save(value),
131            #[cfg(not(feature = "ini"))]
132            Format::Ini => Err(DocumentError::UnsupportedOperation {
133                format: "INI".to_string(),
134                operation: "save".to_string(),
135                detail: "requires Cargo feature `ini`".to_string(),
136            }),
137        }
138    }
139
140    /// Reject mutation before a backend-specific value is changed or written.
141    pub fn ensure_writable(&self, _operation: &str) -> DocumentResult<()> {
142        Ok(())
143    }
144}
145
146#[cfg(feature = "dotenv")]
147pub use dotenv::load as load_dotenv;
148pub use json::{load as load_json, save as save_json};
149#[cfg(feature = "toml")]
150pub use toml::{load as load_toml, save as save_toml};
151#[cfg(feature = "yaml")]
152pub use yaml::{load as load_yaml, save as save_yaml};