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> = (<E as EntityIdentity>::Id, E);
9
10///
11/// ResponseError
12///
13
14#[derive(Debug, ThisError)]
15pub enum ResponseError {
16    #[error("expected exactly one row, found 0 (entity {entity})")]
17    NotFound { entity: &'static str },
18
19    #[error("expected exactly one row, found {count} (entity {entity})")]
20    NotUnique { entity: &'static str, count: u32 },
21}
22
23impl ResponseError {
24    const fn not_found<E: EntityKind>() -> Self {
25        Self::NotFound { entity: E::PATH }
26    }
27
28    const fn not_unique<E: EntityKind>(count: u32) -> Self {
29        Self::NotUnique {
30            entity: E::PATH,
31            count,
32        }
33    }
34}
35
36///
37/// Response
38///
39/// Materialized query result: ordered `(Id, Entity)` pairs.
40///
41
42#[derive(Debug)]
43pub struct Response<E: EntityKind>(pub Vec<Row<E>>);
44
45impl<E: EntityKind> Response<E> {
46    // ------------------------------------------------------------------
47    // Introspection
48    // ------------------------------------------------------------------
49
50    #[must_use]
51    #[expect(clippy::cast_possible_truncation)]
52    pub const fn count(&self) -> u32 {
53        self.0.len() as u32
54    }
55
56    #[must_use]
57    pub const fn is_empty(&self) -> bool {
58        self.0.is_empty()
59    }
60
61    // ------------------------------------------------------------------
62    // Cardinality enforcement
63    // ------------------------------------------------------------------
64
65    pub const fn require_one(&self) -> Result<(), ResponseError> {
66        match self.count() {
67            1 => Ok(()),
68            0 => Err(ResponseError::not_found::<E>()),
69            n => Err(ResponseError::not_unique::<E>(n)),
70        }
71    }
72
73    pub const fn require_some(&self) -> Result<(), ResponseError> {
74        if self.is_empty() {
75            Err(ResponseError::not_found::<E>())
76        } else {
77            Ok(())
78        }
79    }
80
81    // ------------------------------------------------------------------
82    // Rows
83    // ------------------------------------------------------------------
84
85    #[expect(clippy::cast_possible_truncation)]
86    pub fn try_row(self) -> Result<Option<Row<E>>, ResponseError> {
87        match self.0.len() {
88            0 => Ok(None),
89            1 => Ok(Some(self.0.into_iter().next().unwrap())),
90            n => Err(ResponseError::not_unique::<E>(n as u32)),
91        }
92    }
93
94    pub fn row(self) -> Result<Row<E>, ResponseError> {
95        self.try_row()?.ok_or_else(ResponseError::not_found::<E>)
96    }
97
98    #[must_use]
99    pub fn rows(self) -> Vec<Row<E>> {
100        self.0
101    }
102
103    // ------------------------------------------------------------------
104    // Entities
105    // ------------------------------------------------------------------
106
107    pub fn try_entity(self) -> Result<Option<E>, ResponseError> {
108        Ok(self.try_row()?.map(|(_, e)| e))
109    }
110
111    pub fn entity(self) -> Result<E, ResponseError> {
112        self.row().map(|(_, e)| e)
113    }
114
115    #[must_use]
116    pub fn entities(self) -> Vec<E> {
117        self.0.into_iter().map(|(_, e)| e).collect()
118    }
119
120    // ------------------------------------------------------------------
121    // Ids (identity-level)
122    // ------------------------------------------------------------------
123
124    #[must_use]
125    pub fn id(&self) -> Option<E::Id> {
126        self.0.first().map(|(id, _)| *id)
127    }
128
129    pub fn id_strict(self) -> Result<E::Id, ResponseError> {
130        self.row().map(|(id, _)| id)
131    }
132
133    #[must_use]
134    pub fn ids(&self) -> Vec<E::Id> {
135        self.0.iter().map(|(id, _)| *id).collect()
136    }
137
138    pub fn contains_id(&self, id: &E::Id) -> bool {
139        self.0.iter().any(|(k, _)| k == id)
140    }
141
142    // ------------------------------------------------------------------
143    // Views
144    // ------------------------------------------------------------------
145
146    pub fn view(&self) -> Result<View<E>, ResponseError> {
147        self.require_one()?;
148        Ok(self.0[0].1.to_view())
149    }
150
151    pub fn view_opt(&self) -> Result<Option<View<E>>, ResponseError> {
152        match self.count() {
153            0 => Ok(None),
154            1 => Ok(Some(self.0[0].1.to_view())),
155            n => Err(ResponseError::not_unique::<E>(n)),
156        }
157    }
158
159    #[must_use]
160    pub fn views(&self) -> Vec<View<E>> {
161        self.0.iter().map(|(_, e)| e.to_view()).collect()
162    }
163}
164
165impl<E: EntityKind> IntoIterator for Response<E> {
166    type Item = Row<E>;
167    type IntoIter = std::vec::IntoIter<Self::Item>;
168
169    fn into_iter(self) -> Self::IntoIter {
170        self.0.into_iter()
171    }
172}