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