triblespace_core/value/schemas/
range.rs1use 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#[derive(Debug, Clone, Copy)]
26pub struct RangeU128;
27
28#[derive(Debug, Clone, Copy)]
30pub struct RangeInclusiveU128;
31
32impl ConstId for RangeU128 {
33 const ID: Id = id_hex!("A4E25E3B92364FA5AB519C6A77D7CB3A");
34}
35
36impl ConstId for RangeInclusiveU128 {
37 const ID: Id = id_hex!("1D0D82CA84424CD0A2F98DB37039E152");
38}
39
40impl ConstDescribe for RangeU128 {
41 fn describe<B>(blobs: &mut B) -> Result<Fragment, B::PutError>
42 where
43 B: BlobStore<Blake3>,
44 {
45 let id = Self::ID;
46 let description = blobs.put(
47 "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.",
48 )?;
49 let tribles = entity! {
50 ExclusiveId::force_ref(&id) @
51 metadata::name: blobs.put("range_u128")?,
52 metadata::description: description,
53 metadata::tag: metadata::KIND_VALUE_SCHEMA,
54 };
55
56 #[cfg(feature = "wasm")]
57 let tribles = {
58 let mut tribles = tribles;
59 tribles += entity! { ExclusiveId::force_ref(&id) @
60 metadata::value_formatter: blobs.put(wasm_formatters::RANGE_U128_WASM)?,
61 };
62 tribles
63 };
64 Ok(tribles)
65 }
66}
67
68impl ValueSchema for RangeU128 {
69 type ValidationError = Infallible;
70}
71
72impl ConstDescribe for RangeInclusiveU128 {
73 fn describe<B>(blobs: &mut B) -> Result<Fragment, B::PutError>
74 where
75 B: BlobStore<Blake3>,
76 {
77 let id = Self::ID;
78 let description = blobs.put(
79 "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.",
80 )?;
81 let tribles = entity! {
82 ExclusiveId::force_ref(&id) @
83 metadata::name: blobs.put("range_u128_inc")?,
84 metadata::description: description,
85 metadata::tag: metadata::KIND_VALUE_SCHEMA,
86 };
87
88 #[cfg(feature = "wasm")]
89 let tribles = {
90 let mut tribles = tribles;
91 tribles += entity! { ExclusiveId::force_ref(&id) @
92 metadata::value_formatter: blobs.put(wasm_formatters::RANGE_INCLUSIVE_U128_WASM)?,
93 };
94 tribles
95 };
96 Ok(tribles)
97 }
98}
99
100#[cfg(feature = "wasm")]
101mod wasm_formatters {
102 use core::fmt::Write;
103
104 use triblespace_core_macros::value_formatter;
105
106 #[value_formatter]
107 pub(crate) fn range_u128(raw: &[u8; 32], out: &mut impl Write) -> Result<(), u32> {
108 let mut buf = [0u8; 16];
109 buf.copy_from_slice(&raw[..16]);
110 let start = u128::from_be_bytes(buf);
111 buf.copy_from_slice(&raw[16..]);
112 let end = u128::from_be_bytes(buf);
113 write!(out, "{start}..{end}").map_err(|_| 1u32)?;
114 Ok(())
115 }
116
117 #[value_formatter]
118 pub(crate) fn range_inclusive_u128(raw: &[u8; 32], out: &mut impl Write) -> Result<(), u32> {
119 let mut buf = [0u8; 16];
120 buf.copy_from_slice(&raw[..16]);
121 let start = u128::from_be_bytes(buf);
122 buf.copy_from_slice(&raw[16..]);
123 let end = u128::from_be_bytes(buf);
124 write!(out, "{start}..={end}").map_err(|_| 1u32)?;
125 Ok(())
126 }
127}
128
129impl ValueSchema for RangeInclusiveU128 {
130 type ValidationError = Infallible;
131}
132
133fn encode_pair(range: (u128, u128)) -> RawValue {
134 let mut raw = [0u8; 32];
135 raw[..16].copy_from_slice(&range.0.to_be_bytes());
136 raw[16..].copy_from_slice(&range.1.to_be_bytes());
137 raw
138}
139
140fn decode_pair(raw: &RawValue) -> (u128, u128) {
141 let mut first = [0u8; 16];
142 let mut second = [0u8; 16];
143 first.copy_from_slice(&raw[..16]);
144 second.copy_from_slice(&raw[16..]);
145 (u128::from_be_bytes(first), u128::from_be_bytes(second))
146}
147
148fn encode_range_value<S: ValueSchema>(range: (u128, u128)) -> Value<S> {
149 Value::new(encode_pair(range))
150}
151
152fn decode_range_value<S: ValueSchema>(value: &Value<S>) -> (u128, u128) {
153 decode_pair(&value.raw)
154}
155
156impl ToValue<RangeU128> for (u128, u128) {
157 fn to_value(self) -> Value<RangeU128> {
158 encode_range_value(self)
159 }
160}
161
162impl TryFromValue<'_, RangeU128> for (u128, u128) {
163 type Error = Infallible;
164 fn try_from_value(v: &Value<RangeU128>) -> Result<Self, Infallible> {
165 Ok(decode_range_value(v))
166 }
167}
168
169impl ToValue<RangeInclusiveU128> for (u128, u128) {
170 fn to_value(self) -> Value<RangeInclusiveU128> {
171 encode_range_value(self)
172 }
173}
174
175impl TryFromValue<'_, RangeInclusiveU128> for (u128, u128) {
176 type Error = Infallible;
177 fn try_from_value(v: &Value<RangeInclusiveU128>) -> Result<Self, Infallible> {
178 Ok(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}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220 use crate::value::{ToValue, TryFromValue, TryToValue};
221 use proptest::prelude::*;
222
223 proptest! {
224 #[test]
225 fn range_u128_tuple_roundtrip(a: u128, b: u128) {
226 let input = (a, b);
227 let value: Value<RangeU128> = input.to_value();
228 let output: (u128, u128) = value.from_value();
229 prop_assert_eq!(input, output);
230 }
231
232 #[test]
233 fn range_u128_range_roundtrip(a: u128, b: u128) {
234 let input = a..b;
235 let value: Value<RangeU128> = input.clone().try_to_value().unwrap();
236 let output = Range::<u128>::try_from_value(&value).unwrap();
237 prop_assert_eq!(input, output);
238 }
239
240 #[test]
241 fn range_inclusive_tuple_roundtrip(a: u128, b: u128) {
242 let input = (a, b);
243 let value: Value<RangeInclusiveU128> = input.to_value();
244 let output: (u128, u128) = value.from_value();
245 prop_assert_eq!(input, output);
246 }
247
248 #[test]
249 fn range_inclusive_range_roundtrip(a: u128, b: u128) {
250 let input = a..=b;
251 let value: Value<RangeInclusiveU128> = input.clone().try_to_value().unwrap();
252 let output = RangeInclusive::<u128>::try_from_value(&value).unwrap();
253 prop_assert_eq!(input, output);
254 }
255
256 #[test]
257 fn range_u128_tuple_and_range_agree(a: u128, b: u128) {
258 let tuple_val: Value<RangeU128> = (a, b).to_value();
259 let range_val: Value<RangeU128> = (a..b).try_to_value().unwrap();
260 prop_assert_eq!(tuple_val.raw, range_val.raw);
261 }
262
263 #[test]
264 fn range_inclusive_tuple_and_range_agree(a: u128, b: u128) {
265 let tuple_val: Value<RangeInclusiveU128> = (a, b).to_value();
266 let range_val: Value<RangeInclusiveU128> = (a..=b).try_to_value().unwrap();
267 prop_assert_eq!(tuple_val.raw, range_val.raw);
268 }
269
270 #[test]
271 fn range_u128_validates(a: u128, b: u128) {
272 let value: Value<RangeU128> = (a, b).to_value();
273 prop_assert!(RangeU128::validate(value).is_ok());
274 }
275
276 #[test]
277 fn range_inclusive_validates(a: u128, b: u128) {
278 let value: Value<RangeInclusiveU128> = (a, b).to_value();
279 prop_assert!(RangeInclusiveU128::validate(value).is_ok());
280 }
281 }
282}