Skip to main content

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}