Skip to main content

wide_log/
value.rs

1use faststr::FastStr;
2use serde::ser::{Serialize, Serializer};
3use smallvec::SmallVec;
4
5use crate::key::Key;
6use crate::wide_event::WideEvent;
7
8/// Tag byte identifying the active variant in [`Value`].
9#[repr(u8)]
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum ValueTag {
12    Null = 0,
13    Bool = 1,
14    I64 = 2,
15    U64 = 3,
16    F64 = 4,
17    Str = 5,    // owned FastStr
18    StaticStr = 6, // &'static str (zero-copy)
19    Array = 7,  // Box<SmallVec<[Value; 8]>>
20    Object = 8, // Box<WideEvent<K>>
21}
22
23/// Union holding the data for the active variant.
24/// The largest member is `FastStr` at 32 bytes.
25/// Drop-able types are wrapped in `ManuallyDrop` — the `Value`'s `Drop` impl
26/// handles their cleanup based on the tag.
27#[repr(C)]
28pub(crate) union ValueData<K: Key> {
29    pub(crate) b: bool,
30    pub(crate) i: i64,
31    pub(crate) u: u64,
32    pub(crate) f: f64,
33    pub(crate) s: std::mem::ManuallyDrop<FastStr>,
34    pub(crate) static_str: &'static str,
35    pub(crate) array: std::mem::ManuallyDrop<Box<SmallVec<[Value<K>; 8]>>>,
36    pub(crate) object: std::mem::ManuallyDrop<Box<WideEvent<K>>>,
37}
38
39/// A JSON value stored in a wide event.
40///
41/// Uses a tag + union layout (`#[repr(C)]`) to minimize size. The struct is
42/// 40 bytes — a 2x reduction from the previous 80-byte enum. The tag is a
43/// single byte; the remaining 7 bytes are padding for the 8-byte-aligned
44/// `FastStr` (32 bytes) in the union.
45///
46/// # Variants
47///
48/// - [`ValueTag::Null`] — JSON `null`
49/// - [`ValueTag::Bool`] — `bool`
50/// - [`ValueTag::I64`] — `i64`
51/// - [`ValueTag::U64`] — `u64`
52/// - [`ValueTag::F64`] — `f64`
53/// - [`ValueTag::Str`] — owned string via `FastStr` (SSO for short strings)
54/// - [`ValueTag::StaticStr`] — `&'static str` (zero-copy, zero-allocation)
55/// - [`ValueTag::Array`] — `Box<SmallVec<[Value; 8]>>`
56/// - [`ValueTag::Object`] — `Box<WideEvent<K>>`
57///
58/// # Conversions
59///
60/// All primitive types implement `Into<Value<K>>`:
61///
62/// - `bool` → `Bool`
63/// - `i64` → `I64`
64/// - `u64` → `U64`
65/// - `f64` → `F64`
66/// - `&'static str` → `StaticStr` (zero-copy)
67/// - `&str` → `Str` (via `FastStr::new`, SSO for short strings)
68/// - `String` → `Str` (via `FastStr::from_string`, takes ownership)
69/// - `FastStr` → `Str`
70/// - `()` → `Null`
71#[repr(C)]
72pub struct Value<K: Key> {
73    tag: ValueTag,
74    _pad: [u8; 7],
75    pub(crate) data: ValueData<K>,
76}
77
78impl<K: Key> Value<K> {
79    /// Creates an Object value from a WideEvent.
80    #[inline]
81    pub(crate) fn from_object(ev: WideEvent<K>) -> Self {
82        Value { tag: ValueTag::Object, _pad: [0; 7], data: ValueData { object: std::mem::ManuallyDrop::new(Box::new(ev)) } }
83    }
84
85    /// Creates an Array value from a SmallVec of Values.
86    #[inline]
87    #[allow(dead_code)]
88    pub(crate) fn from_array(arr: SmallVec<[Value<K>; 8]>) -> Self {
89        Value { tag: ValueTag::Array, _pad: [0; 7], data: ValueData { array: std::mem::ManuallyDrop::new(Box::new(arr)) } }
90    }
91
92    /// Returns the tag identifying the active variant.
93    #[inline]
94    pub fn tag(&self) -> ValueTag {
95        self.tag
96    }
97
98    /// Returns `true` if this value is an `Object`.
99    #[inline]
100    pub fn is_object(&self) -> bool {
101        self.tag == ValueTag::Object
102    }
103
104    #[inline]
105    pub fn as_str(&self) -> Option<&str> {
106        match self.tag {
107            ValueTag::Str => Some(unsafe { &self.data.s }.as_str()),
108            ValueTag::StaticStr => Some(unsafe { self.data.static_str }),
109            _ => None,
110        }
111    }
112
113    #[inline]
114    pub fn as_u64(&self) -> Option<u64> {
115        match self.tag {
116            ValueTag::U64 => Some(unsafe { self.data.u }),
117            _ => None,
118        }
119    }
120
121    #[inline]
122    pub fn as_i64(&self) -> Option<i64> {
123        match self.tag {
124            ValueTag::I64 => Some(unsafe { self.data.i }),
125            _ => None,
126        }
127    }
128
129    /// Returns a mutable reference to the Array if this value is an Array.
130    #[inline]
131    #[allow(dead_code)]
132    pub(crate) fn as_array_mut(&mut self) -> Option<&mut SmallVec<[Value<K>; 8]>> {
133        if self.tag == ValueTag::Array {
134            Some(unsafe { &mut **self.data.array })
135        } else {
136            None
137        }
138    }
139
140    /// Returns a reference to the Array if this value is an Array.
141    #[inline]
142    #[allow(dead_code)]
143    pub(crate) fn as_array_ref(&self) -> Option<&SmallVec<[Value<K>; 8]>> {
144        if self.tag == ValueTag::Array {
145            Some(unsafe { &**self.data.array })
146        } else {
147            None
148        }
149    }
150}
151
152impl<K: Key> Clone for Value<K> {
153    fn clone(&self) -> Self {
154        let tag = self.tag;
155        let data = match tag {
156            ValueTag::Null => ValueData { b: false },
157            ValueTag::Bool => ValueData { b: unsafe { self.data.b } },
158            ValueTag::I64 => ValueData { i: unsafe { self.data.i } },
159            ValueTag::U64 => ValueData { u: unsafe { self.data.u } },
160            ValueTag::F64 => ValueData { f: unsafe { self.data.f } },
161            ValueTag::Str => ValueData { s: std::mem::ManuallyDrop::new(unsafe { (*self.data.s).clone() }) },
162            ValueTag::StaticStr => ValueData { static_str: unsafe { self.data.static_str } },
163            ValueTag::Array => ValueData { array: std::mem::ManuallyDrop::new(unsafe { (*self.data.array).clone() }) },
164            ValueTag::Object => ValueData { object: std::mem::ManuallyDrop::new(unsafe { (*self.data.object).clone() }) },
165        };
166        Value { tag, _pad: [0; 7], data }
167    }
168}
169
170impl<K: Key> std::fmt::Debug for Value<K> {
171    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172        match self.tag {
173            ValueTag::Null => f.write_str("Value::Null"),
174            ValueTag::Bool => f.debug_tuple("Value::Bool").field(&unsafe { self.data.b }).finish(),
175            ValueTag::I64 => f.debug_tuple("Value::I64").field(&unsafe { self.data.i }).finish(),
176            ValueTag::U64 => f.debug_tuple("Value::U64").field(&unsafe { self.data.u }).finish(),
177            ValueTag::F64 => f.debug_tuple("Value::F64").field(&unsafe { self.data.f }).finish(),
178            ValueTag::Str => f.debug_tuple("Value::Str").field(&unsafe { &self.data.s }).finish(),
179            ValueTag::StaticStr => f.debug_tuple("Value::StaticStr").field(&unsafe { self.data.static_str }).finish(),
180            ValueTag::Array => f.debug_tuple("Value::Array").field(&unsafe { &self.data.array }).finish(),
181            ValueTag::Object => f.debug_tuple("Value::Object").field(&unsafe { &self.data.object }).finish(),
182        }
183    }
184}
185
186impl<K: Key> Drop for Value<K> {
187    fn drop(&mut self) {
188        match self.tag {
189            ValueTag::Str => unsafe { std::ptr::drop_in_place(&mut self.data.s) },
190            ValueTag::Array => unsafe { std::ptr::drop_in_place(&mut self.data.array) },
191            ValueTag::Object => unsafe { std::ptr::drop_in_place(&mut self.data.object) },
192            _ => {}
193        }
194    }
195}
196
197impl<K: Key> Serialize for Value<K> {
198    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
199        match self.tag {
200            ValueTag::Null => serializer.serialize_unit(),
201            ValueTag::Bool => serializer.serialize_bool(unsafe { self.data.b }),
202            ValueTag::I64 => serializer.serialize_i64(unsafe { self.data.i }),
203            ValueTag::U64 => serializer.serialize_u64(unsafe { self.data.u }),
204            ValueTag::F64 => serializer.serialize_f64(unsafe { self.data.f }),
205            ValueTag::Str => serializer.serialize_str(unsafe { &self.data.s }.as_str()),
206            ValueTag::StaticStr => serializer.serialize_str(unsafe { self.data.static_str }),
207            ValueTag::Array => unsafe { &*self.data.array }.serialize(serializer),
208            ValueTag::Object => unsafe { &*self.data.object }.serialize(serializer),
209        }
210    }
211}
212
213// ── From impls ──
214
215impl<K: Key> From<bool> for Value<K> {
216    #[inline]
217    fn from(b: bool) -> Self {
218        Value { tag: ValueTag::Bool, _pad: [0; 7], data: ValueData { b } }
219    }
220}
221
222impl<K: Key> From<i64> for Value<K> {
223    #[inline]
224    fn from(n: i64) -> Self {
225        Value { tag: ValueTag::I64, _pad: [0; 7], data: ValueData { i: n } }
226    }
227}
228
229impl<K: Key> From<u64> for Value<K> {
230    #[inline]
231    fn from(n: u64) -> Self {
232        Value { tag: ValueTag::U64, _pad: [0; 7], data: ValueData { u: n } }
233    }
234}
235
236impl<K: Key> From<f64> for Value<K> {
237    #[inline]
238    fn from(n: f64) -> Self {
239        Value { tag: ValueTag::F64, _pad: [0; 7], data: ValueData { f: n } }
240    }
241}
242
243impl<K: Key> From<&str> for Value<K> {
244    #[inline]
245    fn from(s: &str) -> Self {
246        Value { tag: ValueTag::Str, _pad: [0; 7], data: ValueData { s: std::mem::ManuallyDrop::new(FastStr::new(s)) } }
247    }
248}
249
250// Note: there is no `From<&'static str>` impl because it conflicts with `From<&str>`.
251// Instead, `&'static str` literals go through `From<&str>` which uses `FastStr::new`
252// (SSO). For true zero-copy `&'static str` storage, use `Value::from_static_str`.
253impl<K: Key> Value<K> {
254    /// Creates a `StaticStr` value from a `&'static str` — zero-copy, zero-allocation.
255    #[inline]
256    pub fn from_static_str(s: &'static str) -> Self {
257        Value { tag: ValueTag::StaticStr, _pad: [0; 7], data: ValueData { static_str: s } }
258    }
259}
260
261impl<K: Key> From<String> for Value<K> {
262    #[inline]
263    fn from(s: String) -> Self {
264        Value { tag: ValueTag::Str, _pad: [0; 7], data: ValueData { s: std::mem::ManuallyDrop::new(FastStr::from_string(s)) } }
265    }
266}
267
268impl<K: Key> From<FastStr> for Value<K> {
269    #[inline]
270    fn from(s: FastStr) -> Self {
271        Value { tag: ValueTag::Str, _pad: [0; 7], data: ValueData { s: std::mem::ManuallyDrop::new(s) } }
272    }
273}
274
275impl<K: Key> From<()> for Value<K> {
276    #[inline]
277    fn from(_: ()) -> Self {
278        Value { tag: ValueTag::Null, _pad: [0; 7], data: ValueData { b: false } }
279    }
280}
281
282#[cfg(test)]
283mod tests {
284    use super::*;
285    use crate::key::test_support::TestKey;
286
287    #[test]
288    fn from_bool() {
289        assert!(matches!(Value::<TestKey>::from(true).tag(), ValueTag::Bool));
290        assert!(unsafe { Value::<TestKey>::from(true).data.b });
291        assert!(!unsafe { Value::<TestKey>::from(false).data.b });
292    }
293
294    #[test]
295    fn from_i64() {
296        let v = Value::<TestKey>::from(-42i64);
297        assert_eq!(v.tag(), ValueTag::I64);
298        assert_eq!(unsafe { v.data.i }, -42);
299    }
300
301    #[test]
302    fn from_u64() {
303        let v = Value::<TestKey>::from(99u64);
304        assert_eq!(v.tag(), ValueTag::U64);
305        assert_eq!(unsafe { v.data.u }, 99);
306    }
307
308    #[test]
309    fn from_f64() {
310        let v = Value::<TestKey>::from(3.15f64);
311        assert_eq!(v.tag(), ValueTag::F64);
312        assert_eq!(unsafe { v.data.f }, 3.15);
313    }
314
315    #[test]
316    fn from_str_is_string() {
317        let v = Value::<TestKey>::from("hello");
318        assert_eq!(v.tag(), ValueTag::Str);
319        assert_eq!(v.as_str(), Some("hello"));
320    }
321
322    #[test]
323    fn from_static_str() {
324        let v = Value::<TestKey>::from_static_str("world");
325        assert_eq!(v.tag(), ValueTag::StaticStr);
326        assert_eq!(v.as_str(), Some("world"));
327    }
328
329    #[test]
330    fn from_owned_string_is_string() {
331        let v = Value::<TestKey>::from("world".to_string());
332        assert_eq!(v.tag(), ValueTag::Str);
333        assert_eq!(v.as_str(), Some("world"));
334    }
335
336    #[test]
337    fn from_unit_is_null() {
338        let v = Value::<TestKey>::from(());
339        assert_eq!(v.tag(), ValueTag::Null);
340    }
341
342    #[test]
343    fn serialize_null() {
344        let v = Value::<TestKey>::from(());
345        let s = sonic_rs::to_string(&v).unwrap();
346        assert_eq!(s, "null");
347    }
348
349    #[test]
350    fn serialize_scalars() {
351        assert_eq!(
352            sonic_rs::to_string(&Value::<TestKey>::from(true)).unwrap(),
353            "true"
354        );
355        assert_eq!(
356            sonic_rs::to_string(&Value::<TestKey>::from(false)).unwrap(),
357            "false"
358        );
359        assert_eq!(
360            sonic_rs::to_string(&Value::<TestKey>::from(-7i64)).unwrap(),
361            "-7"
362        );
363        assert_eq!(
364            sonic_rs::to_string(&Value::<TestKey>::from(42u64)).unwrap(),
365            "42"
366        );
367        assert_eq!(
368            sonic_rs::to_string(&Value::<TestKey>::from("hi")).unwrap(),
369            "\"hi\""
370        );
371    }
372
373    #[test]
374    fn serialize_static_str() {
375        let v = Value::<TestKey>::from_static_str("hello");
376        assert_eq!(sonic_rs::to_string(&v).unwrap(), "\"hello\"");
377    }
378
379    #[test]
380    fn serialize_array() {
381        let arr: SmallVec<[Value<TestKey>; 8]> =
382            smallvec::smallvec![Value::from(1i64), Value::from(2i64)];
383        let v = Value::<TestKey>::from_array(arr);
384        assert_eq!(sonic_rs::to_string(&v).unwrap(), "[1,2]");
385    }
386
387    #[test]
388    fn clone_works() {
389        let v = Value::<TestKey>::from("hello");
390        let v2 = v.clone();
391        assert_eq!(v2.as_str(), Some("hello"));
392    }
393
394    #[test]
395    fn clone_static_str() {
396        let v = Value::<TestKey>::from_static_str("world");
397        let v2 = v.clone();
398        assert_eq!(v2.as_str(), Some("world"));
399    }
400
401    #[test]
402    fn clone_object() {
403        let mut ev = WideEvent::<TestKey>::new();
404        ev.add(TestKey::Status, "ok");
405        let v = Value::<TestKey>::from_object(ev);
406        let v2 = v.clone();
407        assert_eq!(v2.tag(), ValueTag::Object);
408        let json = sonic_rs::to_string(&v2).unwrap();
409        assert!(json.contains("\"status\":\"ok\""));
410    }
411}