Skip to main content

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