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