1use crate::{BasicEmitter, Constructor, Emitter, Limits, Result, SafeConstructor, Value};
4use std::io::{Read, Write};
5
6#[derive(Debug, Clone)]
8pub struct YamlConfig {
9 pub loader_type: LoaderType,
11 pub pure: bool,
13 pub preserve_quotes: bool,
15 pub default_flow_style: Option<bool>,
17 pub allow_duplicate_keys: bool,
19 pub encoding: String,
21 pub explicit_start: Option<bool>,
23 pub explicit_end: Option<bool>,
25 pub width: Option<usize>,
27 pub allow_unicode: bool,
29 pub indent: IndentConfig,
31 pub preserve_comments: bool,
33 pub limits: Limits,
35 pub safe_mode: bool,
37 pub strict_mode: bool,
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum LoaderType {
44 Safe,
46 Base,
48 RoundTrip,
50 Full,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct IndentConfig {
57 pub indent: usize,
59 pub map_indent: Option<usize>,
61 pub sequence_indent: Option<usize>,
63 pub sequence_dash_offset: usize,
65}
66
67impl Default for YamlConfig {
68 fn default() -> Self {
69 Self {
70 loader_type: LoaderType::Safe,
71 pure: true,
72 preserve_quotes: false,
73 default_flow_style: None,
74 allow_duplicate_keys: false,
75 encoding: "utf-8".to_string(),
76 explicit_start: None,
77 explicit_end: None,
78 width: Some(80),
79 allow_unicode: true,
80 indent: IndentConfig::default(),
81 preserve_comments: false,
82 limits: Limits::default(),
83 safe_mode: false,
84 strict_mode: false,
85 }
86 }
87}
88
89impl YamlConfig {
90 pub fn secure() -> Self {
92 Self {
93 loader_type: LoaderType::Safe,
94 pure: true,
95 preserve_quotes: false,
96 default_flow_style: None,
97 allow_duplicate_keys: false,
98 encoding: "utf-8".to_string(),
99 explicit_start: None,
100 explicit_end: None,
101 width: Some(80),
102 allow_unicode: true,
103 indent: IndentConfig::default(),
104 preserve_comments: false,
105 limits: Limits::strict(),
106 safe_mode: true,
107 strict_mode: true,
108 }
109 }
110}
111
112impl Default for IndentConfig {
113 fn default() -> Self {
114 Self {
115 indent: 2,
116 map_indent: None,
117 sequence_indent: None,
118 sequence_dash_offset: 0,
119 }
120 }
121}
122
123#[derive(Debug, Clone)]
125pub struct Yaml {
126 config: YamlConfig,
127}
128
129impl Yaml {
130 pub fn new() -> Self {
132 Self {
133 config: YamlConfig::default(),
134 }
135 }
136
137 pub fn with_loader(loader_type: LoaderType) -> Self {
139 let mut config = YamlConfig::default();
140 config.loader_type = loader_type;
141 Self { config }
142 }
143
144 pub const fn with_config(config: YamlConfig) -> Self {
146 Self { config }
147 }
148
149 pub const fn config(&self) -> &YamlConfig {
151 &self.config
152 }
153
154 pub const fn config_mut(&mut self) -> &mut YamlConfig {
156 &mut self.config
157 }
158
159 pub fn load_str(&self, input: &str) -> Result<Value> {
161 self.load(input.as_bytes())
162 }
163
164 pub fn load<R: Read>(&self, mut reader: R) -> Result<Value> {
166 let mut buffer = String::new();
167 reader.read_to_string(&mut buffer)?;
168
169 self.parse_yaml_string(&buffer)
172 }
173
174 pub fn load_all_str(&self, input: &str) -> Result<Vec<Value>> {
176 self.load_all(input.as_bytes())
177 }
178
179 pub fn load_all<R: Read>(&self, mut reader: R) -> Result<Vec<Value>> {
181 let mut buffer = String::new();
182 reader.read_to_string(&mut buffer)?;
183
184 self.parse_yaml_documents(&buffer)
187 }
188
189 pub fn dump_str(&self, value: &Value) -> Result<String> {
191 let mut buffer = Vec::new();
192 self.dump(value, &mut buffer)?;
193 Ok(String::from_utf8(buffer)?)
194 }
195
196 pub fn dump<W: Write>(&self, value: &Value, writer: W) -> Result<()> {
198 self.emit_yaml_value(value, writer)
201 }
202
203 pub fn dump_all_str(&self, values: &[Value]) -> Result<String> {
205 let mut buffer = Vec::new();
206 self.dump_all(values, &mut buffer)?;
207 Ok(String::from_utf8(buffer)?)
208 }
209
210 pub fn dump_all<W: Write>(&self, values: &[Value], mut writer: W) -> Result<()> {
212 for (i, value) in values.iter().enumerate() {
213 if i > 0 {
214 writeln!(writer, "---")?;
215 }
216 self.dump(value, &mut writer)?;
217 }
218 Ok(())
219 }
220
221 fn parse_yaml_string(&self, input: &str) -> Result<Value> {
224 match self.config.loader_type {
226 LoaderType::Safe => {
227 let mut constructor =
228 SafeConstructor::with_limits(input.to_string(), self.config.limits.clone());
229 (constructor.construct()?).map_or_else(|| Ok(Value::Null), Ok)
230 }
231 _ => {
232 let mut constructor =
235 SafeConstructor::with_limits(input.to_string(), self.config.limits.clone());
236 (constructor.construct()?).map_or_else(|| Ok(Value::Null), Ok)
237 }
238 }
239 }
240
241 fn parse_yaml_documents(&self, input: &str) -> Result<Vec<Value>> {
242 let mut constructor =
244 SafeConstructor::with_limits(input.to_string(), self.config.limits.clone());
245 let mut documents = Vec::new();
246
247 while constructor.check_data() {
249 if let Some(doc) = constructor.construct()? {
250 documents.push(doc);
251 } else {
252 break;
253 }
254 }
255
256 if documents.is_empty() {
257 documents.push(Value::Null);
258 }
259
260 Ok(documents)
261 }
262
263 fn emit_yaml_value<W: Write>(&self, value: &Value, writer: W) -> Result<()> {
264 let mut emitter = BasicEmitter::with_indent(self.config.indent.indent);
266 emitter.emit(value, writer)?;
267 Ok(())
268 }
269}
270
271impl Default for Yaml {
272 fn default() -> Self {
273 Self::new()
274 }
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280
281 #[test]
282 fn test_yaml_creation() {
283 let yaml = Yaml::new();
284 assert_eq!(yaml.config().loader_type, LoaderType::Safe);
285
286 let yaml_rt = Yaml::with_loader(LoaderType::RoundTrip);
287 assert_eq!(yaml_rt.config().loader_type, LoaderType::RoundTrip);
288 }
289
290 #[test]
291 fn test_basic_scalar_parsing() {
292 let yaml = Yaml::new();
293
294 assert_eq!(yaml.load_str("null").unwrap(), Value::Null);
295 assert_eq!(yaml.load_str("true").unwrap(), Value::Bool(true));
296 assert_eq!(yaml.load_str("false").unwrap(), Value::Bool(false));
297 assert_eq!(yaml.load_str("42").unwrap(), Value::Int(42));
298 assert_eq!(yaml.load_str("3.14").unwrap(), Value::Float(3.14));
299 assert_eq!(
300 yaml.load_str("hello").unwrap(),
301 Value::String("hello".to_string())
302 );
303 assert_eq!(
304 yaml.load_str("\"quoted\"").unwrap(),
305 Value::String("quoted".to_string())
306 );
307 }
308
309 #[test]
310 fn test_basic_scalar_dumping() {
311 let yaml = Yaml::new();
312
313 assert_eq!(yaml.dump_str(&Value::Null).unwrap().trim(), "null");
314 assert_eq!(yaml.dump_str(&Value::Bool(true)).unwrap().trim(), "true");
315 assert_eq!(yaml.dump_str(&Value::Int(42)).unwrap().trim(), "42");
316 assert_eq!(yaml.dump_str(&Value::Float(3.14)).unwrap().trim(), "3.14");
317 assert_eq!(
318 yaml.dump_str(&Value::String("hello".to_string()))
319 .unwrap()
320 .trim(),
321 "hello"
322 );
323 }
324
325 #[test]
326 fn test_multi_document() {
327 let yaml = Yaml::new();
328 let input = "doc1\n---\ndoc2\n---\ndoc3";
329 let docs = yaml.load_all_str(input).unwrap();
330
331 assert_eq!(docs.len(), 3);
332 assert_eq!(docs[0], Value::String("doc1".to_string()));
333 assert_eq!(docs[1], Value::String("doc2".to_string()));
334 assert_eq!(docs[2], Value::String("doc3".to_string()));
335 }
336
337 #[test]
338 fn test_config_modification() {
339 let mut yaml = Yaml::new();
340 yaml.config_mut().loader_type = LoaderType::Full;
341 yaml.config_mut().allow_unicode = false;
342
343 assert_eq!(yaml.config().loader_type, LoaderType::Full);
344 assert!(!yaml.config().allow_unicode);
345 }
346}