Skip to main content

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, FromValue};
9//! use triblespace_core::metadata::ConstMetadata;
10//! use triblespace_core::id::Id;
11//! use triblespace_core::macros::id_hex;
12//! use std::convert::TryInto;
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 ConstMetadata for MyNumber {
21//!    fn id() -> Id {
22//!        id_hex!("345EAC0C5B5D7D034C87777280B88AE2")
23//!    }
24//! }
25//! impl ValueSchema for MyNumber {
26//!    type ValidationError = ();
27//!    // Every bit pattern is valid for this schema.
28//! }
29//!
30//! // Implement conversion functions for the schema type.
31//! impl FromValue<'_, MyNumber> for u32 {
32//!    fn from_value(v: &Value<MyNumber>) -> Self {
33//!      // Convert the schema type to the Rust type.
34//!     u32::from_le_bytes(v.raw[0..4].try_into().unwrap())
35//!  }
36//! }
37//!
38//! impl ToValue<MyNumber> for u32 {
39//!   fn to_value(self) -> Value<MyNumber> {
40//!      // Convert the Rust type to the schema type, i.e. a 32-byte array.
41//!      let mut bytes = [0; 32];
42//!      bytes[0..4].copy_from_slice(&self.to_le_bytes());
43//!      Value::new(bytes)
44//!   }
45//! }
46//!
47//! // Use the schema type to store and retrieve a Rust type.
48//! let value: Value<MyNumber> = MyNumber::value_from(42u32);
49//! let i: u32 = value.from_value();
50//! assert_eq!(i, 42);
51//!
52//! // You can also implement conversion functions for other Rust types.
53//! impl FromValue<'_, MyNumber> for u64 {
54//!   fn from_value(v: &Value<MyNumber>) -> Self {
55//!    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
77pub mod schemas;
78
79use crate::metadata::ConstMetadata;
80
81use core::fmt;
82use std::borrow::Borrow;
83use std::cmp::Ordering;
84use std::fmt::Debug;
85use std::hash::Hash;
86use std::marker::PhantomData;
87
88use hex::ToHex;
89use zerocopy::Immutable;
90use zerocopy::IntoBytes;
91use zerocopy::KnownLayout;
92use zerocopy::TryFromBytes;
93use zerocopy::Unaligned;
94
95/// The length of a value in bytes.
96pub const VALUE_LEN: usize = 32;
97
98/// A raw value is simply a 32-byte array.
99pub type RawValue = [u8; VALUE_LEN];
100
101/// A value is a 32-byte array that can be (de)serialized as a Rust type.
102/// The schema type parameter is an abstract type that represents the meaning
103/// and valid bit patterns of the bytes.
104///
105/// # Example
106///
107/// ```
108/// use triblespace_core::prelude::*;
109/// use valueschemas::R256;
110/// use num_rational::Ratio;
111///
112/// let ratio = Ratio::new(1, 2);
113/// let value: Value<R256> = R256::value_from(ratio);
114/// let ratio2: Ratio<i128> = value.from_value();
115/// assert_eq!(ratio, ratio2);
116/// ```
117#[derive(TryFromBytes, IntoBytes, Unaligned, Immutable, KnownLayout)]
118#[repr(transparent)]
119pub struct Value<T: ValueSchema> {
120    pub raw: RawValue,
121    _schema: PhantomData<T>,
122}
123
124impl<S: ValueSchema> Value<S> {
125    /// Create a new value from a 32-byte array.
126    ///
127    /// # Example
128    ///
129    /// ```
130    /// use triblespace_core::value::{Value, ValueSchema};
131    /// use triblespace_core::value::schemas::UnknownValue;
132    ///
133    /// let bytes = [0; 32];
134    /// let value = Value::<UnknownValue>::new(bytes);
135    /// ```
136    pub fn new(value: RawValue) -> Self {
137        Self {
138            raw: value,
139            _schema: PhantomData,
140        }
141    }
142
143    /// Validate this value using its schema.
144    pub fn validate(self) -> Result<Self, S::ValidationError> {
145        S::validate(self)
146    }
147
148    /// Check if this value conforms to its schema.
149    pub fn is_valid(&self) -> bool {
150        S::validate(*self).is_ok()
151    }
152
153    /// Transmute a value from one schema type to another.
154    /// This is a safe operation, as the bytes are not changed.
155    /// The schema type is only changed in the type system.
156    /// This is a zero-cost operation.
157    /// This is useful when you have a value with an abstract schema type,
158    /// but you know the concrete schema type.
159    pub fn transmute<O>(self) -> Value<O>
160    where
161        O: ValueSchema,
162    {
163        Value::new(self.raw)
164    }
165
166    /// Transmute a value reference from one schema type to another.
167    /// This is a safe operation, as the bytes are not changed.
168    /// The schema type is only changed in the type system.
169    /// This is a zero-cost operation.
170    /// This is useful when you have a value reference with an abstract schema type,
171    /// but you know the concrete schema type.
172    pub fn as_transmute<O>(&self) -> &Value<O>
173    where
174        O: ValueSchema,
175    {
176        unsafe { std::mem::transmute(self) }
177    }
178
179    /// Transmute a raw value reference to a value reference.
180    ///
181    /// # Example
182    ///
183    /// ```
184    /// use triblespace_core::value::{Value, ValueSchema};
185    /// use triblespace_core::value::schemas::UnknownValue;
186    /// use std::borrow::Borrow;
187    ///
188    /// let bytes = [0; 32];
189    /// let value: Value<UnknownValue> = Value::new(bytes);
190    /// let value_ref: &Value<UnknownValue> = &value;
191    /// let raw_value_ref: &[u8; 32] = value_ref.borrow();
192    /// let value_ref2: &Value<UnknownValue> = Value::as_transmute_raw(raw_value_ref);
193    /// assert_eq!(&value, value_ref2);
194    /// ```
195    pub fn as_transmute_raw(value: &RawValue) -> &Self {
196        unsafe { std::mem::transmute(value) }
197    }
198
199    /// Deserialize a value with an abstract schema type to a concrete Rust type.
200    ///
201    /// Note that this may panic if the conversion is not possible.
202    /// This might happen if the bytes are not valid for the schema type or if the
203    /// rust type can't represent the specific value of the schema type,
204    /// e.g. if the schema type is a fractional number and the rust type is an integer.
205    ///
206    /// For a conversion that always returns a result, use the [Value::try_from_value] method.
207    ///
208    /// # Example
209    ///
210    /// ```
211    /// use triblespace_core::prelude::*;
212    /// use valueschemas::R256;
213    /// use num_rational::Ratio;
214    ///
215    /// let value: Value<R256> = R256::value_from(Ratio::new(1, 2));
216    /// let concrete: Ratio<i128> = value.from_value();
217    /// ```
218    pub fn from_value<'a, T>(&'a self) -> T
219    where
220        T: FromValue<'a, S>,
221    {
222        <T as FromValue<'a, S>>::from_value(self)
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 a conversion that retrieves the value without error handling, 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: ConstMetadata + Sized + 'static {
314    type ValidationError;
315
316    /// Check if the given value conforms to this schema.
317    fn validate(value: Value<Self>) -> Result<Value<Self>, Self::ValidationError> {
318        Ok(value)
319    }
320
321    /// Create a new value from a concrete Rust type.
322    /// This is a convenience method that calls the [ToValue] trait.
323    /// This method might panic if the conversion is not possible.
324    ///
325    /// See the [ValueSchema::value_try_from] method for a conversion that returns a result.
326    fn value_from<T: ToValue<Self>>(t: T) -> Value<Self> {
327        t.to_value()
328    }
329
330    /// Create a new value from a concrete Rust type.
331    /// This is a convenience method that calls the [TryToValue] trait.
332    /// This method might return an error if the conversion is not possible.
333    ///
334    /// See the [ValueSchema::value_from] method for a conversion that always succeeds (or panics).
335    fn value_try_from<T: TryToValue<Self>>(
336        t: T,
337    ) -> Result<Value<Self>, <T as TryToValue<Self>>::Error> {
338        t.try_to_value()
339    }
340}
341
342/// A trait for converting a Rust type to a [Value] with a specific schema type.
343/// This trait is implemented on the concrete Rust type.
344///
345/// This might cause a panic if the conversion is not possible,
346/// see [TryToValue] for a conversion that returns a result.
347///
348/// This is the counterpart to the [FromValue] trait.
349///
350/// See [ToBlob](crate::blob::ToBlob) for the counterpart trait for blobs.
351pub trait ToValue<S: ValueSchema> {
352    /// Convert the Rust type to a [Value] with a specific schema type.
353    /// This might cause a panic if the conversion is not possible.
354    ///
355    /// See the [TryToValue] trait for a conversion that returns a result.
356    fn to_value(self) -> Value<S>;
357}
358
359/// A trait for converting a [Value] with a specific schema type to a Rust type.
360/// This trait is implemented on the concrete Rust type.
361///
362/// This might cause a panic if the conversion is not possible,
363/// see [TryFromValue] for a conversion that returns a result.
364///
365/// This is the counterpart to the [ToValue] trait.
366///
367/// See [TryFromBlob](crate::blob::TryFromBlob) for the counterpart trait for blobs.
368pub trait FromValue<'a, S: ValueSchema> {
369    /// Convert the [Value] with a specific schema type to the Rust type.
370    /// This might cause a panic if the conversion is not possible.
371    ///
372    /// See the [TryFromValue] trait for a conversion that returns a result.
373    fn from_value(v: &'a Value<S>) -> Self;
374}
375
376/// A trait for converting a Rust type to a [Value] with a specific schema type.
377/// This trait is implemented on the concrete Rust type.
378///
379/// This might return an error if the conversion is not possible,
380/// see [ToValue] for cases where the conversion is guaranteed to succeed (or panic).
381///
382/// This is the counterpart to the [TryFromValue] trait.
383///
384pub trait TryToValue<S: ValueSchema> {
385    type Error;
386    /// Convert the Rust type to a [Value] with a specific schema type.
387    /// This might return an error if the conversion is not possible.
388    ///
389    /// See the [ToValue] trait for a conversion that always succeeds (or panics).
390    fn try_to_value(self) -> Result<Value<S>, Self::Error>;
391}
392
393/// A trait for converting a [Value] with a specific schema type to a Rust type.
394/// This trait is implemented on the concrete Rust type.
395///
396/// This might return an error if the conversion is not possible,
397/// see [FromValue] for cases where the conversion is guaranteed to succeed (or panic).
398///
399/// This is the counterpart to the [TryToValue] trait.
400///
401/// See [TryFromBlob](crate::blob::TryFromBlob) for the counterpart trait for blobs.
402pub trait TryFromValue<'a, S: ValueSchema>: Sized {
403    type Error;
404    /// Convert the [Value] with a specific schema type to the Rust type.
405    /// This might return an error if the conversion is not possible.
406    ///
407    /// See the [FromValue] trait for a conversion that always succeeds (or panics).
408    fn try_from_value(v: &'a Value<S>) -> Result<Self, Self::Error>;
409}
410
411impl<S: ValueSchema> ToValue<S> for Value<S> {
412    fn to_value(self) -> Value<S> {
413        self
414    }
415}
416
417impl<S: ValueSchema> ToValue<S> for &Value<S> {
418    fn to_value(self) -> Value<S> {
419        *self
420    }
421}
422
423impl<'a, S: ValueSchema> FromValue<'a, S> for Value<S> {
424    fn from_value(v: &'a Value<S>) -> Self {
425        *v
426    }
427}
428
429impl<'a, S: ValueSchema> FromValue<'a, S> for () {
430    fn from_value(_v: &'a Value<S>) -> Self {}
431}