triblespace_core/inline/encodings/
range.rs1use 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#[derive(Debug, Clone, Copy)]
24pub struct RangeU128;
25
26#[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}