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)]
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}