1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
use crate::algebraic_value::AlgebraicValue;
use crate::product_type::ProductType;
use crate::{ArrayValue, SumValue, ValueWithType};
use spacetimedb_primitives::{ColId, ColList};

/// A product value is made of a list of
/// "elements" / "fields" / "factors" of other `AlgebraicValue`s.
///
/// The type of a product value is a [product type](`ProductType`).
#[derive(Debug, Clone, Ord, PartialOrd, PartialEq, Eq, Hash)]
pub struct ProductValue {
    /// The values that make up this product value.
    pub elements: Vec<AlgebraicValue>,
}

/// Constructs a product value from a list of fields with syntax `product![v1, v2, ...]`.
///
/// Repeat notation from `vec![x; n]` is not supported.
#[macro_export]
macro_rules! product {
    [$($elems:expr),*$(,)?] => {
        $crate::ProductValue {
            elements: vec![$($crate::AlgebraicValue::from($elems)),*],
        }
    }
}

impl ProductValue {
    /// Returns a product value constructed from the given values in `elements`.
    pub fn new(elements: &[AlgebraicValue]) -> Self {
        Self {
            elements: elements.into(),
        }
    }
}

impl FromIterator<AlgebraicValue> for ProductValue {
    fn from_iter<T: IntoIterator<Item = AlgebraicValue>>(iter: T) -> Self {
        let elements = iter.into_iter().collect();
        Self { elements }
    }
}

impl IntoIterator for ProductValue {
    type Item = AlgebraicValue;
    type IntoIter = std::vec::IntoIter<AlgebraicValue>;
    fn into_iter(self) -> Self::IntoIter {
        self.elements.into_iter()
    }
}

impl<'a> IntoIterator for &'a ProductValue {
    type Item = &'a AlgebraicValue;
    type IntoIter = std::slice::Iter<'a, AlgebraicValue>;
    fn into_iter(self) -> Self::IntoIter {
        self.elements.iter()
    }
}

impl crate::Value for ProductValue {
    type Type = ProductType;
}

/// An error that occurs when a field, of a product value, is accessed that doesn't exist.
#[derive(thiserror::Error, Debug, Copy, Clone)]
#[error("Field at position {col_pos} named: {name:?} not found or has an invalid type")]
pub struct InvalidFieldError {
    /// The claimed col_pos of the field within the product value.
    pub col_pos: ColId,
    /// The name of the field, if any.
    pub name: Option<&'static str>,
}

impl ProductValue {
    /// Borrow the value at field of `self` identified by `col_pos`.
    ///
    /// The `name` is non-functional and is only used for error-messages.
    pub fn get_field(&self, col_pos: usize, name: Option<&'static str>) -> Result<&AlgebraicValue, InvalidFieldError> {
        self.elements.get(col_pos).ok_or(InvalidFieldError {
            col_pos: col_pos.into(),
            name,
        })
    }

    /// This function is used to project fields based on the provided `indexes`.
    ///
    /// It will raise an [InvalidFieldError] if any of the supplied `indexes` cannot be found.
    ///
    /// The optional parameter `name: Option<&'static str>` serves a non-functional role and is
    /// solely utilized for generating error messages.
    ///
    /// **Important:**
    ///
    /// The resulting [AlgebraicValue] will wrap into a [ProductValue] when projecting multiple
    /// fields, otherwise it will consist of a single [AlgebraicValue].
    ///
    pub fn project(&self, indexes: &[(ColId, Option<&'static str>)]) -> Result<AlgebraicValue, InvalidFieldError> {
        let fields = match indexes {
            [(index, name)] => self.get_field((*index).into(), *name)?.clone(),
            indexes => {
                let fields: Result<Vec<_>, _> = indexes
                    .iter()
                    .map(|(index, name)| self.get_field((*index).into(), *name).cloned())
                    .collect();
                AlgebraicValue::Product(ProductValue::new(&fields?))
            }
        };

        Ok(fields)
    }

    /// This utility function is designed to project fields based on the supplied `indexes`.
    ///
    /// **Important:**
    ///
    /// The resulting [AlgebraicValue] will wrap into a [ProductValue] when projecting multiple
    /// fields, otherwise it will consist of a single [AlgebraicValue].
    ///
    /// **Parameters:**
    /// - `cols`: A [ColList] containing the indexes of fields to be projected.
    ///
    pub fn project_not_empty(&self, cols: &ColList) -> Result<AlgebraicValue, InvalidFieldError> {
        let proj_len = cols.len();
        if proj_len == 1 {
            self.get_field(cols.head().idx(), None).cloned()
        } else {
            let mut fields = Vec::with_capacity(proj_len as usize);
            for col in cols.iter() {
                fields.push(self.get_field(col.idx(), None)?.clone());
            }
            Ok(AlgebraicValue::product(fields))
        }
    }

    /// Extracts the `value` at field of `self` identified by `index`
    /// and then runs it through the function `f` which possibly returns a `T` derived from `value`.
    pub fn extract_field<'a, T>(
        &'a self,
        col_pos: usize,
        name: Option<&'static str>,
        f: impl 'a + Fn(&'a AlgebraicValue) -> Option<T>,
    ) -> Result<T, InvalidFieldError> {
        f(self.get_field(col_pos, name)?).ok_or(InvalidFieldError {
            col_pos: col_pos.into(),
            name,
        })
    }

    /// Interprets the value at field of `self` identified by `index` as a `bool`.
    pub fn field_as_bool(&self, index: usize, named: Option<&'static str>) -> Result<bool, InvalidFieldError> {
        self.extract_field(index, named, |f| f.as_bool().copied())
    }

    /// Interprets the value at field of `self` identified by `index` as a `u8`.
    pub fn field_as_u8(&self, index: usize, named: Option<&'static str>) -> Result<u8, InvalidFieldError> {
        self.extract_field(index, named, |f| f.as_u8().copied())
    }

    /// Interprets the value at field of `self` identified by `index` as a `u32`.
    pub fn field_as_u32(&self, index: usize, named: Option<&'static str>) -> Result<u32, InvalidFieldError> {
        self.extract_field(index, named, |f| f.as_u32().copied())
    }

    /// Interprets the value at field of `self` identified by `index` as a `u64`.
    pub fn field_as_u64(&self, index: usize, named: Option<&'static str>) -> Result<u64, InvalidFieldError> {
        self.extract_field(index, named, |f| f.as_u64().copied())
    }

    /// Interprets the value at field of `self` identified by `index` as a `i64`.
    pub fn field_as_i64(&self, index: usize, named: Option<&'static str>) -> Result<i64, InvalidFieldError> {
        self.extract_field(index, named, |f| f.as_i64().copied())
    }

    /// Interprets the value at field of `self` identified by `index` as a `i128`.
    pub fn field_as_i128(&self, index: usize, named: Option<&'static str>) -> Result<i128, InvalidFieldError> {
        self.extract_field(index, named, |f| f.as_i128().copied())
    }

    /// Interprets the value at field of `self` identified by `index` as a `u128`.
    pub fn field_as_u128(&self, index: usize, named: Option<&'static str>) -> Result<u128, InvalidFieldError> {
        self.extract_field(index, named, |f| f.as_u128().copied())
    }

    /// Interprets the value at field of `self` identified by `index` as a string slice.
    pub fn field_as_str(&self, index: usize, named: Option<&'static str>) -> Result<&str, InvalidFieldError> {
        self.extract_field(index, named, |f| f.as_string()).map(|x| &**x)
    }

    /// Interprets the value at field of `self` identified by `index` as a byte slice.
    pub fn field_as_bytes(&self, index: usize, named: Option<&'static str>) -> Result<&[u8], InvalidFieldError> {
        self.extract_field(index, named, |f| f.as_bytes())
    }

    /// Interprets the value at field of `self` identified by `index` as an `ArrayValue`.
    pub fn field_as_array(&self, index: usize, named: Option<&'static str>) -> Result<&ArrayValue, InvalidFieldError> {
        self.extract_field(index, named, |f| f.as_array())
    }

    /// Interprets the value at field of `self` identified by `index` as a `SumValue`.
    pub fn field_as_sum(&self, index: usize, named: Option<&'static str>) -> Result<SumValue, InvalidFieldError> {
        self.extract_field(index, named, |f| f.as_sum().cloned())
    }
}

impl<'a> ValueWithType<'a, ProductValue> {
    pub fn elements(&self) -> impl ExactSizeIterator<Item = ValueWithType<'a, AlgebraicValue>> {
        self.ty_s().with_values(self.value())
    }
}