Skip to main content

icydb_core/db/response/
mod.rs

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