Skip to main content

triblespace_core/inline/encodings/
boolean.rs

1use crate::inline::Encodes;
2use crate::id::ExclusiveId;
3use crate::id::Id;
4use crate::id_hex;
5use crate::macros::entity;
6use crate::metadata;
7use crate::metadata::MetaDescribe;
8use crate::trible::Fragment;
9use crate::inline::TryFromInline;
10use crate::inline::TryToInline;
11use crate::inline::Inline;
12use crate::inline::InlineEncoding;
13use crate::inline::INLINE_LEN;
14
15use std::convert::Infallible;
16
17/// Error raised when a value does not match the [`Boolean`] encoding.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct InvalidBoolean;
20
21/// Inline schema that stores boolean flags as either all-zero or all-one bit patterns.
22///
23/// Storing `false` as `0x00` and `true` as `0xFF` in every byte makes it trivial to
24/// distinguish the two cases while leaving room for future SIMD optimisations when
25/// scanning large collections of flags.
26pub struct Boolean;
27
28impl Boolean {
29    fn encode(flag: bool) -> Inline<Self> {
30        if flag {
31            Inline::new([u8::MAX; INLINE_LEN])
32        } else {
33            Inline::new([0u8; INLINE_LEN])
34        }
35    }
36
37    fn decode(value: &Inline<Self>) -> Result<bool, InvalidBoolean> {
38        if value.raw.iter().all(|&b| b == 0) {
39            Ok(false)
40        } else if value.raw.iter().all(|&b| b == u8::MAX) {
41            Ok(true)
42        } else {
43            Err(InvalidBoolean)
44        }
45    }
46}
47
48impl MetaDescribe for Boolean {
49    fn describe() -> Fragment {
50        let id: Id = id_hex!("73B414A3E25B0C0F9E4D6B0694DC33C5");
51        #[allow(unused_mut)]
52        let mut tribles = entity! {
53            ExclusiveId::force_ref(&id) @
54                metadata::name: "boolean",
55                metadata::description: "Boolean stored as all-zero bytes for false and all-0xFF bytes for true. The encoding uses the full 32-byte value, making the two states obvious and cheap to test.\n\nUse for simple flags and binary states. Represent unknown or missing data by omitting the trible rather than inventing a third sentinel value.\n\nMixed patterns are invalid and will fail validation. If you need tri-state or richer states, model it explicitly (for example with ShortString or a dedicated entity).",
56                metadata::tag: metadata::KIND_INLINE_ENCODING,
57        };
58
59        #[cfg(feature = "wasm")]
60        {
61            tribles += entity! { ExclusiveId::force_ref(&id) @
62                metadata::value_formatter: wasm_formatter::BOOLEAN_WASM,
63            };
64        }
65        tribles
66    }
67}
68
69#[cfg(feature = "wasm")]
70mod wasm_formatter {
71    use core::fmt::Write;
72
73    use triblespace_core_macros::value_formatter;
74
75    #[value_formatter]
76    pub(crate) fn boolean(raw: &[u8; 32], out: &mut impl Write) -> Result<(), u32> {
77        let all_zero = raw.iter().all(|&b| b == 0);
78        let all_ones = raw.iter().all(|&b| b == u8::MAX);
79
80        let text = if all_zero {
81            "false"
82        } else if all_ones {
83            "true"
84        } else {
85            return Err(2);
86        };
87
88        out.write_str(text).map_err(|_| 1u32)?;
89        Ok(())
90    }
91}
92
93impl InlineEncoding for Boolean {
94    type ValidationError = InvalidBoolean;
95    type Encoding = Self;
96
97    fn validate(value: Inline<Self>) -> Result<Inline<Self>, Self::ValidationError> {
98        Self::decode(&value)?;
99        Ok(value)
100    }
101}
102
103impl<'a> TryFromInline<'a, Boolean> for bool {
104    type Error = InvalidBoolean;
105
106    fn try_from_inline(v: &'a Inline<Boolean>) -> Result<Self, Self::Error> {
107        Boolean::decode(v)
108    }
109}
110
111impl TryToInline<Boolean> for bool {
112    type Error = Infallible;
113
114    fn try_to_inline(self) -> Result<Inline<Boolean>, Self::Error> {
115        Ok(Boolean::encode(self))
116    }
117}
118
119impl TryToInline<Boolean> for &bool {
120    type Error = Infallible;
121
122    fn try_to_inline(self) -> Result<Inline<Boolean>, Self::Error> {
123        Ok(Boolean::encode(*self))
124    }
125}
126
127impl Encodes<bool> for Boolean
128{
129    type Output = Inline<Boolean>;
130    fn encode(source: bool) -> Inline<Boolean> {
131        Boolean::encode(source)
132    }
133}
134
135impl Encodes<&bool> for Boolean
136{
137    type Output = Inline<Boolean>;
138    fn encode(source: &bool) -> Inline<Boolean> {
139        Boolean::encode(*source)
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::Boolean;
146    use super::InvalidBoolean;
147    use crate::inline::Inline;
148    use crate::inline::InlineEncoding;
149
150    #[test]
151    fn encodes_false_as_zero_bytes() {
152        let value = Boolean::inline_from(false);
153        assert!(value.raw.iter().all(|&b| b == 0));
154        assert_eq!(Boolean::validate(value), Ok(Boolean::inline_from(false)));
155    }
156
157    #[test]
158    fn encodes_true_as_all_ones() {
159        let value = Boolean::inline_from(true);
160        assert!(value.raw.iter().all(|&b| b == u8::MAX));
161        assert_eq!(Boolean::validate(value), Ok(Boolean::inline_from(true)));
162    }
163
164    #[test]
165    fn rejects_mixed_bit_patterns() {
166        let mut mixed = [0u8; crate::inline::INLINE_LEN];
167        mixed[0] = 1;
168        let value = Inline::<Boolean>::new(mixed);
169        assert_eq!(Boolean::validate(value), Err(InvalidBoolean));
170    }
171}