icydb_core/db/response/
mod.rs

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