facet_reflect/peek/
dynamic_value.rs

1//! Support for peeking into DynamicValue types like `facet_value::Value`
2
3use facet_core::{DynDateTimeKind, DynValueKind, DynamicValueDef};
4
5use super::Peek;
6
7/// Lets you read from a dynamic value (implements read-only operations for DynamicValue types)
8///
9/// This is used for types like `facet_value::Value` that can hold any of:
10/// null, bool, number, string, bytes, array, or object - determined at runtime.
11#[derive(Clone, Copy)]
12pub struct PeekDynamicValue<'mem, 'facet> {
13    /// the underlying peek value
14    pub(crate) value: Peek<'mem, 'facet>,
15
16    /// the definition of the dynamic value
17    pub(crate) def: DynamicValueDef,
18}
19
20impl<'mem, 'facet> PeekDynamicValue<'mem, 'facet> {
21    /// Returns the dynamic value definition
22    #[inline(always)]
23    pub fn def(&self) -> DynamicValueDef {
24        self.def
25    }
26
27    /// Returns the underlying peek value
28    #[inline(always)]
29    pub fn peek(&self) -> Peek<'mem, 'facet> {
30        self.value
31    }
32
33    /// Returns the kind of value stored
34    #[inline]
35    pub fn kind(&self) -> DynValueKind {
36        unsafe { (self.def.vtable.get_kind)(self.value.data()) }
37    }
38
39    /// Returns true if the value is null
40    #[inline]
41    pub fn is_null(&self) -> bool {
42        self.kind() == DynValueKind::Null
43    }
44
45    /// Returns the boolean value if this is a bool, None otherwise
46    #[inline]
47    pub fn as_bool(&self) -> Option<bool> {
48        unsafe { (self.def.vtable.get_bool)(self.value.data()) }
49    }
50
51    /// Returns the i64 value if representable, None otherwise
52    #[inline]
53    pub fn as_i64(&self) -> Option<i64> {
54        unsafe { (self.def.vtable.get_i64)(self.value.data()) }
55    }
56
57    /// Returns the u64 value if representable, None otherwise
58    #[inline]
59    pub fn as_u64(&self) -> Option<u64> {
60        unsafe { (self.def.vtable.get_u64)(self.value.data()) }
61    }
62
63    /// Returns the f64 value if this is a number, None otherwise
64    #[inline]
65    pub fn as_f64(&self) -> Option<f64> {
66        unsafe { (self.def.vtable.get_f64)(self.value.data()) }
67    }
68
69    /// Returns the string value if this is a string, None otherwise
70    #[inline]
71    pub fn as_str(&self) -> Option<&'mem str> {
72        unsafe { (self.def.vtable.get_str)(self.value.data()) }
73    }
74
75    /// Returns the bytes value if this is bytes, None otherwise
76    #[inline]
77    pub fn as_bytes(&self) -> Option<&'mem [u8]> {
78        self.def
79            .vtable
80            .get_bytes
81            .and_then(|f| unsafe { f(self.value.data()) })
82    }
83
84    /// Returns the datetime components if this is a datetime, None otherwise
85    ///
86    /// Returns `(year, month, day, hour, minute, second, nanos, kind)`.
87    #[inline]
88    #[allow(clippy::type_complexity)]
89    pub fn as_datetime(&self) -> Option<(i32, u8, u8, u8, u8, u8, u32, DynDateTimeKind)> {
90        self.def
91            .vtable
92            .get_datetime
93            .and_then(|f| unsafe { f(self.value.data()) })
94    }
95
96    /// Returns the length of the array if this is an array, None otherwise
97    #[inline]
98    pub fn array_len(&self) -> Option<usize> {
99        unsafe { (self.def.vtable.array_len)(self.value.data()) }
100    }
101
102    /// Returns an element from the array by index, None if not an array or index out of bounds
103    #[inline]
104    pub fn array_get(&self, index: usize) -> Option<Peek<'mem, 'facet>> {
105        let ptr = unsafe { (self.def.vtable.array_get)(self.value.data(), index)? };
106        // The element is also a DynamicValue with the same shape
107        Some(unsafe { Peek::unchecked_new(ptr, self.value.shape()) })
108    }
109
110    /// Returns the length of the object if this is an object, None otherwise
111    #[inline]
112    pub fn object_len(&self) -> Option<usize> {
113        unsafe { (self.def.vtable.object_len)(self.value.data()) }
114    }
115
116    /// Returns a key-value pair from the object by index, None if not an object or index out of bounds
117    #[inline]
118    pub fn object_get_entry(&self, index: usize) -> Option<(&'mem str, Peek<'mem, 'facet>)> {
119        let (key, value_ptr) =
120            unsafe { (self.def.vtable.object_get_entry)(self.value.data(), index)? };
121        // The value is also a DynamicValue with the same shape
122        Some((key, unsafe {
123            Peek::unchecked_new(value_ptr, self.value.shape())
124        }))
125    }
126
127    /// Returns a value from the object by key, None if not an object or key not found
128    #[inline]
129    pub fn object_get(&self, key: &str) -> Option<Peek<'mem, 'facet>> {
130        let ptr = unsafe { (self.def.vtable.object_get)(self.value.data(), key)? };
131        // The value is also a DynamicValue with the same shape
132        Some(unsafe { Peek::unchecked_new(ptr, self.value.shape()) })
133    }
134
135    /// Returns an iterator over array elements if this is an array
136    #[inline]
137    pub fn array_iter(&self) -> Option<PeekDynamicValueArrayIter<'mem, 'facet>> {
138        let len = self.array_len()?;
139        Some(PeekDynamicValueArrayIter {
140            dyn_value: *self,
141            index: 0,
142            len,
143        })
144    }
145
146    /// Returns an iterator over object entries if this is an object
147    #[inline]
148    pub fn object_iter(&self) -> Option<PeekDynamicValueObjectIter<'mem, 'facet>> {
149        let len = self.object_len()?;
150        Some(PeekDynamicValueObjectIter {
151            dyn_value: *self,
152            index: 0,
153            len,
154        })
155    }
156
157    /// Structurally hash the dynamic value's contents.
158    ///
159    /// This is called by `Peek::structural_hash` for dynamic values.
160    pub fn structural_hash_inner<H: core::hash::Hasher>(&self, hasher: &mut H) {
161        use core::hash::Hash;
162
163        // Hash the kind discriminant
164        let kind = self.kind();
165        core::mem::discriminant(&kind).hash(hasher);
166
167        match kind {
168            DynValueKind::Null => {
169                // Nothing more to hash
170            }
171            DynValueKind::Bool => {
172                if let Some(b) = self.as_bool() {
173                    b.hash(hasher);
174                }
175            }
176            DynValueKind::Number => {
177                // Try to get as various number types and hash
178                if let Some(n) = self.as_i64() {
179                    0u8.hash(hasher); // discriminant for i64
180                    n.hash(hasher);
181                } else if let Some(n) = self.as_u64() {
182                    1u8.hash(hasher); // discriminant for u64
183                    n.hash(hasher);
184                } else if let Some(n) = self.as_f64() {
185                    2u8.hash(hasher); // discriminant for f64
186                    n.to_bits().hash(hasher);
187                }
188            }
189            DynValueKind::String => {
190                if let Some(s) = self.as_str() {
191                    s.hash(hasher);
192                }
193            }
194            DynValueKind::Bytes => {
195                if let Some(b) = self.as_bytes() {
196                    b.hash(hasher);
197                }
198            }
199            DynValueKind::Array => {
200                if let Some(len) = self.array_len() {
201                    len.hash(hasher);
202                    if let Some(iter) = self.array_iter() {
203                        for elem in iter {
204                            elem.structural_hash(hasher);
205                        }
206                    }
207                }
208            }
209            DynValueKind::Object => {
210                if let Some(len) = self.object_len() {
211                    len.hash(hasher);
212                    if let Some(iter) = self.object_iter() {
213                        for (key, value) in iter {
214                            key.hash(hasher);
215                            value.structural_hash(hasher);
216                        }
217                    }
218                }
219            }
220            DynValueKind::DateTime | DynValueKind::QName | DynValueKind::Uuid => {
221                // Hash the string representation
222                if let Some(s) = self.as_str() {
223                    s.hash(hasher);
224                }
225            }
226        }
227    }
228}
229
230/// Iterator over array elements in a dynamic value
231pub struct PeekDynamicValueArrayIter<'mem, 'facet> {
232    dyn_value: PeekDynamicValue<'mem, 'facet>,
233    index: usize,
234    len: usize,
235}
236
237impl<'mem, 'facet> Iterator for PeekDynamicValueArrayIter<'mem, 'facet> {
238    type Item = Peek<'mem, 'facet>;
239
240    #[inline]
241    fn next(&mut self) -> Option<Self::Item> {
242        if self.index >= self.len {
243            return None;
244        }
245        let item = self.dyn_value.array_get(self.index)?;
246        self.index += 1;
247        Some(item)
248    }
249
250    #[inline]
251    fn size_hint(&self) -> (usize, Option<usize>) {
252        let remaining = self.len.saturating_sub(self.index);
253        (remaining, Some(remaining))
254    }
255}
256
257impl ExactSizeIterator for PeekDynamicValueArrayIter<'_, '_> {}
258
259/// Iterator over object entries in a dynamic value
260pub struct PeekDynamicValueObjectIter<'mem, 'facet> {
261    dyn_value: PeekDynamicValue<'mem, 'facet>,
262    index: usize,
263    len: usize,
264}
265
266impl<'mem, 'facet> Iterator for PeekDynamicValueObjectIter<'mem, 'facet> {
267    type Item = (&'mem str, Peek<'mem, 'facet>);
268
269    #[inline]
270    fn next(&mut self) -> Option<Self::Item> {
271        if self.index >= self.len {
272            return None;
273        }
274        let entry = self.dyn_value.object_get_entry(self.index)?;
275        self.index += 1;
276        Some(entry)
277    }
278
279    #[inline]
280    fn size_hint(&self) -> (usize, Option<usize>) {
281        let remaining = self.len.saturating_sub(self.index);
282        (remaining, Some(remaining))
283    }
284}
285
286impl ExactSizeIterator for PeekDynamicValueObjectIter<'_, '_> {}
287
288impl core::fmt::Debug for PeekDynamicValue<'_, '_> {
289    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
290        f.debug_struct("PeekDynamicValue")
291            .field("kind", &self.kind())
292            .finish_non_exhaustive()
293    }
294}