Skip to main content

triblespace_core/inline/encodings/
range.rs

1use crate::inline::Encodes;
2use crate::id::ExclusiveId;
3use crate::id::Id;
4use crate::id_hex;
5use crate::macros::entity;
6use crate::metadata;
7use crate::metadata::MetaDescribe;
8use crate::trible::Fragment;
9use crate::inline::RawInline;
10use crate::inline::TryFromInline;
11use crate::inline::TryToInline;
12use crate::inline::Inline;
13use crate::inline::InlineEncoding;
14use std::convert::Infallible;
15use std::ops::{Range, RangeInclusive};
16
17/// A inline encoding for representing a pair of `u128` values.
18///
19/// [`RangeU128`] encodes the pair as a half-open interval while
20/// [`RangeInclusiveU128`] represents an inclusive range. Both schemas encode the
21/// endpoints by packing the line into the high 64 bits and the column into the
22/// low 64 bits of the `u128`.
23#[derive(Debug, Clone, Copy)]
24pub struct RangeU128;
25
26/// Inclusive range of two `u128` values (`start..=end`), big-endian encoded.
27#[derive(Debug, Clone, Copy)]
28pub struct RangeInclusiveU128;
29
30impl MetaDescribe for RangeU128 {
31    fn describe() -> Fragment {
32        let id: Id = id_hex!("A4E25E3B92364FA5AB519C6A77D7CB3A");
33        #[allow(unused_mut)]
34        let mut tribles = entity! {
35            ExclusiveId::force_ref(&id) @
36                metadata::name: "range_u128",
37                metadata::description: "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.",
38                metadata::tag: metadata::KIND_INLINE_ENCODING,
39        };
40
41        #[cfg(feature = "wasm")]
42        {
43            tribles += entity! { ExclusiveId::force_ref(&id) @
44                metadata::value_formatter: wasm_formatters::RANGE_U128_WASM,
45            };
46        }
47        tribles
48    }
49}
50
51impl InlineEncoding for RangeU128 {
52    type ValidationError = Infallible;
53    type Encoding = Self;
54}
55
56impl MetaDescribe for RangeInclusiveU128 {
57    fn describe() -> Fragment {
58        let id: Id = id_hex!("1D0D82CA84424CD0A2F98DB37039E152");
59        #[allow(unused_mut)]
60        let mut tribles = entity! {
61            ExclusiveId::force_ref(&id) @
62                metadata::name: "range_u128_inc",
63                metadata::description: "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.",
64                metadata::tag: metadata::KIND_INLINE_ENCODING,
65        };
66
67        #[cfg(feature = "wasm")]
68        {
69            tribles += entity! { ExclusiveId::force_ref(&id) @
70                metadata::value_formatter: wasm_formatters::RANGE_INCLUSIVE_U128_WASM,
71            };
72        }
73        tribles
74    }
75}
76
77#[cfg(feature = "wasm")]
78mod wasm_formatters {
79    use core::fmt::Write;
80
81    use triblespace_core_macros::value_formatter;
82
83    #[value_formatter]
84    pub(crate) fn range_u128(raw: &[u8; 32], out: &mut impl Write) -> Result<(), u32> {
85        let mut buf = [0u8; 16];
86        buf.copy_from_slice(&raw[..16]);
87        let start = u128::from_be_bytes(buf);
88        buf.copy_from_slice(&raw[16..]);
89        let end = u128::from_be_bytes(buf);
90        write!(out, "{start}..{end}").map_err(|_| 1u32)?;
91        Ok(())
92    }
93
94    #[value_formatter]
95    pub(crate) fn range_inclusive_u128(raw: &[u8; 32], out: &mut impl Write) -> Result<(), u32> {
96        let mut buf = [0u8; 16];
97        buf.copy_from_slice(&raw[..16]);
98        let start = u128::from_be_bytes(buf);
99        buf.copy_from_slice(&raw[16..]);
100        let end = u128::from_be_bytes(buf);
101        write!(out, "{start}..={end}").map_err(|_| 1u32)?;
102        Ok(())
103    }
104}
105
106impl InlineEncoding for RangeInclusiveU128 {
107    type ValidationError = Infallible;
108    type Encoding = Self;
109}
110
111fn encode_pair(range: (u128, u128)) -> RawInline {
112    let mut raw = [0u8; 32];
113    raw[..16].copy_from_slice(&range.0.to_be_bytes());
114    raw[16..].copy_from_slice(&range.1.to_be_bytes());
115    raw
116}
117
118fn decode_pair(raw: &RawInline) -> (u128, u128) {
119    let mut first = [0u8; 16];
120    let mut second = [0u8; 16];
121    first.copy_from_slice(&raw[..16]);
122    second.copy_from_slice(&raw[16..]);
123    (u128::from_be_bytes(first), u128::from_be_bytes(second))
124}
125
126fn encode_range_value<S: InlineEncoding>(range: (u128, u128)) -> Inline<S> {
127    Inline::new(encode_pair(range))
128}
129
130fn decode_range_value<S: InlineEncoding>(value: &Inline<S>) -> (u128, u128) {
131    decode_pair(&value.raw)
132}
133
134impl Encodes<(u128, u128)> for RangeU128
135{
136    type Output = Inline<RangeU128>;
137    fn encode(source: (u128, u128)) -> Inline<RangeU128> {
138        encode_range_value(source)
139    }
140}
141
142impl TryFromInline<'_, RangeU128> for (u128, u128) {
143    type Error = Infallible;
144    fn try_from_inline(v: &Inline<RangeU128>) -> Result<Self, Infallible> {
145        Ok(decode_range_value(v))
146    }
147}
148
149impl Encodes<(u128, u128)> for RangeInclusiveU128
150{
151    type Output = Inline<RangeInclusiveU128>;
152    fn encode(source: (u128, u128)) -> Inline<RangeInclusiveU128> {
153        encode_range_value(source)
154    }
155}
156
157impl TryFromInline<'_, RangeInclusiveU128> for (u128, u128) {
158    type Error = Infallible;
159    fn try_from_inline(v: &Inline<RangeInclusiveU128>) -> Result<Self, Infallible> {
160        Ok(decode_range_value(v))
161    }
162}
163
164impl TryToInline<RangeU128> for Range<u128> {
165    type Error = Infallible;
166
167    fn try_to_inline(self) -> Result<Inline<RangeU128>, Self::Error> {
168        Ok(encode_range_value((self.start, self.end)))
169    }
170}
171
172impl TryFromInline<'_, RangeU128> for Range<u128> {
173    type Error = Infallible;
174
175    fn try_from_inline(v: &Inline<RangeU128>) -> Result<Self, Self::Error> {
176        let (start, end) = decode_range_value(v);
177        Ok(start..end)
178    }
179}
180
181impl TryToInline<RangeInclusiveU128> for RangeInclusive<u128> {
182    type Error = Infallible;
183
184    fn try_to_inline(self) -> Result<Inline<RangeInclusiveU128>, Self::Error> {
185        let (start, end) = self.into_inner();
186        Ok(encode_range_value((start, end)))
187    }
188}
189
190impl TryFromInline<'_, RangeInclusiveU128> for RangeInclusive<u128> {
191    type Error = Infallible;
192
193    fn try_from_inline(v: &Inline<RangeInclusiveU128>) -> Result<Self, Self::Error> {
194        let (start, end) = decode_range_value(v);
195        Ok(start..=end)
196    }
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202    use crate::inline::{IntoInline, TryFromInline, TryToInline};
203    use proptest::prelude::*;
204
205    proptest! {
206        #[test]
207        fn range_u128_tuple_roundtrip(a: u128, b: u128) {
208            let input = (a, b);
209            let value: Inline<RangeU128> = input.to_inline();
210            let output: (u128, u128) = value.from_inline();
211            prop_assert_eq!(input, output);
212        }
213
214        #[test]
215        fn range_u128_range_roundtrip(a: u128, b: u128) {
216            let input = a..b;
217            let value: Inline<RangeU128> = input.clone().try_to_inline().unwrap();
218            let output = Range::<u128>::try_from_inline(&value).unwrap();
219            prop_assert_eq!(input, output);
220        }
221
222        #[test]
223        fn range_inclusive_tuple_roundtrip(a: u128, b: u128) {
224            let input = (a, b);
225            let value: Inline<RangeInclusiveU128> = input.to_inline();
226            let output: (u128, u128) = value.from_inline();
227            prop_assert_eq!(input, output);
228        }
229
230        #[test]
231        fn range_inclusive_range_roundtrip(a: u128, b: u128) {
232            let input = a..=b;
233            let value: Inline<RangeInclusiveU128> = input.clone().try_to_inline().unwrap();
234            let output = RangeInclusive::<u128>::try_from_inline(&value).unwrap();
235            prop_assert_eq!(input, output);
236        }
237
238        #[test]
239        fn range_u128_tuple_and_range_agree(a: u128, b: u128) {
240            let tuple_val: Inline<RangeU128> = (a, b).to_inline();
241            let range_val: Inline<RangeU128> = (a..b).try_to_inline().unwrap();
242            prop_assert_eq!(tuple_val.raw, range_val.raw);
243        }
244
245        #[test]
246        fn range_inclusive_tuple_and_range_agree(a: u128, b: u128) {
247            let tuple_val: Inline<RangeInclusiveU128> = (a, b).to_inline();
248            let range_val: Inline<RangeInclusiveU128> = (a..=b).try_to_inline().unwrap();
249            prop_assert_eq!(tuple_val.raw, range_val.raw);
250        }
251
252        #[test]
253        fn range_u128_validates(a: u128, b: u128) {
254            let value: Inline<RangeU128> = (a, b).to_inline();
255            prop_assert!(RangeU128::validate(value).is_ok());
256        }
257
258        #[test]
259        fn range_inclusive_validates(a: u128, b: u128) {
260            let value: Inline<RangeInclusiveU128> = (a, b).to_inline();
261            prop_assert!(RangeInclusiveU128::validate(value).is_ok());
262        }
263    }
264}