icydb_core/db/response/
mod.rs

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