From 7407397ef2b80cf8028cb802375b34243592c081 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Fri, 27 Apr 2018 17:13:13 +0300 Subject: [PATCH 1/2] Better error reporting on invalid query It still panics, but previous error was: ``` thread 'main' panicked at 'Box', src/lib.rs:190:23 note: Run with `RUST_BACKTRACE=1` for a backtrace. ``` Now it looks like: ``` hread 'main' panicked at 'Bad query: query parse error: Parse error at 1:2 Unexpected `}[Punctuator]` Expected `Name` or `...` ', src/lib.rs:190:23 note: Run with `RUST_BACKTRACE=1` for a backtrace. ``` --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index f9d6852..5cf9bab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -187,7 +187,7 @@ mod filters { pub fn filter_stream<'de, R>(query: String, reader: R, func: fn(Value)) where R: Read<'de> { // Parse query string to AST match parse_query(&query) { - Err(error) => panic!(error), + Err(error) => panic!("Bad query: {}", error), Ok(ast) => { // Convert AST to selection tree let selection = filters::get_selection(ast); From 224f24db154f246dd4ba2c8389a1e06485e07927 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Fri, 27 Apr 2018 17:47:04 +0300 Subject: [PATCH 2/2] Add `(entries: true)` argument support This allows to iterate over keys of a javascript object treating it like a dict. I.e. filter like ``{ field(entries: true) }`` is treated like the data in the field not an object of ``{"key1": "value1"}`` but an array of ``[{"key": "key1", "value": "value1"}]``. In other words, this allows working with javascript objects with keys unknown in advance. --- src/lib.rs | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5cf9bab..6d8a94a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,11 +75,13 @@ mod filters { use serde_json::{ Value }; use serde_json::map::Map; use graphql_parser::query::*; + use graphql_parser::query::Value as GValue; #[derive(Clone, Debug)] pub enum Filters { Field(String), - Object(String, Vec) + Object(String, Vec), + Entries(String, Vec), } // TODO: Fail when requested fields do not exist @@ -99,6 +101,23 @@ mod filters { map.insert(field, filter_value(fields, value.clone())); } } + Filters::Entries(field, fields) => { + if let Some(value) = object.get(&field) { + if let Value::Object(ref items) = *value { + let array = filter_array(fields, + items.iter().map(|(k, v)| { + Value::Object(vec![ + ("key".into(), Value::String(k.clone())), + ("value".into(), v.clone()), + ].into_iter().collect()) + }).collect()); + map.insert(field, Value::Array(array)); + } else { + map.insert(field, + filter_value(fields, value.clone())); + } + } + } } } @@ -122,8 +141,30 @@ mod filters { selection.items.iter() .filter_map(|selection| { if let Selection::Field(field) = selection.clone() { - if field.selection_set.items.len() > 0 { - Some(Filters::Object(field.name, get_filters(field.selection_set))) + let subfilters = get_filters(field.selection_set); + if field.arguments.len() > 0 { + for argument in &field.arguments { + match &argument.0[..] { + "entries" => { + match argument.1 { + GValue::Boolean(true) => { + return Some(Filters::Entries( + field.name, + subfilters)); + } + GValue::Boolean(false) => {} + _ => { + panic!("invalid argument {:?}", + argument); + } + } + } + _ => panic!("invalid argument {:?}", argument), + } + } + } + if subfilters.len() > 0 { + Some(Filters::Object(field.name, subfilters)) } else { Some(Filters::Field(field.name)) } @@ -356,4 +397,19 @@ mod tests { assert_eq!(super::filter_value(query, data).to_string(), expect); } + + #[test] + fn dictionary() { + let query = String::from("{ dict(entries: true) { value { name } } }"); + let data = json!({ + "dict": { + "item1": {"name": "item one"}, + "item2": {"name": "item two"}, + }, + }); + + let expect = r#"{"dict":[{"value":{"name":"item one"}},{"value":{"name":"item two"}}]}"#; + + assert_eq!(super::filter_value(query, data).to_string(), expect); + } }