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 #[must_use]
56 pub const fn cli_name(self) -> &'static str {
57 match self {
58 Self::Json => "json",
59 Self::Toml => "toml",
60 Self::Yaml => "yaml",
61 Self::Dotenv => "dotenv",
62 Self::Ini => "ini",
63 Self::TomlFrontmatter => "toml-frontmatter",
64 Self::YamlFrontmatter => "yaml-frontmatter",
65 }
66 }
67
68 pub fn detect(path: &Path) -> Option<Self> {
70 let file_name = path.file_name().and_then(|name| name.to_str())?;
71 let file_name_lower = file_name.to_lowercase();
72 if file_name_lower == ".env"
73 || file_name_lower.starts_with(".env.")
74 || path
75 .extension()
76 .and_then(|ext| ext.to_str())
77 .is_some_and(|ext| ext.eq_ignore_ascii_case("env"))
78 {
79 return Some(Format::Dotenv);
80 }
81
82 path.extension().and_then(|ext| ext.to_str()).and_then(|s| {
83 match s.to_lowercase().as_str() {
84 "json" => Some(Format::Json),
85 "toml" => Some(Format::Toml),
86 "yaml" | "yml" => Some(Format::Yaml),
87 "ini" => Some(Format::Ini),
88 _ => None,
89 }
90 })
91 }
92
93 pub fn load(&self, content: &str) -> DocumentResult<Value> {
95 match self {
96 Format::Json => json::load(content),
97
98 #[cfg(feature = "toml")]
99 Format::Toml => toml::load(content),
100 #[cfg(not(feature = "toml"))]
101 Format::Toml => Err(DocumentError::UnsupportedOperation {
102 format: "TOML".to_string(),
103 operation: "load".to_string(),
104 detail: "requires Cargo feature `toml`".to_string(),
105 }),
106
107 #[cfg(feature = "yaml")]
108 Format::Yaml => yaml::load(content),
109 #[cfg(not(feature = "yaml"))]
110 Format::Yaml => Err(DocumentError::UnsupportedOperation {
111 format: "YAML".to_string(),
112 operation: "load".to_string(),
113 detail: "requires Cargo feature `yaml`".to_string(),
114 }),
115
116 #[cfg(feature = "dotenv")]
117 Format::Dotenv => dotenv::load(content),
118 #[cfg(not(feature = "dotenv"))]
119 Format::Dotenv => Err(DocumentError::UnsupportedOperation {
120 format: "dotenv".to_string(),
121 operation: "load".to_string(),
122 detail: "requires Cargo feature `dotenv`".to_string(),
123 }),
124
125 #[cfg(feature = "ini")]
126 Format::Ini => ini::load(content),
127 #[cfg(not(feature = "ini"))]
128 Format::Ini => Err(DocumentError::UnsupportedOperation {
129 format: "INI".to_string(),
130 operation: "load".to_string(),
131 detail: "requires Cargo feature `ini`".to_string(),
132 }),
133
134 #[cfg(feature = "toml")]
135 Format::TomlFrontmatter => {
136 toml::load(frontmatter::split(content, frontmatter::Delimiter::Plus)?.frontmatter)
137 }
138 #[cfg(not(feature = "toml"))]
139 Format::TomlFrontmatter => Err(DocumentError::UnsupportedOperation {
140 format: "TOML frontmatter".to_string(),
141 operation: "load".to_string(),
142 detail: "requires Cargo feature `toml`".to_string(),
143 }),
144
145 #[cfg(feature = "yaml")]
146 Format::YamlFrontmatter => {
147 yaml::load(frontmatter::split(content, frontmatter::Delimiter::Dash)?.frontmatter)
148 }
149 #[cfg(not(feature = "yaml"))]
150 Format::YamlFrontmatter => Err(DocumentError::UnsupportedOperation {
151 format: "YAML frontmatter".to_string(),
152 operation: "load".to_string(),
153 detail: "requires Cargo feature `yaml`".to_string(),
154 }),
155 }
156 }
157
158 pub fn save(&self, value: &Value) -> DocumentResult<String> {
160 match self {
161 Format::Json => json::save(value),
162
163 #[cfg(feature = "toml")]
164 Format::Toml => toml::save(value),
165 #[cfg(not(feature = "toml"))]
166 Format::Toml => Err(DocumentError::UnsupportedOperation {
167 format: "TOML".to_string(),
168 operation: "save".to_string(),
169 detail: "requires Cargo feature `toml`".to_string(),
170 }),
171
172 #[cfg(feature = "yaml")]
173 Format::Yaml => yaml::save(value),
174 #[cfg(not(feature = "yaml"))]
175 Format::Yaml => Err(DocumentError::UnsupportedOperation {
176 format: "YAML".to_string(),
177 operation: "save".to_string(),
178 detail: "requires Cargo feature `yaml`".to_string(),
179 }),
180
181 #[cfg(feature = "dotenv")]
182 Format::Dotenv => dotenv::save(value),
183 #[cfg(not(feature = "dotenv"))]
184 Format::Dotenv => Err(DocumentError::UnsupportedOperation {
185 format: "dotenv".to_string(),
186 operation: "save".to_string(),
187 detail: "requires Cargo feature `dotenv`".to_string(),
188 }),
189
190 #[cfg(feature = "ini")]
191 Format::Ini => ini::save(value),
192 #[cfg(not(feature = "ini"))]
193 Format::Ini => Err(DocumentError::UnsupportedOperation {
194 format: "INI".to_string(),
195 operation: "save".to_string(),
196 detail: "requires Cargo feature `ini`".to_string(),
197 }),
198
199 Format::TomlFrontmatter | Format::YamlFrontmatter => {
204 Err(DocumentError::UnsupportedOperation {
205 format: "frontmatter".to_string(),
206 operation: "save".to_string(),
207 detail:
208 "frontmatter mode has no whole-document re-render; the Markdown body is \
209 not part of the parsed value — use source-preserving set/unset"
210 .to_string(),
211 })
212 }
213 }
214 }
215}
216
217#[cfg(feature = "dotenv")]
218pub use dotenv::load as load_dotenv;
219pub use json::{load as load_json, save as save_json};
220#[cfg(feature = "toml")]
221pub use toml::{load as load_toml, save as save_toml};
222#[cfg(feature = "yaml")]
223pub use yaml::{load as load_yaml, save as save_yaml};
224
225#[cfg(test)]
226mod tests {
227 use super::Format;
228
229 #[test]
230 fn format_names_are_stable() {
231 let cases = [
232 (Format::Json, "JSON"),
233 (Format::Toml, "TOML"),
234 (Format::Yaml, "YAML"),
235 (Format::Dotenv, "dotenv"),
236 (Format::Ini, "INI"),
237 (Format::TomlFrontmatter, "TOML frontmatter"),
238 (Format::YamlFrontmatter, "YAML frontmatter"),
239 ];
240
241 for (format, expected) in cases {
242 assert_eq!(format.name(), expected);
243 }
244
245 let cli_names = [
246 (Format::Json, "json"),
247 (Format::Toml, "toml"),
248 (Format::Yaml, "yaml"),
249 (Format::Dotenv, "dotenv"),
250 (Format::Ini, "ini"),
251 (Format::TomlFrontmatter, "toml-frontmatter"),
252 (Format::YamlFrontmatter, "yaml-frontmatter"),
253 ];
254 for (format, expected) in cli_names {
255 assert_eq!(format.cli_name(), expected);
256 }
257 }
258}