Skip to main content

icydb_core/db/
response.rs

1use crate::{prelude::*, view::View};
2use thiserror::Error as ThisError;
3
4///
5/// Row
6///
7
8pub type Row<E> = (Key, E);
9
10///
11/// ResponseError
12/// Errors related to interpreting a materialized response.
13///
14
15#[derive(Debug, ThisError)]
16pub enum ResponseError {
17    #[error("expected exactly one row, found 0 (entity {entity})")]
18    NotFound { entity: &'static str },
19
20    #[error("expected exactly one row, found {count} (entity {entity})")]
21    NotUnique { entity: &'static str, count: u32 },
22}
23
24///
25/// Response
26/// Materialized query result: ordered `(Key, Entity)` pairs.
27///
28
29#[derive(Debug)]
30pub struct Response<E: EntityKind>(pub Vec<Row<E>>);
31
32impl<E: EntityKind> Response<E> {
33    // ------------------------------------------------------------------
34    // Introspection
35    // ------------------------------------------------------------------
36
37    #[must_use]
38    #[allow(clippy::cast_possible_truncation)]
39    pub const fn count(&self) -> u32 {
40        self.0.len() as u32
41    }
42
43    #[must_use]
44    pub const fn is_empty(&self) -> bool {
45        self.0.is_empty()
46    }
47
48    // ------------------------------------------------------------------
49    // Cardinality enforcement (domain-level)
50    // ------------------------------------------------------------------
51
52    pub const fn require_one(&self) -> Result<(), ResponseError> {
53        match self.count() {
54            1 => Ok(()),
55            0 => Err(ResponseError::NotFound { entity: E::PATH }),
56            n => Err(ResponseError::NotUnique {
57                entity: E::PATH,
58                count: n,
59            }),
60        }
61    }
62
63    pub const fn require_some(&self) -> Result<(), ResponseError> {
64        if self.is_empty() {
65            Err(ResponseError::NotFound { entity: E::PATH })
66        } else {
67            Ok(())
68        }
69    }
70
71    // ------------------------------------------------------------------
72    // Rows
73    // ------------------------------------------------------------------
74
75    pub fn row(self) -> Result<Row<E>, ResponseError> {
76        self.require_one()?;
77        Ok(self.0.into_iter().next().unwrap())
78    }
79
80    pub fn try_row(self) -> Result<Option<Row<E>>, ResponseError> {
81        match self.count() {
82            0 => Ok(None),
83            1 => Ok(Some(self.0.into_iter().next().unwrap())),
84            n => Err(ResponseError::NotUnique {
85                entity: E::PATH,
86                count: n,
87            }),
88        }
89    }
90
91    #[must_use]
92    pub fn rows(self) -> Vec<Row<E>> {
93        self.0
94    }
95
96    // ------------------------------------------------------------------
97    // Entities
98    // ------------------------------------------------------------------
99
100    pub fn entity(self) -> Result<E, ResponseError> {
101        self.row().map(|(_, e)| e)
102    }
103
104    pub fn try_entity(self) -> Result<Option<E>, ResponseError> {
105        Ok(self.try_row()?.map(|(_, e)| e))
106    }
107
108    #[must_use]
109    pub fn entities(self) -> Vec<E> {
110        self.0.into_iter().map(|(_, e)| e).collect()
111    }
112
113    // ------------------------------------------------------------------
114    // Store keys (delete ergonomics)
115    // ------------------------------------------------------------------
116
117    #[must_use]
118    pub fn key(&self) -> Option<Key> {
119        self.0.first().map(|(k, _)| *k)
120    }
121
122    pub fn key_strict(self) -> Result<Key, ResponseError> {
123        self.row().map(|(k, _)| k)
124    }
125
126    pub fn try_key(self) -> Result<Option<Key>, ResponseError> {
127        Ok(self.try_row()?.map(|(k, _)| k))
128    }
129
130    #[must_use]
131    pub fn keys(&self) -> Vec<Key> {
132        self.0.iter().map(|(k, _)| *k).collect()
133    }
134
135    #[must_use]
136    pub fn contains_key(&self, key: &Key) -> bool {
137        self.0.iter().any(|(k, _)| k == key)
138    }
139
140    // ------------------------------------------------------------------
141    // Primary keys (domain-level, strict)
142    // ------------------------------------------------------------------
143
144    pub fn primary_key(self) -> Result<E::PrimaryKey, ResponseError> {
145        Ok(self.entity()?.primary_key())
146    }
147
148    pub fn try_primary_key(self) -> Result<Option<E::PrimaryKey>, ResponseError> {
149        Ok(self.try_entity()?.map(|e| e.primary_key()))
150    }
151
152    #[must_use]
153    pub fn primary_keys(self) -> Vec<E::PrimaryKey> {
154        self.entities()
155            .into_iter()
156            .map(|e| e.primary_key())
157            .collect()
158    }
159
160    // ------------------------------------------------------------------
161    // Views (first-class, canonical)
162    // ------------------------------------------------------------------
163
164    pub fn view(&self) -> Result<View<E>, ResponseError> {
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>>, ResponseError> {
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        }
183    }
184
185    #[must_use]
186    pub fn views(&self) -> Vec<View<E>> {
187        self.0.iter().map(|(_, e)| e.to_view()).collect()
188    }
189}
190
191impl<E: EntityKind> IntoIterator for Response<E> {
192    type Item = Row<E>;
193    type IntoIter = std::vec::IntoIter<Self::Item>;
194
195    fn into_iter(self) -> Self::IntoIter {
196        self.0.into_iter()
197    }
198}