Skip to main content

icydb_core/db/query/api/
result_ext.rs

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