Skip to main content

icydb_core/db/query/api/
result_ext.rs

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