Skip to main content

triblespace_core/value/schemas/
boolean.rs

1use crate::blob::schemas::longstring::LongString;
2use crate::id::ExclusiveId;
3use crate::id::Id;
4use crate::id_hex;
5use crate::macros::entity;
6use crate::metadata;
7use crate::metadata::ConstMetadata;
8use crate::repo::BlobStore;
9use crate::trible::TribleSet;
10use crate::value::schemas::hash::Blake3;
11use crate::value::FromValue;
12use crate::value::ToValue;
13use crate::value::TryFromValue;
14use crate::value::TryToValue;
15use crate::value::Value;
16use crate::value::ValueSchema;
17use crate::value::VALUE_LEN;
18
19use std::convert::Infallible;
20
21#[cfg(feature = "wasm")]
22use crate::blob::schemas::wasmcode::WasmCode;
23/// Error raised when a value does not match the [`Boolean`] encoding.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub struct InvalidBoolean;
26
27/// Value schema that stores boolean flags as either all-zero or all-one bit patterns.
28///
29/// Storing `false` as `0x00` and `true` as `0xFF` in every byte makes it trivial to
30/// distinguish the two cases while leaving room for future SIMD optimisations when
31/// scanning large collections of flags.
32pub struct Boolean;
33
34impl Boolean {
35    fn encode(flag: bool) -> Value<Self> {
36        if flag {
37            Value::new([u8::MAX; VALUE_LEN])
38        } else {
39            Value::new([0u8; VALUE_LEN])
40        }
41    }
42
43    fn decode(value: &Value<Self>) -> Result<bool, InvalidBoolean> {
44        if value.raw.iter().all(|&b| b == 0) {
45            Ok(false)
46        } else if value.raw.iter().all(|&b| b == u8::MAX) {
47            Ok(true)
48        } else {
49            Err(InvalidBoolean)
50        }
51    }
52}
53
54impl ConstMetadata for Boolean {
55    fn id() -> Id {
56        id_hex!("73B414A3E25B0C0F9E4D6B0694DC33C5")
57    }
58
59    fn describe<B>(blobs: &mut B) -> Result<TribleSet, B::PutError>
60    where
61        B: BlobStore<Blake3>,
62    {
63        let id = Self::id();
64        let description = blobs.put::<LongString, _>(
65            "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).",
66        )?;
67        let tribles = entity! {
68            ExclusiveId::force_ref(&id) @
69                metadata::shortname: "boolean",
70                metadata::description: description,
71                metadata::tag: metadata::KIND_VALUE_SCHEMA,
72        };
73
74        #[cfg(feature = "wasm")]
75        let tribles = {
76            let mut tribles = tribles;
77            tribles += entity! { ExclusiveId::force_ref(&id) @
78                metadata::value_formatter: blobs.put::<WasmCode, _>(wasm_formatter::BOOLEAN_WASM)?,
79            };
80            tribles
81        };
82        Ok(tribles)
83    }
84}
85
86#[cfg(feature = "wasm")]
87mod wasm_formatter {
88    use core::fmt::Write;
89
90    use triblespace_core_macros::value_formatter;
91
92    #[value_formatter]
93    pub(crate) fn boolean(raw: &[u8; 32], out: &mut impl Write) -> Result<(), u32> {
94        let all_zero = raw.iter().all(|&b| b == 0);
95        let all_ones = raw.iter().all(|&b| b == u8::MAX);
96
97        let text = if all_zero {
98            "false"
99        } else if all_ones {
100            "true"
101        } else {
102            return Err(2);
103        };
104
105        out.write_str(text).map_err(|_| 1u32)?;
106        Ok(())
107    }
108}
109
110impl ValueSchema for Boolean {
111    type ValidationError = InvalidBoolean;
112
113    fn validate(value: Value<Self>) -> Result<Value<Self>, Self::ValidationError> {
114        Self::decode(&value)?;
115        Ok(value)
116    }
117}
118
119impl<'a> TryFromValue<'a, Boolean> for bool {
120    type Error = InvalidBoolean;
121
122    fn try_from_value(v: &'a Value<Boolean>) -> Result<Self, Self::Error> {
123        Boolean::decode(v)
124    }
125}
126
127impl<'a> FromValue<'a, Boolean> for bool {
128    fn from_value(v: &'a Value<Boolean>) -> Self {
129        v.try_from_value()
130            .expect("boolean values must be well-formed")
131    }
132}
133
134impl TryToValue<Boolean> for bool {
135    type Error = Infallible;
136
137    fn try_to_value(self) -> Result<Value<Boolean>, Self::Error> {
138        Ok(Boolean::encode(self))
139    }
140}
141
142impl TryToValue<Boolean> for &bool {
143    type Error = Infallible;
144
145    fn try_to_value(self) -> Result<Value<Boolean>, Self::Error> {
146        Ok(Boolean::encode(*self))
147    }
148}
149
150impl ToValue<Boolean> for bool {
151    fn to_value(self) -> Value<Boolean> {
152        Boolean::encode(self)
153    }
154}
155
156impl ToValue<Boolean> for &bool {
157    fn to_value(self) -> Value<Boolean> {
158        Boolean::encode(*self)
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use super::Boolean;
165    use super::InvalidBoolean;
166    use crate::value::Value;
167    use crate::value::ValueSchema;
168
169    #[test]
170    fn encodes_false_as_zero_bytes() {
171        let value = Boolean::value_from(false);
172        assert!(value.raw.iter().all(|&b| b == 0));
173        assert_eq!(Boolean::validate(value), Ok(Boolean::value_from(false)));
174    }
175
176    #[test]
177    fn encodes_true_as_all_ones() {
178        let value = Boolean::value_from(true);
179        assert!(value.raw.iter().all(|&b| b == u8::MAX));
180        assert_eq!(Boolean::validate(value), Ok(Boolean::value_from(true)));
181    }
182
183    #[test]
184    fn rejects_mixed_bit_patterns() {
185        let mut mixed = [0u8; crate::value::VALUE_LEN];
186        mixed[0] = 1;
187        let value = Value::<Boolean>::new(mixed);
188        assert_eq!(Boolean::validate(value), Err(InvalidBoolean));
189    }
190}