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 write-specific helpers live in `write`.
8mod write;
9
10use crate::{prelude::*, traits::AsView, types::Id};
11use thiserror::Error as ThisError;
12
13// re-exports
14pub use write::{WriteBatchResponse, WriteResponse};
15
16///
17/// Row
18///
19
20pub type Row<E> = (Id<E>, E);
21
22///
23/// ResponseError
24///
25
26#[derive(Debug, ThisError)]
27pub enum ResponseError {
28    #[error("expected exactly one row, found 0 (entity {entity})")]
29    NotFound { entity: &'static str },
30
31    #[error("expected exactly one row, found {count} (entity {entity})")]
32    NotUnique { entity: &'static str, count: u32 },
33}
34
35impl ResponseError {
36    const fn not_found<E: EntityKind>() -> Self {
37        Self::NotFound { entity: E::PATH }
38    }
39
40    const fn not_unique<E: EntityKind>(count: u32) -> Self {
41        Self::NotUnique {
42            entity: E::PATH,
43            count,
44        }
45    }
46}
47
48///
49/// Response
50///
51/// Materialized query result: ordered `(Id, Entity)` pairs.
52/// IDs are returned for correlation, reporting, and lookup; they are public values and do not imply
53/// authorization, ownership, or row existence outside this response payload.
54///
55
56#[derive(Debug)]
57pub struct Response<E: EntityKind>(pub Vec<Row<E>>);
58
59impl<E: EntityKind> Response<E> {
60    // ------------------------------------------------------------------
61    // Introspection
62    // ------------------------------------------------------------------
63
64    #[must_use]
65    #[expect(clippy::cast_possible_truncation)]
66    pub const fn count(&self) -> u32 {
67        self.0.len() as u32
68    }
69
70    #[must_use]
71    pub const fn is_empty(&self) -> bool {
72        self.0.is_empty()
73    }
74
75    // ------------------------------------------------------------------
76    // Cardinality enforcement
77    // ------------------------------------------------------------------
78
79    pub const fn require_one(&self) -> Result<(), ResponseError> {
80        match self.count() {
81            1 => Ok(()),
82            0 => Err(ResponseError::not_found::<E>()),
83            n => Err(ResponseError::not_unique::<E>(n)),
84        }
85    }
86
87    pub const fn require_some(&self) -> Result<(), ResponseError> {
88        if self.is_empty() {
89            Err(ResponseError::not_found::<E>())
90        } else {
91            Ok(())
92        }
93    }
94
95    // ------------------------------------------------------------------
96    // Rows
97    // ------------------------------------------------------------------
98
99    #[expect(clippy::cast_possible_truncation)]
100    pub fn try_row(self) -> Result<Option<Row<E>>, ResponseError> {
101        match self.0.len() {
102            0 => Ok(None),
103            1 => Ok(Some(self.0.into_iter().next().unwrap())),
104            n => Err(ResponseError::not_unique::<E>(n as u32)),
105        }
106    }
107
108    pub fn row(self) -> Result<Row<E>, ResponseError> {
109        self.try_row()?.ok_or_else(ResponseError::not_found::<E>)
110    }
111
112    #[must_use]
113    pub fn rows(self) -> Vec<Row<E>> {
114        self.0
115    }
116
117    // ------------------------------------------------------------------
118    // Entities
119    // ------------------------------------------------------------------
120
121    pub fn try_entity(self) -> Result<Option<E>, ResponseError> {
122        Ok(self.try_row()?.map(|(_, e)| e))
123    }
124
125    pub fn entity(self) -> Result<E, ResponseError> {
126        self.row().map(|(_, e)| e)
127    }
128
129    #[must_use]
130    pub fn entities(self) -> Vec<E> {
131        self.0.into_iter().map(|(_, e)| e).collect()
132    }
133
134    // ------------------------------------------------------------------
135    // Ids (identity-level)
136    // ------------------------------------------------------------------
137
138    /// Return the first row identifier, if present.
139    ///
140    /// This identifier is a public value for correlation, reporting, and lookup only.
141    #[must_use]
142    pub fn id(&self) -> Option<Id<E>> {
143        self.0.first().map(|(id, _)| *id)
144    }
145
146    /// Require exactly one row and return its identifier.
147    pub fn require_id(self) -> Result<Id<E>, ResponseError> {
148        self.row().map(|(id, _)| id)
149    }
150
151    /// Return all row identifiers in response order for correlation/reporting/lookup.
152    #[must_use]
153    pub fn ids(&self) -> Vec<Id<E>> {
154        self.0.iter().map(|(id, _)| *id).collect()
155    }
156
157    /// Check whether the response contains the provided identifier.
158    pub fn contains_id(&self, id: &Id<E>) -> bool {
159        self.0.iter().any(|(k, _)| k == id)
160    }
161
162    // ------------------------------------------------------------------
163    // Views
164    // ------------------------------------------------------------------
165
166    pub fn view(&self) -> Result<<E as AsView>::ViewType, ResponseError> {
167        self.require_one()?;
168        Ok(self.0[0].1.as_view())
169    }
170
171    pub fn view_opt(&self) -> Result<Option<<E as AsView>::ViewType>, ResponseError> {
172        match self.count() {
173            0 => Ok(None),
174            1 => Ok(Some(self.0[0].1.as_view())),
175            n => Err(ResponseError::not_unique::<E>(n)),
176        }
177    }
178
179    #[must_use]
180    pub fn views(&self) -> Vec<<E as AsView>::ViewType> {
181        self.0.iter().map(|(_, e)| e.as_view()).collect()
182    }
183}
184
185impl<E: EntityKind> IntoIterator for Response<E> {
186    type Item = Row<E>;
187    type IntoIter = std::vec::IntoIter<Self::Item>;
188
189    fn into_iter(self) -> Self::IntoIter {
190        self.0.into_iter()
191    }
192}