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