rust_yaml/
value_borrowed.rs

1//! Zero-copy YAML value representation using borrowed data
2//!
3//! This module provides a borrowed version of the Value enum that minimizes
4//! allocations by using Cow (Clone-on-Write) for strings and borrowed slices
5//! where possible.
6
7use indexmap::IndexMap;
8use std::borrow::Cow;
9use std::fmt;
10use std::hash::{Hash, Hasher};
11
12/// A zero-copy YAML value that borrows data where possible
13#[derive(Debug, Clone, PartialEq)]
14pub enum BorrowedValue<'a> {
15    /// Null value
16    Null,
17    /// Boolean value
18    Bool(bool),
19    /// Integer value
20    Int(i64),
21    /// Floating point value
22    Float(f64),
23    /// String value (borrowed or owned)
24    String(Cow<'a, str>),
25    /// Sequence (array/list) value
26    Sequence(Vec<BorrowedValue<'a>>),
27    /// Mapping (dictionary/object) value
28    Mapping(IndexMap<BorrowedValue<'a>, BorrowedValue<'a>>),
29}
30
31impl<'a> BorrowedValue<'a> {
32    /// Create a null value
33    pub const fn null() -> Self {
34        Self::Null
35    }
36
37    /// Create a boolean value
38    pub const fn bool(b: bool) -> Self {
39        Self::Bool(b)
40    }
41
42    /// Create an integer value
43    pub const fn int(i: i64) -> Self {
44        Self::Int(i)
45    }
46
47    /// Create a float value
48    pub const fn float(f: f64) -> Self {
49        Self::Float(f)
50    }
51
52    /// Create a borrowed string value
53    pub const fn borrowed_string(s: &'a str) -> Self {
54        Self::String(Cow::Borrowed(s))
55    }
56
57    /// Create an owned string value
58    pub fn owned_string(s: String) -> Self {
59        Self::String(Cow::Owned(s))
60    }
61
62    /// Create a string value from a Cow
63    pub const fn string(s: Cow<'a, str>) -> Self {
64        Self::String(s)
65    }
66
67    /// Create an empty sequence
68    pub const fn sequence() -> Self {
69        Self::Sequence(Vec::new())
70    }
71
72    /// Create a sequence with values
73    pub const fn sequence_with(values: Vec<Self>) -> Self {
74        Self::Sequence(values)
75    }
76
77    /// Create an empty mapping
78    pub fn mapping() -> Self {
79        Self::Mapping(IndexMap::new())
80    }
81
82    /// Create a mapping with key-value pairs
83    pub fn mapping_with(pairs: Vec<(Self, Self)>) -> Self {
84        let mut map = IndexMap::new();
85        for (key, value) in pairs {
86            map.insert(key, value);
87        }
88        Self::Mapping(map)
89    }
90
91    /// Get the type name of this value
92    pub const fn type_name(&self) -> &'static str {
93        match self {
94            Self::Null => "null",
95            Self::Bool(_) => "bool",
96            Self::Int(_) => "int",
97            Self::Float(_) => "float",
98            Self::String(_) => "string",
99            Self::Sequence(_) => "sequence",
100            Self::Mapping(_) => "mapping",
101        }
102    }
103
104    /// Check if this value is null
105    pub const fn is_null(&self) -> bool {
106        matches!(self, Self::Null)
107    }
108
109    /// Check if this value is a boolean
110    pub const fn is_bool(&self) -> bool {
111        matches!(self, Self::Bool(_))
112    }
113
114    /// Check if this value is an integer
115    pub const fn is_int(&self) -> bool {
116        matches!(self, Self::Int(_))
117    }
118
119    /// Check if this value is a float
120    pub const fn is_float(&self) -> bool {
121        matches!(self, Self::Float(_))
122    }
123
124    /// Check if this value is a string
125    pub const fn is_string(&self) -> bool {
126        matches!(self, Self::String(_))
127    }
128
129    /// Check if this value is a sequence
130    pub const fn is_sequence(&self) -> bool {
131        matches!(self, Self::Sequence(_))
132    }
133
134    /// Check if this value is a mapping
135    pub const fn is_mapping(&self) -> bool {
136        matches!(self, Self::Mapping(_))
137    }
138
139    /// Try to get this value as a boolean
140    pub const fn as_bool(&self) -> Option<bool> {
141        if let Self::Bool(b) = self {
142            Some(*b)
143        } else {
144            None
145        }
146    }
147
148    /// Try to get this value as an integer
149    pub const fn as_int(&self) -> Option<i64> {
150        if let Self::Int(i) = self {
151            Some(*i)
152        } else {
153            None
154        }
155    }
156
157    /// Try to get this value as a float
158    pub const fn as_float(&self) -> Option<f64> {
159        match self {
160            Self::Float(f) => Some(*f),
161            Self::Int(i) => Some(*i as f64),
162            _ => None,
163        }
164    }
165
166    /// Try to get this value as a string slice
167    pub fn as_str(&self) -> Option<&str> {
168        if let Self::String(s) = self {
169            Some(s.as_ref())
170        } else {
171            None
172        }
173    }
174
175    /// Try to get this value as a sequence
176    pub const fn as_sequence(&self) -> Option<&Vec<BorrowedValue<'a>>> {
177        if let Self::Sequence(seq) = self {
178            Some(seq)
179        } else {
180            None
181        }
182    }
183
184    /// Try to get this value as a mutable sequence
185    pub fn as_sequence_mut(&mut self) -> Option<&mut Vec<BorrowedValue<'a>>> {
186        if let Self::Sequence(seq) = self {
187            Some(seq)
188        } else {
189            None
190        }
191    }
192
193    /// Try to get this value as a mapping
194    pub const fn as_mapping(&self) -> Option<&IndexMap<BorrowedValue<'a>, BorrowedValue<'a>>> {
195        if let Self::Mapping(map) = self {
196            Some(map)
197        } else {
198            None
199        }
200    }
201
202    /// Try to get this value as a mutable mapping
203    pub fn as_mapping_mut(
204        &mut self,
205    ) -> Option<&mut IndexMap<BorrowedValue<'a>, BorrowedValue<'a>>> {
206        if let Self::Mapping(map) = self {
207            Some(map)
208        } else {
209            None
210        }
211    }
212
213    /// Convert to an owned Value (for when lifetime constraints require it)
214    pub fn into_owned(self) -> BorrowedValue<'static> {
215        match self {
216            Self::Null => BorrowedValue::Null,
217            Self::Bool(b) => BorrowedValue::Bool(b),
218            Self::Int(i) => BorrowedValue::Int(i),
219            Self::Float(f) => BorrowedValue::Float(f),
220            Self::String(s) => BorrowedValue::String(Cow::Owned(s.into_owned())),
221            Self::Sequence(seq) => {
222                BorrowedValue::Sequence(seq.into_iter().map(|v| v.into_owned()).collect())
223            }
224            Self::Mapping(map) => BorrowedValue::Mapping(
225                map.into_iter()
226                    .map(|(k, v)| (k.into_owned(), v.into_owned()))
227                    .collect(),
228            ),
229        }
230    }
231
232    /// Clone only if necessary (for Cow optimization)
233    pub fn clone_if_needed(&self) -> Self {
234        match self {
235            Self::String(Cow::Borrowed(s)) => Self::String(Cow::Borrowed(s)),
236            _ => self.clone(),
237        }
238    }
239}
240
241impl<'a> Default for BorrowedValue<'a> {
242    fn default() -> Self {
243        Self::Null
244    }
245}
246
247impl<'a> fmt::Display for BorrowedValue<'a> {
248    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
249        match self {
250            Self::Null => write!(f, "null"),
251            Self::Bool(b) => write!(f, "{}", b),
252            Self::Int(i) => write!(f, "{}", i),
253            Self::Float(fl) => write!(f, "{}", fl),
254            Self::String(s) => write!(f, "{}", s),
255            Self::Sequence(seq) => {
256                write!(f, "[")?;
257                for (i, item) in seq.iter().enumerate() {
258                    if i > 0 {
259                        write!(f, ", ")?;
260                    }
261                    write!(f, "{}", item)?;
262                }
263                write!(f, "]")
264            }
265            Self::Mapping(map) => {
266                write!(f, "{{")?;
267                for (i, (key, value)) in map.iter().enumerate() {
268                    if i > 0 {
269                        write!(f, ", ")?;
270                    }
271                    write!(f, "{}: {}", key, value)?;
272                }
273                write!(f, "}}")
274            }
275        }
276    }
277}
278
279impl<'a> Hash for BorrowedValue<'a> {
280    fn hash<H: Hasher>(&self, state: &mut H) {
281        match self {
282            Self::Null => 0.hash(state),
283            Self::Bool(b) => {
284                1.hash(state);
285                b.hash(state);
286            }
287            Self::Int(i) => {
288                2.hash(state);
289                i.hash(state);
290            }
291            Self::Float(f) => {
292                3.hash(state);
293                f.to_bits().hash(state);
294            }
295            Self::String(s) => {
296                4.hash(state);
297                s.hash(state);
298            }
299            Self::Sequence(seq) => {
300                5.hash(state);
301                seq.hash(state);
302            }
303            Self::Mapping(_) => {
304                6.hash(state);
305                // Note: IndexMap iteration order is deterministic
306                // but we can't hash the map directly
307            }
308        }
309    }
310}
311
312impl<'a> Eq for BorrowedValue<'a> {}
313
314// Conversion from owned Value to BorrowedValue
315impl<'a> From<crate::Value> for BorrowedValue<'a> {
316    fn from(value: crate::Value) -> Self {
317        match value {
318            crate::Value::Null => Self::Null,
319            crate::Value::Bool(b) => Self::Bool(b),
320            crate::Value::Int(i) => Self::Int(i),
321            crate::Value::Float(f) => Self::Float(f),
322            crate::Value::String(s) => Self::String(Cow::Owned(s)),
323            crate::Value::Sequence(seq) => {
324                Self::Sequence(seq.into_iter().map(Into::into).collect())
325            }
326            crate::Value::Mapping(map) => {
327                Self::Mapping(map.into_iter().map(|(k, v)| (k.into(), v.into())).collect())
328            }
329        }
330    }
331}
332
333// Conversion from BorrowedValue to owned Value
334impl From<BorrowedValue<'_>> for crate::Value {
335    fn from(value: BorrowedValue<'_>) -> Self {
336        match value {
337            BorrowedValue::Null => Self::Null,
338            BorrowedValue::Bool(b) => Self::Bool(b),
339            BorrowedValue::Int(i) => Self::Int(i),
340            BorrowedValue::Float(f) => Self::Float(f),
341            BorrowedValue::String(s) => Self::String(s.into_owned()),
342            BorrowedValue::Sequence(seq) => {
343                Self::Sequence(seq.into_iter().map(Into::into).collect())
344            }
345            BorrowedValue::Mapping(map) => {
346                Self::Mapping(map.into_iter().map(|(k, v)| (k.into(), v.into())).collect())
347            }
348        }
349    }
350}
351
352#[cfg(test)]
353mod tests {
354    use super::*;
355
356    #[test]
357    fn test_borrowed_string() {
358        let s = "hello world";
359        let value = BorrowedValue::borrowed_string(s);
360        assert_eq!(value.as_str(), Some("hello world"));
361
362        // Verify it's actually borrowed
363        if let BorrowedValue::String(cow) = &value {
364            assert!(matches!(cow, Cow::Borrowed(_)));
365        }
366    }
367
368    #[test]
369    fn test_owned_string() {
370        let value = BorrowedValue::owned_string("hello".to_string());
371        assert_eq!(value.as_str(), Some("hello"));
372
373        // Verify it's owned
374        if let BorrowedValue::String(cow) = &value {
375            assert!(matches!(cow, Cow::Owned(_)));
376        }
377    }
378
379    #[test]
380    fn test_into_owned() {
381        let s = "test";
382        let borrowed = BorrowedValue::borrowed_string(s);
383        let owned: BorrowedValue<'static> = borrowed.into_owned();
384
385        // Verify the owned version is actually owned
386        if let BorrowedValue::String(cow) = &owned {
387            assert!(matches!(cow, Cow::Owned(_)));
388        }
389    }
390
391    #[test]
392    fn test_zero_copy_mapping() {
393        let key = "key";
394        let value = "value";
395
396        let map = BorrowedValue::mapping_with(vec![(
397            BorrowedValue::borrowed_string(key),
398            BorrowedValue::borrowed_string(value),
399        )]);
400
401        assert!(map.is_mapping());
402        if let BorrowedValue::Mapping(m) = &map {
403            assert_eq!(m.len(), 1);
404        }
405    }
406}