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