rbx_types/
axes.rs

1use std::fmt;
2
3use crate::lister::Lister;
4
5bitflags::bitflags! {
6    struct AxisFlags: u8 {
7        const X = 1;
8        const Y = 2;
9        const Z = 4;
10    }
11}
12
13/// Represents a set of zero or more 3D axes.
14///
15/// ## See Also
16/// * [Axes on Roblox Developer Hub](https://developer.roblox.com/en-us/api-reference/datatype/Axes)
17#[derive(Clone, Copy, PartialEq, Eq)]
18pub struct Axes {
19    flags: AxisFlags,
20}
21
22impl Axes {
23    pub const X: Self = Self {
24        flags: AxisFlags::X,
25    };
26
27    pub const Y: Self = Self {
28        flags: AxisFlags::Y,
29    };
30
31    pub const Z: Self = Self {
32        flags: AxisFlags::Z,
33    };
34}
35
36impl Axes {
37    pub const fn empty() -> Self {
38        Self {
39            flags: AxisFlags::empty(),
40        }
41    }
42
43    pub const fn all() -> Self {
44        Self {
45            flags: AxisFlags::all(),
46        }
47    }
48
49    pub const fn contains(self, other: Self) -> bool {
50        self.flags.contains(other.flags)
51    }
52
53    pub const fn bits(self) -> u8 {
54        self.flags.bits()
55    }
56
57    pub const fn from_bits(bits: u8) -> Option<Self> {
58        match AxisFlags::from_bits(bits) {
59            Some(flags) => Some(Self { flags }),
60            None => None,
61        }
62    }
63
64    #[cfg(feature = "serde")]
65    fn len(self) -> usize {
66        self.bits().count_ones() as usize
67    }
68}
69
70impl fmt::Debug for Axes {
71    fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
72        let mut list = Lister::new();
73
74        write!(out, "Axes(")?;
75
76        if self.contains(Self::X) {
77            list.write(out, "X")?;
78        }
79
80        if self.contains(Self::Y) {
81            list.write(out, "Y")?;
82        }
83
84        if self.contains(Self::Z) {
85            list.write(out, "Z")?;
86        }
87
88        write!(out, ")")
89    }
90}
91
92#[cfg(feature = "serde")]
93mod serde_impl {
94    use super::*;
95
96    use std::fmt;
97
98    use serde::{
99        de::{Error as _, SeqAccess, Visitor},
100        ser::SerializeSeq,
101        Deserialize, Deserializer, Serialize, Serializer,
102    };
103
104    impl Serialize for Axes {
105        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
106            if serializer.is_human_readable() {
107                let mut seq = serializer.serialize_seq(Some(self.len()))?;
108
109                if self.contains(Self::X) {
110                    seq.serialize_element("X")?;
111                }
112
113                if self.contains(Self::Y) {
114                    seq.serialize_element("Y")?;
115                }
116
117                if self.contains(Self::Z) {
118                    seq.serialize_element("Z")?;
119                }
120
121                seq.end()
122            } else {
123                serializer.serialize_u8(self.bits())
124            }
125        }
126    }
127
128    struct HumanVisitor;
129
130    impl<'de> Visitor<'de> for HumanVisitor {
131        type Value = Axes;
132
133        fn expecting(&self, out: &mut fmt::Formatter) -> fmt::Result {
134            write!(out, "a list of strings representing axes")
135        }
136
137        fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
138            let mut flags = AxisFlags::empty();
139
140            while let Some(axis_str) = seq.next_element::<String>()? {
141                match axis_str.as_str() {
142                    "X" => flags |= AxisFlags::X,
143                    "Y" => flags |= AxisFlags::Y,
144                    "Z" => flags |= AxisFlags::Z,
145                    _ => {
146                        return Err(A::Error::custom(format!("invalid axis '{axis_str}'")));
147                    }
148                }
149            }
150
151            Ok(Axes { flags })
152        }
153    }
154
155    impl<'de> Deserialize<'de> for Axes {
156        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
157            if deserializer.is_human_readable() {
158                deserializer.deserialize_seq(HumanVisitor)
159            } else {
160                let value = u8::deserialize(deserializer)?;
161
162                Axes::from_bits(value)
163                    .ok_or_else(|| D::Error::custom("value must a u8 bitmask of axes"))
164            }
165        }
166    }
167}
168
169#[cfg(all(test, feature = "serde"))]
170mod serde_test {
171    use super::*;
172
173    #[test]
174    fn human_de() {
175        let empty: Axes = serde_json::from_str("[]").unwrap();
176        assert_eq!(empty, Axes::empty());
177
178        let x: Axes = serde_json::from_str(r#"["X"]"#).unwrap();
179        assert_eq!(x, Axes::X);
180
181        let all: Axes = serde_json::from_str(r#"["X", "Y", "Z"]"#).unwrap();
182        assert_eq!(all, Axes::all());
183    }
184
185    #[test]
186    fn human_ser() {
187        let empty = serde_json::to_string(&Axes::empty()).unwrap();
188        assert_eq!(empty, "[]");
189
190        let x = serde_json::to_string(&Axes::X).unwrap();
191        assert_eq!(x, r#"["X"]"#);
192
193        let all = serde_json::to_string(&Axes::all()).unwrap();
194        assert_eq!(all, r#"["X","Y","Z"]"#);
195    }
196
197    #[test]
198    fn human_duplicate() {
199        let x: Axes = serde_json::from_str(r#"["X", "X", "X", "X"]"#).unwrap();
200        assert_eq!(x, Axes::X);
201    }
202
203    #[test]
204    fn human_invalid() {
205        // pizza is not an axis in 3D space.
206        let invalid = serde_json::from_str::<Axes>(r#"["pizza"]"#);
207        assert!(invalid.is_err());
208    }
209
210    #[test]
211    fn non_human() {
212        let empty = Axes::empty();
213        let ser_empty = bincode::serialize(&empty).unwrap();
214        let de_empty = bincode::deserialize(&ser_empty).unwrap();
215        assert_eq!(empty, de_empty);
216
217        let x = Axes::X;
218        let ser_x = bincode::serialize(&x).unwrap();
219        let de_x = bincode::deserialize(&ser_x).unwrap();
220        assert_eq!(x, de_x);
221
222        let all = Axes::all();
223        let ser_all = bincode::serialize(&all).unwrap();
224        let de_all = bincode::deserialize(&ser_all).unwrap();
225        assert_eq!(all, de_all);
226    }
227}