steam_vdf_parser/value/
types.rs

1//! Type definitions for VDF values.
2
3use alloc::borrow::Cow;
4use indexmap::IndexMap;
5
6use super::hasher::DefaultHashBuilder;
7
8/// A key in VDF - zero-copy when possible
9pub(crate) type Key<'text> = Cow<'text, str>;
10
11/// VDF Value - can be a string, number, object, or other types
12#[derive(Clone, Debug, PartialEq)]
13pub enum Value<'text> {
14    /// A string value (text format and WideString from binary)
15    Str(Cow<'text, str>),
16    /// An object containing nested key-value pairs
17    Obj(Obj<'text>),
18    /// A 32-bit signed integer (binary Int32 type)
19    I32(i32),
20    /// A 64-bit unsigned integer (binary UInt64 type)
21    U64(u64),
22    /// A 32-bit float (binary Float type)
23    Float(f32),
24    /// A pointer value (binary Ptr type, stored as u32)
25    Pointer(u32),
26    /// A color value (binary Color type, RGBA)
27    Color([u8; 4]),
28}
29
30impl<'text> Value<'text> {
31    /// Returns `true` if this value is a string.
32    pub fn is_str(&self) -> bool {
33        matches!(self, Value::Str(_))
34    }
35
36    /// Returns `true` if this value is an object.
37    pub fn is_obj(&self) -> bool {
38        matches!(self, Value::Obj(_))
39    }
40
41    /// Returns `true` if this value is an i32.
42    pub fn is_i32(&self) -> bool {
43        matches!(self, Value::I32(_))
44    }
45
46    /// Returns `true` if this value is a u64.
47    pub fn is_u64(&self) -> bool {
48        matches!(self, Value::U64(_))
49    }
50
51    /// Returns `true` if this value is a float.
52    pub fn is_float(&self) -> bool {
53        matches!(self, Value::Float(_))
54    }
55
56    /// Returns `true` if this value is a pointer.
57    pub fn is_pointer(&self) -> bool {
58        matches!(self, Value::Pointer(_))
59    }
60
61    /// Returns `true` if this value is a color.
62    pub fn is_color(&self) -> bool {
63        matches!(self, Value::Color(_))
64    }
65
66    /// Returns a reference to the string value if this is a string.
67    pub fn as_str(&self) -> Option<&str> {
68        match self {
69            Value::Str(s) => Some(s.as_ref()),
70            _ => None,
71        }
72    }
73
74    /// Returns a reference to the object if this is an object.
75    pub fn as_obj(&self) -> Option<&Obj<'text>> {
76        match self {
77            Value::Obj(obj) => Some(obj),
78            _ => None,
79        }
80    }
81
82    /// Returns a mutable reference to the object if this is an object.
83    pub fn as_obj_mut(&mut self) -> Option<&mut Obj<'text>> {
84        match self {
85            Value::Obj(obj) => Some(obj),
86            _ => None,
87        }
88    }
89
90    /// Returns the i32 value if this is an i32.
91    pub fn as_i32(&self) -> Option<i32> {
92        match self {
93            Value::I32(n) => Some(*n),
94            _ => None,
95        }
96    }
97
98    /// Returns the u64 value if this is a u64.
99    pub fn as_u64(&self) -> Option<u64> {
100        match self {
101            Value::U64(n) => Some(*n),
102            _ => None,
103        }
104    }
105
106    /// Returns the float value if this is a float.
107    pub fn as_float(&self) -> Option<f32> {
108        match self {
109            Value::Float(n) => Some(*n),
110            _ => None,
111        }
112    }
113
114    /// Returns the pointer value if this is a pointer.
115    pub fn as_pointer(&self) -> Option<u32> {
116        match self {
117            Value::Pointer(n) => Some(*n),
118            _ => None,
119        }
120    }
121
122    /// Returns the color value if this is a color.
123    pub fn as_color(&self) -> Option<[u8; 4]> {
124        match self {
125            Value::Color(c) => Some(*c),
126            _ => None,
127        }
128    }
129
130    /// Returns a reference to a nested value by key.
131    ///
132    /// Shorthand for `self.as_obj()?.get(key)`.
133    pub fn get(&self, key: &str) -> Option<&Value<'text>> {
134        self.as_obj()?.get(key)
135    }
136
137    /// Traverse nested objects by path.
138    ///
139    /// Returns `None` if any segment doesn't exist or isn't an object.
140    pub fn get_path(&self, path: &[&str]) -> Option<&Value<'text>> {
141        let mut current = self;
142        for key in path {
143            current = current.get(key)?;
144        }
145        Some(current)
146    }
147
148    /// Get a string at the given path.
149    pub fn get_str(&self, path: &[&str]) -> Option<&str> {
150        self.get_path(path)?.as_str()
151    }
152
153    /// Get an object at the given path.
154    pub fn get_obj(&self, path: &[&str]) -> Option<&Obj<'text>> {
155        self.get_path(path)?.as_obj()
156    }
157
158    /// Get an i32 at the given path.
159    pub fn get_i32(&self, path: &[&str]) -> Option<i32> {
160        self.get_path(path)?.as_i32()
161    }
162
163    /// Get a u64 at the given path.
164    pub fn get_u64(&self, path: &[&str]) -> Option<u64> {
165        self.get_path(path)?.as_u64()
166    }
167
168    /// Get a float at the given path.
169    pub fn get_float(&self, path: &[&str]) -> Option<f32> {
170        self.get_path(path)?.as_float()
171    }
172}
173
174/// Object - map from keys to values
175///
176/// Uses `IndexMap` for O(1) lookup while preserving insertion order.
177/// Binary VDF doesn't have duplicate keys, and for text VDF we use
178/// "last value wins" semantics.
179#[derive(Clone, Debug, PartialEq)]
180pub struct Obj<'text> {
181    pub(crate) inner: IndexMap<Key<'text>, Value<'text>, DefaultHashBuilder>,
182}
183
184impl<'text> Obj<'text> {
185    /// Creates a new empty VDF object.
186    pub fn new() -> Self {
187        Self {
188            inner: IndexMap::with_hasher(DefaultHashBuilder::default()),
189        }
190    }
191
192    /// Returns the number of key-value pairs in the object.
193    pub fn len(&self) -> usize {
194        self.inner.len()
195    }
196
197    /// Returns `true` if the object contains no key-value pairs.
198    pub fn is_empty(&self) -> bool {
199        self.inner.is_empty()
200    }
201
202    /// Returns a reference to the value corresponding to the key.
203    pub fn get(&self, key: &str) -> Option<&Value<'text>> {
204        self.inner.get(key)
205    }
206
207    /// Returns an iterator over the key-value pairs.
208    pub fn iter(&self) -> impl Iterator<Item = (&Key<'text>, &Value<'text>)> {
209        self.inner.iter()
210    }
211
212    /// Returns an iterator over the keys.
213    pub fn keys(&self) -> impl Iterator<Item = &str> {
214        self.inner.keys().map(|k| k.as_ref())
215    }
216
217    /// Returns an iterator over the values.
218    pub fn values(&self) -> impl Iterator<Item = &Value<'text>> {
219        self.inner.values()
220    }
221
222    /// Returns `true` if the object contains the given key.
223    pub fn contains_key(&self, key: &str) -> bool {
224        self.inner.contains_key(key)
225    }
226
227    /// Returns a mutable reference to the value corresponding to the key.
228    pub fn get_mut(&mut self, key: &str) -> Option<&mut Value<'text>> {
229        self.inner.get_mut(key)
230    }
231
232    /// Inserts a key-value pair into the object.
233    ///
234    /// Returns the previous value if one existed for this key.
235    pub fn insert(
236        &mut self,
237        key: impl Into<Key<'text>>,
238        value: Value<'text>,
239    ) -> Option<Value<'text>> {
240        self.inner.insert(key.into(), value)
241    }
242
243    /// Removes a key from the object, preserving insertion order.
244    ///
245    /// This is O(n) as it shifts subsequent elements. Use [`swap_remove`](Self::swap_remove)
246    /// for O(1) removal when order doesn't matter.
247    ///
248    /// Returns the value if the key was present.
249    pub fn remove(&mut self, key: &str) -> Option<Value<'text>> {
250        self.inner.shift_remove(key)
251    }
252
253    /// Removes a key from the object by swapping with the last element.
254    ///
255    /// This is O(1) but does not preserve insertion order.
256    /// Use [`remove`](Self::remove) if order preservation is needed.
257    ///
258    /// Returns the value if the key was present.
259    pub fn swap_remove(&mut self, key: &str) -> Option<Value<'text>> {
260        self.inner.swap_remove(key)
261    }
262}
263
264/// Top-level VDF document
265///
266/// A VDF document is essentially a single key-value pair at the root level.
267#[derive(Clone, Debug, PartialEq)]
268pub struct Vdf<'text> {
269    key: Key<'text>,
270    value: Value<'text>,
271}
272
273impl<'text> Vdf<'text> {
274    /// Creates a new VDF document.
275    pub fn new(key: impl Into<Key<'text>>, value: Value<'text>) -> Self {
276        Self {
277            key: key.into(),
278            value,
279        }
280    }
281
282    /// Returns the root key.
283    pub fn key(&self) -> &str {
284        &self.key
285    }
286
287    /// Returns a reference to the root value.
288    pub fn value(&self) -> &Value<'text> {
289        &self.value
290    }
291
292    /// Returns a mutable reference to the root value.
293    pub fn value_mut(&mut self) -> &mut Value<'text> {
294        &mut self.value
295    }
296
297    /// Consumes the Vdf and returns its parts.
298    pub fn into_parts(self) -> (Cow<'text, str>, Value<'text>) {
299        (self.key, self.value)
300    }
301
302    /// Returns `true` if the root value is an object.
303    pub fn is_obj(&self) -> bool {
304        self.value.is_obj()
305    }
306
307    /// Returns a reference to the root object if it is one.
308    pub fn as_obj(&self) -> Option<&Obj<'text>> {
309        self.value.as_obj()
310    }
311
312    /// Returns a reference to a nested value by key.
313    pub fn get(&self, key: &str) -> Option<&Value<'text>> {
314        self.value.get(key)
315    }
316
317    /// Traverse nested objects by path from the root value.
318    pub fn get_path(&self, path: &[&str]) -> Option<&Value<'text>> {
319        self.value.get_path(path)
320    }
321
322    /// Get a string at the given path.
323    pub fn get_str(&self, path: &[&str]) -> Option<&str> {
324        self.value.get_str(path)
325    }
326
327    /// Get an object at the given path.
328    pub fn get_obj(&self, path: &[&str]) -> Option<&Obj<'text>> {
329        self.value.get_obj(path)
330    }
331
332    /// Get an i32 at the given path.
333    pub fn get_i32(&self, path: &[&str]) -> Option<i32> {
334        self.value.get_i32(path)
335    }
336
337    /// Get a u64 at the given path.
338    pub fn get_u64(&self, path: &[&str]) -> Option<u64> {
339        self.value.get_u64(path)
340    }
341
342    /// Get a float at the given path.
343    pub fn get_float(&self, path: &[&str]) -> Option<f32> {
344        self.value.get_float(path)
345    }
346}