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    /// If you want to wrap single elements in a [ProductValue] as well, see [Self::project_product].
89    ///
90    /// **Parameters:**
91    /// - `cols`: A [ColList] containing the indexes of fields to be projected.
92    pub fn project(&self, cols: &ColList) -> Result<AlgebraicValue, InvalidFieldError> {
93        if let Some(head) = cols.as_singleton() {
94            self.get_field(head.idx(), None).cloned()
95        } else {
96            let mut fields = Vec::with_capacity(cols.len() as usize);
97            for col in cols.iter() {
98                fields.push(self.get_field(col.idx(), None)?.clone());
99            }
100            Ok(AlgebraicValue::product(fields))
101        }
102    }
103
104    /// This utility function is designed to project fields based on the supplied `indexes`.
105    ///
106    /// **Important:**
107    ///
108    /// Returns a [ProductValue] even when projecting a single element.
109    /// If you don't want to wrap a single element in a [ProductValue], see [Self::project].
110    ///
111    /// **Parameters:**
112    /// - `cols`: A [ColList] containing the indexes of fields to be projected.
113    pub fn project_product(&self, cols: &ColList) -> Result<ProductValue, InvalidFieldError> {
114        let mut fields = Vec::with_capacity(cols.len() as usize);
115        for col in cols.iter() {
116            fields.push(self.get_field(col.idx(), None)?.clone());
117        }
118        Ok(ProductValue::from(fields))
119    }
120
121    /// Extracts the `value` at field of `self` identified by `index`
122    /// and then runs it through the function `f` which possibly returns a `T` derived from `value`.
123    pub fn extract_field<'a, T>(
124        &'a self,
125        col_pos: usize,
126        name: Option<&'static str>,
127        f: impl 'a + Fn(&'a AlgebraicValue) -> Option<T>,
128    ) -> Result<T, InvalidFieldError> {
129        f(self.get_field(col_pos, name)?).ok_or(InvalidFieldError {
130            col_pos: col_pos.into(),
131            name,
132        })
133    }
134
135    /// Interprets the value at field of `self` identified by `index` as a `bool`.
136    pub fn field_as_bool(&self, index: usize, named: Option<&'static str>) -> Result<bool, InvalidFieldError> {
137        self.extract_field(index, named, |f| f.as_bool().copied())
138    }
139
140    /// Interprets the value at field of `self` identified by `index` as a `u8`.
141    pub fn field_as_u8(&self, index: usize, named: Option<&'static str>) -> Result<u8, InvalidFieldError> {
142        self.extract_field(index, named, |f| f.as_u8().copied())
143    }
144
145    /// Interprets the value at field of `self` identified by `index` as a `u32`.
146    pub fn field_as_u32(&self, index: usize, named: Option<&'static str>) -> Result<u32, InvalidFieldError> {
147        self.extract_field(index, named, |f| f.as_u32().copied())
148    }
149
150    /// Interprets the value at field of `self` identified by `index` as a `u64`.
151    pub fn field_as_u64(&self, index: usize, named: Option<&'static str>) -> Result<u64, InvalidFieldError> {
152        self.extract_field(index, named, |f| f.as_u64().copied())
153    }
154
155    /// Interprets the value at field of `self` identified by `index` as a `i64`.
156    pub fn field_as_i64(&self, index: usize, named: Option<&'static str>) -> Result<i64, InvalidFieldError> {
157        self.extract_field(index, named, |f| f.as_i64().copied())
158    }
159
160    /// Interprets the value at field of `self` identified by `index` as a `i128`.
161    pub fn field_as_i128(&self, index: usize, named: Option<&'static str>) -> Result<i128, InvalidFieldError> {
162        self.extract_field(index, named, |f| f.as_i128().copied().map(|x| x.0))
163    }
164
165    /// Interprets the value at field of `self` identified by `index` as a `u128`.
166    pub fn field_as_u128(&self, index: usize, named: Option<&'static str>) -> Result<u128, InvalidFieldError> {
167        self.extract_field(index, named, |f| f.as_u128().copied().map(|x| x.0))
168    }
169
170    /// Interprets the value at field of `self` identified by `index` as a string slice.
171    pub fn field_as_str(&self, index: usize, named: Option<&'static str>) -> Result<&str, InvalidFieldError> {
172        self.extract_field(index, named, |f| f.as_string()).map(|x| &**x)
173    }
174
175    /// Interprets the value at field of `self` identified by `index` as a byte slice.
176    pub fn field_as_bytes(&self, index: usize, named: Option<&'static str>) -> Result<&[u8], InvalidFieldError> {
177        self.extract_field(index, named, |f| f.as_bytes())
178    }
179
180    /// Interprets the value at field of `self` identified by `index` as an `ArrayValue`.
181    pub fn field_as_array(&self, index: usize, named: Option<&'static str>) -> Result<&ArrayValue, InvalidFieldError> {
182        self.extract_field(index, named, |f| f.as_array())
183    }
184
185    /// Interprets the value at field of `self` identified by `index` as a `SumValue`.
186    pub fn field_as_sum(&self, index: usize, named: Option<&'static str>) -> Result<SumValue, InvalidFieldError> {
187        self.extract_field(index, named, |f| f.as_sum().cloned())
188    }
189}
190
191impl<'a> ValueWithType<'a, ProductValue> {
192    pub fn elements(&self) -> impl ExactSizeIterator<Item = ValueWithType<'a, AlgebraicValue>> {
193        self.ty_s().with_values(self.value())
194    }
195}