triblespace_core/value.rs
1//! Value type and conversion traits for schema types. For a deeper look at
2//! portability goals, common formats, and schema design, refer to the
3//! "Portability & Common Formats" chapter in the project book.
4//!
5//! # Example
6//!
7//! ```
8//! use triblespace_core::value::{Value, ValueSchema, ToValue, TryFromValue};
9//! use triblespace_core::metadata::ConstId;
10//! use triblespace_core::id::Id;
11//! use triblespace_core::macros::id_hex;
12//! use std::convert::{TryInto, Infallible};
13//!
14//! // Define a new schema type.
15//! // We're going to define an unsigned integer type that is stored as a little-endian 32-byte array.
16//! // Note that makes our example easier, as we don't have to worry about sign-extension or padding bytes.
17//! pub struct MyNumber;
18//!
19//! // Implement the ValueSchema trait for the schema type.
20//! impl ConstId for MyNumber {
21//! const ID: Id = id_hex!("345EAC0C5B5D7D034C87777280B88AE2");
22//! }
23//! impl ValueSchema for MyNumber {
24//! type ValidationError = ();
25//! // Every bit pattern is valid for this schema.
26//! }
27//!
28//! // Implement conversion functions for the schema type.
29//! // Use `Error = Infallible` when the conversion cannot fail.
30//! impl TryFromValue<'_, MyNumber> for u32 {
31//! type Error = Infallible;
32//! fn try_from_value(v: &Value<MyNumber>) -> Result<Self, Infallible> {
33//! Ok(u32::from_le_bytes(v.raw[0..4].try_into().unwrap()))
34//! }
35//! }
36//!
37//! impl ToValue<MyNumber> for u32 {
38//! fn to_value(self) -> Value<MyNumber> {
39//! // Convert the Rust type to the schema type, i.e. a 32-byte array.
40//! let mut bytes = [0; 32];
41//! bytes[0..4].copy_from_slice(&self.to_le_bytes());
42//! Value::new(bytes)
43//! }
44//! }
45//!
46//! // Use the schema type to store and retrieve a Rust type.
47//! let value: Value<MyNumber> = MyNumber::value_from(42u32);
48//! let i: u32 = value.from_value();
49//! assert_eq!(i, 42);
50//!
51//! // You can also implement conversion functions for other Rust types.
52//! impl TryFromValue<'_, MyNumber> for u64 {
53//! type Error = Infallible;
54//! fn try_from_value(v: &Value<MyNumber>) -> Result<Self, Infallible> {
55//! Ok(u64::from_le_bytes(v.raw[0..8].try_into().unwrap()))
56//! }
57//! }
58//!
59//! impl ToValue<MyNumber> for u64 {
60//! fn to_value(self) -> Value<MyNumber> {
61//! let mut bytes = [0; 32];
62//! bytes[0..8].copy_from_slice(&self.to_le_bytes());
63//! Value::new(bytes)
64//! }
65//! }
66//!
67//! let value: Value<MyNumber> = MyNumber::value_from(42u64);
68//! let i: u64 = value.from_value();
69//! assert_eq!(i, 42);
70//!
71//! // And use a value round-trip to convert between Rust types.
72//! let value: Value<MyNumber> = MyNumber::value_from(42u32);
73//! let i: u64 = value.from_value();
74//! assert_eq!(i, 42);
75//! ```
76
77/// Built-in value schema types and their conversion implementations.
78pub mod schemas;
79
80use crate::metadata::ConstId;
81
82use core::fmt;
83use std::borrow::Borrow;
84use std::cmp::Ordering;
85use std::fmt::Debug;
86use std::hash::Hash;
87use std::marker::PhantomData;
88
89use hex::ToHex;
90use zerocopy::Immutable;
91use zerocopy::IntoBytes;
92use zerocopy::KnownLayout;
93use zerocopy::TryFromBytes;
94use zerocopy::Unaligned;
95
96/// The length of a value in bytes.
97pub const VALUE_LEN: usize = 32;
98
99/// A raw value is simply a 32-byte array.
100pub type RawValue = [u8; VALUE_LEN];
101
102/// A value is a 32-byte array that can be (de)serialized as a Rust type.
103/// The schema type parameter is an abstract type that represents the meaning
104/// and valid bit patterns of the bytes.
105///
106/// # Example
107///
108/// ```
109/// use triblespace_core::prelude::*;
110/// use valueschemas::R256;
111/// use num_rational::Ratio;
112///
113/// let ratio = Ratio::new(1, 2);
114/// let value: Value<R256> = R256::value_from(ratio);
115/// let ratio2: Ratio<i128> = value.try_from_value().unwrap();
116/// assert_eq!(ratio, ratio2);
117/// ```
118#[derive(TryFromBytes, IntoBytes, Unaligned, Immutable, KnownLayout)]
119#[repr(transparent)]
120pub struct Value<T: ValueSchema> {
121 /// The 32-byte representation of this value.
122 pub raw: RawValue,
123 _schema: PhantomData<T>,
124}
125
126impl<S: ValueSchema> Value<S> {
127 /// Create a new value from a 32-byte array.
128 ///
129 /// # Example
130 ///
131 /// ```
132 /// use triblespace_core::value::{Value, ValueSchema};
133 /// use triblespace_core::value::schemas::UnknownValue;
134 ///
135 /// let bytes = [0; 32];
136 /// let value = Value::<UnknownValue>::new(bytes);
137 /// ```
138 pub fn new(value: RawValue) -> Self {
139 Self {
140 raw: value,
141 _schema: PhantomData,
142 }
143 }
144
145 /// Validate this value using its schema.
146 pub fn validate(self) -> Result<Self, S::ValidationError> {
147 S::validate(self)
148 }
149
150 /// Check if this value conforms to its schema.
151 pub fn is_valid(&self) -> bool {
152 S::validate(*self).is_ok()
153 }
154
155 /// Transmute a value from one schema type to another.
156 /// This is a safe operation, as the bytes are not changed.
157 /// The schema type is only changed in the type system.
158 /// This is a zero-cost operation.
159 /// This is useful when you have a value with an abstract schema type,
160 /// but you know the concrete schema type.
161 pub fn transmute<O>(self) -> Value<O>
162 where
163 O: ValueSchema,
164 {
165 Value::new(self.raw)
166 }
167
168 /// Transmute a value reference from one schema type to another.
169 /// This is a safe operation, as the bytes are not changed.
170 /// The schema type is only changed in the type system.
171 /// This is a zero-cost operation.
172 /// This is useful when you have a value reference with an abstract schema type,
173 /// but you know the concrete schema type.
174 pub fn as_transmute<O>(&self) -> &Value<O>
175 where
176 O: ValueSchema,
177 {
178 unsafe { std::mem::transmute(self) }
179 }
180
181 /// Transmute a raw value reference to a value reference.
182 ///
183 /// # Example
184 ///
185 /// ```
186 /// use triblespace_core::value::{Value, ValueSchema};
187 /// use triblespace_core::value::schemas::UnknownValue;
188 /// use std::borrow::Borrow;
189 ///
190 /// let bytes = [0; 32];
191 /// let value: Value<UnknownValue> = Value::new(bytes);
192 /// let value_ref: &Value<UnknownValue> = &value;
193 /// let raw_value_ref: &[u8; 32] = value_ref.borrow();
194 /// let value_ref2: &Value<UnknownValue> = Value::as_transmute_raw(raw_value_ref);
195 /// assert_eq!(&value, value_ref2);
196 /// ```
197 pub fn as_transmute_raw(value: &RawValue) -> &Self {
198 unsafe { std::mem::transmute(value) }
199 }
200
201 /// Deserialize a value with an abstract schema type to a concrete Rust type.
202 ///
203 /// This method only works for infallible conversions (where `Error = Infallible`).
204 /// For fallible conversions, use the [Value::try_from_value] method.
205 ///
206 /// # Example
207 ///
208 /// ```
209 /// use triblespace_core::prelude::*;
210 /// use valueschemas::F64;
211 ///
212 /// let value: Value<F64> = (3.14f64).to_value();
213 /// let concrete: f64 = value.from_value();
214 /// ```
215 pub fn from_value<'a, T>(&'a self) -> T
216 where
217 T: TryFromValue<'a, S, Error = std::convert::Infallible>,
218 {
219 match <T as TryFromValue<'a, S>>::try_from_value(self) {
220 Ok(v) => v,
221 Err(e) => match e {},
222 }
223 }
224
225 /// Deserialize a value with an abstract schema type to a concrete Rust type.
226 ///
227 /// This method returns an error if the conversion is not possible.
228 /// This might happen if the bytes are not valid for the schema type or if the
229 /// rust type can't represent the specific value of the schema type,
230 /// e.g. if the schema type is a fractional number and the rust type is an integer.
231 ///
232 /// For infallible conversions, use the [Value::from_value] method.
233 ///
234 /// # Example
235 ///
236 /// ```
237 /// use triblespace_core::prelude::*;
238 /// use valueschemas::R256;
239 /// use num_rational::Ratio;
240 ///
241 /// let value: Value<R256> = R256::value_from(Ratio::new(1, 2));
242 /// let concrete: Result<Ratio<i128>, _> = value.try_from_value();
243 /// ```
244 ///
245 pub fn try_from_value<'a, T>(&'a self) -> Result<T, <T as TryFromValue<'a, S>>::Error>
246 where
247 T: TryFromValue<'a, S>,
248 {
249 <T as TryFromValue<'a, S>>::try_from_value(self)
250 }
251}
252
253impl<T: ValueSchema> Copy for Value<T> {}
254
255impl<T: ValueSchema> Clone for Value<T> {
256 fn clone(&self) -> Self {
257 *self
258 }
259}
260
261impl<T: ValueSchema> PartialEq for Value<T> {
262 fn eq(&self, other: &Self) -> bool {
263 self.raw == other.raw
264 }
265}
266
267impl<T: ValueSchema> Eq for Value<T> {}
268
269impl<T: ValueSchema> Hash for Value<T> {
270 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
271 self.raw.hash(state);
272 }
273}
274
275impl<T: ValueSchema> Ord for Value<T> {
276 fn cmp(&self, other: &Self) -> Ordering {
277 self.raw.cmp(&other.raw)
278 }
279}
280
281impl<T: ValueSchema> PartialOrd for Value<T> {
282 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
283 Some(self.cmp(other))
284 }
285}
286
287impl<S: ValueSchema> Borrow<RawValue> for Value<S> {
288 fn borrow(&self) -> &RawValue {
289 &self.raw
290 }
291}
292
293impl<T: ValueSchema> Debug for Value<T> {
294 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295 write!(
296 f,
297 "Value<{}>({})",
298 std::any::type_name::<T>(),
299 ToHex::encode_hex::<String>(&self.raw)
300 )
301 }
302}
303
304/// A trait that represents an abstract schema type that can be (de)serialized as a [Value].
305///
306/// This trait is usually implemented on a type-level empty struct,
307/// but may contain additional information about the schema type as associated constants or types.
308/// The [Handle](crate::value::schemas::hash::Handle) type for example contains type information about the hash algorithm,
309/// and the schema of the referenced blob.
310///
311/// See the [value](crate::value) module for more information.
312/// See the [BlobSchema](crate::blob::BlobSchema) trait for the counterpart trait for blobs.
313pub trait ValueSchema: ConstId + Sized + 'static {
314 /// The error type returned by [`validate`](ValueSchema::validate).
315 /// Use `()` or [`Infallible`](std::convert::Infallible) when every bit pattern is valid.
316 type ValidationError;
317
318 /// Check if the given value conforms to this schema.
319 fn validate(value: Value<Self>) -> Result<Value<Self>, Self::ValidationError> {
320 Ok(value)
321 }
322
323 /// Create a new value from a concrete Rust type.
324 /// This is a convenience method that calls the [ToValue] trait.
325 /// This method might panic if the conversion is not possible.
326 ///
327 /// See the [ValueSchema::value_try_from] method for a conversion that returns a result.
328 fn value_from<T: ToValue<Self>>(t: T) -> Value<Self> {
329 t.to_value()
330 }
331
332 /// Create a new value from a concrete Rust type.
333 /// This is a convenience method that calls the [TryToValue] trait.
334 /// This method might return an error if the conversion is not possible.
335 ///
336 /// See the [ValueSchema::value_from] method for a conversion that always succeeds (or panics).
337 fn value_try_from<T: TryToValue<Self>>(
338 t: T,
339 ) -> Result<Value<Self>, <T as TryToValue<Self>>::Error> {
340 t.try_to_value()
341 }
342}
343
344/// A trait for converting a Rust type to a [Value] with a specific schema type.
345/// This trait is implemented on the concrete Rust type.
346///
347/// This might cause a panic if the conversion is not possible,
348/// see [TryToValue] for a conversion that returns a result.
349///
350/// This is the counterpart to the [TryFromValue] trait.
351///
352/// See [ToBlob](crate::blob::ToBlob) for the counterpart trait for blobs.
353pub trait ToValue<S: ValueSchema> {
354 /// Convert the Rust type to a [Value] with a specific schema type.
355 /// This might cause a panic if the conversion is not possible.
356 ///
357 /// See the [TryToValue] trait for a conversion that returns a result.
358 fn to_value(self) -> Value<S>;
359}
360
361/// A trait for converting a Rust type to a [Value] with a specific schema type.
362/// This trait is implemented on the concrete Rust type.
363///
364/// This might return an error if the conversion is not possible,
365/// see [ToValue] for cases where the conversion is guaranteed to succeed (or panic).
366///
367/// This is the counterpart to the [TryFromValue] trait.
368///
369pub trait TryToValue<S: ValueSchema> {
370 /// The error type returned when the conversion fails.
371 type Error;
372 /// Convert the Rust type to a [Value] with a specific schema type.
373 /// This might return an error if the conversion is not possible.
374 ///
375 /// See the [ToValue] trait for a conversion that always succeeds (or panics).
376 fn try_to_value(self) -> Result<Value<S>, Self::Error>;
377}
378
379/// A trait for converting a [Value] with a specific schema type to a Rust type.
380/// This trait is implemented on the concrete Rust type.
381///
382/// Values are 32-byte arrays that represent data at a deserialization boundary.
383/// Conversions may fail depending on the schema and target type. Use
384/// `Error = Infallible` for conversions that genuinely cannot fail (e.g.
385/// `ethnum::U256` from `U256BE`), and a real error type for narrowing
386/// conversions (e.g. `u64` from `U256BE`).
387///
388/// This is the counterpart to the [TryToValue] trait.
389///
390/// See [TryFromBlob](crate::blob::TryFromBlob) for the counterpart trait for blobs.
391pub trait TryFromValue<'a, S: ValueSchema>: Sized {
392 /// The error type returned when the conversion fails.
393 type Error;
394 /// Convert the [Value] with a specific schema type to the Rust type.
395 fn try_from_value(v: &'a Value<S>) -> Result<Self, Self::Error>;
396}
397
398impl<S: ValueSchema> ToValue<S> for Value<S> {
399 fn to_value(self) -> Value<S> {
400 self
401 }
402}
403
404impl<S: ValueSchema> ToValue<S> for &Value<S> {
405 fn to_value(self) -> Value<S> {
406 *self
407 }
408}
409
410impl<'a, S: ValueSchema> TryFromValue<'a, S> for Value<S> {
411 type Error = std::convert::Infallible;
412 fn try_from_value(v: &'a Value<S>) -> Result<Self, std::convert::Infallible> {
413 Ok(*v)
414 }
415}
416
417impl<'a, S: ValueSchema> TryFromValue<'a, S> for () {
418 type Error = std::convert::Infallible;
419 fn try_from_value(_v: &'a Value<S>) -> Result<Self, std::convert::Infallible> {
420 Ok(())
421 }
422}