Skip to main content

icydb_core/db/
response.rs

1use crate::{
2    error::{ErrorClass, ErrorOrigin, InternalError},
3    prelude::*,
4    view::View,
5};
6use thiserror::Error as ThisError;
7
8///
9/// Row
10///
11
12pub type Row<E> = (Key, E);
13
14///
15/// ResponseError
16/// Errors related to interpreting a materialized response.
17///
18
19#[derive(Debug, ThisError)]
20pub enum ResponseError {
21    #[error("expected exactly one row, found 0 (entity {entity})")]
22    NotFound { entity: &'static str },
23
24    #[error("expected exactly one row, found {count} (entity {entity})")]
25    NotUnique { entity: &'static str, count: u64 },
26}
27
28impl ResponseError {
29    pub(crate) const fn class(&self) -> ErrorClass {
30        match self {
31            Self::NotFound { .. } => ErrorClass::NotFound,
32            Self::NotUnique { .. } => ErrorClass::Conflict,
33        }
34    }
35}
36
37impl From<ResponseError> for InternalError {
38    fn from(err: ResponseError) -> Self {
39        Self::new(err.class(), ErrorOrigin::Response, err.to_string())
40    }
41}
42
43///
44/// Response
45/// Materialized query result: ordered `(Key, Entity)` pairs.
46///
47
48#[derive(Debug)]
49pub struct Response<E: EntityKind>(pub Vec<Row<E>>);
50
51impl<E: EntityKind> Response<E> {
52    // ------------------------------------------------------------------
53    // Introspection
54    // ------------------------------------------------------------------
55
56    #[must_use]
57    pub const fn count(&self) -> u64 {
58        self.0.len() as u64
59    }
60
61    #[must_use]
62    pub const fn is_empty(&self) -> bool {
63        self.0.is_empty()
64    }
65
66    // ------------------------------------------------------------------
67    // Cardinality enforcement
68    // ------------------------------------------------------------------
69
70    pub fn require_one(&self) -> Result<(), InternalError> {
71        match self.count() {
72            1 => Ok(()),
73            0 => Err(ResponseError::NotFound { entity: E::PATH }.into()),
74            n => Err(ResponseError::NotUnique {
75                entity: E::PATH,
76                count: n,
77            }
78            .into()),
79        }
80    }
81
82    pub fn require_some(&self) -> Result<(), InternalError> {
83        if self.is_empty() {
84            Err(ResponseError::NotFound { entity: E::PATH }.into())
85        } else {
86            Ok(())
87        }
88    }
89
90    // ------------------------------------------------------------------
91    // Rows
92    // ------------------------------------------------------------------
93
94    pub fn row(self) -> Result<Row<E>, InternalError> {
95        self.require_one()?;
96        Ok(self.0.into_iter().next().unwrap())
97    }
98
99    pub fn try_row(self) -> Result<Option<Row<E>>, InternalError> {
100        match self.count() {
101            0 => Ok(None),
102            1 => Ok(Some(self.0.into_iter().next().unwrap())),
103            n => Err(ResponseError::NotUnique {
104                entity: E::PATH,
105                count: n,
106            }
107            .into()),
108        }
109    }
110
111    #[must_use]
112    pub fn rows(self) -> Vec<Row<E>> {
113        self.0
114    }
115
116    // ------------------------------------------------------------------
117    // Entities
118    // ------------------------------------------------------------------
119
120    pub fn entity(self) -> Result<E, InternalError> {
121        self.row().map(|(_, e)| e)
122    }
123
124    pub fn try_entity(self) -> Result<Option<E>, InternalError> {
125        Ok(self.try_row()?.map(|(_, e)| e))
126    }
127
128    #[must_use]
129    pub fn entities(self) -> Vec<E> {
130        self.0.into_iter().map(|(_, e)| e).collect()
131    }
132
133    // ------------------------------------------------------------------
134    // Keys (delete ergonomics)
135    // ------------------------------------------------------------------
136
137    #[must_use]
138    pub fn key(&self) -> Option<Key> {
139        self.0.first().map(|(k, _)| *k)
140    }
141
142    pub fn key_strict(self) -> Result<Key, InternalError> {
143        self.row().map(|(k, _)| k)
144    }
145
146    pub fn try_key(self) -> Result<Option<Key>, InternalError> {
147        Ok(self.try_row()?.map(|(k, _)| k))
148    }
149
150    #[must_use]
151    pub fn keys(&self) -> Vec<Key> {
152        self.0.iter().map(|(k, _)| *k).collect()
153    }
154
155    #[must_use]
156    pub fn contains_key(&self, key: &Key) -> bool {
157        self.0.iter().any(|(k, _)| k == key)
158    }
159
160    // ------------------------------------------------------------------
161    // Views (first-class, canonical)
162    // ------------------------------------------------------------------
163
164    pub fn view(&self) -> Result<View<E>, InternalError> {
165        self.require_one()?;
166        Ok(self
167            .0
168            .first()
169            .expect("require_one guarantees a row")
170            .1
171            .to_view())
172    }
173
174    pub fn view_opt(&self) -> Result<Option<View<E>>, InternalError> {
175        match self.count() {
176            0 => Ok(None),
177            1 => Ok(Some(self.0[0].1.to_view())),
178            n => Err(ResponseError::NotUnique {
179                entity: E::PATH,
180                count: n,
181            }
182            .into()),
183        }
184    }
185
186    #[must_use]
187    pub fn views(&self) -> Vec<View<E>> {
188        self.0.iter().map(|(_, e)| e.to_view()).collect()
189    }
190
191    // ------------------------------------------------------------------
192    // Explicitly non-strict access (escape hatches)
193    // ------------------------------------------------------------------
194
195    /// NOTE: Bypasses cardinality checks. Prefer strict APIs unless intentional.
196    #[must_use]
197    pub fn first(self) -> Option<Row<E>> {
198        self.0.into_iter().next()
199    }
200
201    #[must_use]
202    pub fn first_entity(self) -> Option<E> {
203        self.first().map(|(_, e)| e)
204    }
205
206    #[must_use]
207    pub fn first_pk(self) -> Option<E::PrimaryKey> {
208        self.first_entity().map(|e| e.primary_key())
209    }
210}
211
212impl<E: EntityKind> IntoIterator for Response<E> {
213    type Item = Row<E>;
214    type IntoIter = std::vec::IntoIter<Self::Item>;
215
216    fn into_iter(self) -> Self::IntoIter {
217        self.0.into_iter()
218    }
219}