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::not_found(E::PATH)),
48            n => Err(ResponseError::not_unique(E::PATH, n)),
49        }
50    }
51
52    fn require_some(&self) -> Result<(), ResponseError> {
53        if self.is_empty() {
54            Err(ResponseError::not_found(E::PATH))
55        } else {
56            Ok(())
57        }
58    }
59
60    #[expect(clippy::cast_possible_truncation)]
61    fn try_row(self) -> Result<Option<Row<E>>, ResponseError> {
62        let mut rows = self.rows();
63
64        match rows.len() {
65            0 => Ok(None),
66            1 => Ok(rows.pop()),
67            n => Err(ResponseError::not_unique(E::PATH, n as u32)),
68        }
69    }
70
71    fn row(self) -> Result<Row<E>, ResponseError> {
72        self.try_row()?.ok_or(ResponseError::not_found(E::PATH))
73    }
74
75    fn try_entity(self) -> Result<Option<E>, ResponseError> {
76        Ok(self.try_row()?.map(Row::entity))
77    }
78
79    fn entity(self) -> Result<E, ResponseError> {
80        self.try_entity()?.ok_or(ResponseError::not_found(E::PATH))
81    }
82
83    fn require_id(self) -> Result<Id<E>, ResponseError> {
84        self.row().map(|row| row.id())
85    }
86}