icydb_core/db/response/
mod.rs

1mod ext;
2
3pub use ext::*;
4
5use crate::{
6    error::{ErrorClass, ErrorOrigin, InternalError},
7    prelude::*,
8};
9use thiserror::Error as ThisError;
10
11///
12/// Page
13///
14
15pub struct Page<T> {
16    pub items: Vec<T>,
17    pub has_more: bool,
18}
19
20impl<T> Page<T> {
21    #[must_use]
22    pub const fn is_empty(&self) -> bool {
23        self.items.is_empty()
24    }
25
26    #[must_use]
27    pub const fn len(&self) -> usize {
28        self.items.len()
29    }
30}
31
32///
33/// Row
34///
35
36pub type Row<E> = (Key, E);
37
38///
39/// ResponseError
40/// Errors related to interpreting a materialized response.
41///
42
43#[derive(Debug, ThisError)]
44pub enum ResponseError {
45    #[error("expected exactly one row, found 0 (entity {entity})")]
46    NotFound { entity: &'static str },
47
48    #[error("expected exactly one row, found {count} (entity {entity})")]
49    NotUnique { entity: &'static str, count: u32 },
50}
51
52impl ResponseError {
53    pub(crate) const fn class(&self) -> ErrorClass {
54        match self {
55            Self::NotFound { .. } => ErrorClass::NotFound,
56            Self::NotUnique { .. } => ErrorClass::Unsupported,
57        }
58    }
59}
60
61impl From<ResponseError> for InternalError {
62    fn from(err: ResponseError) -> Self {
63        Self::new(err.class(), ErrorOrigin::Response, err.to_string())
64    }
65}
66
67///
68/// Response
69/// Materialized query result: ordered `(Key, Entity)` pairs.
70///
71
72#[derive(Debug)]
73pub struct Response<E: EntityKind>(pub Vec<Row<E>>);
74
75impl<E: EntityKind> Response<E> {
76    // ======================================================================
77    // Cardinality (introspection only)
78    // ======================================================================
79
80    /// Number of rows in the response, truncated to `u32`.
81    #[must_use]
82    #[allow(clippy::cast_possible_truncation)]
83    pub const fn count(&self) -> u32 {
84        self.0.len() as u32
85    }
86
87    /// True when no rows were returned.
88    #[must_use]
89    pub const fn is_empty(&self) -> bool {
90        self.0.is_empty()
91    }
92
93    // ======================================================================
94    // Cardinality guards (non-consuming)
95    // ======================================================================
96
97    /// Require exactly one row.
98    pub fn require_one(&self) -> Result<(), InternalError> {
99        match self.count() {
100            1 => Ok(()),
101            0 => Err(ResponseError::NotFound { entity: E::PATH }.into()),
102            n => Err(ResponseError::NotUnique {
103                entity: E::PATH,
104                count: n,
105            }
106            .into()),
107        }
108    }
109
110    /// Require at least one row.
111    pub fn require_some(&self) -> Result<(), InternalError> {
112        match self.count() {
113            0 => Err(ResponseError::NotFound { entity: E::PATH }.into()),
114            _ => Ok(()),
115        }
116    }
117
118    /// Require exactly `expected` rows.
119    pub fn require_len(&self, expected: u32) -> Result<(), InternalError> {
120        let actual = self.count();
121        if actual == expected {
122            Ok(())
123        } else if actual == 0 {
124            Err(ResponseError::NotFound { entity: E::PATH }.into())
125        } else {
126            Err(ResponseError::NotUnique {
127                entity: E::PATH,
128                count: actual,
129            }
130            .into())
131        }
132    }
133
134    // ======================================================================
135    // Row extractors (consume self)
136    // ======================================================================
137
138    /// Require exactly one row and return it.
139    pub fn one(self) -> Result<Row<E>, InternalError> {
140        self.require_one()?;
141        Ok(self.0.into_iter().next().unwrap())
142    }
143
144    /// Require at most one row and return it.
145    #[allow(clippy::cast_possible_truncation)]
146    pub fn one_opt(self) -> Result<Option<Row<E>>, InternalError> {
147        match self.0.len() {
148            0 => Ok(None),
149            1 => Ok(Some(self.0.into_iter().next().unwrap())),
150            n => Err(ResponseError::NotUnique {
151                entity: E::PATH,
152                count: n as u32,
153            }
154            .into()),
155        }
156    }
157
158    /// Convert the response into a page of entities with a `has_more` indicator.
159    ///
160    /// This consumes at most `limit + 1` rows to determine whether more results
161    /// exist. Ordering is preserved.
162    ///
163    /// NOTE:
164    /// - `has_more` only indicates the existence of additional rows
165    /// - Page boundaries are not stable unless the underlying query ordering is stable
166    #[must_use]
167    pub fn into_page(self, limit: usize) -> Page<E> {
168        let mut iter = self.0.into_iter();
169
170        let mut items = Vec::with_capacity(limit);
171        for _ in 0..limit {
172            if let Some((_, entity)) = iter.next() {
173                items.push(entity);
174            } else {
175                return Page {
176                    items,
177                    has_more: false,
178                };
179            }
180        }
181
182        Page {
183            items,
184            has_more: iter.next().is_some(),
185        }
186    }
187
188    // ======================================================================
189    // Key extractors
190    // ======================================================================
191
192    /// First key in the response, if present.
193    #[must_use]
194    pub fn key(&self) -> Option<Key> {
195        self.0.first().map(|(k, _)| *k)
196    }
197
198    /// Collect all keys in order.
199    #[must_use]
200    pub fn keys(&self) -> Vec<Key> {
201        self.0.iter().map(|(k, _)| *k).collect()
202    }
203
204    /// Require exactly one row and return its key.
205    pub fn one_key(self) -> Result<Key, InternalError> {
206        self.one().map(|(k, _)| k)
207    }
208
209    /// Require at most one row and return its key.
210    pub fn one_opt_key(self) -> Result<Option<Key>, InternalError> {
211        Ok(self.one_opt()?.map(|(k, _)| k))
212    }
213
214    #[must_use]
215    pub fn contains_key(&self, key: &Key) -> bool {
216        self.0.iter().any(|(k, _)| k == key)
217    }
218
219    // ======================================================================
220    // Entity extractors
221    // ======================================================================
222
223    /// Consume the response and return the first entity, if any.
224    #[must_use]
225    pub fn entity(self) -> Option<E> {
226        self.0.into_iter().next().map(|(_, e)| e)
227    }
228
229    /// Consume the response and collect all entities.
230    #[must_use]
231    pub fn entities(self) -> Vec<E> {
232        self.0.into_iter().map(|(_, e)| e).collect()
233    }
234
235    /// Require exactly one entity.
236    pub fn one_entity(self) -> Result<E, InternalError> {
237        self.one().map(|(_, e)| e)
238    }
239
240    /// Require at most one entity.
241    pub fn one_opt_entity(self) -> Result<Option<E>, InternalError> {
242        Ok(self.one_opt()?.map(|(_, e)| e))
243    }
244
245    // ======================================================================
246    // Primary key extractors
247    // ======================================================================
248
249    /// First primary key in the response, if present.
250    #[must_use]
251    pub fn pk(&self) -> Option<E::PrimaryKey> {
252        self.0.first().map(|(_, e)| e.primary_key())
253    }
254
255    /// Collect all primary keys in order.
256    #[must_use]
257    pub fn pks(&self) -> Vec<E::PrimaryKey> {
258        self.0.iter().map(|(_, e)| e.primary_key()).collect()
259    }
260
261    /// Require exactly one primary key.
262    pub fn one_pk(self) -> Result<E::PrimaryKey, InternalError> {
263        self.one_entity().map(|e| e.primary_key())
264    }
265
266    /// Require at most one primary key.
267    pub fn one_opt_pk(self) -> Result<Option<E::PrimaryKey>, InternalError> {
268        Ok(self.one_opt_entity()?.map(|e| e.primary_key()))
269    }
270
271    // ======================================================================
272    // View extractors
273    // ======================================================================
274
275    /// Convert the first entity to its view type, if present.
276    #[must_use]
277    pub fn view(self) -> Option<E::ViewType> {
278        self.entity().map(|e| e.to_view())
279    }
280
281    /// Require exactly one view.
282    pub fn one_view(self) -> Result<E::ViewType, InternalError> {
283        self.one_entity().map(|e| e.to_view())
284    }
285
286    /// Require at most one view.
287    pub fn one_opt_view(self) -> Result<Option<E::ViewType>, InternalError> {
288        Ok(self.one_opt_entity()?.map(|e| e.to_view()))
289    }
290
291    /// Convert all entities to their view types.
292    #[must_use]
293    pub fn views(self) -> Vec<E::ViewType> {
294        self.entities().into_iter().map(|e| e.to_view()).collect()
295    }
296
297    // ======================================================================
298    // Arbitrary row access (no cardinality guarantees)
299    // ======================================================================
300
301    /// Return the first row in the response, if any.
302    ///
303    /// This does NOT enforce cardinality. Use only when row order is
304    /// well-defined and uniqueness is irrelevant.
305    #[must_use]
306    pub fn first(self) -> Option<Row<E>> {
307        self.0.into_iter().next()
308    }
309
310    /// Return the first entity in the response, if any.
311    ///
312    /// This does NOT enforce cardinality. Use only when row order is
313    /// well-defined and uniqueness is irrelevant.
314    #[must_use]
315    pub fn first_entity(self) -> Option<E> {
316        self.first().map(|(_, e)| e)
317    }
318
319    /// Return the first primary key in the response, if any.
320    ///
321    /// This does NOT enforce cardinality.
322    #[must_use]
323    pub fn first_pk(self) -> Option<E::PrimaryKey> {
324        self.first_entity().map(|e| e.primary_key())
325    }
326}
327
328impl<E: EntityKind> IntoIterator for Response<E> {
329    type Item = Row<E>;
330    type IntoIter = std::vec::IntoIter<Self::Item>;
331
332    fn into_iter(self) -> Self::IntoIter {
333        self.0.into_iter()
334    }
335}