graphlite_sdk/result.rs
1//! Result handling and typed deserialization
2//!
3//! This module provides utilities for working with query results, including
4//! type-safe deserialization into Rust structs.
5
6use crate::error::{Error, Result};
7use graphlite::{QueryResult, Row, Value};
8use serde::de::DeserializeOwned;
9
10/// Wrapper around QueryResult with additional type-safe methods
11///
12/// TypedResult provides convenient methods for deserializing query results
13/// into Rust types.
14///
15/// # Examples
16///
17/// ```no_run
18/// use serde::Deserialize;
19/// use graphlite_sdk::GraphLite;
20///
21/// #[derive(Deserialize, Debug)]
22/// struct Person {
23/// name: String,
24/// age: u32,
25/// }
26///
27/// # fn main() -> Result<(), graphlite_sdk::Error> {
28/// # let db = GraphLite::open("./mydb")?;
29/// # let session = db.session("admin")?;
30/// let result = session.query("MATCH (p:Person) RETURN p.name as name, p.age as age")?;
31/// let typed = TypedResult::from(result);
32///
33/// // Deserialize each row into a Person struct
34/// for person in typed.deserialize_rows::<Person>()? {
35/// println!("Person: {:?}", person);
36/// }
37/// # Ok(())
38/// # }
39/// ```
40pub struct TypedResult {
41 inner: QueryResult,
42}
43
44impl TypedResult {
45 /// Create a new TypedResult from a QueryResult
46 pub fn new(result: QueryResult) -> Self {
47 TypedResult { inner: result }
48 }
49
50 /// Get the underlying QueryResult
51 pub fn inner(&self) -> &QueryResult {
52 &self.inner
53 }
54
55 /// Consume and get the underlying QueryResult
56 pub fn into_inner(self) -> QueryResult {
57 self.inner
58 }
59
60 /// Get the number of rows
61 pub fn row_count(&self) -> usize {
62 self.inner.rows.len()
63 }
64
65 /// Get the column names (variables from RETURN clause)
66 pub fn column_names(&self) -> Vec<String> {
67 self.inner.variables.clone()
68 }
69
70 /// Get a specific row by index
71 pub fn get_row(&self, index: usize) -> Option<&Row> {
72 self.inner.rows.get(index)
73 }
74
75 /// Deserialize all rows into a vector of the given type
76 ///
77 /// Each row is converted to a JSON object and then deserialized
78 /// into the target type using serde.
79 ///
80 /// # Type Parameters
81 ///
82 /// * `T` - Type to deserialize each row into (must implement Deserialize)
83 ///
84 /// # Examples
85 ///
86 /// ```no_run
87 /// use serde::Deserialize;
88 /// # use graphlite_sdk::GraphLite;
89 ///
90 /// #[derive(Deserialize)]
91 /// struct Person { name: String, age: u32 }
92 ///
93 /// # fn main() -> Result<(), graphlite_sdk::Error> {
94 /// # let db = GraphLite::open("./mydb")?;
95 /// # let session = db.session("admin")?;
96 /// let result = session.query("MATCH (p:Person) RETURN p.name as name, p.age as age")?;
97 /// let typed = TypedResult::from(result);
98 /// let people: Vec<Person> = typed.deserialize_rows()?;
99 /// # Ok(())
100 /// # }
101 /// ```
102 pub fn deserialize_rows<T: DeserializeOwned>(&self) -> Result<Vec<T>> {
103 let mut results = Vec::new();
104
105 for row in &self.inner.rows {
106 let item = self.deserialize_row::<T>(row)?;
107 results.push(item);
108 }
109
110 Ok(results)
111 }
112
113 /// Deserialize a single row into the given type
114 ///
115 /// # Type Parameters
116 ///
117 /// * `T` - Type to deserialize the row into (must implement Deserialize)
118 ///
119 /// # Examples
120 ///
121 /// ```no_run
122 /// use serde::Deserialize;
123 /// # use graphlite_sdk::GraphLite;
124 ///
125 /// #[derive(Deserialize)]
126 /// struct Person { name: String, age: u32 }
127 ///
128 /// # fn main() -> Result<(), graphlite_sdk::Error> {
129 /// # let db = GraphLite::open("./mydb")?;
130 /// # let session = db.session("admin")?;
131 /// let result = session.query("MATCH (p:Person) RETURN p.name as name, p.age as age LIMIT 1")?;
132 /// let typed = TypedResult::from(result);
133 /// if let Some(row) = typed.get_row(0) {
134 /// let person: Person = typed.deserialize_row(row)?;
135 /// }
136 /// # Ok(())
137 /// # }
138 /// ```
139 pub fn deserialize_row<T: DeserializeOwned>(&self, row: &Row) -> Result<T> {
140 // Convert row.values HashMap to JSON
141 let json_value = serde_json::to_value(&row.values)?;
142 let result = serde_json::from_value(json_value)?;
143 Ok(result)
144 }
145
146 /// Get the first row as the given type
147 ///
148 /// Convenience method for queries that return a single row.
149 ///
150 /// # Type Parameters
151 ///
152 /// * `T` - Type to deserialize into (must implement Deserialize)
153 ///
154 /// # Examples
155 ///
156 /// ```no_run
157 /// use serde::Deserialize;
158 /// # use graphlite_sdk::GraphLite;
159 ///
160 /// #[derive(Deserialize)]
161 /// struct Count { count: i64 }
162 ///
163 /// # fn main() -> Result<(), graphlite_sdk::Error> {
164 /// # let db = GraphLite::open("./mydb")?;
165 /// # let session = db.session("admin")?;
166 /// let result = session.query("MATCH (p:Person) RETURN count(p) as count")?;
167 /// let typed = TypedResult::from(result);
168 /// let count: Count = typed.first()?;
169 /// # Ok(())
170 /// # }
171 /// ```
172 pub fn first<T: DeserializeOwned>(&self) -> Result<T> {
173 let row = self
174 .get_row(0)
175 .ok_or_else(|| Error::NotFound("No rows returned".to_string()))?;
176
177 self.deserialize_row(row)
178 }
179
180 /// Get a single value from the first row and first column
181 ///
182 /// Useful for queries that return a single scalar value.
183 ///
184 /// # Examples
185 ///
186 /// ```no_run
187 /// # use graphlite_sdk::GraphLite;
188 /// # fn main() -> Result<(), graphlite_sdk::Error> {
189 /// # let db = GraphLite::open("./mydb")?;
190 /// # let session = db.session("admin")?;
191 /// let result = session.query("MATCH (p:Person) RETURN count(p)")?;
192 /// let typed = TypedResult::from(result);
193 /// let count: i64 = typed.scalar()?;
194 /// # Ok(())
195 /// # }
196 /// ```
197 pub fn scalar<T: DeserializeOwned>(&self) -> Result<T> {
198 let row = self
199 .get_row(0)
200 .ok_or_else(|| Error::NotFound("No rows returned".to_string()))?;
201
202 let columns = &self.inner.variables;
203 if columns.is_empty() {
204 return Err(Error::NotFound("No columns returned".to_string()));
205 }
206
207 let value = row
208 .get_value(&columns[0])
209 .ok_or_else(|| Error::NotFound("Column value not found".to_string()))?;
210
211 value_to_type(value)
212 }
213
214 /// Check if the result is empty (no rows)
215 pub fn is_empty(&self) -> bool {
216 self.inner.rows.is_empty()
217 }
218
219 /// Iterate over rows
220 pub fn rows(&self) -> &[Row] {
221 &self.inner.rows
222 }
223}
224
225impl From<QueryResult> for TypedResult {
226 fn from(result: QueryResult) -> Self {
227 TypedResult::new(result)
228 }
229}
230
231/// Convert a GraphLite Value to a Rust type
232fn value_to_type<T: DeserializeOwned>(value: &Value) -> Result<T> {
233 let json_value = value_to_json(value);
234 serde_json::from_value(json_value).map_err(|e| e.into())
235}
236
237/// Convert a GraphLite Value to a serde_json Value
238fn value_to_json(value: &Value) -> serde_json::Value {
239 match value {
240 Value::Null => serde_json::Value::Null,
241 Value::Boolean(b) => serde_json::Value::Bool(*b),
242 Value::Number(n) => serde_json::json!(n),
243 Value::String(s) => serde_json::Value::String(s.clone()),
244 Value::Array(arr) | Value::List(arr) => {
245 let items: Vec<serde_json::Value> = arr.iter().map(|v| value_to_json(v)).collect();
246 serde_json::Value::Array(items)
247 }
248 // For complex types like Node, Edge, Path, etc., use serde serialization
249 _ => serde_json::to_value(value).unwrap_or(serde_json::Value::Null),
250 }
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256
257 #[test]
258 fn test_value_conversion() {
259 let value = Value::Number(42.0);
260 let json = value_to_json(&value);
261 assert_eq!(json, serde_json::json!(42.0));
262 }
263}