Skip to main content

icydb_core/db/response/
mod.rs

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