Skip to main content

corium_client/
result.rs

1//! Query results and typed row access.
2
3use corium_query::edn::Edn;
4
5use crate::ClientError;
6use crate::value::FromEdn;
7
8/// The shape of a query result, fixed by the query's `:find` clause.
9#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10pub enum ResultShape {
11    /// A set of tuples (`:find ?a ?b`).
12    Relation,
13    /// A flat collection (`:find [?x ...]`).
14    Collection,
15    /// A single tuple (`:find [?a ?b]`).
16    Tuple,
17    /// A single value (`:find ?x .`).
18    Scalar,
19}
20
21/// A query result: the boundary [`Edn`] value plus its [`ResultShape`], with
22/// typed accessors keyed to the shape.
23#[derive(Clone, Debug)]
24pub struct QueryResult {
25    shape: ResultShape,
26    value: Edn,
27}
28
29impl QueryResult {
30    /// Wraps a boundary result value with its shape.
31    #[must_use]
32    pub fn new(shape: ResultShape, value: Edn) -> Self {
33        Self { shape, value }
34    }
35
36    /// The result shape.
37    #[must_use]
38    pub fn shape(&self) -> ResultShape {
39        self.shape
40    }
41
42    /// The raw boundary [`Edn`] value.
43    #[must_use]
44    pub fn edn(&self) -> &Edn {
45        &self.value
46    }
47
48    /// Consumes the result, yielding the raw boundary [`Edn`] value.
49    #[must_use]
50    pub fn into_edn(self) -> Edn {
51        self.value
52    }
53
54    /// Rows of a relation result. Empty for other shapes.
55    #[must_use]
56    pub fn rows(&self) -> Vec<Row<'_>> {
57        match (&self.shape, &self.value) {
58            (ResultShape::Relation, Edn::Vector(rows)) => rows
59                .iter()
60                .filter_map(|row| match row {
61                    Edn::Vector(cells) => Some(Row(cells)),
62                    _ => None,
63                })
64                .collect(),
65            _ => Vec::new(),
66        }
67    }
68
69    /// Values of a collection result. Empty for other shapes.
70    #[must_use]
71    pub fn values(&self) -> Vec<&Edn> {
72        match (&self.shape, &self.value) {
73            (ResultShape::Collection, Edn::Vector(items)) => items.iter().collect(),
74            _ => Vec::new(),
75        }
76    }
77
78    /// Typed values of a collection result.
79    ///
80    /// # Errors
81    /// Returns [`ClientError::Decode`] if any value is not of type `T`.
82    pub fn values_as<T: FromEdn>(&self) -> Result<Vec<T>, ClientError> {
83        self.values().into_iter().map(T::from_edn).collect()
84    }
85
86    /// The single tuple of a tuple result, or `None` when empty.
87    #[must_use]
88    pub fn tuple(&self) -> Option<Row<'_>> {
89        match (&self.shape, &self.value) {
90            (ResultShape::Tuple, Edn::Vector(cells)) => Some(Row(cells)),
91            _ => None,
92        }
93    }
94
95    /// The single value of a scalar result, or `None` when empty (`nil`).
96    #[must_use]
97    pub fn scalar(&self) -> Option<&Edn> {
98        match (&self.shape, &self.value) {
99            (ResultShape::Scalar, Edn::Nil) => None,
100            (ResultShape::Scalar, value) => Some(value),
101            _ => None,
102        }
103    }
104
105    /// The typed scalar value of a scalar result.
106    ///
107    /// # Errors
108    /// Returns [`ClientError::Decode`] if the value is not of type `T`.
109    pub fn scalar_as<T: FromEdn>(&self) -> Result<Option<T>, ClientError> {
110        self.scalar().map(T::from_edn).transpose()
111    }
112
113    /// Whether the result is empty (no rows/values, or a `nil` tuple/scalar).
114    #[must_use]
115    pub fn is_empty(&self) -> bool {
116        match &self.value {
117            Edn::Nil => true,
118            Edn::Vector(items) => items.is_empty(),
119            _ => false,
120        }
121    }
122}
123
124/// A borrowed view of one result row (a relation row or the tuple result),
125/// with typed cell access.
126#[derive(Clone, Copy, Debug)]
127pub struct Row<'a>(&'a [Edn]);
128
129impl<'a> Row<'a> {
130    /// The number of cells.
131    #[must_use]
132    pub fn len(&self) -> usize {
133        self.0.len()
134    }
135
136    /// Whether the row has no cells.
137    #[must_use]
138    pub fn is_empty(&self) -> bool {
139        self.0.is_empty()
140    }
141
142    /// The raw cell at `index`.
143    #[must_use]
144    pub fn edn(&self, index: usize) -> Option<&'a Edn> {
145        self.0.get(index)
146    }
147
148    /// The cells as a slice.
149    #[must_use]
150    pub fn cells(&self) -> &'a [Edn] {
151        self.0
152    }
153
154    /// The typed cell at `index`.
155    ///
156    /// # Errors
157    /// Returns [`ClientError::Decode`] if the cell is missing or not of type
158    /// `T`.
159    pub fn get<T: FromEdn>(&self, index: usize) -> Result<T, ClientError> {
160        let cell = self
161            .0
162            .get(index)
163            .ok_or_else(|| ClientError::Decode(format!("row has no cell at index {index}")))?;
164        T::from_edn(cell)
165    }
166}