spacetimedb_sats/
product_value.rs

1use crate::algebraic_value::AlgebraicValue;
2use crate::product_type::ProductType;
3use crate::{ArrayValue, SumValue, ValueWithType};
4use spacetimedb_primitives::{ColId, ColList};
5
6/// A product value is made of a list of
7/// "elements" / "fields" / "factors" of other `AlgebraicValue`s.
8///
9/// The type of a product value is a [product type](`ProductType`).
10#[derive(Debug, Clone, Ord, PartialOrd, PartialEq, Eq, Default)]
11pub struct ProductValue {
12    /// The values that make up this product value.
13    pub elements: Box<[AlgebraicValue]>,
14}
15
16/// Constructs a product value from a list of fields with syntax `product![v1, v2, ...]`.
17///
18/// Repeat notation from `vec![x; n]` is not supported.
19#[macro_export]
20macro_rules! product {
21    [$($elems:expr),*$(,)?] => {
22        $crate::ProductValue {
23            elements: [$($crate::AlgebraicValue::from($elems)),*].into(),
24        }
25    }
26}
27
28impl FromIterator<AlgebraicValue> for ProductValue {
29    fn from_iter<T: IntoIterator<Item = AlgebraicValue>>(iter: T) -> Self {
30        let elements = iter.into_iter().collect();
31        Self { elements }
32    }
33}
34
35impl IntoIterator for ProductValue {
36    type Item = AlgebraicValue;
37    type IntoIter = std::vec::IntoIter<AlgebraicValue>;
38    fn into_iter(self) -> Self::IntoIter {
39        Vec::from(self.elements).into_iter()
40    }
41}
42
43impl<'a> IntoIterator for &'a ProductValue {
44    type Item = &'a AlgebraicValue;
45    type IntoIter = std::slice::Iter<'a, AlgebraicValue>;
46    fn into_iter(self) -> Self::IntoIter {
47        self.elements.iter()
48    }
49}
50
51impl crate::Value for ProductValue {
52    type Type = ProductType;
53}
54
55/// An error that occurs when a field, of a product value, is accessed that doesn't exist.
56#[derive(thiserror::Error, Debug, Copy, Clone)]
57#[error("Field at position {col_pos} named: {name:?} not found or has an invalid type")]
58pub struct InvalidFieldError {
59    /// The claimed col_pos of the field within the product value.
60    pub col_pos: ColId,
61    /// The name of the field, if any.
62    pub name: Option<&'static str>,
63}
64
65impl From<ColId> for InvalidFieldError {
66    fn from(col_pos: ColId) -> Self {
67        Self { col_pos, name: None }
68    }
69}
70
71impl ProductValue {
72    /// Borrow the value at field of `self` identified by `col_pos`.
73    ///
74    /// The `name` is non-functional and is only used for error-messages.
75    pub fn get_field(&self, col_pos: usize, name: Option<&'static str>) -> Result<&AlgebraicValue, InvalidFieldError> {
76        self.elements.get(col_pos).ok_or(InvalidFieldError {
77            col_pos: col_pos.into(),
78            name,
79        })
80    }
81
82    /// This utility function is designed to project fields based on the supplied `indexes`.
83    ///
84    /// **Important:**
85    ///
86    /// The resulting [AlgebraicValue] will wrap into a [ProductValue] when projecting multiple
87    /// (including zero) fields, otherwise it will consist of a single [AlgebraicValue].
88    ///
89    /// **Parameters:**
90    /// - `cols`: A [ColList] containing the indexes of fields to be projected.
91    pub fn project(&self, cols: &ColList) -> Result<AlgebraicValue, InvalidFieldError> {
92        if let Some(head) = cols.as_singleton() {
93            self.get_field(head.idx(), None).cloned()
94        } else {
95            let mut fields = Vec::with_capacity(cols.len() as usize);
96            for col in cols.iter() {
97                fields.push(self.get_field(col.idx(), None)?.clone());
98            }
99            Ok(AlgebraicValue::product(fields))
100        }
101    }
102
103    /// Extracts the `value` at field of `self` identified by `index`
104    /// and then runs it through the function `f` which possibly returns a `T` derived from `value`.
105    pub fn extract_field<'a, T>(
106        &'a self,
107        col_pos: usize,
108        name: Option<&'static str>,
109        f: impl 'a + Fn(&'a AlgebraicValue) -> Option<T>,
110    ) -> Result<T, InvalidFieldError> {
111        f(self.get_field(col_pos, name)?).ok_or(InvalidFieldError {
112            col_pos: col_pos.into(),
113            name,
114        })
115    }
116
117    /// Interprets the value at field of `self` identified by `index` as a `bool`.
118    pub fn field_as_bool(&self, index: usize, named: Option<&'static str>) -> Result<bool, InvalidFieldError> {
119        self.extract_field(index, named, |f| f.as_bool().copied())
120    }
121
122    /// Interprets the value at field of `self` identified by `index` as a `u8`.
123    pub fn field_as_u8(&self, index: usize, named: Option<&'static str>) -> Result<u8, InvalidFieldError> {
124        self.extract_field(index, named, |f| f.as_u8().copied())
125    }
126
127    /// Interprets the value at field of `self` identified by `index` as a `u32`.
128    pub fn field_as_u32(&self, index: usize, named: Option<&'static str>) -> Result<u32, InvalidFieldError> {
129        self.extract_field(index, named, |f| f.as_u32().copied())
130    }
131
132    /// Interprets the value at field of `self` identified by `index` as a `u64`.
133    pub fn field_as_u64(&self, index: usize, named: Option<&'static str>) -> Result<u64, InvalidFieldError> {
134        self.extract_field(index, named, |f| f.as_u64().copied())
135    }
136
137    /// Interprets the value at field of `self` identified by `index` as a `i64`.
138    pub fn field_as_i64(&self, index: usize, named: Option<&'static str>) -> Result<i64, InvalidFieldError> {
139        self.extract_field(index, named, |f| f.as_i64().copied())
140    }
141
142    /// Interprets the value at field of `self` identified by `index` as a `i128`.
143    pub fn field_as_i128(&self, index: usize, named: Option<&'static str>) -> Result<i128, InvalidFieldError> {
144        self.extract_field(index, named, |f| f.as_i128().copied().map(|x| x.0))
145    }
146
147    /// Interprets the value at field of `self` identified by `index` as a `u128`.
148    pub fn field_as_u128(&self, index: usize, named: Option<&'static str>) -> Result<u128, InvalidFieldError> {
149        self.extract_field(index, named, |f| f.as_u128().copied().map(|x| x.0))
150    }
151
152    /// Interprets the value at field of `self` identified by `index` as a string slice.
153    pub fn field_as_str(&self, index: usize, named: Option<&'static str>) -> Result<&str, InvalidFieldError> {
154        self.extract_field(index, named, |f| f.as_string()).map(|x| &**x)
155    }
156
157    /// Interprets the value at field of `self` identified by `index` as a byte slice.
158    pub fn field_as_bytes(&self, index: usize, named: Option<&'static str>) -> Result<&[u8], InvalidFieldError> {
159        self.extract_field(index, named, |f| f.as_bytes())
160    }
161
162    /// Interprets the value at field of `self` identified by `index` as an `ArrayValue`.
163    pub fn field_as_array(&self, index: usize, named: Option<&'static str>) -> Result<&ArrayValue, InvalidFieldError> {
164        self.extract_field(index, named, |f| f.as_array())
165    }
166
167    /// Interprets the value at field of `self` identified by `index` as a `SumValue`.
168    pub fn field_as_sum(&self, index: usize, named: Option<&'static str>) -> Result<SumValue, InvalidFieldError> {
169        self.extract_field(index, named, |f| f.as_sum().cloned())
170    }
171}
172
173impl<'a> ValueWithType<'a, ProductValue> {
174    pub fn elements(&self) -> impl ExactSizeIterator<Item = ValueWithType<'a, AlgebraicValue>> {
175        self.ty_s().with_values(self.value())
176    }
177}