agent_first_data/document/format/
mod.rs1#[allow(unused_imports)]
4use crate::document::{DocumentError, DocumentResult, Value};
5use std::path::Path;
6
7#[cfg(feature = "dotenv")]
8pub mod dotenv;
9pub mod frontmatter;
12#[cfg(feature = "ini")]
13pub mod ini;
14pub mod json;
17#[cfg(feature = "toml")]
18pub mod toml;
19#[cfg(feature = "yaml")]
20pub mod yaml;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum Format {
24 Json,
25 Toml,
26 Yaml,
27 Dotenv,
28 Ini,
29 TomlFrontmatter,
32 YamlFrontmatter,
35}
36
37impl Format {
38 #[must_use]
40 pub const fn name(self) -> &'static str {
41 match self {
42 Self::Json => "JSON",
43 Self::Toml => "TOML",
44 Self::Yaml => "YAML",
45 Self::Dotenv => "dotenv",
46 Self::Ini => "INI",
47 Self::TomlFrontmatter => "TOML frontmatter",
48 Self::YamlFrontmatter => "YAML frontmatter",
49 }
50 }
51
52 pub fn detect(path: &Path) -> Option<Self> {
54 let file_name = path.file_name().and_then(|name| name.to_str())?;
55 let file_name_lower = file_name.to_lowercase();
56 if file_name_lower == ".env"
57 || file_name_lower.starts_with(".env.")
58 || path
59 .extension()
60 .and_then(|ext| ext.to_str())
61 .is_some_and(|ext| ext.eq_ignore_ascii_case("env"))
62 {
63 return Some(Format::Dotenv);
64 }
65
66 path.extension().and_then(|ext| ext.to_str()).and_then(|s| {
67 match s.to_lowercase().as_str() {
68 "json" => Some(Format::Json),
69 "toml" => Some(Format::Toml),
70 "yaml" | "yml" => Some(Format::Yaml),
71 "ini" => Some(Format::Ini),
72 _ => None,
73 }
74 })
75 }
76
77 pub fn load(&self, content: &str) -> DocumentResult<Value> {
79 match self {
80 Format::Json => json::load(content),
81
82 #[cfg(feature = "toml")]
83 Format::Toml => toml::load(content),
84 #[cfg(not(feature = "toml"))]
85 Format::Toml => Err(DocumentError::UnsupportedOperation {
86 format: "TOML".to_string(),
87 operation: "load".to_string(),
88 detail: "requires Cargo feature `toml`".to_string(),
89 }),
90
91 #[cfg(feature = "yaml")]
92 Format::Yaml => yaml::load(content),
93 #[cfg(not(feature = "yaml"))]
94 Format::Yaml => Err(DocumentError::UnsupportedOperation {
95 format: "YAML".to_string(),
96 operation: "load".to_string(),
97 detail: "requires Cargo feature `yaml`".to_string(),
98 }),
99
100 #[cfg(feature = "dotenv")]
101 Format::Dotenv => dotenv::load(content),
102 #[cfg(not(feature = "dotenv"))]
103 Format::Dotenv => Err(DocumentError::UnsupportedOperation {
104 format: "dotenv".to_string(),
105 operation: "load".to_string(),
106 detail: "requires Cargo feature `dotenv`".to_string(),
107 }),
108
109 #[cfg(feature = "ini")]
110 Format::Ini => ini::load(content),
111 #[cfg(not(feature = "ini"))]
112 Format::Ini => Err(DocumentError::UnsupportedOperation {
113 format: "INI".to_string(),
114 operation: "load".to_string(),
115 detail: "requires Cargo feature `ini`".to_string(),
116 }),
117
118 #[cfg(feature = "toml")]
119 Format::TomlFrontmatter => {
120 toml::load(frontmatter::split(content, frontmatter::Delimiter::Plus)?.frontmatter)
121 }
122 #[cfg(not(feature = "toml"))]
123 Format::TomlFrontmatter => Err(DocumentError::UnsupportedOperation {
124 format: "TOML frontmatter".to_string(),
125 operation: "load".to_string(),
126 detail: "requires Cargo feature `toml`".to_string(),
127 }),
128
129 #[cfg(feature = "yaml")]
130 Format::YamlFrontmatter => {
131 yaml::load(frontmatter::split(content, frontmatter::Delimiter::Dash)?.frontmatter)
132 }
133 #[cfg(not(feature = "yaml"))]
134 Format::YamlFrontmatter => Err(DocumentError::UnsupportedOperation {
135 format: "YAML frontmatter".to_string(),
136 operation: "load".to_string(),
137 detail: "requires Cargo feature `yaml`".to_string(),
138 }),
139 }
140 }
141
142 pub fn save(&self, value: &Value) -> DocumentResult<String> {
144 match self {
145 Format::Json => json::save(value),
146
147 #[cfg(feature = "toml")]
148 Format::Toml => toml::save(value),
149 #[cfg(not(feature = "toml"))]
150 Format::Toml => Err(DocumentError::UnsupportedOperation {
151 format: "TOML".to_string(),
152 operation: "save".to_string(),
153 detail: "requires Cargo feature `toml`".to_string(),
154 }),
155
156 #[cfg(feature = "yaml")]
157 Format::Yaml => yaml::save(value),
158 #[cfg(not(feature = "yaml"))]
159 Format::Yaml => Err(DocumentError::UnsupportedOperation {
160 format: "YAML".to_string(),
161 operation: "save".to_string(),
162 detail: "requires Cargo feature `yaml`".to_string(),
163 }),
164
165 #[cfg(feature = "dotenv")]
166 Format::Dotenv => dotenv::save(value),
167 #[cfg(not(feature = "dotenv"))]
168 Format::Dotenv => Err(DocumentError::UnsupportedOperation {
169 format: "dotenv".to_string(),
170 operation: "save".to_string(),
171 detail: "requires Cargo feature `dotenv`".to_string(),
172 }),
173
174 #[cfg(feature = "ini")]
175 Format::Ini => ini::save(value),
176 #[cfg(not(feature = "ini"))]
177 Format::Ini => Err(DocumentError::UnsupportedOperation {
178 format: "INI".to_string(),
179 operation: "save".to_string(),
180 detail: "requires Cargo feature `ini`".to_string(),
181 }),
182
183 Format::TomlFrontmatter | Format::YamlFrontmatter => {
188 Err(DocumentError::UnsupportedOperation {
189 format: "frontmatter".to_string(),
190 operation: "save".to_string(),
191 detail:
192 "frontmatter mode has no whole-document re-render; the Markdown body is \
193 not part of the parsed value — use source-preserving set/unset"
194 .to_string(),
195 })
196 }
197 }
198 }
199
200 pub fn ensure_writable(&self, _operation: &str) -> DocumentResult<()> {
202 Ok(())
203 }
204}
205
206#[cfg(feature = "dotenv")]
207pub use dotenv::load as load_dotenv;
208pub use json::{load as load_json, save as save_json};
209#[cfg(feature = "toml")]
210pub use toml::{load as load_toml, save as save_toml};
211#[cfg(feature = "yaml")]
212pub use yaml::{load as load_yaml, save as save_yaml};
213
214#[cfg(test)]
215mod tests {
216 use super::Format;
217
218 #[test]
219 fn format_names_are_stable() {
220 let cases = [
221 (Format::Json, "JSON"),
222 (Format::Toml, "TOML"),
223 (Format::Yaml, "YAML"),
224 (Format::Dotenv, "dotenv"),
225 (Format::Ini, "INI"),
226 (Format::TomlFrontmatter, "TOML frontmatter"),
227 (Format::YamlFrontmatter, "YAML frontmatter"),
228 ];
229
230 for (format, expected) in cases {
231 assert_eq!(format.name(), expected);
232 }
233 }
234}