Skip to main content

icydb_core/db/query/api/
result_ext.rs

1use crate::{
2    db::{EntityResponse, ResponseError, Row, query::api::private::SealedResponseCardinalityExt},
3    prelude::*,
4    types::Id,
5};
6
7///
8/// ResponseCardinalityExt
9///
10/// Query/session-layer cardinality helpers for scalar `EntityResponse<E>` payloads.
11/// These methods intentionally live outside `db::response` so cardinality
12/// semantics remain owned by the query/session API boundary.
13///
14
15pub trait ResponseCardinalityExt<E: EntityKind>: SealedResponseCardinalityExt<E> {
16    /// Require exactly one row in this response.
17    fn require_one(&self) -> Result<(), ResponseError>;
18
19    /// Require at least one row in this response.
20    fn require_some(&self) -> Result<(), ResponseError>;
21
22    /// Consume and return `None` for empty, `Some(row)` for one row, or error for many rows.
23    fn try_row(self) -> Result<Option<Row<E>>, ResponseError>;
24
25    /// Consume and return the single row, or fail on zero/many rows.
26    fn row(self) -> Result<Row<E>, ResponseError>;
27
28    /// Consume and return the single entity or `None`, failing on many rows.
29    fn try_entity(self) -> Result<Option<E>, ResponseError>;
30
31    /// Consume and return the single entity, failing on zero/many rows.
32    fn entity(self) -> Result<E, ResponseError>;
33
34    /// Require exactly one row and return its identifier.
35    fn require_id(self) -> Result<Id<E>, ResponseError>;
36}
37
38impl<E: EntityKind> ResponseCardinalityExt<E> for EntityResponse<E> {
39    fn require_one(&self) -> Result<(), ResponseError> {
40        match self.count() {
41            1 => Ok(()),
42            0 => Err(ResponseError::NotFound { entity: E::PATH }),
43            n => Err(ResponseError::NotUnique {
44                entity: E::PATH,
45                count: n,
46            }),
47        }
48    }
49
50    fn require_some(&self) -> Result<(), ResponseError> {
51        if self.is_empty() {
52            Err(ResponseError::NotFound { entity: E::PATH })
53        } else {
54            Ok(())
55        }
56    }
57
58    #[expect(clippy::cast_possible_truncation)]
59    fn try_row(self) -> Result<Option<Row<E>>, ResponseError> {
60        let mut rows = self.rows();
61
62        match rows.len() {
63            0 => Ok(None),
64            1 => Ok(rows.pop()),
65            n => Err(ResponseError::NotUnique {
66                entity: E::PATH,
67                count: n as u32,
68            }),
69        }
70    }
71
72    fn row(self) -> Result<Row<E>, ResponseError> {
73        self.try_row()?
74            .ok_or(ResponseError::NotFound { entity: E::PATH })
75    }
76
77    fn try_entity(self) -> Result<Option<E>, ResponseError> {
78        Ok(self.try_row()?.map(Row::entity))
79    }
80
81    fn entity(self) -> Result<E, ResponseError> {
82        self.try_entity()?
83            .ok_or(ResponseError::NotFound { entity: E::PATH })
84    }
85
86    fn require_id(self) -> Result<Id<E>, ResponseError> {
87        self.row().map(|row| row.id())
88    }
89}