Skip to main content

triblespace_core/value/schemas/
range.rs

1use 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::RawValue;
12use crate::value::ToValue;
13use crate::value::TryFromValue;
14use crate::value::TryToValue;
15use crate::value::Value;
16use crate::value::ValueSchema;
17use std::convert::Infallible;
18use std::ops::{Range, RangeInclusive};
19
20#[cfg(feature = "wasm")]
21use crate::blob::schemas::wasmcode::WasmCode;
22/// A value schema for representing a pair of `u128` values.
23///
24/// [`RangeU128`] encodes the pair as a half-open interval while
25/// [`RangeInclusiveU128`] represents an inclusive range. Both schemas encode the
26/// endpoints by packing the line into the high 64 bits and the column into the
27/// low 64 bits of the `u128`.
28#[derive(Debug, Clone, Copy)]
29pub struct RangeU128;
30
31#[derive(Debug, Clone, Copy)]
32pub struct RangeInclusiveU128;
33
34impl ConstMetadata for RangeU128 {
35    fn id() -> Id {
36        id_hex!("A4E25E3B92364FA5AB519C6A77D7CB3A")
37    }
38
39    fn describe<B>(blobs: &mut B) -> Result<TribleSet, B::PutError>
40    where
41        B: BlobStore<Blake3>,
42    {
43        let id = Self::id();
44        let description = blobs.put(
45            "Half-open range encoded as two big-endian u128 values (start..end). This mirrors common slice semantics where the end is exclusive.\n\nUse for offsets, byte ranges, and spans where length matters and empty ranges are valid. Use RangeInclusiveU128 when both endpoints should be included.\n\nNo normalization is enforced; callers should ensure start <= end and interpret units consistently.",
46        )?;
47        let tribles = entity! {
48            ExclusiveId::force_ref(&id) @
49                metadata::name: blobs.put("range_u128".to_string())?,
50                metadata::description: description,
51                metadata::tag: metadata::KIND_VALUE_SCHEMA,
52        };
53
54        #[cfg(feature = "wasm")]
55        let tribles = {
56            let mut tribles = tribles;
57            tribles += entity! { ExclusiveId::force_ref(&id) @
58                metadata::value_formatter: blobs.put(wasm_formatters::RANGE_U128_WASM)?,
59            };
60            tribles
61        };
62        Ok(tribles)
63    }
64}
65
66impl ValueSchema for RangeU128 {
67    type ValidationError = Infallible;
68}
69
70impl ConstMetadata for RangeInclusiveU128 {
71    fn id() -> Id {
72        id_hex!("1D0D82CA84424CD0A2F98DB37039E152")
73    }
74
75    fn describe<B>(blobs: &mut B) -> Result<TribleSet, B::PutError>
76    where
77        B: BlobStore<Blake3>,
78    {
79        let id = Self::id();
80        let description = blobs.put(
81            "Inclusive range encoded as two big-endian u128 values (start..=end). This is convenient when both endpoints are meaningful.\n\nUse for closed intervals such as line/column ranges or inclusive numeric bounds. Prefer RangeU128 for half-open intervals and length-based calculations.\n\nCallers should decide how to handle empty or reversed ranges; the schema only defines the byte layout.",
82        )?;
83        let tribles = entity! {
84            ExclusiveId::force_ref(&id) @
85                metadata::name: blobs.put("range_u128_inc".to_string())?,
86                metadata::description: description,
87                metadata::tag: metadata::KIND_VALUE_SCHEMA,
88        };
89
90        #[cfg(feature = "wasm")]
91        let tribles = {
92            let mut tribles = tribles;
93            tribles += entity! { ExclusiveId::force_ref(&id) @
94                metadata::value_formatter: blobs.put(wasm_formatters::RANGE_INCLUSIVE_U128_WASM)?,
95            };
96            tribles
97        };
98        Ok(tribles)
99    }
100}
101
102#[cfg(feature = "wasm")]
103mod wasm_formatters {
104    use core::fmt::Write;
105
106    use triblespace_core_macros::value_formatter;
107
108    #[value_formatter]
109    pub(crate) fn range_u128(raw: &[u8; 32], out: &mut impl Write) -> Result<(), u32> {
110        let mut buf = [0u8; 16];
111        buf.copy_from_slice(&raw[..16]);
112        let start = u128::from_be_bytes(buf);
113        buf.copy_from_slice(&raw[16..]);
114        let end = u128::from_be_bytes(buf);
115        write!(out, "{start}..{end}").map_err(|_| 1u32)?;
116        Ok(())
117    }
118
119    #[value_formatter]
120    pub(crate) fn range_inclusive_u128(raw: &[u8; 32], out: &mut impl Write) -> Result<(), u32> {
121        let mut buf = [0u8; 16];
122        buf.copy_from_slice(&raw[..16]);
123        let start = u128::from_be_bytes(buf);
124        buf.copy_from_slice(&raw[16..]);
125        let end = u128::from_be_bytes(buf);
126        write!(out, "{start}..={end}").map_err(|_| 1u32)?;
127        Ok(())
128    }
129}
130
131impl ValueSchema for RangeInclusiveU128 {
132    type ValidationError = Infallible;
133}
134
135fn encode_pair(range: (u128, u128)) -> RawValue {
136    let mut raw = [0u8; 32];
137    raw[..16].copy_from_slice(&range.0.to_be_bytes());
138    raw[16..].copy_from_slice(&range.1.to_be_bytes());
139    raw
140}
141
142fn decode_pair(raw: &RawValue) -> (u128, u128) {
143    let mut first = [0u8; 16];
144    let mut second = [0u8; 16];
145    first.copy_from_slice(&raw[..16]);
146    second.copy_from_slice(&raw[16..]);
147    (u128::from_be_bytes(first), u128::from_be_bytes(second))
148}
149
150fn encode_range_value<S: ValueSchema>(range: (u128, u128)) -> Value<S> {
151    Value::new(encode_pair(range))
152}
153
154fn decode_range_value<S: ValueSchema>(value: &Value<S>) -> (u128, u128) {
155    decode_pair(&value.raw)
156}
157
158impl ToValue<RangeU128> for (u128, u128) {
159    fn to_value(self) -> Value<RangeU128> {
160        encode_range_value(self)
161    }
162}
163
164impl FromValue<'_, RangeU128> for (u128, u128) {
165    fn from_value(v: &Value<RangeU128>) -> Self {
166        decode_range_value(v)
167    }
168}
169
170impl ToValue<RangeInclusiveU128> for (u128, u128) {
171    fn to_value(self) -> Value<RangeInclusiveU128> {
172        encode_range_value(self)
173    }
174}
175
176impl FromValue<'_, RangeInclusiveU128> for (u128, u128) {
177    fn from_value(v: &Value<RangeInclusiveU128>) -> Self {
178        decode_range_value(v)
179    }
180}
181
182impl TryToValue<RangeU128> for Range<u128> {
183    type Error = Infallible;
184
185    fn try_to_value(self) -> Result<Value<RangeU128>, Self::Error> {
186        Ok(encode_range_value((self.start, self.end)))
187    }
188}
189
190impl TryFromValue<'_, RangeU128> for Range<u128> {
191    type Error = Infallible;
192
193    fn try_from_value(v: &Value<RangeU128>) -> Result<Self, Self::Error> {
194        let (start, end) = decode_range_value(v);
195        Ok(start..end)
196    }
197}
198
199impl TryToValue<RangeInclusiveU128> for RangeInclusive<u128> {
200    type Error = Infallible;
201
202    fn try_to_value(self) -> Result<Value<RangeInclusiveU128>, Self::Error> {
203        let (start, end) = self.into_inner();
204        Ok(encode_range_value((start, end)))
205    }
206}
207
208impl TryFromValue<'_, RangeInclusiveU128> for RangeInclusive<u128> {
209    type Error = Infallible;
210
211    fn try_from_value(v: &Value<RangeInclusiveU128>) -> Result<Self, Self::Error> {
212        let (start, end) = decode_range_value(v);
213        Ok(start..=end)
214    }
215}