Skip to main content

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