Skip to main content

icydb_core/db/response/
mod.rs

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