Skip to main content

icydb_core/db/response/
mod.rs

1//! Module: response
2//! Responsibility: materialized query/write response payload contracts.
3//! Does not own: execution routing, planning policy, or cursor token protocol.
4//! Boundary: Tier-2 db API DTO surface returned by session execution.
5mod paged;
6
7use crate::{
8    prelude::*,
9    traits::{AsView, EntityValue},
10    types::Id,
11};
12use thiserror::Error as ThisError;
13
14// re-exports
15pub use paged::{PagedLoadExecution, PagedLoadExecutionWithTrace};
16
17///
18/// Row
19///
20
21pub type Row<E> = (Id<E>, E);
22
23///
24/// ResponseError
25///
26
27#[derive(Debug, ThisError)]
28pub enum ResponseError {
29    #[error("expected exactly one row, found 0 (entity {entity})")]
30    NotFound { entity: &'static str },
31
32    #[error("expected exactly one row, found {count} (entity {entity})")]
33    NotUnique { entity: &'static str, count: u32 },
34}
35
36impl ResponseError {
37    const fn not_found<E: EntityKind>() -> Self {
38        Self::NotFound { entity: E::PATH }
39    }
40
41    const fn not_unique<E: EntityKind>(count: u32) -> Self {
42        Self::NotUnique {
43            entity: E::PATH,
44            count,
45        }
46    }
47}
48
49///
50/// Response
51///
52/// Materialized query result: ordered `(Id, Entity)` pairs.
53/// IDs are returned for correlation, reporting, and lookup; they are public values and do not imply
54/// authorization, ownership, or row existence outside this response payload.
55///
56
57#[derive(Debug)]
58pub struct Response<E: EntityKind>(pub Vec<Row<E>>);
59
60impl<E: EntityKind> Response<E> {
61    // ------------------------------------------------------------------
62    // Introspection
63    // ------------------------------------------------------------------
64
65    /// Return the number of rows as a u32 API contract count.
66    #[must_use]
67    #[expect(clippy::cast_possible_truncation)]
68    pub const fn count(&self) -> u32 {
69        self.0.len() as u32
70    }
71
72    /// Return whether this response has no rows.
73    #[must_use]
74    pub const fn is_empty(&self) -> bool {
75        self.0.is_empty()
76    }
77
78    // ------------------------------------------------------------------
79    // Cardinality enforcement
80    // ------------------------------------------------------------------
81
82    /// Require exactly one row in this response.
83    pub const fn require_one(&self) -> Result<(), ResponseError> {
84        match self.count() {
85            1 => Ok(()),
86            0 => Err(ResponseError::not_found::<E>()),
87            n => Err(ResponseError::not_unique::<E>(n)),
88        }
89    }
90
91    /// Require at least one row in this response.
92    pub const fn require_some(&self) -> Result<(), ResponseError> {
93        if self.is_empty() {
94            Err(ResponseError::not_found::<E>())
95        } else {
96            Ok(())
97        }
98    }
99
100    // ------------------------------------------------------------------
101    // Rows
102    // ------------------------------------------------------------------
103
104    /// Consume and return `None` for empty, `Some(row)` for one row, or error for many rows.
105    #[expect(clippy::cast_possible_truncation)]
106    pub fn try_row(self) -> Result<Option<Row<E>>, ResponseError> {
107        match self.0.len() {
108            0 => Ok(None),
109            1 => Ok(Some(self.0.into_iter().next().unwrap())),
110            n => Err(ResponseError::not_unique::<E>(n as u32)),
111        }
112    }
113
114    /// Consume and return the single row, or fail on zero/many rows.
115    pub fn row(self) -> Result<Row<E>, ResponseError> {
116        self.try_row()?.ok_or_else(ResponseError::not_found::<E>)
117    }
118
119    /// Consume and return all rows in response order.
120    #[must_use]
121    pub fn rows(self) -> Vec<Row<E>> {
122        self.0
123    }
124
125    // ------------------------------------------------------------------
126    // Entities
127    // ------------------------------------------------------------------
128
129    /// Consume and return the single entity or `None`, failing on many rows.
130    pub fn try_entity(self) -> Result<Option<E>, ResponseError> {
131        Ok(self.try_row()?.map(|(_, e)| e))
132    }
133
134    /// Consume and return the single entity, failing on zero/many rows.
135    pub fn entity(self) -> Result<E, ResponseError> {
136        self.row().map(|(_, e)| e)
137    }
138
139    /// Consume and return all entities in response order.
140    #[must_use]
141    pub fn entities(self) -> Vec<E> {
142        self.0.into_iter().map(|(_, e)| e).collect()
143    }
144
145    // ------------------------------------------------------------------
146    // Ids (identity-level)
147    // ------------------------------------------------------------------
148
149    /// Return the first row identifier, if present.
150    ///
151    /// This identifier is a public value for correlation, reporting, and lookup only.
152    #[must_use]
153    pub fn id(&self) -> Option<Id<E>> {
154        self.0.first().map(|(id, _)| *id)
155    }
156
157    /// Require exactly one row and return its identifier.
158    pub fn require_id(self) -> Result<Id<E>, ResponseError> {
159        self.row().map(|(id, _)| id)
160    }
161
162    /// Return all row identifiers in response order for correlation/reporting/lookup.
163    #[must_use]
164    pub fn ids(&self) -> Vec<Id<E>> {
165        self.0.iter().map(|(id, _)| *id).collect()
166    }
167
168    /// Check whether the response contains the provided identifier.
169    pub fn contains_id(&self, id: &Id<E>) -> bool {
170        self.0.iter().any(|(k, _)| k == id)
171    }
172
173    // ------------------------------------------------------------------
174    // Views
175    // ------------------------------------------------------------------
176
177    /// Return the single-row view, failing on zero/many rows.
178    pub fn view(&self) -> Result<<E as AsView>::ViewType, ResponseError> {
179        self.require_one()?;
180        Ok(self.0[0].1.as_view())
181    }
182
183    /// Return an optional single-row view, failing on many rows.
184    pub fn view_opt(&self) -> Result<Option<<E as AsView>::ViewType>, ResponseError> {
185        match self.count() {
186            0 => Ok(None),
187            1 => Ok(Some(self.0[0].1.as_view())),
188            n => Err(ResponseError::not_unique::<E>(n)),
189        }
190    }
191
192    /// Return all row views in response order.
193    #[must_use]
194    pub fn views(&self) -> Vec<<E as AsView>::ViewType> {
195        self.0.iter().map(|(_, e)| e.as_view()).collect()
196    }
197}
198
199impl<E: EntityKind> IntoIterator for Response<E> {
200    type Item = Row<E>;
201    type IntoIter = std::vec::IntoIter<Self::Item>;
202
203    fn into_iter(self) -> Self::IntoIter {
204        self.0.into_iter()
205    }
206}
207
208///
209/// WriteResponse
210///
211/// Result of a single write operation.
212/// Provides explicit access to the stored entity and its identifier.
213///
214
215#[derive(Debug)]
216pub struct WriteResponse<E> {
217    entity: E,
218}
219
220impl<E> WriteResponse<E> {
221    /// Construct a write response from the stored entity.
222    #[must_use]
223    pub const fn new(entity: E) -> Self {
224        Self { entity }
225    }
226
227    /// Return the stored entity.
228    #[must_use]
229    pub fn entity(self) -> E {
230        self.entity
231    }
232
233    /// Return the stored entity's identity
234    #[must_use]
235    pub fn id(&self) -> Id<E>
236    where
237        E: EntityValue,
238    {
239        self.entity.id()
240    }
241
242    /// Return the stored entity as its view type.
243    #[must_use]
244    pub fn view(&self) -> <E as AsView>::ViewType
245    where
246        E: AsView,
247    {
248        self.entity.as_view()
249    }
250}
251
252///
253/// WriteBatchResponse
254///
255/// Result of a batch write operation.
256/// Provides explicit access to stored entities and their identifiers.
257///
258
259#[derive(Debug)]
260pub struct WriteBatchResponse<E> {
261    entries: Vec<WriteResponse<E>>,
262}
263
264impl<E> WriteBatchResponse<E> {
265    /// Construct a batch response from stored entities.
266    #[must_use]
267    pub fn new(entities: Vec<E>) -> Self {
268        Self {
269            entries: entities.into_iter().map(WriteResponse::new).collect(),
270        }
271    }
272
273    /// Return all write responses.
274    #[must_use]
275    pub fn entries(&self) -> &[WriteResponse<E>] {
276        &self.entries
277    }
278
279    /// Return the number of entries.
280    #[must_use]
281    pub const fn len(&self) -> usize {
282        self.entries.len()
283    }
284
285    /// Returns `true` if the batch is empty.
286    #[must_use]
287    pub const fn is_empty(&self) -> bool {
288        self.entries.is_empty()
289    }
290
291    /// Return all stored entities.
292    #[must_use]
293    pub fn entities(self) -> Vec<E> {
294        self.entries
295            .into_iter()
296            .map(WriteResponse::entity)
297            .collect()
298    }
299
300    /// Return all primary keys for correlation, reporting, and lookup.
301    #[must_use]
302    pub fn ids(&self) -> Vec<Id<E>>
303    where
304        E: EntityValue,
305    {
306        self.entries.iter().map(WriteResponse::id).collect()
307    }
308
309    /// Return all views.
310    #[must_use]
311    pub fn views(&self) -> Vec<<E as AsView>::ViewType>
312    where
313        E: AsView,
314    {
315        self.entries.iter().map(WriteResponse::view).collect()
316    }
317}
318
319impl<E> IntoIterator for WriteBatchResponse<E> {
320    type Item = WriteResponse<E>;
321    type IntoIter = std::vec::IntoIter<WriteResponse<E>>;
322
323    fn into_iter(self) -> Self::IntoIter {
324        self.entries.into_iter()
325    }
326}
327
328impl<E> WriteBatchResponse<E> {
329    /// Borrow an iterator over write entries in stable batch order.
330    pub fn iter(&self) -> std::slice::Iter<'_, WriteResponse<E>> {
331        self.entries.iter()
332    }
333}
334
335impl<'a, E> IntoIterator for &'a WriteBatchResponse<E> {
336    type Item = &'a WriteResponse<E>;
337    type IntoIter = std::slice::Iter<'a, WriteResponse<E>>;
338
339    fn into_iter(self) -> Self::IntoIter {
340        self.iter()
341    }
342}