use crate::id::ExclusiveId;
use crate::id::Id;
use crate::id_hex;
use crate::macros::entity;
use crate::metadata;
use crate::metadata::{ConstDescribe, ConstId};
use crate::repo::BlobStore;
use crate::trible::Fragment;
use crate::value::schemas::hash::Blake3;
use crate::value::ToValue;
use crate::value::TryFromValue;
use crate::value::TryToValue;
use crate::value::Value;
use crate::value::ValueSchema;
use crate::value::VALUE_LEN;
use std::convert::Infallible;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InvalidBoolean;
pub struct Boolean;
impl ConstId for Boolean {
const ID: Id = id_hex!("73B414A3E25B0C0F9E4D6B0694DC33C5");
}
impl Boolean {
fn encode(flag: bool) -> Value<Self> {
if flag {
Value::new([u8::MAX; VALUE_LEN])
} else {
Value::new([0u8; VALUE_LEN])
}
}
fn decode(value: &Value<Self>) -> Result<bool, InvalidBoolean> {
if value.raw.iter().all(|&b| b == 0) {
Ok(false)
} else if value.raw.iter().all(|&b| b == u8::MAX) {
Ok(true)
} else {
Err(InvalidBoolean)
}
}
}
impl ConstDescribe for Boolean {
fn describe<B>(blobs: &mut B) -> Result<Fragment, B::PutError>
where
B: BlobStore<Blake3>,
{
let id = Self::ID;
let description = blobs.put(
"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).",
)?;
let name = blobs.put("boolean")?;
let tribles = entity! {
ExclusiveId::force_ref(&id) @
metadata::name: name,
metadata::description: description,
metadata::tag: metadata::KIND_VALUE_SCHEMA,
};
#[cfg(feature = "wasm")]
let tribles = {
let mut tribles = tribles;
tribles += entity! { ExclusiveId::force_ref(&id) @
metadata::value_formatter: blobs.put(wasm_formatter::BOOLEAN_WASM)?,
};
tribles
};
Ok(tribles)
}
}
#[cfg(feature = "wasm")]
mod wasm_formatter {
use core::fmt::Write;
use triblespace_core_macros::value_formatter;
#[value_formatter]
pub(crate) fn boolean(raw: &[u8; 32], out: &mut impl Write) -> Result<(), u32> {
let all_zero = raw.iter().all(|&b| b == 0);
let all_ones = raw.iter().all(|&b| b == u8::MAX);
let text = if all_zero {
"false"
} else if all_ones {
"true"
} else {
return Err(2);
};
out.write_str(text).map_err(|_| 1u32)?;
Ok(())
}
}
impl ValueSchema for Boolean {
type ValidationError = InvalidBoolean;
fn validate(value: Value<Self>) -> Result<Value<Self>, Self::ValidationError> {
Self::decode(&value)?;
Ok(value)
}
}
impl<'a> TryFromValue<'a, Boolean> for bool {
type Error = InvalidBoolean;
fn try_from_value(v: &'a Value<Boolean>) -> Result<Self, Self::Error> {
Boolean::decode(v)
}
}
impl TryToValue<Boolean> for bool {
type Error = Infallible;
fn try_to_value(self) -> Result<Value<Boolean>, Self::Error> {
Ok(Boolean::encode(self))
}
}
impl TryToValue<Boolean> for &bool {
type Error = Infallible;
fn try_to_value(self) -> Result<Value<Boolean>, Self::Error> {
Ok(Boolean::encode(*self))
}
}
impl ToValue<Boolean> for bool {
fn to_value(self) -> Value<Boolean> {
Boolean::encode(self)
}
}
impl ToValue<Boolean> for &bool {
fn to_value(self) -> Value<Boolean> {
Boolean::encode(*self)
}
}
#[cfg(test)]
mod tests {
use super::Boolean;
use super::InvalidBoolean;
use crate::value::Value;
use crate::value::ValueSchema;
#[test]
fn encodes_false_as_zero_bytes() {
let value = Boolean::value_from(false);
assert!(value.raw.iter().all(|&b| b == 0));
assert_eq!(Boolean::validate(value), Ok(Boolean::value_from(false)));
}
#[test]
fn encodes_true_as_all_ones() {
let value = Boolean::value_from(true);
assert!(value.raw.iter().all(|&b| b == u8::MAX));
assert_eq!(Boolean::validate(value), Ok(Boolean::value_from(true)));
}
#[test]
fn rejects_mixed_bit_patterns() {
let mut mixed = [0u8; crate::value::VALUE_LEN];
mixed[0] = 1;
let value = Value::<Boolean>::new(mixed);
assert_eq!(Boolean::validate(value), Err(InvalidBoolean));
}
}