Skip to main content

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}