redisgraphio/
parse.rs

1use indexmap::IndexMap;
2use redis::{from_redis_value, FromRedisValue, RedisResult, Value};
3use std::{
4    collections::HashMap,
5};
6
7use crate::{
8    from_graph_value,
9    helpers::{create_rediserror, apply_macro}
10};
11
12/// [Official enum](https://github.com/RedisGraph/RedisGraph/blob/master/src/resultset/formatters/resultset_formatter.h#L20-L33) from redis-graph 
13mod types {
14    pub const VALUE_UNKNOWN: i64 = 0;
15    pub const VALUE_NULL: i64 = 1;
16    pub const VALUE_STRING: i64 = 2;
17    pub const VALUE_INTEGER: i64 = 3;
18    pub const VALUE_BOOLEAN: i64 = 4;
19    pub const VALUE_DOUBLE: i64 = 5;
20    pub const VALUE_ARRAY: i64 = 6;
21    pub const VALUE_EDGE: i64 = 7;
22    pub const VALUE_NODE: i64 = 8;
23    pub const VALUE_PATH: i64 = 9;
24    pub const VALUE_MAP: i64 = 10;
25    pub const VALUE_POINT: i64 = 11;
26}
27
28/// An enum containing every possible type that can be returned by redisgraph
29#[derive(Clone, Debug, PartialEq)]
30pub enum GraphValue {
31    /// Value is Unknown and stored as a [redis::Value]
32    Unknown(Value),
33    /// A Map as returned by
34    /// ```cypher
35    /// Return {a: 2, b: "Hello"}
36    /// ```
37    Map(GraphMap),
38    /// A Point as returned by
39    /// ```cypher
40    /// Return point({latitude: 32.070794860, longitude: 34.820751118})
41    /// ```
42    Point(GeoPoint),
43    /// A Path as returned by 
44    /// ```cypher
45    /// Match p=(:A)-[:B]->(:C) Return p
46    /// ```
47    Path(GraphPath),
48    /// A Node as returned by 
49    /// ```cypher
50    /// Match (a:A) Return a
51    /// ```
52    Node(Node),
53    /// A Relationship as returned by 
54    /// ```cypher
55    /// Match (:A)-[b:B]->(:C) Return b
56    /// ```
57    Relation(Relationship),
58    /// A Array as returned by 
59    /// ```cypher
60    /// Return [1, 2.0, "Hi"]
61    /// ```
62    Array(Vec<GraphValue>),
63    /// A Integer as returned by 
64    /// ```cypher
65    /// Return 1337
66    /// ```
67    Integer(i64),
68    /// A Double as returned by 
69    /// ```cypher
70    /// Return 1337.0
71    /// ```
72    Double(f64),
73    /// A String as returned by 
74    /// ```cypher
75    /// Return '1337'
76    /// ```
77    String(String),
78    /// A Boolean as returned by 
79    /// ```cypher
80    /// Return true, false, 1=1
81    /// ```
82    Boolean(bool),
83    /// A Null type which is returned when an Optinal Match does not match
84    /// or when a property of a node is returned but the node does not have this property
85    Null,
86}
87
88/// The type returned by the point method in cypher
89#[derive(Debug, Clone, PartialEq)]
90pub struct GeoPoint {
91    /// latitude
92    pub latitude: f32,
93    /// longitude
94    pub longitude: f32,
95}
96
97/// Map typed as returned by RETURN {a: 1}
98#[derive(Debug, Clone, PartialEq)]
99pub struct GraphMap(pub HashMap<String, GraphValue>);
100
101impl GraphMap {
102    /// Take ownership of the underlying HashMap 
103    pub fn into_inner(self) -> HashMap<String, GraphValue> {
104        self.0
105    }
106
107    /// Gets a value by its key and converts it to a given return type
108    pub fn get<T: FromGraphValue>(&self, key: &str) -> RedisResult<Option<T>> {
109        match self.0.get(key) {
110            Some(val) => from_graph_value(val.clone()),
111            None => Ok(None),
112        }
113    }
114}
115
116/// Node Type
117#[derive(Debug, Clone, PartialEq)]
118pub struct Node {
119    /// Redisgraph internal node id
120    pub id: i64,
121    /// Ids of the nodes labels that can be mapped to db.labels()
122    pub label_ids: Vec<i64>,
123    /// Map of property ids to property values can be mapped to db.propertyKeys()
124    pub properties: IndexMap<i64, GraphValue>,
125}
126
127impl Node {
128    /// Full constructor for Node
129    pub fn new(id: i64, label_ids: Vec<i64>, properties: IndexMap<i64, GraphValue>) -> Self {
130        Self {
131            id,
132            label_ids,
133            properties,
134        }
135    }
136}
137
138/// Relationship Type
139#[derive(Debug, Clone, PartialEq)]
140pub struct Relationship {
141    /// Redisgraph internal relationship id
142    pub id: i64,
143    /// Id of the relationships label that can be mapped to db.relationshipTypes()
144    pub label_id: i64,
145    /// Source Node Id
146    pub src: i64,
147    /// Destination Node
148    pub dest: i64,
149    /// Map of property ids to property values can be mapped by db.propertyKeys()
150    pub properties: IndexMap<i64, GraphValue>,
151}
152
153impl Relationship {
154    /// Full constructor for Relationship
155    pub fn new(
156        id: i64,
157        label_id: i64,
158        src: i64,
159        dest: i64,
160        properties: IndexMap<i64, GraphValue>,
161    ) -> Self {
162        Self {
163            id,
164            label_id,
165            src,
166            dest,
167            properties,
168        }
169    }
170}
171
172/// Trait for unifying access to Node and Relationship properties
173pub trait PropertyAccess {
174    /// Returns a reference to the IndexMap containing the properties in order of definition and with the property key ids
175    fn properties(&self) -> &IndexMap<i64, GraphValue>;
176
177    /// get property by property label id
178    fn get_property_by_label_id<T: FromGraphValue>(&self, label_id: i64) -> RedisResult<Option<T>> {
179        match self.properties().get(&label_id) {
180            Some(val) => from_graph_value(val.clone()),
181            None => Ok(None),
182        }
183    }
184
185    /// gets a property by its order of definition
186    /// Note when relying on property order make sure every CREATE has the same order of these properties
187    fn get_property_by_index<T: FromGraphValue>(&self, idx: usize) -> RedisResult<T> {
188        from_graph_value(self.properties()[idx].clone())
189    }
190
191    /// get property values in the order they were defined
192    fn property_values<T: FromGraphValue>(&self) -> RedisResult<T> {
193        from_graph_value(GraphValue::Array(
194            self.properties().values().cloned().collect(),
195        ))
196    }
197
198    /// Same as `property_values()` but consumes the object taking ownership of the `Graphvalue`s
199    fn into_property_values<T: FromGraphValue>(self) -> RedisResult<T>;
200}
201
202impl PropertyAccess for Node {
203    #[inline(always)]
204    fn properties(&self) -> &IndexMap<i64, GraphValue> {
205        &self.properties
206    }
207
208    fn into_property_values<T: FromGraphValue>(self) -> RedisResult<T> {
209        FromGraphValue::from_graph_value(GraphValue::Array(
210            self.properties.into_values().collect(),
211        ))
212    }
213}
214
215impl PropertyAccess for Relationship {
216    #[inline(always)]
217    fn properties(&self) -> &IndexMap<i64, GraphValue> {
218        &self.properties
219    }
220
221    fn into_property_values<T: FromGraphValue>(self) -> RedisResult<T> {
222        FromGraphValue::from_graph_value(GraphValue::Array(
223            self.properties.into_values().collect(),
224        ))
225    }
226}
227
228/// Type for graph paths as returned by MATCH p=(\:A)-[\:B]->(\:C) RETURN p
229#[derive(Debug, PartialEq, Clone)]
230pub struct GraphPath {
231    /// Nodes of the GraphPath
232    pub nodes: Vec<Node>,
233    /// Relationships of the GraphPath
234    pub relationships: Vec<Relationship>,
235}
236
237/// Trait for converting the response to an arbitray type which implents the trait
238/// This is similar to the FromRedisValue trait from redis
239/// 
240/// ## Example
241/// ```no_run
242/// struct MyType {
243///     a: i32
244///     b: Vec<String>
245/// }
246/// 
247/// impl FromGraphValue for MyType {
248///     fn from_graph_value(value: GraphValue) -> RedisResult<Self> {
249///         let (a, b): (i32, Vec<String>) = from_graph_value(value)?;
250///         // You dont even need the type annotations above as they are inferred in this case
251///         Ok(MyType {
252///             a,
253///             b
254///         })
255///     }
256/// }
257/// // Now you can write code like this
258/// let con = // Connection to redis
259/// let data: Vec<MyType> = con.graph_query("graphname", query!("RETURN 1, ['a', 'b']"))?.data;
260/// ```
261pub trait FromGraphValue: Sized {
262    /// Converts the GraphValue to the implementing Type
263    fn from_graph_value(value: GraphValue) -> RedisResult<Self>;
264}
265
266
267/// Macro for implementing the FromGraphValue Trait for a int type
268macro_rules! from_graph_value_for_int {
269    ( $t:ty ) => {
270        impl FromGraphValue for $t {
271            fn from_graph_value(value: GraphValue) -> RedisResult<Self> {
272                match value {
273                    GraphValue::Integer(val) => <$t>::try_from(val).map_err(|_| create_rediserror(concat!("Could not convert to ", stringify!($t)))),
274                    _ => Err(create_rediserror(&format!(
275                        concat!("Cant convert {:?} to ", stringify!($t)),
276                        value
277                    ))),
278                }
279            }
280        }
281    };
282}
283
284/// Macro for implementing the FromGraphValue Trait for a float type
285impl FromGraphValue for f64 {
286    fn from_graph_value(value: GraphValue) -> RedisResult<Self> {
287        match value {
288            GraphValue::Double(val) => Ok(val),
289            _ => Err(create_rediserror(&format!(
290                concat!("Cant convert {:?} to ", stringify!($t)),
291                value
292            ))),
293        }
294    }
295}
296apply_macro!(
297    from_graph_value_for_int,
298    i8,
299    i16,
300    i32,
301    i64,
302    u8,
303    u16,
304    u32,
305    u64
306);
307
308impl FromGraphValue for bool {
309    fn from_graph_value(value: GraphValue) -> RedisResult<Self> {
310        match value {
311            GraphValue::Boolean(val) => Ok(val),
312            _ => Err(create_rediserror(&format!(
313                "Cant convert {:?} to bool",
314                value
315            ))),
316        }
317    }
318}
319
320impl FromGraphValue for () {
321    fn from_graph_value(_: GraphValue) -> RedisResult<Self> {
322        Ok(())
323    }
324}
325
326impl<T: FromGraphValue> FromGraphValue for Vec<T> {
327    fn from_graph_value(value: GraphValue) -> RedisResult<Self> {
328        match value {
329            GraphValue::Array(val) => Ok(val
330                .into_iter()
331                .map(FromGraphValue::from_graph_value)
332                .collect::<RedisResult<Self>>()?),
333            _ => Err(create_rediserror(&format!(
334                "Cant convert {:?} to Vec",
335                value
336            ))),
337        }
338    }
339}
340
341impl FromGraphValue for GraphMap {
342    fn from_graph_value(value: GraphValue) -> RedisResult<Self> {
343        match value {
344            GraphValue::Map(map) => Ok(map),
345            _ => Err(create_rediserror(&format!(
346                "Cant convert {:?} to GraphMap",
347                value
348            ))),
349        }
350    }
351}
352
353impl FromGraphValue for GraphPath {
354    fn from_graph_value(value: GraphValue) -> RedisResult<Self> {
355        match value {
356            GraphValue::Path(path) => Ok(path),
357            _ => Err(create_rediserror(&format!(
358                "Cant convert {:?} to GraphPath",
359                value
360            ))),
361        }
362    }
363}
364
365impl FromGraphValue for GeoPoint {
366    fn from_graph_value(value: GraphValue) -> RedisResult<Self> {
367        match value {
368            GraphValue::Point(point) => Ok(point),
369            _ => Err(create_rediserror(&format!(
370                "Cant convert {:?} to GeoPoint",
371                value
372            ))),
373        }
374    }
375}
376
377impl FromGraphValue for Node {
378    fn from_graph_value(value: GraphValue) -> RedisResult<Self> {
379        match value {
380            GraphValue::Node(node) => Ok(node),
381            _ => Err(create_rediserror(&format!(
382                "Cant convert {:?} to Node",
383                value
384            ))),
385        }
386    }
387}
388
389impl FromGraphValue for Relationship {
390    fn from_graph_value(value: GraphValue) -> RedisResult<Self> {
391        match value {
392            GraphValue::Relation(rel) => Ok(rel),
393            _ => Err(create_rediserror(&format!(
394                "Cant convert {:?} to Relationship",
395                value
396            ))),
397        }
398    }
399}
400
401impl<T: FromGraphValue> FromGraphValue for Option<T> {
402    fn from_graph_value(value: GraphValue) -> RedisResult<Self> {
403        match value {
404            GraphValue::Null => Ok(None),
405            val => Ok(Some(from_graph_value(val)?)),
406        }
407    }
408}
409
410impl FromGraphValue for GraphValue {
411    fn from_graph_value(value: GraphValue) -> RedisResult<Self> {
412        Ok(value)
413    }
414}
415
416impl FromGraphValue for String {
417    fn from_graph_value(value: GraphValue) -> RedisResult<Self> {
418        match value {
419            GraphValue::String(s) => Ok(s.to_string()),
420            _ => Err(create_rediserror(&format!(
421                "Cant convert {:?} to String",
422                value
423            ))),
424        }
425    }
426}
427
428/// This is copied and modified from the rust redis lib and modified for Graphvalue
429macro_rules! from_graph_value_for_tuple {
430    () => ();
431    ($($name:ident,)+) => (
432        #[doc(hidden)]
433        impl<$($name: FromGraphValue),+> FromGraphValue for ($($name,)*) {
434            // we have local variables named T1 as dummies and those
435            // variables are unused.
436            #[allow(non_snake_case, unused_variables)]
437            fn from_graph_value(v: GraphValue) -> RedisResult<($($name,)*)> {
438                match v {
439                    GraphValue::Array(mut items) => {
440                        // hacky way to count the tuple size
441                        let mut n = 0;
442                        $(let $name = (); n += 1;)*
443                        if items.len() != n {
444                            return Err(create_rediserror(&format!("Wrong length to create Tuple {} from {:?}", std::any::type_name::<Self>(), &items)))
445                        }
446
447                        Ok(($({
448                            let $name = ();
449                            FromGraphValue::from_graph_value(items.remove(0))?
450                        },)*))
451                    }
452                    _ => Err(create_rediserror(&format!("Can not create Tuple from {:?}", v)))
453                }
454            }
455        }
456        from_graph_value_for_tuple_peel!($($name,)*);
457    )
458}
459
460/// This chips of the leading one and recurses for the rest. So if the first
461/// iteration was T1, T2, T3 it will recurse to T2, T3. It stops for tuples
462/// of size 1 (does not implement down to unit).
463macro_rules! from_graph_value_for_tuple_peel {
464    ($name:ident, $($other:ident,)*) => (from_graph_value_for_tuple!($($other,)*);)
465}
466
467from_graph_value_for_tuple! { T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, }
468
469impl FromRedisValue for GraphValue {
470    fn from_redis_value(v: &Value) -> RedisResult<Self> {
471        match v {
472            Value::Bulk(data) if data.len() == 2 => match &data[0] {
473                Value::Int(type_) => convert_to_graphvalue(*type_, &data[1]),
474                value => Err(create_rediserror(&format!(
475                    "Couldnt convert {:?} to GraphValue",
476                    value
477                ))),
478            },
479            value => Err(create_rediserror(&format!(
480                "Couldnt convert {:?} to GraphValue",
481                value
482            ))),
483        }
484    }
485}
486
487impl FromRedisValue for GraphPath {
488    fn from_redis_value(v: &Value) -> RedisResult<Self> {
489        let (nodes, relationships): (GraphValue, GraphValue) = from_redis_value(v)?;
490        Ok(GraphPath {
491            nodes: from_graph_value(nodes)?,
492            relationships: from_graph_value(relationships)?,
493        })
494    }
495}
496
497impl FromRedisValue for GeoPoint {
498    fn from_redis_value(v: &Value) -> RedisResult<Self> {
499        let (latitude, longitude): (f32, f32) = from_redis_value(v)?;
500        Ok(GeoPoint {
501            latitude,
502            longitude,
503        })
504    }
505}
506
507impl FromRedisValue for GraphMap {
508    fn from_redis_value(v: &Value) -> RedisResult<Self> {
509        match v {
510            Value::Bulk(values) => {
511                let temp: Vec<(String, GraphValue)> = FromRedisValue::from_redis_values(values)?;
512                Ok(GraphMap(temp.into_iter().collect()))
513            }
514            value => Err(create_rediserror(&format!(
515                "Couldnt convert {:?} to GraphMap",
516                value
517            ))),
518        }
519    }
520}
521
522impl FromRedisValue for Node {
523    fn from_redis_value(v: &Value) -> RedisResult<Self> {
524        match v {
525            Value::Bulk(ref values) if values.len() == 3 => Ok(Node::new(
526                from_redis_value(&values[0])?,
527                from_redis_value(&values[1])?,
528                parse_properties(&values[2])?,
529            )),
530            val => Err(create_rediserror(&format!(
531                "Couldnt convert {:?} to Node",
532                val
533            ))),
534        }
535    }
536}
537
538
539impl FromRedisValue for Relationship {
540    fn from_redis_value(v: &Value) -> RedisResult<Self> {
541        match v {
542            Value::Bulk(ref values) if values.len() == 5 => Ok(Relationship::new(
543                from_redis_value(&values[0])?,
544                from_redis_value(&values[1])?,
545                from_redis_value(&values[2])?,
546                from_redis_value(&values[3])?,
547                parse_properties(&values[4])?,
548            )),
549            val => Err(create_rediserror(&format!(
550                "Couldnt convert {:?} to Relationship",
551                val
552            ))),
553        }
554    }
555}
556
557fn parse_properties(value: &Value) -> RedisResult<IndexMap<i64, GraphValue>> {
558    // Same issue as in parse_header of Graphresponse
559    let temp: Vec<Value> = from_redis_value(value)?;
560    let properties: Vec<(i64, i64, Value)> = temp
561        .into_iter()
562        .map(|v| from_redis_value::<(i64, i64, Value)>(&v))
563        .collect::<RedisResult<_>>()?;
564    properties
565        .into_iter()
566        .map(
567            |(property_id, type_, value)| match convert_to_graphvalue(type_, &value) {
568                Ok(gvalue) => Ok((property_id, gvalue)),
569                Err(err) => Err(err),
570            },
571        )
572        .collect()
573}
574
575fn convert_to_graphvalue(type_: i64, val: &Value) -> RedisResult<GraphValue> {
576    use types::*;
577    match type_ {
578        VALUE_NODE => Ok(GraphValue::Node(from_redis_value(val)?)),
579        VALUE_EDGE => Ok(GraphValue::Relation(from_redis_value(val)?)),
580        VALUE_PATH => Ok(GraphValue::Path(from_redis_value(val)?)),
581        VALUE_MAP => Ok(GraphValue::Map(from_redis_value(val)?)),
582        VALUE_POINT => Ok(GraphValue::Point(from_redis_value(val)?)),
583        VALUE_NULL => Ok(GraphValue::Null),
584        VALUE_DOUBLE => Ok(GraphValue::Double(from_redis_value(val)?)),
585        VALUE_INTEGER => Ok(GraphValue::Integer(from_redis_value(val)?)),
586        VALUE_ARRAY => Ok(GraphValue::Array(from_redis_value(val)?)),
587        VALUE_STRING => Ok(GraphValue::String(from_redis_value(val)?)),
588        VALUE_BOOLEAN => Ok(GraphValue::Boolean({
589            // The FromRedisValue impl for bool does not support this conversion (for good reason)
590            match from_redis_value::<String>(val)?.as_str() {
591                "true" => true,
592                "false" => false,
593                _ => {
594                    return Err(create_rediserror(&format!(
595                        "Cant convert {:?} to bool",
596                        val
597                    )))
598                }
599            }
600        })),
601        VALUE_UNKNOWN | _ => Ok(GraphValue::Unknown(val.to_owned())),
602    }
603}