Skip to main content

boon/entity/
field_value.rs

1use std::fmt;
2
3use crate::error::FieldValueConversionError;
4
5/// Represents a decoded entity field value from the demo's entity system.
6///
7/// Each variant corresponds to a Source 2 network field type. The variant
8/// chosen at runtime depends on the field's serializer metadata.
9#[derive(Clone)]
10pub enum FieldValue {
11    Bool(bool),
12    I32(i32),
13    I64(i64),
14    U32(u32),
15    U64(u64),
16    F32(f32),
17    /// Raw byte string. Stored as `Vec<u8>` rather than `std::string::String`
18    /// because some Source 2 strings are not guaranteed to be valid UTF-8.
19    String(Vec<u8>),
20    Vector2([f32; 2]),
21    Vector3([f32; 3]),
22    Vector4([f32; 4]),
23    /// Euler angles (pitch, yaw, roll) in degrees.
24    QAngle([f32; 3]),
25}
26
27impl fmt::Debug for FieldValue {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        match self {
30            Self::Bool(v) => write!(f, "{v}"),
31            Self::I32(v) => write!(f, "{v}"),
32            Self::I64(v) => write!(f, "{v}"),
33            Self::U32(v) => write!(f, "{v}"),
34            Self::U64(v) => write!(f, "{v}"),
35            Self::F32(v) => write!(f, "{v}"),
36            Self::String(v) => write!(f, "{}", String::from_utf8_lossy(v)),
37            Self::Vector2(v) => write!(f, "[{}, {}]", v[0], v[1]),
38            Self::Vector3(v) => write!(f, "[{}, {}, {}]", v[0], v[1], v[2]),
39            Self::Vector4(v) => write!(f, "[{}, {}, {}, {}]", v[0], v[1], v[2], v[3]),
40            Self::QAngle(v) => write!(f, "QAngle({}, {}, {})", v[0], v[1], v[2]),
41        }
42    }
43}
44
45impl fmt::Display for FieldValue {
46    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47        fmt::Debug::fmt(self, f)
48    }
49}
50
51impl serde::Serialize for FieldValue {
52    fn serialize<S: serde::Serializer>(
53        &self,
54        serializer: S,
55    ) -> std::result::Result<S::Ok, S::Error> {
56        match self {
57            Self::Bool(v) => serializer.serialize_bool(*v),
58            Self::I32(v) => serializer.serialize_i32(*v),
59            Self::I64(v) => serializer.serialize_i64(*v),
60            Self::U32(v) => serializer.serialize_u32(*v),
61            Self::U64(v) => serializer.serialize_u64(*v),
62            Self::F32(v) => serializer.serialize_f32(*v),
63            Self::String(v) => serializer.serialize_str(&String::from_utf8_lossy(v)),
64            Self::Vector2(v) => v.serialize(serializer),
65            Self::Vector3(v) => v.serialize(serializer),
66            Self::Vector4(v) => v.serialize(serializer),
67            Self::QAngle(v) => v.serialize(serializer),
68        }
69    }
70}
71
72/// Generates `TryFrom<FieldValue>` for signed integer types.
73/// Accepts `I32` and `I64` variants, with range checking via `try_from`.
74macro_rules! impl_try_from_signed {
75    ($($ty:ty),+) => {
76        $(
77            impl TryFrom<FieldValue> for $ty {
78                type Error = FieldValueConversionError;
79                fn try_from(v: FieldValue) -> std::result::Result<Self, Self::Error> {
80                    match v {
81                        FieldValue::I32(val) => <$ty>::try_from(val).map_err(|_| FieldValueConversionError),
82                        FieldValue::I64(val) => <$ty>::try_from(val).map_err(|_| FieldValueConversionError),
83                        _ => Err(FieldValueConversionError),
84                    }
85                }
86            }
87        )+
88    }
89}
90
91/// Generates `TryFrom<FieldValue>` for unsigned integer types.
92/// Accepts `U32` and `U64` variants, with range checking via `try_from`.
93macro_rules! impl_try_from_unsigned {
94    ($($ty:ty),+) => {
95        $(
96            impl TryFrom<FieldValue> for $ty {
97                type Error = FieldValueConversionError;
98                fn try_from(v: FieldValue) -> std::result::Result<Self, Self::Error> {
99                    match v {
100                        FieldValue::U32(val) => <$ty>::try_from(val).map_err(|_| FieldValueConversionError),
101                        FieldValue::U64(val) => <$ty>::try_from(val).map_err(|_| FieldValueConversionError),
102                        _ => Err(FieldValueConversionError),
103                    }
104                }
105            }
106        )+
107    }
108}
109
110impl_try_from_signed!(i8, i16, i32, i64);
111impl_try_from_unsigned!(u8, u16, u32, u64);
112
113impl TryFrom<FieldValue> for f32 {
114    type Error = FieldValueConversionError;
115    fn try_from(v: FieldValue) -> std::result::Result<Self, Self::Error> {
116        match v {
117            FieldValue::F32(val) => Ok(val),
118            _ => Err(FieldValueConversionError),
119        }
120    }
121}
122
123impl TryFrom<FieldValue> for bool {
124    type Error = FieldValueConversionError;
125    fn try_from(v: FieldValue) -> std::result::Result<Self, Self::Error> {
126        match v {
127            FieldValue::Bool(val) => Ok(val),
128            _ => Err(FieldValueConversionError),
129        }
130    }
131}
132
133impl TryFrom<FieldValue> for [f32; 2] {
134    type Error = FieldValueConversionError;
135    fn try_from(v: FieldValue) -> std::result::Result<Self, Self::Error> {
136        match v {
137            FieldValue::Vector2(val) => Ok(val),
138            _ => Err(FieldValueConversionError),
139        }
140    }
141}
142
143impl TryFrom<FieldValue> for [f32; 3] {
144    type Error = FieldValueConversionError;
145    fn try_from(v: FieldValue) -> std::result::Result<Self, Self::Error> {
146        match v {
147            FieldValue::Vector3(val) | FieldValue::QAngle(val) => Ok(val),
148            _ => Err(FieldValueConversionError),
149        }
150    }
151}
152
153impl TryFrom<FieldValue> for [f32; 4] {
154    type Error = FieldValueConversionError;
155    fn try_from(v: FieldValue) -> std::result::Result<Self, Self::Error> {
156        match v {
157            FieldValue::Vector4(val) => Ok(val),
158            _ => Err(FieldValueConversionError),
159        }
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166
167    // ── TryFrom conversions ──
168
169    #[test]
170    fn try_from_i32_to_i32() {
171        let v = FieldValue::I32(42);
172        assert_eq!(i32::try_from(v).unwrap(), 42);
173    }
174
175    #[test]
176    fn try_from_i64_to_i32() {
177        let v = FieldValue::I64(100);
178        assert_eq!(i32::try_from(v).unwrap(), 100);
179    }
180
181    #[test]
182    fn try_from_u32_to_u32() {
183        let v = FieldValue::U32(999);
184        assert_eq!(u32::try_from(v).unwrap(), 999);
185    }
186
187    #[test]
188    fn try_from_u64_to_u16() {
189        let v = FieldValue::U64(65535);
190        assert_eq!(u16::try_from(v).unwrap(), 65535);
191    }
192
193    #[test]
194    fn try_from_f32() {
195        let v = FieldValue::F32(1.5);
196        assert!((f32::try_from(v).unwrap() - 1.5).abs() < f32::EPSILON);
197    }
198
199    #[test]
200    fn try_from_bool() {
201        let v = FieldValue::Bool(true);
202        assert!(bool::try_from(v).unwrap());
203    }
204
205    #[test]
206    fn try_from_vector2() {
207        let v = FieldValue::Vector2([1.0, 2.0]);
208        assert_eq!(<[f32; 2]>::try_from(v).unwrap(), [1.0, 2.0]);
209    }
210
211    #[test]
212    fn try_from_vector3() {
213        let v = FieldValue::Vector3([1.0, 2.0, 3.0]);
214        assert_eq!(<[f32; 3]>::try_from(v).unwrap(), [1.0, 2.0, 3.0]);
215    }
216
217    #[test]
218    fn try_from_vector4() {
219        let v = FieldValue::Vector4([1.0, 2.0, 3.0, 4.0]);
220        assert_eq!(<[f32; 4]>::try_from(v).unwrap(), [1.0, 2.0, 3.0, 4.0]);
221    }
222
223    #[test]
224    fn try_from_qangle_to_f32_3() {
225        let v = FieldValue::QAngle([10.0, 20.0, 30.0]);
226        assert_eq!(<[f32; 3]>::try_from(v).unwrap(), [10.0, 20.0, 30.0]);
227    }
228
229    #[test]
230    fn try_from_i64_overflow_to_i8() {
231        let v = FieldValue::I64(200);
232        assert!(i8::try_from(v).is_err());
233    }
234
235    #[test]
236    fn try_from_u32_to_signed_fails() {
237        let v = FieldValue::U32(1);
238        assert!(i32::try_from(v).is_err());
239    }
240
241    #[test]
242    fn try_from_i32_to_unsigned_fails() {
243        let v = FieldValue::I32(1);
244        assert!(u32::try_from(v).is_err());
245    }
246
247    // ── Debug / Display formatting ──
248
249    #[test]
250    fn debug_bool() {
251        assert_eq!(format!("{:?}", FieldValue::Bool(true)), "true");
252    }
253
254    #[test]
255    fn debug_i32() {
256        assert_eq!(format!("{:?}", FieldValue::I32(-42)), "-42");
257    }
258
259    #[test]
260    fn debug_string_utf8() {
261        let v = FieldValue::String(b"hello".to_vec());
262        assert_eq!(format!("{:?}", v), "hello");
263    }
264
265    #[test]
266    fn debug_string_invalid_utf8_no_panic() {
267        let v = FieldValue::String(vec![0xFF, 0xFE]);
268        let s = format!("{:?}", v);
269        assert!(!s.is_empty());
270    }
271
272    #[test]
273    fn debug_vector3() {
274        let v = FieldValue::Vector3([1.0, 2.0, 3.0]);
275        assert_eq!(format!("{:?}", v), "[1, 2, 3]");
276    }
277
278    #[test]
279    fn debug_qangle() {
280        let v = FieldValue::QAngle([1.0, 2.0, 3.0]);
281        assert!(format!("{:?}", v).starts_with("QAngle("));
282    }
283
284    #[test]
285    fn display_delegates_to_debug() {
286        let v = FieldValue::Bool(false);
287        assert_eq!(format!("{}", v), format!("{:?}", v));
288    }
289
290    // ── Serialize ──
291
292    #[test]
293    fn serialize_bool_and_numbers() {
294        let b = serde_json::to_value(FieldValue::Bool(true)).unwrap();
295        assert_eq!(b, serde_json::json!(true));
296
297        let n = serde_json::to_value(FieldValue::I32(-7)).unwrap();
298        assert_eq!(n, serde_json::json!(-7));
299
300        let u = serde_json::to_value(FieldValue::U32(42)).unwrap();
301        assert_eq!(u, serde_json::json!(42));
302    }
303
304    #[test]
305    fn serialize_string_and_vector() {
306        let s = serde_json::to_value(FieldValue::String(b"hi".to_vec())).unwrap();
307        assert_eq!(s, serde_json::json!("hi"));
308
309        let v = serde_json::to_value(FieldValue::Vector3([1.0, 2.0, 3.0])).unwrap();
310        assert!(v.is_array());
311        assert_eq!(v.as_array().unwrap().len(), 3);
312    }
313}