triblespace_core/value/schemas/
boolean.rs1use crate::id::ExclusiveId;
2use crate::id::Id;
3use crate::id_hex;
4use crate::macros::entity;
5use crate::metadata;
6use crate::metadata::ConstMetadata;
7use crate::repo::BlobStore;
8use crate::trible::TribleSet;
9use crate::value::schemas::hash::Blake3;
10use crate::value::FromValue;
11use crate::value::ToValue;
12use crate::value::TryFromValue;
13use crate::value::TryToValue;
14use crate::value::Value;
15use crate::value::ValueSchema;
16use crate::value::VALUE_LEN;
17
18use std::convert::Infallible;
19
20#[cfg(feature = "wasm")]
21use crate::blob::schemas::wasmcode::WasmCode;
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct InvalidBoolean;
25
26pub struct Boolean;
32
33impl Boolean {
34 fn encode(flag: bool) -> Value<Self> {
35 if flag {
36 Value::new([u8::MAX; VALUE_LEN])
37 } else {
38 Value::new([0u8; VALUE_LEN])
39 }
40 }
41
42 fn decode(value: &Value<Self>) -> Result<bool, InvalidBoolean> {
43 if value.raw.iter().all(|&b| b == 0) {
44 Ok(false)
45 } else if value.raw.iter().all(|&b| b == u8::MAX) {
46 Ok(true)
47 } else {
48 Err(InvalidBoolean)
49 }
50 }
51}
52
53impl ConstMetadata for Boolean {
54 fn id() -> Id {
55 id_hex!("73B414A3E25B0C0F9E4D6B0694DC33C5")
56 }
57
58 fn describe<B>(blobs: &mut B) -> Result<TribleSet, B::PutError>
59 where
60 B: BlobStore<Blake3>,
61 {
62 let id = Self::id();
63 let description = blobs.put(
64 "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).",
65 )?;
66 let name = blobs.put("boolean".to_string())?;
67 let tribles = entity! {
68 ExclusiveId::force_ref(&id) @
69 metadata::name: name,
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(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}