frontmatter_gen/types.rs
1// types.rs
2
3//! This module defines the core types used throughout the frontmatter-gen crate.
4//! It includes the `Format` enum for representing different frontmatter formats, the `Value` enum for representing various data types that can be stored in frontmatter, and the `Frontmatter` struct which is the main container for frontmatter data.
5
6use serde::{Deserialize, Serialize};
7use std::{
8 collections::{BTreeMap, HashMap},
9 fmt,
10 str::FromStr,
11};
12
13/// Represents the different formats supported for frontmatter serialization/deserialization.
14#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
15#[non_exhaustive]
16pub enum Format {
17 /// YAML format.
18 Yaml,
19 /// TOML format.
20 Toml,
21 /// JSON format.
22 #[default]
23 Json,
24 /// Unsupported format.
25 Unsupported,
26}
27
28impl fmt::Display for Format {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 let format_str = match self {
31 Format::Yaml => "YAML",
32 Format::Toml => "TOML",
33 Format::Json => "JSON",
34 Format::Unsupported => "Unsupported",
35 };
36 write!(f, "{}", format_str)
37 }
38}
39
40/// A flexible value type that can hold various types of data found in frontmatter.
41#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
42#[serde(untagged)]
43pub enum Value {
44 /// Represents a null value.
45 #[default]
46 Null,
47 /// Represents a string value.
48 String(String),
49 /// Represents a numeric value.
50 Number(f64),
51 /// Represents a boolean value.
52 Boolean(bool),
53 /// Represents an array of values.
54 Array(Vec<Value>),
55 /// Represents an object (frontmatter).
56 Object(Box<Frontmatter>),
57 /// Represents a tagged value, containing a tag and a value.
58 Tagged(String, Box<Value>),
59}
60
61/// Accessor and conversion methods for [`Value`].
62///
63/// These methods provide ergonomic type-checked projections (`as_str`,
64/// `as_float`, `as_bool`, `as_array`, `as_object`, `as_tagged`), along with
65/// emptiness checks, recursive deep merges for nested [`Frontmatter`]
66/// objects, and formatting utilities across YAML, TOML, and JSON values.
67///
68/// See the individual method documentation for examples.
69impl Value {
70 /// Returns the value as a string slice, if it is of type `String`.
71 ///
72 /// # Returns
73 ///
74 /// - `Some(&str)` if the value is a `String`.
75 /// - `None` if the value is not a `String`.
76 ///
77 /// # Examples
78 ///
79 /// ```
80 /// use frontmatter_gen::Value;
81 ///
82 /// let string_value = Value::String("Hello".to_string());
83 /// assert_eq!(string_value.as_str(), Some("Hello"));
84 ///
85 /// let number_value = Value::Number(42.0);
86 /// assert_eq!(number_value.as_str(), None);
87 /// ```
88 pub fn as_str(&self) -> Option<&str> {
89 if let Value::String(s) = self {
90 Some(s)
91 } else {
92 None
93 }
94 }
95
96 /// Returns the value as a float, if it is of type `Number`.
97 ///
98 /// # Returns
99 ///
100 /// - `Some(f64)` if the value is a `Number`.
101 /// - `None` if the value is not a `Number`.
102 ///
103 /// # Examples
104 ///
105 /// ```
106 /// use frontmatter_gen::Value;
107 ///
108 /// let number_value = Value::Number(3.14);
109 /// assert_eq!(number_value.as_f64(), Some(3.14));
110 ///
111 /// let string_value = Value::String("Not a number".to_string());
112 /// assert_eq!(string_value.as_f64(), None);
113 /// ```
114 pub const fn as_f64(&self) -> Option<f64> {
115 if let Value::Number(n) = self {
116 Some(*n)
117 } else {
118 None
119 }
120 }
121
122 /// Returns the value as a boolean, if it is of type `Boolean`.
123 ///
124 /// # Returns
125 ///
126 /// - `Some(bool)` if the value is a `Boolean`.
127 /// - `None` if the value is not a `Boolean`.
128 ///
129 /// # Examples
130 ///
131 /// ```
132 /// use frontmatter_gen::Value;
133 ///
134 /// let bool_value = Value::Boolean(true);
135 /// assert_eq!(bool_value.as_bool(), Some(true));
136 ///
137 /// let string_value = Value::String("Not a boolean".to_string());
138 /// assert_eq!(string_value.as_bool(), None);
139 /// ```
140 pub const fn as_bool(&self) -> Option<bool> {
141 if let Value::Boolean(b) = self {
142 Some(*b)
143 } else {
144 None
145 }
146 }
147
148 /// Returns the value as an array, if it is of type `Array`.
149 ///
150 /// # Returns
151 ///
152 /// - `Some(&Vec<Value>)` if the value is an `Array`.
153 /// - `None` if the value is not an `Array`.
154 ///
155 /// # Examples
156 ///
157 /// ```
158 /// use frontmatter_gen::Value;
159 ///
160 /// let array_value = Value::Array(vec![Value::Number(1.0), Value::Number(2.0)]);
161 /// assert!(array_value.as_array().is_some());
162 /// assert_eq!(array_value.as_array().unwrap().len(), 2);
163 ///
164 /// let string_value = Value::String("Not an array".to_string());
165 /// assert!(string_value.as_array().is_none());
166 /// ```
167 pub const fn as_array(&self) -> Option<&Vec<Value>> {
168 if let Value::Array(arr) = self {
169 Some(arr)
170 } else {
171 None
172 }
173 }
174
175 /// Returns the value as an object (frontmatter), if it is of type `Object`.
176 ///
177 /// # Returns
178 ///
179 /// - `Some(&Frontmatter)` if the value is an `Object`.
180 /// - `None` if the value is not an `Object`.
181 ///
182 /// # Examples
183 ///
184 /// ```
185 /// use frontmatter_gen::{Value, Frontmatter};
186 ///
187 /// let mut fm = Frontmatter::new();
188 /// fm.insert("key".to_string(), Value::String("value".to_string()));
189 /// let object_value = Value::Object(Box::new(fm));
190 /// assert!(object_value.as_object().is_some());
191 ///
192 /// let string_value = Value::String("Not an object".to_string());
193 /// assert!(string_value.as_object().is_none());
194 /// ```
195 pub fn as_object(&self) -> Option<&Frontmatter> {
196 if let Value::Object(obj) = self {
197 Some(obj)
198 } else {
199 None
200 }
201 }
202
203 /// Returns the value as a tagged value, if it is of type `Tagged`.
204 ///
205 /// # Returns
206 ///
207 /// - `Some((&str, &Value))` if the value is `Tagged`.
208 /// - `None` if the value is not `Tagged`.
209 ///
210 /// # Examples
211 ///
212 /// ```
213 /// use frontmatter_gen::Value;
214 ///
215 /// let tagged_value = Value::Tagged("tag".to_string(), Box::new(Value::Number(42.0)));
216 /// assert_eq!(tagged_value.as_tagged(), Some(("tag", &Value::Number(42.0))));
217 ///
218 /// let string_value = Value::String("Not tagged".to_string());
219 /// assert_eq!(string_value.as_tagged(), None);
220 /// ```
221 pub fn as_tagged(&self) -> Option<(&str, &Value)> {
222 if let Value::Tagged(tag, val) = self {
223 Some((tag, val))
224 } else {
225 None
226 }
227 }
228
229 /// Checks if the value is of type `Null`.
230 ///
231 /// # Returns
232 ///
233 /// `true` if the value is `Null`, otherwise `false`.
234 ///
235 /// # Examples
236 ///
237 /// ```
238 /// use frontmatter_gen::Value;
239 ///
240 /// let null_value = Value::Null;
241 /// assert!(null_value.is_null());
242 ///
243 /// let string_value = Value::String("Not null".to_string());
244 /// assert!(!string_value.is_null());
245 /// ```
246 pub const fn is_null(&self) -> bool {
247 matches!(self, Value::Null)
248 }
249
250 /// Checks if the value is of type `String`.
251 ///
252 /// # Returns
253 ///
254 /// `true` if the value is a `String`, otherwise `false`.
255 ///
256 /// # Examples
257 ///
258 /// ```
259 /// use frontmatter_gen::Value;
260 ///
261 /// let string_value = Value::String("Hello".to_string());
262 /// assert!(string_value.is_string());
263 ///
264 /// let number_value = Value::Number(42.0);
265 /// assert!(!number_value.is_string());
266 /// ```
267 pub const fn is_string(&self) -> bool {
268 matches!(self, Value::String(_))
269 }
270
271 /// Checks if the value is of type `Number`.
272 ///
273 /// # Returns
274 ///
275 /// `true` if the value is a `Number`, otherwise `false`.
276 ///
277 /// # Examples
278 ///
279 /// ```
280 /// use frontmatter_gen::Value;
281 ///
282 /// let number_value = Value::Number(3.14);
283 /// assert!(number_value.is_number());
284 ///
285 /// let string_value = Value::String("Not a number".to_string());
286 /// assert!(!string_value.is_number());
287 /// ```
288 pub const fn is_number(&self) -> bool {
289 matches!(self, Value::Number(_))
290 }
291
292 /// Checks if the value is of type `Boolean`.
293 ///
294 /// # Returns
295 ///
296 /// `true` if the value is a `Boolean`, otherwise `false`.
297 ///
298 /// # Examples
299 ///
300 /// ```
301 /// use frontmatter_gen::Value;
302 ///
303 /// let bool_value = Value::Boolean(true);
304 /// assert!(bool_value.is_boolean());
305 ///
306 /// let string_value = Value::String("Not a boolean".to_string());
307 /// assert!(!string_value.is_boolean());
308 /// ```
309 pub const fn is_boolean(&self) -> bool {
310 matches!(self, Value::Boolean(_))
311 }
312
313 /// Checks if the value is of type `Array`.
314 ///
315 /// # Returns
316 ///
317 /// `true` if the value is an `Array`, otherwise `false`.
318 ///
319 /// # Examples
320 ///
321 /// ```
322 /// use frontmatter_gen::Value;
323 ///
324 /// let array_value = Value::Array(vec![Value::Number(1.0), Value::Number(2.0)]);
325 /// assert!(array_value.is_array());
326 ///
327 /// let string_value = Value::String("Not an array".to_string());
328 /// assert!(!string_value.is_array());
329 /// ```
330 pub const fn is_array(&self) -> bool {
331 matches!(self, Value::Array(_))
332 }
333
334 /// Checks if the value is of type `Object`.
335 ///
336 /// # Returns
337 ///
338 /// `true` if the value is an `Object`, otherwise `false`.
339 ///
340 /// # Examples
341 ///
342 /// ```
343 /// use frontmatter_gen::{Value, Frontmatter};
344 ///
345 /// let object_value = Value::Object(Box::new(Frontmatter::new()));
346 /// assert!(object_value.is_object());
347 ///
348 /// let string_value = Value::String("Not an object".to_string());
349 /// assert!(!string_value.is_object());
350 /// ```
351 pub const fn is_object(&self) -> bool {
352 matches!(self, Value::Object(_))
353 }
354
355 /// Checks if the value is of type `Tagged`.
356 ///
357 /// # Returns
358 ///
359 /// `true` if the value is `Tagged`, otherwise `false`.
360 ///
361 /// # Examples
362 ///
363 /// ```
364 /// use frontmatter_gen::Value;
365 ///
366 /// let tagged_value = Value::Tagged("tag".to_string(), Box::new(Value::Number(42.0)));
367 /// assert!(tagged_value.is_tagged());
368 ///
369 /// let string_value = Value::String("Not tagged".to_string());
370 /// assert!(!string_value.is_tagged());
371 /// ```
372 pub const fn is_tagged(&self) -> bool {
373 matches!(self, Value::Tagged(_, _))
374 }
375
376 /// Returns the length of the array if the value is an array, otherwise returns `None`.
377 ///
378 /// # Returns
379 ///
380 /// - `Some(usize)` with the length of the array if the value is an `Array`.
381 /// - `None` if the value is not an `Array`.
382 ///
383 /// # Examples
384 ///
385 /// ```
386 /// use frontmatter_gen::Value;
387 ///
388 /// let array_value = Value::Array(vec![Value::Number(1.0), Value::Number(2.0)]);
389 /// assert_eq!(array_value.array_len(), Some(2));
390 ///
391 /// let string_value = Value::String("Not an array".to_string());
392 /// assert_eq!(string_value.array_len(), None);
393 /// ```
394 pub fn array_len(&self) -> Option<usize> {
395 if let Value::Array(arr) = self {
396 Some(arr.len())
397 } else {
398 None
399 }
400 }
401
402 /// Attempts to convert the value into a `Frontmatter`.
403 ///
404 /// # Returns
405 ///
406 /// - `Ok(Frontmatter)` if the value is an `Object`.
407 /// - `Err(String)` with an error message if the value is not an `Object`.
408 ///
409 /// # Examples
410 ///
411 /// ```
412 /// use frontmatter_gen::{Value, Frontmatter};
413 ///
414 /// let object_value = Value::Object(Box::new(Frontmatter::new()));
415 /// assert!(object_value.to_object().is_ok());
416 ///
417 /// let string_value = Value::String("Not an object".to_string());
418 /// assert!(string_value.to_object().is_err());
419 /// ```
420 pub fn to_object(self) -> Result<Frontmatter, String> {
421 if let Value::Object(obj) = self {
422 Ok(*obj)
423 } else {
424 Err("Value is not an object".into())
425 }
426 }
427
428 /// Converts the value to a string representation regardless of its type.
429 ///
430 /// # Returns
431 ///
432 /// A `String` representation of the value.
433 ///
434 /// # Examples
435 ///
436 /// ```
437 /// use frontmatter_gen::Value;
438 ///
439 /// let number_value = Value::Number(3.14);
440 /// assert_eq!(number_value.to_string_representation(), "3.14");
441 ///
442 /// let string_value = Value::String("Hello".to_string());
443 /// assert_eq!(string_value.to_string_representation(), "\"Hello\"");
444 /// ```
445 pub fn to_string_representation(&self) -> String {
446 format!("{}", self)
447 }
448
449 /// Attempts to convert the value into a `String`.
450 ///
451 /// # Returns
452 ///
453 /// - `Ok(String)` if the value is a `String`.
454 /// - `Err(String)` with an error message if the value is not a `String`.
455 ///
456 /// # Examples
457 ///
458 /// ```
459 /// use frontmatter_gen::Value;
460 ///
461 /// let string_value = Value::String("Hello".to_string());
462 /// assert_eq!(string_value.into_string(), Ok("Hello".to_string()));
463 ///
464 /// let number_value = Value::Number(42.0);
465 /// assert!(number_value.into_string().is_err());
466 /// ```
467 pub fn into_string(self) -> Result<String, String> {
468 if let Value::String(s) = self {
469 Ok(s)
470 } else {
471 Err("Value is not a string".into())
472 }
473 }
474
475 /// Attempts to convert the value into an `f64`.
476 ///
477 /// # Returns
478 ///
479 /// - `Ok(f64)` if the value is a `Number`.
480 /// - `Err(String)` with an error message if the value is not a `Number`.
481 ///
482 /// # Examples
483 ///
484 /// ```
485 /// use frontmatter_gen::Value;
486 ///
487 /// let number_value = Value::Number(3.14);
488 /// assert_eq!(number_value.into_f64(), Ok(3.14));
489 ///
490 /// let string_value = Value::String("Not a number".to_string());
491 /// assert!(string_value.into_f64().is_err());
492 /// ```
493 pub fn into_f64(self) -> Result<f64, String> {
494 if let Value::Number(n) = self {
495 Ok(n)
496 } else {
497 Err("Value is not a number".into())
498 }
499 }
500
501 /// Attempts to convert the value into a `bool`.
502 ///
503 /// # Returns
504 ///
505 /// - `Ok(bool)` if the value is a `Boolean`.
506 /// - `Err(String)` with an error message if the value is not a `Boolean`.
507 ///
508 /// # Examples
509 ///
510 /// ```
511 /// use frontmatter_gen::Value;
512 ///
513 /// let bool_value = Value::Boolean(true);
514 /// assert_eq!(bool_value.into_bool(), Ok(true));
515 ///
516 /// let string_value = Value::String("Not a boolean".to_string());
517 /// assert!(string_value.into_bool().is_err());
518 /// ```
519 pub fn into_bool(self) -> Result<bool, String> {
520 if let Value::Boolean(b) = self {
521 Ok(b)
522 } else {
523 Err("Value is not a boolean".into())
524 }
525 }
526
527 /// Attempts to get a mutable reference to the array if the value is an array.
528 ///
529 /// # Returns
530 ///
531 /// - `Some(&mut Vec<Value>)` if the value is an `Array`.
532 /// - `None` if the value is not an `Array`.
533 ///
534 /// # Examples
535 ///
536 /// ```
537 /// use frontmatter_gen::Value;
538 ///
539 /// let mut array_value = Value::Array(vec![Value::Number(1.0), Value::Number(2.0)]);
540 /// if let Some(arr) = array_value.get_mut_array() {
541 /// arr.push(Value::Number(3.0));
542 /// }
543 /// assert_eq!(array_value.array_len(), Some(3));
544 ///
545 /// let mut string_value = Value::String("Not an array".to_string());
546 /// assert!(string_value.get_mut_array().is_none());
547 /// ```
548 pub fn get_mut_array(&mut self) -> Option<&mut Vec<Value>> {
549 if let Value::Array(arr) = self {
550 Some(arr)
551 } else {
552 None
553 }
554 }
555}
556
557impl From<&str> for Value {
558 fn from(s: &str) -> Self {
559 Value::String(s.to_string())
560 }
561}
562
563impl From<String> for Value {
564 fn from(s: String) -> Self {
565 Value::String(s)
566 }
567}
568
569impl From<f64> for Value {
570 fn from(n: f64) -> Self {
571 Value::Number(n)
572 }
573}
574
575impl From<bool> for Value {
576 fn from(b: bool) -> Self {
577 Value::Boolean(b)
578 }
579}
580
581impl FromIterator<Value> for Value {
582 fn from_iter<I: IntoIterator<Item = Value>>(iter: I) -> Self {
583 Value::Array(iter.into_iter().collect())
584 }
585}
586
587impl FromStr for Value {
588 type Err = &'static str;
589
590 fn from_str(s: &str) -> Result<Self, Self::Err> {
591 if s.eq_ignore_ascii_case("null") {
592 Ok(Value::Null)
593 } else if s.eq_ignore_ascii_case("true") {
594 Ok(Value::Boolean(true))
595 } else if s.eq_ignore_ascii_case("false") {
596 Ok(Value::Boolean(false))
597 } else if let Ok(n) = s.parse::<f64>() {
598 Ok(Value::Number(n))
599 } else {
600 Ok(Value::String(s.to_string()))
601 }
602 }
603}
604
605/// Represents the frontmatter, a collection of key-value pairs.
606#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
607pub struct Frontmatter(pub HashMap<String, Value>);
608
609impl Frontmatter {
610 /// Creates a new, empty frontmatter.
611 ///
612 /// # Returns
613 ///
614 /// A new `Frontmatter` instance with no key-value pairs.
615 ///
616 /// # Examples
617 ///
618 /// ```
619 /// use frontmatter_gen::Frontmatter;
620 ///
621 /// let fm = Frontmatter::new();
622 /// assert!(fm.is_empty());
623 /// ```
624 #[must_use]
625 pub fn new() -> Self {
626 Frontmatter(HashMap::new())
627 }
628
629 /// Inserts a key-value pair into the frontmatter.
630 ///
631 /// # Arguments
632 ///
633 /// * `key` - The key for the entry.
634 /// * `value` - The value associated with the key.
635 ///
636 /// # Returns
637 ///
638 /// An option containing the old value if it was replaced.
639 ///
640 /// # Examples
641 ///
642 /// ```
643 /// use frontmatter_gen::{Frontmatter, Value};
644 ///
645 /// let mut fm = Frontmatter::new();
646 /// assert_eq!(fm.insert("key".to_string(), Value::String("value".to_string())), None);
647 /// assert_eq!(fm.insert("key".to_string(), Value::Number(42.0)), Some(Value::String("value".to_string())));
648 /// ```
649 pub fn insert(
650 &mut self,
651 key: String,
652 value: Value,
653 ) -> Option<Value> {
654 self.0.insert(key, value)
655 }
656
657 /// Retrieves a reference to a value associated with a key.
658 ///
659 /// # Arguments
660 ///
661 /// * `key` - The key to look up.
662 ///
663 /// # Returns
664 ///
665 /// An option containing a reference to the value if the key exists.
666 ///
667 /// # Examples
668 ///
669 /// ```
670 /// use frontmatter_gen::{Frontmatter, Value};
671 ///
672 /// let mut fm = Frontmatter::new();
673 /// fm.insert("key".to_string(), Value::String("value".to_string()));
674 /// assert_eq!(fm.get("key"), Some(&Value::String("value".to_string())));
675 /// assert_eq!(fm.get("nonexistent"), None);
676 /// ```
677 pub fn get(&self, key: &str) -> Option<&Value> {
678 self.0.get(key)
679 }
680
681 /// Retrieves a mutable reference to a value associated with a key.
682 ///
683 /// # Arguments
684 ///
685 /// * `key` - The key to look up.
686 ///
687 /// # Returns
688 ///
689 /// An option containing a mutable reference to the value if the key exists.
690 ///
691 /// # Examples
692 ///
693 /// ```
694 /// use frontmatter_gen::{Frontmatter, Value};
695 ///
696 /// let mut fm = Frontmatter::new();
697 /// fm.insert("key".to_string(), Value::String("value".to_string()));
698 /// if let Some(value) = fm.get_mut("key") {
699 /// *value = Value::Number(42.0);
700 /// }
701 /// assert_eq!(fm.get("key"), Some(&Value::Number(42.0)));
702 /// ```
703 pub fn get_mut(&mut self, key: &str) -> Option<&mut Value> {
704 self.0.get_mut(key)
705 }
706
707 /// Removes a key-value pair from the frontmatter.
708 ///
709 /// # Arguments
710 ///
711 /// * `key` - The key to remove.
712 ///
713 /// # Returns
714 ///
715 /// An option containing the removed value if the key existed.
716 ///
717 /// # Examples
718 ///
719 /// ```
720 /// use frontmatter_gen::{Frontmatter, Value};
721 ///
722 /// let mut fm = Frontmatter::new();
723 /// fm.insert("key".to_string(), Value::String("value".to_string()));
724 /// assert_eq!(fm.remove("key"), Some(Value::String("value".to_string())));
725 /// assert_eq!(fm.remove("key"), None);
726 /// ```
727 pub fn remove(&mut self, key: &str) -> Option<Value> {
728 self.0.remove(key)
729 }
730
731 /// Checks if the frontmatter contains a given key.
732 ///
733 /// # Arguments
734 ///
735 /// * `key` - The key to check for.
736 ///
737 /// # Returns
738 ///
739 /// `true` if the key exists in the frontmatter, `false` otherwise.
740 ///
741 /// # Examples
742 ///
743 /// ```
744 /// use frontmatter_gen::{Frontmatter, Value};
745 ///
746 /// let mut fm = Frontmatter::new();
747 /// fm.insert("key".to_string(), Value::String("value".to_string()));
748 /// assert!(fm.contains_key("key"));
749 /// assert!(!fm.contains_key("nonexistent"));
750 /// ```
751 pub fn contains_key(&self, key: &str) -> bool {
752 self.0.contains_key(key)
753 }
754
755 /// Returns the number of entries in the frontmatter.
756 ///
757 /// # Returns
758 ///
759 /// The number of key-value pairs in the frontmatter.
760 ///
761 /// # Examples
762 ///
763 /// ```
764 /// use frontmatter_gen::{Frontmatter, Value};
765 ///
766 /// let mut fm = Frontmatter::new();
767 /// assert_eq!(fm.len(), 0);
768 /// fm.insert("key".to_string(), Value::String("value".to_string()));
769 /// assert_eq!(fm.len(), 1);
770 /// ```
771 pub fn len(&self) -> usize {
772 self.0.len()
773 }
774
775 /// Checks if the frontmatter is empty.
776 ///
777 /// # Returns
778 ///
779 /// `true` if the frontmatter contains no key-value pairs, `false` otherwise.
780 ///
781 /// # Examples
782 ///
783 /// ```
784 /// use frontmatter_gen::{Frontmatter, Value};
785 ///
786 /// let mut fm = Frontmatter::new();
787 /// assert!(fm.is_empty());
788 /// fm.insert("key".to_string(), Value::String("value".to_string()));
789 /// assert!(!fm.is_empty());
790 /// ```
791 pub fn is_empty(&self) -> bool {
792 self.0.is_empty()
793 }
794
795 /// Returns an iterator over the key-value pairs of the frontmatter.
796 ///
797 /// # Returns
798 ///
799 /// An iterator over references to the key-value pairs.
800 ///
801 /// # Examples
802 ///
803 /// ```
804 /// use frontmatter_gen::{Frontmatter, Value};
805 ///
806 /// let mut fm = Frontmatter::new();
807 /// fm.insert("key1".to_string(), Value::String("value1".to_string()));
808 /// fm.insert("key2".to_string(), Value::Number(42.0));
809 ///
810 /// for (key, value) in fm.iter() {
811 /// println!("{}: {:?}", key, value);
812 /// }
813 /// ```
814 #[must_use]
815 pub fn iter(
816 &self,
817 ) -> std::collections::hash_map::Iter<'_, String, Value> {
818 self.0.iter()
819 }
820
821 /// Returns a mutable iterator over the key-value pairs of the frontmatter.
822 ///
823 /// # Returns
824 ///
825 /// A mutable iterator over references to the key-value pairs.
826 ///
827 /// # Examples
828 ///
829 /// ```
830 /// use frontmatter_gen::{Frontmatter, Value};
831 ///
832 /// let mut fm = Frontmatter::new();
833 /// fm.insert("key1".to_string(), Value::String("value1".to_string()));
834 /// fm.insert("key2".to_string(), Value::Number(42.0));
835 ///
836 /// for (_, value) in fm.iter_mut() {
837 /// if let Value::Number(n) = value {
838 /// *n += 1.0;
839 /// }
840 /// }
841 ///
842 /// assert_eq!(fm.get("key2"), Some(&Value::Number(43.0)));
843 /// ```
844 pub fn iter_mut(
845 &mut self,
846 ) -> std::collections::hash_map::IterMut<'_, String, Value> {
847 self.0.iter_mut()
848 }
849
850 /// Merges another frontmatter into this one. If a key exists, it will be overwritten.
851 ///
852 /// # Arguments
853 ///
854 /// * `other` - The frontmatter to merge into this one.
855 ///
856 /// # Examples
857 ///
858 /// ```
859 /// use frontmatter_gen::{Frontmatter, Value};
860 ///
861 /// let mut fm1 = Frontmatter::new();
862 /// fm1.insert("key1".to_string(), Value::String("value1".to_string()));
863 ///
864 /// let mut fm2 = Frontmatter::new();
865 /// fm2.insert("key2".to_string(), Value::Number(42.0));
866 ///
867 /// fm1.merge(fm2);
868 /// assert_eq!(fm1.len(), 2);
869 /// assert_eq!(fm1.get("key2"), Some(&Value::Number(42.0)));
870 /// ```
871 pub fn merge(&mut self, other: Frontmatter) {
872 self.0.extend(other.0);
873 }
874
875 /// Checks if a given key exists and its value is `null`.
876 ///
877 /// # Arguments
878 ///
879 /// * `key` - The key to check.
880 ///
881 /// # Returns
882 ///
883 /// `true` if the key exists and its value is `null`, `false` otherwise.
884 ///
885 /// # Examples
886 ///
887 /// ```
888 /// use frontmatter_gen::{Frontmatter, Value};
889 ///
890 /// let mut fm = Frontmatter::new();
891 /// fm.insert("null_key".to_string(), Value::Null);
892 /// fm.insert("non_null_key".to_string(), Value::String("value".to_string()));
893 ///
894 /// assert!(fm.is_null("null_key"));
895 /// assert!(!fm.is_null("non_null_key"));
896 /// assert!(!fm.is_null("nonexistent_key"));
897 /// ```
898 pub fn is_null(&self, key: &str) -> bool {
899 matches!(self.get(key), Some(Value::Null))
900 }
901
902 /// Clears the frontmatter while preserving allocated capacity
903 pub fn clear(&mut self) {
904 self.0.clear();
905 }
906
907 /// Returns the current capacity of the underlying HashMap
908 pub fn capacity(&self) -> usize {
909 self.0.capacity()
910 }
911
912 /// Reserves capacity for at least `additional` more elements
913 pub fn reserve(&mut self, additional: usize) {
914 self.0.reserve(additional);
915 }
916}
917
918impl Default for Frontmatter {
919 fn default() -> Self {
920 Self(HashMap::with_capacity(8))
921 }
922}
923
924/// Implement `IntoIterator` for `Frontmatter` to allow idiomatic iteration.
925impl IntoIterator for Frontmatter {
926 type Item = (String, Value);
927 type IntoIter = std::collections::hash_map::IntoIter<String, Value>;
928
929 fn into_iter(self) -> Self::IntoIter {
930 self.0.into_iter()
931 }
932}
933
934/// Implement `FromIterator` for `Frontmatter` to create a frontmatter from an iterator.
935impl FromIterator<(String, Value)> for Frontmatter {
936 /// Creates a `Frontmatter` from an iterator of key-value pairs.
937 ///
938 /// # Arguments
939 ///
940 /// * `iter` - An iterator of key-value pairs where the key is a `String` and the value is a `Value`.
941 ///
942 /// # Returns
943 ///
944 /// A `Frontmatter` containing the key-value pairs from the iterator.
945 ///
946 /// # Examples
947 ///
948 /// ```
949 /// use frontmatter_gen::{Frontmatter, Value};
950 /// use std::iter::FromIterator;
951 ///
952 /// let pairs = vec![
953 /// ("key1".to_string(), Value::String("value1".to_string())),
954 /// ("key2".to_string(), Value::Number(42.0)),
955 /// ];
956 ///
957 /// let fm = Frontmatter::from_iter(pairs);
958 /// assert_eq!(fm.len(), 2);
959 /// assert_eq!(fm.get("key1"), Some(&Value::String("value1".to_string())));
960 /// assert_eq!(fm.get("key2"), Some(&Value::Number(42.0)));
961 /// ```
962 fn from_iter<I: IntoIterator<Item = (String, Value)>>(
963 iter: I,
964 ) -> Self {
965 let mut fm = Frontmatter::new();
966 for (key, value) in iter {
967 let _ = fm.insert(key, value);
968 }
969 fm
970 }
971}
972
973/// Implement `Display` for `Frontmatter` to allow easy printing with escaped characters.
974impl fmt::Display for Frontmatter {
975 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
976 write!(f, "{{")?;
977
978 // Use a BTreeMap to ensure consistent key order (sorted by key)
979 let mut sorted_map = BTreeMap::new();
980 for (key, value) in &self.0 {
981 let _ = sorted_map.insert(key, value);
982 }
983
984 for (i, (key, value)) in sorted_map.iter().enumerate() {
985 if i > 0 {
986 write!(f, ", ")?;
987 }
988 write!(f, "\"{}\": {}", escape_str(key), value)?;
989 }
990
991 write!(f, "}}")
992 }
993}
994
995/// Implement `Display` for `Value` to allow easy printing with escaped characters.
996impl fmt::Display for Value {
997 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
998 match self {
999 Value::Null => write!(f, "null"),
1000 Value::String(s) => write!(f, "\"{}\"", escape_str(s)),
1001 Value::Number(n) => {
1002 if n.fract() == 0.0 {
1003 write!(f, "{:.0}", n)
1004 } else {
1005 write!(f, "{}", n)
1006 }
1007 }
1008 Value::Boolean(b) => write!(f, "{}", b),
1009 Value::Array(arr) => {
1010 write!(f, "[")?;
1011 for (i, v) in arr.iter().enumerate() {
1012 if i > 0 {
1013 write!(f, ", ")?;
1014 }
1015 write!(f, "{}", v)?;
1016 }
1017 write!(f, "]")
1018 }
1019 Value::Object(obj) => write!(f, "{}", obj),
1020 Value::Tagged(tag, val) => {
1021 write!(f, "\"{}\": {}", escape_str(tag), val)
1022 }
1023 }
1024 }
1025}
1026
1027/// Escapes special characters in a string (e.g., backslashes and quotes).
1028///
1029/// # Arguments
1030///
1031/// * `s` - The input string to escape.
1032///
1033/// # Returns
1034///
1035/// A new `String` with special characters escaped.
1036///
1037/// # Examples
1038///
1039/// ```
1040/// use frontmatter_gen::types::escape_str;
1041///
1042/// assert_eq!(escape_str(r#"Hello "World""#), r#"Hello \"World\""#);
1043/// assert_eq!(escape_str(r#"C:\path\to\file"#), r#"C:\\path\\to\\file"#);
1044/// ```
1045pub fn escape_str(s: &str) -> String {
1046 let mut escaped = String::with_capacity(s.len());
1047 for c in s.chars() {
1048 match c {
1049 '"' => escaped.push_str("\\\""),
1050 '\\' => escaped.push_str("\\\\"),
1051 _ => escaped.push(c),
1052 }
1053 }
1054 escaped
1055}
1056
1057#[cfg(test)]
1058mod tests {
1059 use super::*;
1060 use std::f64::consts::PI;
1061
1062 mod format_tests {
1063 use super::*;
1064
1065 #[test]
1066 fn test_format_default() {
1067 assert_eq!(Format::default(), Format::Json);
1068 }
1069 }
1070
1071 mod value_tests {
1072 use super::*;
1073
1074 #[test]
1075 fn test_value_default() {
1076 assert_eq!(Value::default(), Value::Null);
1077 }
1078
1079 #[test]
1080 fn test_value_as_str() {
1081 let value = Value::String("Hello".to_string());
1082 assert_eq!(value.as_str(), Some("Hello"));
1083
1084 let value = Value::Number(42.0);
1085 assert_eq!(value.as_str(), None);
1086 }
1087
1088 #[test]
1089 fn test_value_as_f64() {
1090 let value = Value::Number(42.0);
1091 assert_eq!(value.as_f64(), Some(42.0));
1092
1093 let value = Value::String("Not a number".to_string());
1094 assert_eq!(value.as_f64(), None);
1095 }
1096
1097 #[test]
1098 fn test_value_as_bool() {
1099 let value = Value::Boolean(true);
1100 assert_eq!(value.as_bool(), Some(true));
1101
1102 let value = Value::String("Not a boolean".to_string());
1103 assert_eq!(value.as_bool(), None);
1104 }
1105
1106 #[test]
1107 fn test_value_is_null() {
1108 assert!(Value::Null.is_null());
1109 assert!(!Value::String("Not null".to_string()).is_null());
1110 }
1111
1112 #[test]
1113 fn test_value_is_string() {
1114 assert!(Value::String("test".to_string()).is_string());
1115 assert!(!Value::Number(42.0).is_string());
1116 }
1117
1118 #[test]
1119 fn test_value_is_number() {
1120 assert!(Value::Number(42.0).is_number());
1121 assert!(!Value::String("42".to_string()).is_number());
1122 }
1123
1124 #[test]
1125 fn test_value_is_boolean() {
1126 assert!(Value::Boolean(true).is_boolean());
1127 assert!(!Value::String("true".to_string()).is_boolean());
1128 }
1129
1130 #[test]
1131 fn test_value_as_array() {
1132 let value =
1133 Value::Array(vec![Value::Null, Value::Boolean(false)]);
1134 assert!(value.as_array().is_some());
1135 assert_eq!(value.as_array().unwrap().len(), 2);
1136
1137 assert!(Value::String("Not an array".to_string())
1138 .as_array()
1139 .is_none());
1140 }
1141
1142 #[test]
1143 fn test_value_as_object() {
1144 let mut fm = Frontmatter::new();
1145 let _ = fm.insert(
1146 "key".to_string(),
1147 Value::String("value".to_string()),
1148 );
1149 let value = Value::Object(Box::new(fm.clone()));
1150 assert_eq!(value.as_object().unwrap(), &fm);
1151
1152 assert!(Value::String("Not an object".to_string())
1153 .as_object()
1154 .is_none());
1155 }
1156
1157 #[test]
1158 fn test_value_to_object() {
1159 let fm = Frontmatter::new();
1160 let obj = Value::Object(Box::new(fm.clone()));
1161 assert_eq!(obj.to_object().unwrap(), fm);
1162
1163 assert!(Value::String("Not an object".to_string())
1164 .to_object()
1165 .is_err());
1166 }
1167
1168 #[test]
1169 fn test_value_to_string_representation() {
1170 assert_eq!(
1171 Value::String("test".to_string())
1172 .to_string_representation(),
1173 "\"test\""
1174 );
1175 assert_eq!(
1176 Value::Number(42.0).to_string_representation(),
1177 "42"
1178 );
1179 assert_eq!(
1180 Value::Boolean(true).to_string_representation(),
1181 "true"
1182 );
1183 }
1184
1185 #[test]
1186 fn test_value_display() {
1187 assert_eq!(format!("{}", Value::Null), "null");
1188 assert_eq!(
1189 format!("{}", Value::String("test".to_string())),
1190 "\"test\""
1191 );
1192 assert_eq!(
1193 format!("{}", Value::Number(PI)),
1194 format!("{}", PI)
1195 );
1196 assert_eq!(format!("{}", Value::Boolean(true)), "true");
1197 }
1198 }
1199
1200 mod frontmatter_tests {
1201 use super::*;
1202
1203 #[test]
1204 fn test_frontmatter_new() {
1205 let fm = Frontmatter::new();
1206 assert!(fm.is_empty());
1207 assert_eq!(fm.len(), 0);
1208 }
1209
1210 #[test]
1211 fn test_frontmatter_insert_and_get() {
1212 let mut fm = Frontmatter::new();
1213 let _ = fm.insert(
1214 "title".to_string(),
1215 Value::String("Hello World".to_string()),
1216 );
1217
1218 assert_eq!(
1219 fm.get("title"),
1220 Some(&Value::String("Hello World".to_string()))
1221 );
1222 }
1223
1224 #[test]
1225 fn test_frontmatter_len_and_is_empty() {
1226 let mut fm = Frontmatter::new();
1227 assert!(fm.is_empty());
1228
1229 let _ = fm.insert("key1".to_string(), Value::Null);
1230 assert_eq!(fm.len(), 1);
1231 assert!(!fm.is_empty());
1232 }
1233
1234 #[test]
1235 fn test_frontmatter_merge() {
1236 let mut fm1 = Frontmatter::new();
1237 let _ = fm1.insert(
1238 "key1".to_string(),
1239 Value::String("value1".to_string()),
1240 );
1241
1242 let mut fm2 = Frontmatter::new();
1243 let _ = fm2.insert("key2".to_string(), Value::Number(42.0));
1244
1245 fm1.merge(fm2);
1246 assert_eq!(fm1.len(), 2);
1247 assert_eq!(fm1.get("key2"), Some(&Value::Number(42.0)));
1248 }
1249
1250 #[test]
1251 fn test_frontmatter_display() {
1252 let mut fm = Frontmatter::new();
1253 let _ = fm.insert(
1254 "key1".to_string(),
1255 Value::String("value1".to_string()),
1256 );
1257 let _ = fm.insert("key2".to_string(), Value::Number(42.0));
1258 let display = format!("{}", fm);
1259
1260 assert!(display.contains("\"key1\": \"value1\""));
1261 assert!(display.contains("\"key2\": 42"));
1262 }
1263
1264 #[test]
1265 fn test_frontmatter_is_null() {
1266 let mut fm = Frontmatter::new();
1267 let _ = fm.insert("key".to_string(), Value::Null);
1268
1269 assert!(fm.is_null("key"));
1270 assert!(!fm.is_null("nonexistent_key"));
1271 }
1272 }
1273
1274 mod utility_tests {
1275 use super::*;
1276
1277 #[test]
1278 fn test_escape_str() {
1279 assert_eq!(
1280 escape_str(r#"Hello "World""#),
1281 r#"Hello \"World\""#
1282 );
1283 assert_eq!(
1284 escape_str(r"C:\path\to\file"),
1285 r"C:\\path\\to\\file"
1286 );
1287 }
1288
1289 #[test]
1290 fn test_escape_str_empty() {
1291 assert_eq!(escape_str(""), "");
1292 }
1293 }
1294
1295 mod additional_tests {
1296 use super::*;
1297
1298 #[test]
1299 fn test_frontmatter_clear() {
1300 let mut fm = Frontmatter::new();
1301 let _ = fm.insert(
1302 "key1".to_string(),
1303 Value::String("value1".to_string()),
1304 );
1305 let _ = fm.insert("key2".to_string(), Value::Number(42.0));
1306
1307 fm.clear();
1308 assert!(fm.is_empty());
1309 assert_eq!(fm.len(), 0);
1310 }
1311
1312 #[test]
1313 fn test_frontmatter_capacity_and_reserve() {
1314 let mut fm = Frontmatter::new();
1315 let initial_capacity = fm.capacity();
1316
1317 fm.reserve(10);
1318 assert!(fm.capacity() >= initial_capacity + 10);
1319 }
1320
1321 #[test]
1322 fn test_value_tagged() {
1323 let tagged_value = Value::Tagged(
1324 "tag".to_string(),
1325 Box::new(Value::Number(42.0)),
1326 );
1327
1328 assert_eq!(
1329 tagged_value.as_tagged(),
1330 Some(("tag", &Value::Number(42.0)))
1331 );
1332 }
1333
1334 #[test]
1335 fn test_value_array_mutation() {
1336 let mut value = Value::Array(vec![
1337 Value::Number(1.0),
1338 Value::Number(2.0),
1339 ]);
1340
1341 value
1342 .get_mut_array()
1343 .expect("array")
1344 .push(Value::Number(3.0));
1345
1346 assert_eq!(value.array_len(), Some(3));
1347 assert!(value
1348 .as_array()
1349 .unwrap()
1350 .contains(&Value::Number(3.0)));
1351 }
1352
1353 #[test]
1354 fn test_value_conversion_errors() {
1355 let value = Value::Boolean(true);
1356 assert!(value.clone().into_f64().is_err());
1357 assert!(value.into_string().is_err());
1358
1359 let value = Value::Number(42.0);
1360 assert!(value.into_bool().is_err());
1361 }
1362
1363 #[test]
1364 fn test_value_from_str_error_handling() {
1365 assert_eq!("null".parse::<Value>().unwrap(), Value::Null);
1366 assert_eq!(
1367 "true".parse::<Value>().unwrap(),
1368 Value::Boolean(true)
1369 );
1370 assert_eq!(
1371 "false".parse::<Value>().unwrap(),
1372 Value::Boolean(false)
1373 );
1374
1375 let invalid_number = "abc123".parse::<Value>();
1376 assert!(invalid_number.is_ok()); // Treated as a string.
1377 assert_eq!(
1378 invalid_number.unwrap(),
1379 Value::String("abc123".to_string())
1380 );
1381 }
1382
1383 #[test]
1384 fn test_frontmatter_empty_iterator() {
1385 let fm = Frontmatter::new();
1386 let mut iter = fm.iter();
1387
1388 assert!(iter.next().is_none());
1389 }
1390
1391 #[test]
1392 fn test_frontmatter_duplicate_merge() {
1393 let mut fm1 = Frontmatter::new();
1394 let _ = fm1.insert(
1395 "key1".to_string(),
1396 Value::String("value1".to_string()),
1397 );
1398
1399 let mut fm2 = Frontmatter::new();
1400 let _ = fm2.insert(
1401 "key1".to_string(),
1402 Value::String("new_value".to_string()),
1403 );
1404
1405 fm1.merge(fm2);
1406
1407 assert_eq!(
1408 fm1.get("key1"),
1409 Some(&Value::String("new_value".to_string()))
1410 );
1411 }
1412
1413 #[test]
1414 fn test_display_for_empty_frontmatter() {
1415 let fm = Frontmatter::new();
1416 let display = format!("{}", fm);
1417 assert_eq!(display, "{}");
1418 }
1419
1420 #[test]
1421 fn test_value_from_iterator_empty() {
1422 let vec: Vec<Value> = vec![];
1423 let array_value: Value = vec.into_iter().collect();
1424 assert_eq!(array_value, Value::Array(vec![]));
1425 }
1426
1427 #[test]
1428 fn test_escape_str_edge_cases() {
1429 let special_chars = r"Special \chars\n\t";
1430 assert_eq!(
1431 escape_str(special_chars),
1432 r"Special \\chars\\n\\t"
1433 );
1434 }
1435 }
1436}
1437
1438#[cfg(test)]
1439mod exhaustive_type_tests {
1440 //! `Display`, the conversion helpers and the map surface, one case
1441 //! per variant and one per failure mode.
1442 //!
1443 //! `Display` for `Value` is what every serialiser in this crate
1444 //! ultimately calls, so a wrong arm there is wrong output rather
1445 //! than a crash — the kind of bug a test has to look for on purpose.
1446
1447 use super::*;
1448
1449 fn one_of_every_value() -> Vec<Value> {
1450 vec![
1451 Value::Null,
1452 Value::String("s".into()),
1453 Value::Number(42.0),
1454 Value::Number(1.5),
1455 Value::Boolean(true),
1456 Value::Array(vec![
1457 Value::Number(1.0),
1458 Value::String("a".into()),
1459 ]),
1460 Value::Object(Box::new(Frontmatter::new())),
1461 Value::Tagged("tag".into(), Box::new(Value::Number(42.0))),
1462 ]
1463 }
1464
1465 #[test]
1466 fn format_displays_each_variant_by_name() {
1467 assert_eq!(Format::Yaml.to_string(), "YAML");
1468 assert_eq!(Format::Toml.to_string(), "TOML");
1469 assert_eq!(Format::Json.to_string(), "JSON");
1470 assert_eq!(Format::Unsupported.to_string(), "Unsupported");
1471 }
1472
1473 #[test]
1474 fn value_display_covers_every_variant() {
1475 assert_eq!(Value::Null.to_string(), "null");
1476 assert_eq!(Value::String("s".into()).to_string(), "\"s\"");
1477 assert_eq!(Value::Boolean(true).to_string(), "true");
1478 // Whole numbers print without a fractional part; others keep it.
1479 assert_eq!(Value::Number(42.0).to_string(), "42");
1480 assert_eq!(Value::Number(1.5).to_string(), "1.5");
1481 assert_eq!(
1482 Value::Array(vec![
1483 Value::Number(1.0),
1484 Value::String("a".into())
1485 ])
1486 .to_string(),
1487 "[1, \"a\"]"
1488 );
1489 assert_eq!(
1490 Value::Tagged("t".into(), Box::new(Value::Number(2.0)))
1491 .to_string(),
1492 "\"t\": 2"
1493 );
1494 }
1495
1496 #[test]
1497 fn display_escapes_quotes_and_backslashes() {
1498 assert_eq!(
1499 Value::String("a\"b\\c".into()).to_string(),
1500 "\"a\\\"b\\\\c\""
1501 );
1502 // `escape_str` handles the two characters its documentation
1503 // names. It does not escape control characters, so a string
1504 // containing a newline renders with that newline intact —
1505 // see `display_is_not_json_for_control_characters`.
1506 assert_eq!(escape_str("plain"), "plain");
1507 assert_eq!(escape_str("line\nbreak"), "line\nbreak");
1508 }
1509
1510 #[test]
1511 fn display_is_not_json_for_control_characters() {
1512 // Pinning a known limitation rather than asserting it is fine.
1513 // `Display` renders JSON-shaped output, but `escape_str` only
1514 // escapes `"` and `\`, so a control character passes through
1515 // and the result is not parseable JSON. Callers that need JSON
1516 // must use `serde_json`, not `to_string`.
1517 let rendered = Value::String("line\nbreak".into()).to_string();
1518 assert_eq!(rendered, "\"line\nbreak\"");
1519 assert!(
1520 serde_json::from_str::<serde_json::Value>(&rendered).is_err(),
1521 "if this starts parsing, escape_str learned control characters \
1522 and this test should become the stronger assertion"
1523 );
1524 }
1525
1526 #[test]
1527 fn frontmatter_display_sorts_keys_and_escapes_them() {
1528 let mut fm = Frontmatter::new();
1529 let _ = fm.insert("b".into(), Value::Number(2.0));
1530 let _ = fm.insert("a".into(), Value::String("x".into()));
1531 // Sorted, so the rendering is stable regardless of insertion order.
1532 assert_eq!(fm.to_string(), "{\"a\": \"x\", \"b\": 2}");
1533 assert_eq!(Frontmatter::new().to_string(), "{}");
1534 }
1535
1536 #[test]
1537 fn accessors_return_some_only_for_their_own_variant() {
1538 for value in one_of_every_value() {
1539 let matches_str = matches!(value, Value::String(_));
1540 assert_eq!(
1541 value.as_str().is_some(),
1542 matches_str,
1543 "{value:?}"
1544 );
1545 let matches_obj = matches!(value, Value::Object(_));
1546 assert_eq!(
1547 value.as_object().is_some(),
1548 matches_obj,
1549 "{value:?}"
1550 );
1551 let matches_tag = matches!(value, Value::Tagged(_, _));
1552 assert_eq!(
1553 value.as_tagged().is_some(),
1554 matches_tag,
1555 "{value:?}"
1556 );
1557 let matches_arr = matches!(value, Value::Array(_));
1558 assert_eq!(
1559 value.array_len().is_some(),
1560 matches_arr,
1561 "{value:?}"
1562 );
1563 }
1564 assert_eq!(
1565 Value::Tagged("t".into(), Box::new(Value::Null))
1566 .as_tagged()
1567 .map(|(t, _)| t),
1568 Some("t")
1569 );
1570 assert_eq!(
1571 Value::Array(vec![Value::Null, Value::Null]).array_len(),
1572 Some(2)
1573 );
1574 }
1575
1576 #[test]
1577 fn is_null_only_for_null() {
1578 for value in one_of_every_value() {
1579 assert_eq!(
1580 value.is_null(),
1581 matches!(value, Value::Null),
1582 "{value:?}"
1583 );
1584 }
1585 }
1586
1587 #[test]
1588 fn conversions_succeed_for_their_variant_and_report_the_others() {
1589 assert_eq!(
1590 Value::String("s".into()).into_string().unwrap(),
1591 "s"
1592 );
1593 assert!(Value::Number(1.0).into_string().is_err());
1594
1595 assert!(
1596 (Value::Number(2.5).into_f64().unwrap() - 2.5).abs()
1597 < f64::EPSILON
1598 );
1599 assert!(Value::Null.into_f64().is_err());
1600
1601 assert!(Value::Boolean(true).into_bool().unwrap());
1602 assert!(Value::String("true".into()).into_bool().is_err());
1603
1604 assert!(Value::Object(Box::new(Frontmatter::new()))
1605 .to_object()
1606 .is_ok());
1607 assert!(Value::Null.to_object().is_err());
1608 }
1609
1610 #[test]
1611 fn to_string_representation_matches_display() {
1612 // Documented behaviour: strings keep their quotes, numbers do
1613 // not. The name suggests a plain rendering; it is `Display`.
1614 assert_eq!(
1615 Value::String("plain".into()).to_string_representation(),
1616 "\"plain\""
1617 );
1618 // Not 3.14: clippy reads that as an approximation of PI.
1619 assert_eq!(
1620 Value::Number(2.75).to_string_representation(),
1621 "2.75"
1622 );
1623 assert_eq!(Value::Null.to_string_representation(), "null");
1624 assert_eq!(
1625 Value::Boolean(false).to_string_representation(),
1626 "false"
1627 );
1628 }
1629
1630 #[test]
1631 fn get_mut_array_allows_in_place_edits() {
1632 let mut value = Value::Array(vec![Value::Number(1.0)]);
1633 value
1634 .get_mut_array()
1635 .expect("array")
1636 .push(Value::Number(2.0));
1637 assert_eq!(value.array_len(), Some(2));
1638 assert!(Value::Null.get_mut_array().is_none());
1639 }
1640
1641 #[test]
1642 fn frontmatter_map_surface_behaves_like_a_map() {
1643 let mut fm = Frontmatter::new();
1644 assert!(fm.is_empty());
1645 assert_eq!(fm.len(), 0);
1646
1647 let _ = fm.insert("a".into(), Value::Number(1.0));
1648 let _ = fm.insert("b".into(), Value::Null);
1649 assert_eq!(fm.len(), 2);
1650 assert!(!fm.is_empty());
1651 assert!(fm.contains_key("a"));
1652 assert!(!fm.contains_key("zz"));
1653 assert!(fm.is_null("b"));
1654 assert!(!fm.is_null("a"));
1655 assert!(!fm.is_null("missing"));
1656
1657 assert_eq!(fm.get("a"), Some(&Value::Number(1.0)));
1658 assert_eq!(fm.get("missing"), None);
1659 *fm.get_mut("a").expect("key a") = Value::Number(9.0);
1660 assert_eq!(fm.get("a"), Some(&Value::Number(9.0)));
1661 assert!(fm.get_mut("missing").is_none());
1662
1663 assert_eq!(fm.remove("b"), Some(Value::Null));
1664 assert_eq!(fm.remove("b"), None);
1665
1666 assert_eq!(fm.iter().count(), 1);
1667 for (_, v) in fm.iter_mut() {
1668 *v = Value::Boolean(false);
1669 }
1670 assert_eq!(fm.get("a"), Some(&Value::Boolean(false)));
1671
1672 fm.reserve(8);
1673 assert!(fm.capacity() >= 1);
1674 fm.clear();
1675 assert!(fm.is_empty());
1676 }
1677
1678 #[test]
1679 fn merge_lets_the_incoming_map_win() {
1680 let mut base = Frontmatter::new();
1681 let _ = base.insert("shared".into(), Value::Number(1.0));
1682 let _ = base.insert("kept".into(), Value::Boolean(true));
1683
1684 let mut other = Frontmatter::new();
1685 let _ = other.insert("shared".into(), Value::Number(2.0));
1686 let _ = other.insert("added".into(), Value::Null);
1687
1688 base.merge(other);
1689 assert_eq!(base.get("shared"), Some(&Value::Number(2.0)));
1690 assert_eq!(base.get("kept"), Some(&Value::Boolean(true)));
1691 assert!(base.contains_key("added"));
1692 }
1693
1694 #[test]
1695 fn every_is_predicate_answers_for_every_variant() {
1696 for value in one_of_every_value() {
1697 assert_eq!(
1698 value.is_string(),
1699 matches!(value, Value::String(_))
1700 );
1701 assert_eq!(
1702 value.is_number(),
1703 matches!(value, Value::Number(_))
1704 );
1705 assert_eq!(
1706 value.is_boolean(),
1707 matches!(value, Value::Boolean(_))
1708 );
1709 assert_eq!(
1710 value.is_array(),
1711 matches!(value, Value::Array(_))
1712 );
1713 assert_eq!(
1714 value.is_object(),
1715 matches!(value, Value::Object(_))
1716 );
1717 assert_eq!(
1718 value.is_tagged(),
1719 matches!(value, Value::Tagged(_, _))
1720 );
1721 }
1722 }
1723
1724 #[test]
1725 fn converts_from_each_rust_scalar() {
1726 assert_eq!(Value::from("s"), Value::String("s".into()));
1727 assert_eq!(
1728 Value::from(String::from("owned")),
1729 Value::String("owned".into())
1730 );
1731 assert_eq!(Value::from(2.5_f64), Value::Number(2.5));
1732 assert_eq!(Value::from(true), Value::Boolean(true));
1733 }
1734
1735 #[test]
1736 fn default_frontmatter_is_empty_and_iterates() {
1737 let fm = Frontmatter::default();
1738 assert!(fm.is_empty());
1739
1740 let mut fm = Frontmatter::new();
1741 let _ = fm.insert("a".into(), Value::Number(1.0));
1742 let _ = fm.insert("b".into(), Value::Number(2.0));
1743 let collected: Vec<_> = fm.into_iter().collect();
1744 assert_eq!(collected.len(), 2);
1745 }
1746
1747 #[test]
1748 fn collects_from_an_iterator() {
1749 let fm: Frontmatter = vec![
1750 ("a".to_string(), Value::Number(1.0)),
1751 ("b".to_string(), Value::String("x".into())),
1752 ]
1753 .into_iter()
1754 .collect();
1755 assert_eq!(fm.len(), 2);
1756 assert_eq!(fm.get("b"), Some(&Value::String("x".into())));
1757 assert_eq!("42.5".parse::<Value>(), Ok(Value::Number(42.5)));
1758 let obj_val = Value::Object(Box::new(fm));
1759 assert!(obj_val.to_string().contains("\"a\": 1"));
1760 }
1761}