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