1use crate::{
4 BasicEmitter, CommentPreservingConstructor, CommentedValue, Constructor, Emitter, Limits,
5 Result, RoundTripConstructor, SafeConstructor, Schema, SchemaValidator, Value,
6};
7use std::io::{Read, Write};
8
9#[derive(Debug, Clone)]
11pub struct YamlConfig {
12 pub loader_type: LoaderType,
14 pub pure: bool,
16 pub preserve_quotes: bool,
18 pub default_flow_style: Option<bool>,
20 pub allow_duplicate_keys: bool,
22 pub encoding: String,
24 pub explicit_start: Option<bool>,
26 pub explicit_end: Option<bool>,
28 pub width: Option<usize>,
30 pub allow_unicode: bool,
32 pub indent: IndentConfig,
34 pub preserve_comments: bool,
36 pub limits: Limits,
38 pub safe_mode: bool,
40 pub strict_mode: bool,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum LoaderType {
47 Safe,
49 Base,
51 RoundTrip,
53 Full,
55}
56
57#[derive(Debug, Clone, PartialEq, Eq)]
59pub struct IndentConfig {
60 pub indent: usize,
62 pub map_indent: Option<usize>,
64 pub sequence_indent: Option<usize>,
66 pub sequence_dash_offset: usize,
68}
69
70impl Default for YamlConfig {
71 fn default() -> Self {
72 Self {
73 loader_type: LoaderType::Safe,
74 pure: true,
75 preserve_quotes: false,
76 default_flow_style: None,
77 allow_duplicate_keys: false,
78 encoding: "utf-8".to_string(),
79 explicit_start: None,
80 explicit_end: None,
81 width: Some(80),
82 allow_unicode: true,
83 indent: IndentConfig::default(),
84 preserve_comments: false,
85 limits: Limits::default(),
86 safe_mode: false,
87 strict_mode: false,
88 }
89 }
90}
91
92impl YamlConfig {
93 pub fn secure() -> Self {
95 Self {
96 loader_type: LoaderType::Safe,
97 pure: true,
98 preserve_quotes: false,
99 default_flow_style: None,
100 allow_duplicate_keys: false,
101 encoding: "utf-8".to_string(),
102 explicit_start: None,
103 explicit_end: None,
104 width: Some(80),
105 allow_unicode: true,
106 indent: IndentConfig::default(),
107 preserve_comments: false,
108 limits: Limits::strict(),
109 safe_mode: true,
110 strict_mode: true,
111 }
112 }
113}
114
115impl Default for IndentConfig {
116 fn default() -> Self {
117 Self {
118 indent: 2,
119 map_indent: None,
120 sequence_indent: None,
121 sequence_dash_offset: 0,
122 }
123 }
124}
125
126#[derive(Debug, Clone)]
128pub struct Yaml {
129 config: YamlConfig,
130}
131
132impl Yaml {
133 pub fn new() -> Self {
135 Self {
136 config: YamlConfig::default(),
137 }
138 }
139
140 pub fn with_loader(loader_type: LoaderType) -> Self {
142 let mut config = YamlConfig::default();
143 config.loader_type = loader_type;
144 Self { config }
145 }
146
147 pub const fn with_config(config: YamlConfig) -> Self {
149 Self { config }
150 }
151
152 pub const fn config(&self) -> &YamlConfig {
154 &self.config
155 }
156
157 pub const fn config_mut(&mut self) -> &mut YamlConfig {
159 &mut self.config
160 }
161
162 pub fn load_str(&self, input: &str) -> Result<Value> {
164 self.load(input.as_bytes())
165 }
166
167 pub fn load<R: Read>(&self, mut reader: R) -> Result<Value> {
169 let mut buffer = String::new();
170 reader.read_to_string(&mut buffer)?;
171
172 self.parse_yaml_string(&buffer)
175 }
176
177 pub fn load_all_str(&self, input: &str) -> Result<Vec<Value>> {
179 self.load_all(input.as_bytes())
180 }
181
182 pub fn load_all<R: Read>(&self, mut reader: R) -> Result<Vec<Value>> {
184 let mut buffer = String::new();
185 reader.read_to_string(&mut buffer)?;
186
187 self.parse_yaml_documents(&buffer)
190 }
191
192 pub fn dump_str(&self, value: &Value) -> Result<String> {
194 let mut buffer = Vec::new();
195 self.dump(value, &mut buffer)?;
196 Ok(String::from_utf8(buffer)?)
197 }
198
199 pub fn dump<W: Write>(&self, value: &Value, writer: W) -> Result<()> {
201 self.emit_yaml_value(value, writer)
204 }
205
206 pub fn dump_all_str(&self, values: &[Value]) -> Result<String> {
208 let mut buffer = Vec::new();
209 self.dump_all(values, &mut buffer)?;
210 Ok(String::from_utf8(buffer)?)
211 }
212
213 pub fn dump_all<W: Write>(&self, values: &[Value], mut writer: W) -> Result<()> {
215 for (i, value) in values.iter().enumerate() {
216 if i > 0 {
217 writeln!(writer, "---")?;
218 }
219 self.dump(value, &mut writer)?;
220 }
221 Ok(())
222 }
223
224 pub fn load_str_with_comments(&self, input: &str) -> Result<CommentedValue> {
226 if !self.config.preserve_comments || self.config.loader_type != LoaderType::RoundTrip {
227 let value = self.load_str(input)?;
229 return Ok(CommentedValue::new(value));
230 }
231
232 self.parse_yaml_string_with_comments(input)
233 }
234
235 pub fn dump_str_with_comments(&self, value: &CommentedValue) -> Result<String> {
237 let mut buffer = Vec::new();
238 self.dump_with_comments(value, &mut buffer)?;
239 Ok(String::from_utf8(buffer)?)
240 }
241
242 pub fn dump_with_comments<W: Write>(&self, value: &CommentedValue, writer: W) -> Result<()> {
244 self.emit_commented_value(value, writer)
245 }
246
247 pub fn validate_with_schema(&self, value: &Value, schema: &Schema) -> Result<()> {
249 let validator = SchemaValidator::new(schema.clone());
250 validator.validate_with_report(value)
251 }
252
253 pub fn load_str_with_schema(&self, input: &str, schema: &Schema) -> Result<Value> {
255 let value = self.load_str(input)?;
256 self.validate_with_schema(&value, schema)?;
257 Ok(value)
258 }
259
260 pub fn load_all_str_with_schema(&self, input: &str, schema: &Schema) -> Result<Vec<Value>> {
262 let values = self.load_all_str(input)?;
263 for value in &values {
264 self.validate_with_schema(value, schema)?;
265 }
266 Ok(values)
267 }
268
269 fn parse_yaml_string(&self, input: &str) -> Result<Value> {
272 match self.config.loader_type {
274 LoaderType::Safe => {
275 let mut constructor =
276 SafeConstructor::with_limits(input.to_string(), self.config.limits.clone());
277 (constructor.construct()?).map_or_else(|| Ok(Value::Null), Ok)
278 }
279 _ => {
280 let mut constructor =
283 SafeConstructor::with_limits(input.to_string(), self.config.limits.clone());
284 (constructor.construct()?).map_or_else(|| Ok(Value::Null), Ok)
285 }
286 }
287 }
288
289 fn parse_yaml_documents(&self, input: &str) -> Result<Vec<Value>> {
290 let mut constructor =
292 SafeConstructor::with_limits(input.to_string(), self.config.limits.clone());
293 let mut documents = Vec::new();
294
295 while constructor.check_data() {
297 if let Some(doc) = constructor.construct()? {
298 documents.push(doc);
299 } else {
300 break;
301 }
302 }
303
304 if documents.is_empty() {
305 documents.push(Value::Null);
306 }
307
308 Ok(documents)
309 }
310
311 fn emit_yaml_value<W: Write>(&self, value: &Value, writer: W) -> Result<()> {
312 let mut emitter = BasicEmitter::with_indent(self.config.indent.indent);
314 emitter.emit(value, writer)?;
315 Ok(())
316 }
317
318 fn parse_yaml_string_with_comments(&self, input: &str) -> Result<CommentedValue> {
319 let mut constructor =
321 RoundTripConstructor::with_limits(input.to_string(), self.config.limits.clone());
322
323 match constructor.construct_commented()? {
324 Some(commented_value) => Ok(commented_value),
325 None => Ok(CommentedValue::new(Value::Null)),
326 }
327 }
328
329 fn emit_commented_value<W: Write>(&self, value: &CommentedValue, writer: W) -> Result<()> {
330 let mut emitter = BasicEmitter::with_indent(self.config.indent.indent);
332 emitter.emit_commented_value_public(value, writer)?;
333 Ok(())
334 }
335
336 fn emit_yaml_documents<W: Write>(&self, values: &[Value], mut writer: W) -> Result<()> {
337 for (i, value) in values.iter().enumerate() {
338 if i > 0 {
339 writeln!(writer, "---")?;
340 }
341 self.emit_yaml_value(value, &mut writer)?;
342 }
343 Ok(())
344 }
345}
346
347impl Default for Yaml {
348 fn default() -> Self {
349 Self::new()
350 }
351}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356
357 #[test]
358 fn test_yaml_creation() {
359 let yaml = Yaml::new();
360 assert_eq!(yaml.config().loader_type, LoaderType::Safe);
361
362 let yaml_rt = Yaml::with_loader(LoaderType::RoundTrip);
363 assert_eq!(yaml_rt.config().loader_type, LoaderType::RoundTrip);
364 }
365
366 #[test]
367 fn test_basic_scalar_parsing() {
368 let yaml = Yaml::new();
369
370 assert_eq!(yaml.load_str("null").unwrap(), Value::Null);
371 assert_eq!(yaml.load_str("true").unwrap(), Value::Bool(true));
372 assert_eq!(yaml.load_str("false").unwrap(), Value::Bool(false));
373 assert_eq!(yaml.load_str("42").unwrap(), Value::Int(42));
374 assert_eq!(yaml.load_str("3.14").unwrap(), Value::Float(3.14));
375 assert_eq!(
376 yaml.load_str("hello").unwrap(),
377 Value::String("hello".to_string())
378 );
379 assert_eq!(
380 yaml.load_str("\"quoted\"").unwrap(),
381 Value::String("quoted".to_string())
382 );
383 }
384
385 #[test]
386 fn test_basic_scalar_dumping() {
387 let yaml = Yaml::new();
388
389 assert_eq!(yaml.dump_str(&Value::Null).unwrap().trim(), "null");
390 assert_eq!(yaml.dump_str(&Value::Bool(true)).unwrap().trim(), "true");
391 assert_eq!(yaml.dump_str(&Value::Int(42)).unwrap().trim(), "42");
392 assert_eq!(yaml.dump_str(&Value::Float(3.14)).unwrap().trim(), "3.14");
393 assert_eq!(
394 yaml.dump_str(&Value::String("hello".to_string()))
395 .unwrap()
396 .trim(),
397 "hello"
398 );
399 }
400
401 #[test]
402 fn test_multi_document() {
403 let yaml = Yaml::new();
404 let input = "doc1\n---\ndoc2\n---\ndoc3";
405 let docs = yaml.load_all_str(input).unwrap();
406
407 assert_eq!(docs.len(), 3);
408 assert_eq!(docs[0], Value::String("doc1".to_string()));
409 assert_eq!(docs[1], Value::String("doc2".to_string()));
410 assert_eq!(docs[2], Value::String("doc3".to_string()));
411 }
412
413 #[test]
414 fn test_config_modification() {
415 let mut yaml = Yaml::new();
416 yaml.config_mut().loader_type = LoaderType::Full;
417 yaml.config_mut().allow_unicode = false;
418
419 assert_eq!(yaml.config().loader_type, LoaderType::Full);
420 assert!(!yaml.config().allow_unicode);
421 }
422}