From 90b89f00eef551f86fba8df4f5649f9bd88b8dbd Mon Sep 17 00:00:00 2001 From: Georg Semmler Date: Mon, 13 Aug 2018 13:47:18 +0000 Subject: [PATCH] Performance improvements (#202) Performance improvements * Replace the IndexMap in the serialized object with a plain `Vec<(String, Value)>` because linear search is faster for few elements * Some general tweaks to skip some allocations --- juniper/src/executor_tests/directives.rs | 88 +++++----- juniper/src/executor_tests/enums.rs | 14 +- .../src/executor_tests/introspection/enums.rs | 38 ++-- .../introspection/input_object.rs | 36 ++-- .../src/executor_tests/introspection/mod.rs | 105 +++++++---- juniper/src/executor_tests/variables.rs | 84 +++++---- juniper/src/integrations/serde.rs | 18 +- juniper/src/lib.rs | 2 +- juniper/src/macros/tests/args.rs | 8 +- juniper/src/macros/tests/field.rs | 97 +++++----- juniper/src/macros/tests/interface.rs | 45 +++-- juniper/src/macros/tests/object.rs | 103 +++++++---- juniper/src/macros/tests/scalar.rs | 24 ++- juniper/src/macros/tests/union.rs | 37 ++-- juniper/src/parser/utils.rs | 2 +- juniper/src/schema/model.rs | 4 + juniper/src/schema/schema.rs | 22 +++ juniper/src/tests/introspection_tests.rs | 6 +- juniper/src/types/base.rs | 45 +++-- juniper/src/types/containers.rs | 12 +- juniper/src/value.rs | 165 ++++++++++++++++-- juniper_tests/src/codegen/derive_object.rs | 12 +- 22 files changed, 614 insertions(+), 353 deletions(-) diff --git a/juniper/src/executor_tests/directives.rs b/juniper/src/executor_tests/directives.rs index 50102036..185bbc24 100644 --- a/juniper/src/executor_tests/directives.rs +++ b/juniper/src/executor_tests/directives.rs @@ -1,9 +1,7 @@ -use indexmap::IndexMap; - use executor::Variables; use schema::model::RootNode; use types::scalars::EmptyMutation; -use value::Value; +use value::{Value, Object}; struct TestType; @@ -19,7 +17,7 @@ graphql_object!(TestType: () |&self| { fn run_variable_query(query: &str, vars: Variables, f: F) where - F: Fn(&IndexMap) -> (), + F: Fn(&Object) -> (), { let schema = RootNode::new(TestType, EmptyMutation::<()>::new()); @@ -36,7 +34,7 @@ where fn run_query(query: &str, f: F) where - F: Fn(&IndexMap) -> (), + F: Fn(&Object) -> (), { run_variable_query(query, Variables::new(), f); } @@ -44,32 +42,32 @@ where #[test] fn scalar_include_true() { run_query("{ a, b @include(if: true) }", |result| { - assert_eq!(result.get("a"), Some(&Value::string("a"))); - assert_eq!(result.get("b"), Some(&Value::string("b"))); + assert_eq!(result.get_field_value("a"), Some(&Value::string("a"))); + assert_eq!(result.get_field_value("b"), Some(&Value::string("b"))); }); } #[test] fn scalar_include_false() { run_query("{ a, b @include(if: false) }", |result| { - assert_eq!(result.get("a"), Some(&Value::string("a"))); - assert_eq!(result.get("b"), None); + assert_eq!(result.get_field_value("a"), Some(&Value::string("a"))); + assert_eq!(result.get_field_value("b"), None); }); } #[test] fn scalar_skip_false() { run_query("{ a, b @skip(if: false) }", |result| { - assert_eq!(result.get("a"), Some(&Value::string("a"))); - assert_eq!(result.get("b"), Some(&Value::string("b"))); + assert_eq!(result.get_field_value("a"), Some(&Value::string("a"))); + assert_eq!(result.get_field_value("b"), Some(&Value::string("b"))); }); } #[test] fn scalar_skip_true() { run_query("{ a, b @skip(if: true) }", |result| { - assert_eq!(result.get("a"), Some(&Value::string("a"))); - assert_eq!(result.get("b"), None); + assert_eq!(result.get_field_value("a"), Some(&Value::string("a"))); + assert_eq!(result.get_field_value("b"), None); }); } @@ -78,8 +76,8 @@ fn fragment_spread_include_true() { run_query( "{ a, ...Frag @include(if: true) } fragment Frag on TestType { b }", |result| { - assert_eq!(result.get("a"), Some(&Value::string("a"))); - assert_eq!(result.get("b"), Some(&Value::string("b"))); + assert_eq!(result.get_field_value("a"), Some(&Value::string("a"))); + assert_eq!(result.get_field_value("b"), Some(&Value::string("b"))); }, ); } @@ -89,8 +87,8 @@ fn fragment_spread_include_false() { run_query( "{ a, ...Frag @include(if: false) } fragment Frag on TestType { b }", |result| { - assert_eq!(result.get("a"), Some(&Value::string("a"))); - assert_eq!(result.get("b"), None); + assert_eq!(result.get_field_value("a"), Some(&Value::string("a"))); + assert_eq!(result.get_field_value("b"), None); }, ); } @@ -100,8 +98,8 @@ fn fragment_spread_skip_false() { run_query( "{ a, ...Frag @skip(if: false) } fragment Frag on TestType { b }", |result| { - assert_eq!(result.get("a"), Some(&Value::string("a"))); - assert_eq!(result.get("b"), Some(&Value::string("b"))); + assert_eq!(result.get_field_value("a"), Some(&Value::string("a"))); + assert_eq!(result.get_field_value("b"), Some(&Value::string("b"))); }, ); } @@ -111,8 +109,8 @@ fn fragment_spread_skip_true() { run_query( "{ a, ...Frag @skip(if: true) } fragment Frag on TestType { b }", |result| { - assert_eq!(result.get("a"), Some(&Value::string("a"))); - assert_eq!(result.get("b"), None); + assert_eq!(result.get_field_value("a"), Some(&Value::string("a"))); + assert_eq!(result.get_field_value("b"), None); }, ); } @@ -122,8 +120,8 @@ fn inline_fragment_include_true() { run_query( "{ a, ... on TestType @include(if: true) { b } }", |result| { - assert_eq!(result.get("a"), Some(&Value::string("a"))); - assert_eq!(result.get("b"), Some(&Value::string("b"))); + assert_eq!(result.get_field_value("a"), Some(&Value::string("a"))); + assert_eq!(result.get_field_value("b"), Some(&Value::string("b"))); }, ); } @@ -133,8 +131,8 @@ fn inline_fragment_include_false() { run_query( "{ a, ... on TestType @include(if: false) { b } }", |result| { - assert_eq!(result.get("a"), Some(&Value::string("a"))); - assert_eq!(result.get("b"), None); + assert_eq!(result.get_field_value("a"), Some(&Value::string("a"))); + assert_eq!(result.get_field_value("b"), None); }, ); } @@ -142,79 +140,79 @@ fn inline_fragment_include_false() { #[test] fn inline_fragment_skip_false() { run_query("{ a, ... on TestType @skip(if: false) { b } }", |result| { - assert_eq!(result.get("a"), Some(&Value::string("a"))); - assert_eq!(result.get("b"), Some(&Value::string("b"))); + assert_eq!(result.get_field_value("a"), Some(&Value::string("a"))); + assert_eq!(result.get_field_value("b"), Some(&Value::string("b"))); }); } #[test] fn inline_fragment_skip_true() { run_query("{ a, ... on TestType @skip(if: true) { b } }", |result| { - assert_eq!(result.get("a"), Some(&Value::string("a"))); - assert_eq!(result.get("b"), None); + assert_eq!(result.get_field_value("a"), Some(&Value::string("a"))); + assert_eq!(result.get_field_value("b"), None); }); } #[test] fn anonymous_inline_fragment_include_true() { run_query("{ a, ... @include(if: true) { b } }", |result| { - assert_eq!(result.get("a"), Some(&Value::string("a"))); - assert_eq!(result.get("b"), Some(&Value::string("b"))); + assert_eq!(result.get_field_value("a"), Some(&Value::string("a"))); + assert_eq!(result.get_field_value("b"), Some(&Value::string("b"))); }); } #[test] fn anonymous_inline_fragment_include_false() { run_query("{ a, ... @include(if: false) { b } }", |result| { - assert_eq!(result.get("a"), Some(&Value::string("a"))); - assert_eq!(result.get("b"), None); + assert_eq!(result.get_field_value("a"), Some(&Value::string("a"))); + assert_eq!(result.get_field_value("b"), None); }); } #[test] fn anonymous_inline_fragment_skip_false() { run_query("{ a, ... @skip(if: false) { b } }", |result| { - assert_eq!(result.get("a"), Some(&Value::string("a"))); - assert_eq!(result.get("b"), Some(&Value::string("b"))); + assert_eq!(result.get_field_value("a"), Some(&Value::string("a"))); + assert_eq!(result.get_field_value("b"), Some(&Value::string("b"))); }); } #[test] fn anonymous_inline_fragment_skip_true() { run_query("{ a, ... @skip(if: true) { b } }", |result| { - assert_eq!(result.get("a"), Some(&Value::string("a"))); - assert_eq!(result.get("b"), None); + assert_eq!(result.get_field_value("a"), Some(&Value::string("a"))); + assert_eq!(result.get_field_value("b"), None); }); } #[test] fn scalar_include_true_skip_true() { run_query("{ a, b @include(if: true) @skip(if: true) }", |result| { - assert_eq!(result.get("a"), Some(&Value::string("a"))); - assert_eq!(result.get("b"), None); + assert_eq!(result.get_field_value("a"), Some(&Value::string("a"))); + assert_eq!(result.get_field_value("b"), None); }); } #[test] fn scalar_include_true_skip_false() { run_query("{ a, b @include(if: true) @skip(if: false) }", |result| { - assert_eq!(result.get("a"), Some(&Value::string("a"))); - assert_eq!(result.get("b"), Some(&Value::string("b"))); + assert_eq!(result.get_field_value("a"), Some(&Value::string("a"))); + assert_eq!(result.get_field_value("b"), Some(&Value::string("b"))); }); } #[test] fn scalar_include_false_skip_true() { run_query("{ a, b @include(if: false) @skip(if: true) }", |result| { - assert_eq!(result.get("a"), Some(&Value::string("a"))); - assert_eq!(result.get("b"), None); + assert_eq!(result.get_field_value("a"), Some(&Value::string("a"))); + assert_eq!(result.get_field_value("b"), None); }); } #[test] fn scalar_include_false_skip_false() { run_query("{ a, b @include(if: false) @skip(if: false) }", |result| { - assert_eq!(result.get("a"), Some(&Value::string("a"))); - assert_eq!(result.get("b"), None); + assert_eq!(result.get_field_value("a"), Some(&Value::string("a"))); + assert_eq!(result.get_field_value("b"), None); }); } diff --git a/juniper/src/executor_tests/enums.rs b/juniper/src/executor_tests/enums.rs index 4793ab84..2f32ef5d 100644 --- a/juniper/src/executor_tests/enums.rs +++ b/juniper/src/executor_tests/enums.rs @@ -1,12 +1,10 @@ -use indexmap::IndexMap; - use ast::InputValue; use executor::Variables; use parser::SourcePosition; use schema::model::RootNode; use types::scalars::EmptyMutation; use validation::RuleError; -use value::Value; +use value::{Value, Object}; use GraphQLError::ValidationError; #[derive(GraphQLEnum, Debug)] @@ -30,7 +28,7 @@ graphql_object!(TestType: () |&self| { fn run_variable_query(query: &str, vars: Variables, f: F) where - F: Fn(&IndexMap) -> (), + F: Fn(&Object) -> (), { let schema = RootNode::new(TestType, EmptyMutation::<()>::new()); @@ -47,7 +45,7 @@ where fn run_query(query: &str, f: F) where - F: Fn(&IndexMap) -> (), + F: Fn(&Object) -> (), { run_variable_query(query, Variables::new(), f); } @@ -55,14 +53,14 @@ where #[test] fn accepts_enum_literal() { run_query("{ toString(color: RED) }", |result| { - assert_eq!(result.get("toString"), Some(&Value::string("Color::Red"))); + assert_eq!(result.get_field_value("toString"), Some(&Value::string("Color::Red"))); }); } #[test] fn serializes_as_output() { run_query("{ aColor }", |result| { - assert_eq!(result.get("aColor"), Some(&Value::string("RED"))); + assert_eq!(result.get_field_value("aColor"), Some(&Value::string("RED"))); }); } @@ -92,7 +90,7 @@ fn accepts_strings_in_variables() { .into_iter() .collect(), |result| { - assert_eq!(result.get("toString"), Some(&Value::string("Color::Red"))); + assert_eq!(result.get_field_value("toString"), Some(&Value::string("Color::Red"))); }, ); } diff --git a/juniper/src/executor_tests/introspection/enums.rs b/juniper/src/executor_tests/introspection/enums.rs index 9e21d0a0..f09a5e83 100644 --- a/juniper/src/executor_tests/introspection/enums.rs +++ b/juniper/src/executor_tests/introspection/enums.rs @@ -1,9 +1,7 @@ -use indexmap::IndexMap; - use executor::Variables; use schema::model::RootNode; use types::scalars::EmptyMutation; -use value::Value; +use value::{Value, Object}; /* @@ -76,7 +74,7 @@ graphql_object!(Root: () |&self| { fn run_type_info_query(doc: &str, f: F) where - F: Fn((&IndexMap, &Vec)) -> (), + F: Fn((&Object, &Vec)) -> (), { let schema = RootNode::new(Root {}, EmptyMutation::<()>::new()); @@ -90,13 +88,13 @@ where let type_info = result .as_object_value() .expect("Result is not an object") - .get("__type") + .get_field_value("__type") .expect("__type field missing") .as_object_value() .expect("__type field not an object value"); let values = type_info - .get("enumValues") + .get_field_value("enumValues") .expect("enumValues field missing") .as_list_value() .expect("enumValues not a list"); @@ -122,8 +120,8 @@ fn default_name_introspection() { "#; run_type_info_query(doc, |(type_info, values)| { - assert_eq!(type_info.get("name"), Some(&Value::string("DefaultName"))); - assert_eq!(type_info.get("description"), Some(&Value::null())); + assert_eq!(type_info.get_field_value("name"), Some(&Value::string("DefaultName"))); + assert_eq!(type_info.get_field_value("description"), Some(&Value::null())); assert_eq!(values.len(), 2); @@ -171,8 +169,8 @@ fn named_introspection() { "#; run_type_info_query(doc, |(type_info, values)| { - assert_eq!(type_info.get("name"), Some(&Value::string("ANamedEnum"))); - assert_eq!(type_info.get("description"), Some(&Value::null())); + assert_eq!(type_info.get_field_value("name"), Some(&Value::string("ANamedEnum"))); + assert_eq!(type_info.get_field_value("description"), Some(&Value::null())); assert_eq!(values.len(), 2); @@ -221,10 +219,10 @@ fn no_trailing_comma_introspection() { run_type_info_query(doc, |(type_info, values)| { assert_eq!( - type_info.get("name"), + type_info.get_field_value("name"), Some(&Value::string("NoTrailingComma")) ); - assert_eq!(type_info.get("description"), Some(&Value::null())); + assert_eq!(type_info.get_field_value("description"), Some(&Value::null())); assert_eq!(values.len(), 2); @@ -273,11 +271,11 @@ fn enum_description_introspection() { run_type_info_query(doc, |(type_info, values)| { assert_eq!( - type_info.get("name"), + type_info.get_field_value("name"), Some(&Value::string("EnumDescription")) ); assert_eq!( - type_info.get("description"), + type_info.get_field_value("description"), Some(&Value::string("A description of the enum itself")) ); @@ -328,10 +326,10 @@ fn enum_value_description_introspection() { run_type_info_query(doc, |(type_info, values)| { assert_eq!( - type_info.get("name"), + type_info.get_field_value("name"), Some(&Value::string("EnumValueDescription")) ); - assert_eq!(type_info.get("description"), Some(&Value::null())); + assert_eq!(type_info.get_field_value("description"), Some(&Value::null())); assert_eq!(values.len(), 2); @@ -380,10 +378,10 @@ fn enum_deprecation_introspection() { run_type_info_query(doc, |(type_info, values)| { assert_eq!( - type_info.get("name"), + type_info.get_field_value("name"), Some(&Value::string("EnumDeprecation")) ); - assert_eq!(type_info.get("description"), Some(&Value::null())); + assert_eq!(type_info.get_field_value("description"), Some(&Value::null())); assert_eq!(values.len(), 2); @@ -438,10 +436,10 @@ fn enum_deprecation_no_values_introspection() { run_type_info_query(doc, |(type_info, values)| { assert_eq!( - type_info.get("name"), + type_info.get_field_value("name"), Some(&Value::string("EnumDeprecation")) ); - assert_eq!(type_info.get("description"), Some(&Value::null())); + assert_eq!(type_info.get_field_value("description"), Some(&Value::null())); assert_eq!(values.len(), 0); }); diff --git a/juniper/src/executor_tests/introspection/input_object.rs b/juniper/src/executor_tests/introspection/input_object.rs index 6d48b2e2..a07b8b96 100644 --- a/juniper/src/executor_tests/introspection/input_object.rs +++ b/juniper/src/executor_tests/introspection/input_object.rs @@ -1,10 +1,8 @@ -use indexmap::IndexMap; - use ast::{FromInputValue, InputValue}; use executor::Variables; use schema::model::RootNode; use types::scalars::EmptyMutation; -use value::Value; +use value::{Value, Object}; struct Root; @@ -106,7 +104,7 @@ graphql_object!(Root: () |&self| { fn run_type_info_query(doc: &str, f: F) where - F: Fn(&IndexMap, &Vec) -> (), + F: Fn(&Object, &Vec) -> (), { let schema = RootNode::new(Root {}, EmptyMutation::<()>::new()); @@ -120,13 +118,13 @@ where let type_info = result .as_object_value() .expect("Result is not an object") - .get("__type") + .get_field_value("__type") .expect("__type field missing") .as_object_value() .expect("__type field not an object value"); let fields = type_info - .get("inputFields") + .get_field_value("inputFields") .expect("inputFields field missing") .as_list_value() .expect("inputFields not a list"); @@ -156,8 +154,8 @@ fn default_name_introspection() { "#; run_type_info_query(doc, |type_info, fields| { - assert_eq!(type_info.get("name"), Some(&Value::string("DefaultName"))); - assert_eq!(type_info.get("description"), Some(&Value::null())); + assert_eq!(type_info.get_field_value("name"), Some(&Value::string("DefaultName"))); + assert_eq!(type_info.get_field_value("description"), Some(&Value::null())); assert_eq!(fields.len(), 2); @@ -256,10 +254,10 @@ fn no_trailing_comma_introspection() { run_type_info_query(doc, |type_info, fields| { assert_eq!( - type_info.get("name"), + type_info.get_field_value("name"), Some(&Value::string("NoTrailingComma")) ); - assert_eq!(type_info.get("description"), Some(&Value::null())); + assert_eq!(type_info.get_field_value("description"), Some(&Value::null())); assert_eq!(fields.len(), 2); @@ -337,8 +335,8 @@ fn derive_introspection() { "#; run_type_info_query(doc, |type_info, fields| { - assert_eq!(type_info.get("name"), Some(&Value::string("Derive"))); - assert_eq!(type_info.get("description"), Some(&Value::null())); + assert_eq!(type_info.get_field_value("name"), Some(&Value::string("Derive"))); + assert_eq!(type_info.get_field_value("description"), Some(&Value::null())); assert_eq!(fields.len(), 1); @@ -405,10 +403,10 @@ fn named_introspection() { run_type_info_query(doc, |type_info, fields| { assert_eq!( - type_info.get("name"), + type_info.get_field_value("name"), Some(&Value::string("ANamedInputObject")) ); - assert_eq!(type_info.get("description"), Some(&Value::null())); + assert_eq!(type_info.get_field_value("description"), Some(&Value::null())); assert_eq!(fields.len(), 1); @@ -461,9 +459,9 @@ fn description_introspection() { "#; run_type_info_query(doc, |type_info, fields| { - assert_eq!(type_info.get("name"), Some(&Value::string("Description"))); + assert_eq!(type_info.get_field_value("name"), Some(&Value::string("Description"))); assert_eq!( - type_info.get("description"), + type_info.get_field_value("description"), Some(&Value::string("Description for the input object")) ); @@ -519,10 +517,10 @@ fn field_description_introspection() { run_type_info_query(doc, |type_info, fields| { assert_eq!( - type_info.get("name"), + type_info.get_field_value("name"), Some(&Value::string("FieldDescription")) ); - assert_eq!(type_info.get("description"), Some(&Value::null())); + assert_eq!(type_info.get_field_value("description"), Some(&Value::null())); assert_eq!(fields.len(), 2); @@ -597,7 +595,7 @@ fn field_with_defaults_introspection() { run_type_info_query(doc, |type_info, fields| { assert_eq!( - type_info.get("name"), + type_info.get_field_value("name"), Some(&Value::string("FieldWithDefaults")) ); diff --git a/juniper/src/executor_tests/introspection/mod.rs b/juniper/src/executor_tests/introspection/mod.rs index 4351745e..ef073173 100644 --- a/juniper/src/executor_tests/introspection/mod.rs +++ b/juniper/src/executor_tests/introspection/mod.rs @@ -126,21 +126,39 @@ fn enum_introspection() { let type_info = result .as_object_value() .expect("Result is not an object") - .get("__type") + .get_field_value("__type") .expect("__type field missing") .as_object_value() .expect("__type field not an object value"); - assert_eq!(type_info.get("name"), Some(&Value::string("SampleEnum"))); - assert_eq!(type_info.get("kind"), Some(&Value::string("ENUM"))); - assert_eq!(type_info.get("description"), Some(&Value::null())); - assert_eq!(type_info.get("interfaces"), Some(&Value::null())); - assert_eq!(type_info.get("possibleTypes"), Some(&Value::null())); - assert_eq!(type_info.get("inputFields"), Some(&Value::null())); - assert_eq!(type_info.get("ofType"), Some(&Value::null())); + assert_eq!( + type_info.get_field_value("name"), + Some(&Value::string("SampleEnum")) + ); + assert_eq!( + type_info.get_field_value("kind"), + Some(&Value::string("ENUM")) + ); + assert_eq!( + type_info.get_field_value("description"), + Some(&Value::null()) + ); + assert_eq!( + type_info.get_field_value("interfaces"), + Some(&Value::null()) + ); + assert_eq!( + type_info.get_field_value("possibleTypes"), + Some(&Value::null()) + ); + assert_eq!( + type_info.get_field_value("inputFields"), + Some(&Value::null()) + ); + assert_eq!(type_info.get_field_value("ofType"), Some(&Value::null())); let values = type_info - .get("enumValues") + .get_field_value("enumValues") .expect("enumValues field missing") .as_list_value() .expect("enumValues not a list"); @@ -219,27 +237,39 @@ fn interface_introspection() { let type_info = result .as_object_value() .expect("Result is not an object") - .get("__type") + .get_field_value("__type") .expect("__type field missing") .as_object_value() .expect("__type field not an object value"); assert_eq!( - type_info.get("name"), + type_info.get_field_value("name"), Some(&Value::string("SampleInterface")) ); - assert_eq!(type_info.get("kind"), Some(&Value::string("INTERFACE"))); assert_eq!( - type_info.get("description"), + type_info.get_field_value("kind"), + Some(&Value::string("INTERFACE")) + ); + assert_eq!( + type_info.get_field_value("description"), Some(&Value::string("A sample interface")) ); - assert_eq!(type_info.get("interfaces"), Some(&Value::null())); - assert_eq!(type_info.get("enumValues"), Some(&Value::null())); - assert_eq!(type_info.get("inputFields"), Some(&Value::null())); - assert_eq!(type_info.get("ofType"), Some(&Value::null())); + assert_eq!( + type_info.get_field_value("interfaces"), + Some(&Value::null()) + ); + assert_eq!( + type_info.get_field_value("enumValues"), + Some(&Value::null()) + ); + assert_eq!( + type_info.get_field_value("inputFields"), + Some(&Value::null()) + ); + assert_eq!(type_info.get_field_value("ofType"), Some(&Value::null())); let possible_types = type_info - .get("possibleTypes") + .get_field_value("possibleTypes") .expect("possibleTypes field missing") .as_list_value() .expect("possibleTypes not a list"); @@ -251,7 +281,7 @@ fn interface_introspection() { ))); let fields = type_info - .get("fields") + .get_field_value("fields") .expect("fields field missing") .as_list_value() .expect("fields field not an object value"); @@ -353,32 +383,47 @@ fn object_introspection() { let type_info = result .as_object_value() .expect("Result is not an object") - .get("__type") + .get_field_value("__type") .expect("__type field missing") .as_object_value() .expect("__type field not an object value"); - assert_eq!(type_info.get("name"), Some(&Value::string("Root"))); - assert_eq!(type_info.get("kind"), Some(&Value::string("OBJECT"))); assert_eq!( - type_info.get("description"), + type_info.get_field_value("name"), + Some(&Value::string("Root")) + ); + assert_eq!( + type_info.get_field_value("kind"), + Some(&Value::string("OBJECT")) + ); + assert_eq!( + type_info.get_field_value("description"), Some(&Value::string("The root query object in the schema")) ); assert_eq!( - type_info.get("interfaces"), + type_info.get_field_value("interfaces"), Some(&Value::list(vec![Value::object( vec![("name", Value::string("SampleInterface"))] .into_iter() .collect(), )])) ); - assert_eq!(type_info.get("enumValues"), Some(&Value::null())); - assert_eq!(type_info.get("inputFields"), Some(&Value::null())); - assert_eq!(type_info.get("ofType"), Some(&Value::null())); - assert_eq!(type_info.get("possibleTypes"), Some(&Value::null())); + assert_eq!( + type_info.get_field_value("enumValues"), + Some(&Value::null()) + ); + assert_eq!( + type_info.get_field_value("inputFields"), + Some(&Value::null()) + ); + assert_eq!(type_info.get_field_value("ofType"), Some(&Value::null())); + assert_eq!( + type_info.get_field_value("possibleTypes"), + Some(&Value::null()) + ); let fields = type_info - .get("fields") + .get_field_value("fields") .expect("fields field missing") .as_list_value() .expect("fields field not an object value"); @@ -538,7 +583,7 @@ fn scalar_introspection() { let type_info = result .as_object_value() .expect("Result is not an object") - .get("__type") + .get_field_value("__type") .expect("__type field missing"); assert_eq!( diff --git a/juniper/src/executor_tests/variables.rs b/juniper/src/executor_tests/variables.rs index f76c288e..83a9db5f 100644 --- a/juniper/src/executor_tests/variables.rs +++ b/juniper/src/executor_tests/variables.rs @@ -1,12 +1,10 @@ -use indexmap::IndexMap; - use ast::InputValue; use executor::Variables; use parser::SourcePosition; use schema::model::RootNode; use types::scalars::EmptyMutation; use validation::RuleError; -use value::Value; +use value::{Value, Object}; use GraphQLError::ValidationError; #[derive(Debug)] @@ -116,7 +114,7 @@ graphql_object!(TestType: () |&self| { fn run_variable_query(query: &str, vars: Variables, f: F) where - F: Fn(&IndexMap) -> (), + F: Fn(&Object) -> (), { let schema = RootNode::new(TestType, EmptyMutation::<()>::new()); @@ -133,7 +131,7 @@ where fn run_query(query: &str, f: F) where - F: Fn(&IndexMap) -> (), + F: Fn(&Object) -> (), { run_variable_query(query, Variables::new(), f); } @@ -144,7 +142,7 @@ fn inline_complex_input() { r#"{ fieldWithObjectInput(input: {a: "foo", b: ["bar"], c: "baz"}) }"#, |result| { assert_eq!( - result.get("fieldWithObjectInput"), + result.get_field_value("fieldWithObjectInput"), Some(&Value::string(r#"Some(TestInputObject { a: Some("foo"), b: Some([Some("bar")]), c: "baz", d: None })"#))); }, ); @@ -156,7 +154,7 @@ fn inline_parse_single_value_to_list() { r#"{ fieldWithObjectInput(input: {a: "foo", b: "bar", c: "baz"}) }"#, |result| { assert_eq!( - result.get("fieldWithObjectInput"), + result.get_field_value("fieldWithObjectInput"), Some(&Value::string(r#"Some(TestInputObject { a: Some("foo"), b: Some([Some("bar")]), c: "baz", d: None })"#))); }, ); @@ -168,7 +166,7 @@ fn inline_runs_from_input_value_on_scalar() { r#"{ fieldWithObjectInput(input: {c: "baz", d: "SerializedValue"}) }"#, |result| { assert_eq!( - result.get("fieldWithObjectInput"), + result.get_field_value("fieldWithObjectInput"), Some(&Value::string(r#"Some(TestInputObject { a: None, b: None, c: "baz", d: Some(TestComplexScalar) })"#))); }, ); @@ -192,7 +190,7 @@ fn variable_complex_input() { .collect(), |result| { assert_eq!( - result.get("fieldWithObjectInput"), + result.get_field_value("fieldWithObjectInput"), Some(&Value::string(r#"Some(TestInputObject { a: Some("foo"), b: Some([Some("bar")]), c: "baz", d: None })"#))); }, ); @@ -216,7 +214,7 @@ fn variable_parse_single_value_to_list() { .collect(), |result| { assert_eq!( - result.get("fieldWithObjectInput"), + result.get_field_value("fieldWithObjectInput"), Some(&Value::string(r#"Some(TestInputObject { a: Some("foo"), b: Some([Some("bar")]), c: "baz", d: None })"#))); }, ); @@ -239,7 +237,7 @@ fn variable_runs_from_input_value_on_scalar() { .collect(), |result| { assert_eq!( - result.get("fieldWithObjectInput"), + result.get_field_value("fieldWithObjectInput"), Some(&Value::string(r#"Some(TestInputObject { a: None, b: None, c: "baz", d: Some(TestComplexScalar) })"#))); }, ); @@ -387,7 +385,7 @@ fn variable_error_on_additional_field() { fn allow_nullable_inputs_to_be_omitted() { run_query(r#"{ fieldWithNullableStringInput }"#, |result| { assert_eq!( - result.get("fieldWithNullableStringInput"), + result.get_field_value("fieldWithNullableStringInput"), Some(&Value::string(r#"None"#)) ); }); @@ -399,7 +397,7 @@ fn allow_nullable_inputs_to_be_omitted_in_variable() { r#"query q($value: String) { fieldWithNullableStringInput(input: $value) }"#, |result| { assert_eq!( - result.get("fieldWithNullableStringInput"), + result.get_field_value("fieldWithNullableStringInput"), Some(&Value::string(r#"None"#)) ); }, @@ -412,7 +410,7 @@ fn allow_nullable_inputs_to_be_explicitly_null() { r#"{ fieldWithNullableStringInput(input: null) }"#, |result| { assert_eq!( - result.get("fieldWithNullableStringInput"), + result.get_field_value("fieldWithNullableStringInput"), Some(&Value::string(r#"None"#)) ); }, @@ -428,7 +426,7 @@ fn allow_nullable_inputs_to_be_set_to_null_in_variable() { .collect(), |result| { assert_eq!( - result.get("fieldWithNullableStringInput"), + result.get_field_value("fieldWithNullableStringInput"), Some(&Value::string(r#"None"#)) ); }, @@ -444,7 +442,7 @@ fn allow_nullable_inputs_to_be_set_to_value_in_variable() { .collect(), |result| { assert_eq!( - result.get("fieldWithNullableStringInput"), + result.get_field_value("fieldWithNullableStringInput"), Some(&Value::string(r#"Some("a")"#)) ); }, @@ -457,7 +455,7 @@ fn allow_nullable_inputs_to_be_set_to_value_directly() { r#"{ fieldWithNullableStringInput(input: "a") }"#, |result| { assert_eq!( - result.get("fieldWithNullableStringInput"), + result.get_field_value("fieldWithNullableStringInput"), Some(&Value::string(r#"Some("a")"#)) ); }, @@ -511,7 +509,7 @@ fn allow_non_nullable_inputs_to_be_set_to_value_in_variable() { .collect(), |result| { assert_eq!( - result.get("fieldWithNonNullableStringInput"), + result.get_field_value("fieldWithNonNullableStringInput"), Some(&Value::string(r#""a""#)) ); }, @@ -524,7 +522,7 @@ fn allow_non_nullable_inputs_to_be_set_to_value_directly() { r#"{ fieldWithNonNullableStringInput(input: "a") }"#, |result| { assert_eq!( - result.get("fieldWithNonNullableStringInput"), + result.get_field_value("fieldWithNonNullableStringInput"), Some(&Value::string(r#""a""#)) ); }, @@ -539,7 +537,7 @@ fn allow_lists_to_be_null() { .into_iter() .collect(), |result| { - assert_eq!(result.get("list"), Some(&Value::string(r#"None"#))); + assert_eq!(result.get_field_value("list"), Some(&Value::string(r#"None"#))); }, ); } @@ -555,7 +553,7 @@ fn allow_lists_to_contain_values() { .collect(), |result| { assert_eq!( - result.get("list"), + result.get_field_value("list"), Some(&Value::string(r#"Some([Some("A")])"#)) ); }, @@ -577,7 +575,7 @@ fn allow_lists_to_contain_null() { .collect(), |result| { assert_eq!( - result.get("list"), + result.get_field_value("list"), Some(&Value::string(r#"Some([Some("A"), None, Some("B")])"#)) ); }, @@ -614,7 +612,7 @@ fn allow_non_null_lists_to_contain_values() { )].into_iter() .collect(), |result| { - assert_eq!(result.get("nnList"), Some(&Value::string(r#"[Some("A")]"#))); + assert_eq!(result.get_field_value("nnList"), Some(&Value::string(r#"[Some("A")]"#))); }, ); } @@ -633,7 +631,7 @@ fn allow_non_null_lists_to_contain_null() { .collect(), |result| { assert_eq!( - result.get("nnList"), + result.get_field_value("nnList"), Some(&Value::string(r#"[Some("A"), None, Some("B")]"#)) ); }, @@ -648,7 +646,7 @@ fn allow_lists_of_non_null_to_be_null() { .into_iter() .collect(), |result| { - assert_eq!(result.get("listNn"), Some(&Value::string(r#"None"#))); + assert_eq!(result.get_field_value("listNn"), Some(&Value::string(r#"None"#))); }, ); } @@ -663,7 +661,7 @@ fn allow_lists_of_non_null_to_contain_values() { )].into_iter() .collect(), |result| { - assert_eq!(result.get("listNn"), Some(&Value::string(r#"Some(["A"])"#))); + assert_eq!(result.get_field_value("listNn"), Some(&Value::string(r#"Some(["A"])"#))); }, ); } @@ -748,7 +746,7 @@ fn allow_non_null_lists_of_non_null_to_contain_values() { )].into_iter() .collect(), |result| { - assert_eq!(result.get("nnListNn"), Some(&Value::string(r#"["A"]"#))); + assert_eq!(result.get_field_value("nnListNn"), Some(&Value::string(r#"["A"]"#))); }, ); } @@ -799,7 +797,7 @@ fn does_not_allow_unknown_types_to_be_used_as_values() { fn default_argument_when_not_provided() { run_query(r#"{ fieldWithDefaultArgumentValue }"#, |result| { assert_eq!( - result.get("fieldWithDefaultArgumentValue"), + result.get_field_value("fieldWithDefaultArgumentValue"), Some(&Value::string(r#""Hello World""#)) ); }); @@ -811,7 +809,7 @@ fn default_argument_when_nullable_variable_not_provided() { r#"query q($input: String) { fieldWithDefaultArgumentValue(input: $input) }"#, |result| { assert_eq!( - result.get("fieldWithDefaultArgumentValue"), + result.get_field_value("fieldWithDefaultArgumentValue"), Some(&Value::string(r#""Hello World""#)) ); }, @@ -827,7 +825,7 @@ fn default_argument_when_nullable_variable_set_to_null() { .collect(), |result| { assert_eq!( - result.get("fieldWithDefaultArgumentValue"), + result.get_field_value("fieldWithDefaultArgumentValue"), Some(&Value::string(r#""Hello World""#)) ); }, @@ -838,14 +836,14 @@ fn default_argument_when_nullable_variable_set_to_null() { fn nullable_input_object_arguments_successful_without_variables() { run_query(r#"{ exampleInput(arg: {a: "abc", b: 123}) }"#, |result| { assert_eq!( - result.get("exampleInput"), + result.get_field_value("exampleInput"), Some(&Value::string(r#"a: Some("abc"), b: 123"#)) ); }); run_query(r#"{ exampleInput(arg: {a: null, b: 1}) }"#, |result| { assert_eq!( - result.get("exampleInput"), + result.get_field_value("exampleInput"), Some(&Value::string(r#"a: None, b: 1"#)) ); }); @@ -860,7 +858,7 @@ fn nullable_input_object_arguments_successful_with_variables() { .collect(), |result| { assert_eq!( - result.get("exampleInput"), + result.get_field_value("exampleInput"), Some(&Value::string(r#"a: None, b: 123"#)) ); }, @@ -873,7 +871,7 @@ fn nullable_input_object_arguments_successful_with_variables() { .collect(), |result| { assert_eq!( - result.get("exampleInput"), + result.get_field_value("exampleInput"), Some(&Value::string(r#"a: None, b: 1"#)) ); }, @@ -884,7 +882,7 @@ fn nullable_input_object_arguments_successful_with_variables() { vec![].into_iter().collect(), |result| { assert_eq!( - result.get("exampleInput"), + result.get_field_value("exampleInput"), Some(&Value::string(r#"a: None, b: 1"#)) ); }, @@ -969,7 +967,7 @@ fn does_not_allow_null_variable_for_required_field() { fn input_object_with_default_values() { run_query(r#"{ inputWithDefaults(arg: {a: 1}) }"#, |result| { assert_eq!( - result.get("inputWithDefaults"), + result.get_field_value("inputWithDefaults"), Some(&Value::string(r#"a: 1"#)) ); }); @@ -981,7 +979,7 @@ fn input_object_with_default_values() { .collect(), |result| { assert_eq!( - result.get("inputWithDefaults"), + result.get_field_value("inputWithDefaults"), Some(&Value::string(r#"a: 1"#)) ); }, @@ -992,7 +990,7 @@ fn input_object_with_default_values() { vec![].into_iter().collect(), |result| { assert_eq!( - result.get("inputWithDefaults"), + result.get_field_value("inputWithDefaults"), Some(&Value::string(r#"a: 1"#)) ); }, @@ -1005,7 +1003,7 @@ fn input_object_with_default_values() { .collect(), |result| { assert_eq!( - result.get("inputWithDefaults"), + result.get_field_value("inputWithDefaults"), Some(&Value::string(r#"a: 2"#)) ); }, @@ -1024,7 +1022,7 @@ mod integers { .collect(), |result| { assert_eq!( - result.get("integerInput"), + result.get_field_value("integerInput"), Some(&Value::string(r#"value: 1"#)) ); }, @@ -1037,7 +1035,7 @@ mod integers { .collect(), |result| { assert_eq!( - result.get("integerInput"), + result.get_field_value("integerInput"), Some(&Value::string(r#"value: -1"#)) ); }, @@ -1097,7 +1095,7 @@ mod floats { .collect(), |result| { assert_eq!( - result.get("floatInput"), + result.get_field_value("floatInput"), Some(&Value::string(r#"value: 10"#)) ); }, @@ -1113,7 +1111,7 @@ mod floats { .collect(), |result| { assert_eq!( - result.get("floatInput"), + result.get_field_value("floatInput"), Some(&Value::string(r#"value: -1"#)) ); }, diff --git a/juniper/src/integrations/serde.rs b/juniper/src/integrations/serde.rs index 12952997..98fdd76b 100644 --- a/juniper/src/integrations/serde.rs +++ b/juniper/src/integrations/serde.rs @@ -8,7 +8,7 @@ use ast::InputValue; use executor::ExecutionError; use parser::{ParseError, SourcePosition, Spanning}; use validation::RuleError; -use {GraphQLError, Value}; +use {GraphQLError, Object, Value}; #[derive(Serialize)] struct SerializeHelper { @@ -250,6 +250,22 @@ impl<'a> ser::Serialize for Spanning> { } } +impl ser::Serialize for Object { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + let mut map = serializer.serialize_map(Some(self.field_count()))?; + + for &(ref f, ref v) in self.iter() { + map.serialize_key(f)?; + map.serialize_value(v)?; + } + + map.end() + } +} + impl ser::Serialize for Value { fn serialize(&self, serializer: S) -> Result where diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index cb7b470a..a1d88f6e 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -162,7 +162,7 @@ pub use schema::model::RootNode; pub use types::base::{Arguments, GraphQLType, TypeKind}; pub use types::scalars::{EmptyMutation, ID}; pub use validation::RuleError; -pub use value::Value; +pub use value::{Value, Object}; pub use schema::meta; diff --git a/juniper/src/macros/tests/args.rs b/juniper/src/macros/tests/args.rs index ec80170d..b714cf34 100644 --- a/juniper/src/macros/tests/args.rs +++ b/juniper/src/macros/tests/args.rs @@ -112,13 +112,13 @@ where let type_info = result .as_object_value() .expect("Result is not an object") - .get("__type") + .get_field_value("__type") .expect("__type field missing") .as_object_value() .expect("__type field not an object value"); let fields = type_info - .get("fields") + .get_field_value("fields") .expect("fields field missing") .as_list_value() .expect("fields not a list"); @@ -128,7 +128,7 @@ where .filter(|f| { f.as_object_value() .expect("Field not an object") - .get("name") + .get_field_value("name") .expect("name field missing from field") .as_string_value() .expect("name is not a string") == field_name @@ -141,7 +141,7 @@ where println!("Field: {:?}", field); let args = field - .get("args") + .get_field_value("args") .expect("args missing from field") .as_list_value() .expect("args is not a list"); diff --git a/juniper/src/macros/tests/field.rs b/juniper/src/macros/tests/field.rs index e902f488..2c691106 100644 --- a/juniper/src/macros/tests/field.rs +++ b/juniper/src/macros/tests/field.rs @@ -1,10 +1,8 @@ -use indexmap::IndexMap; - use ast::InputValue; use executor::FieldResult; use schema::model::RootNode; use types::scalars::EmptyMutation; -use value::Value; +use value::{Object, Value}; struct Interface; struct Root; @@ -59,7 +57,7 @@ graphql_interface!(Interface: () |&self| { fn run_field_info_query(type_name: &str, field_name: &str, f: F) where - F: Fn(&IndexMap) -> (), + F: Fn(&Object) -> (), { let doc = r#" query ($typeName: String!) { @@ -87,13 +85,13 @@ where let type_info = result .as_object_value() .expect("Result is not an object") - .get("__type") + .get_field_value("__type") .expect("__type field missing") .as_object_value() .expect("__type field not an object value"); let fields = type_info - .get("fields") + .get_field_value("fields") .expect("fields field missing") .as_list_value() .expect("fields not a list"); @@ -103,7 +101,7 @@ where .filter(|f| { f.as_object_value() .expect("Field not an object") - .get("name") + .get_field_value("name") .expect("name field missing from field") .as_string_value() .expect("name is not a string") == field_name @@ -121,57 +119,78 @@ where #[test] fn introspect_object_field_simple() { run_field_info_query("Root", "simple", |field| { - assert_eq!(field.get("name"), Some(&Value::string("simple"))); - assert_eq!(field.get("description"), Some(&Value::null())); - assert_eq!(field.get("isDeprecated"), Some(&Value::boolean(false))); - assert_eq!(field.get("deprecationReason"), Some(&Value::null())); + assert_eq!( + field.get_field_value("name"), + Some(&Value::string("simple")) + ); + assert_eq!(field.get_field_value("description"), Some(&Value::null())); + assert_eq!( + field.get_field_value("isDeprecated"), + Some(&Value::boolean(false)) + ); + assert_eq!( + field.get_field_value("deprecationReason"), + Some(&Value::null()) + ); }); } #[test] fn introspect_interface_field_simple() { run_field_info_query("Interface", "simple", |field| { - assert_eq!(field.get("name"), Some(&Value::string("simple"))); - assert_eq!(field.get("description"), Some(&Value::null())); - assert_eq!(field.get("isDeprecated"), Some(&Value::boolean(false))); - assert_eq!(field.get("deprecationReason"), Some(&Value::null())); + assert_eq!( + field.get_field_value("name"), + Some(&Value::string("simple")) + ); + assert_eq!(field.get_field_value("description"), Some(&Value::null())); + assert_eq!( + field.get_field_value("isDeprecated"), + Some(&Value::boolean(false)) + ); + assert_eq!( + field.get_field_value("deprecationReason"), + Some(&Value::null()) + ); }); } #[test] fn introspect_object_field_description() { run_field_info_query("Root", "description", |field| { - assert_eq!(field.get("name"), Some(&Value::string("description"))); assert_eq!( - field.get("description"), + field.get_field_value("name"), + Some(&Value::string("description")) + ); + assert_eq!( + field.get_field_value("description"), Some(&Value::string("Field description")) ); - assert_eq!(field.get("isDeprecated"), Some(&Value::boolean(false))); - assert_eq!(field.get("deprecationReason"), Some(&Value::null())); + assert_eq!(field.get_field_value("isDeprecated"), Some(&Value::boolean(false))); + assert_eq!(field.get_field_value("deprecationReason"), Some(&Value::null())); }); } #[test] fn introspect_interface_field_description() { run_field_info_query("Interface", "description", |field| { - assert_eq!(field.get("name"), Some(&Value::string("description"))); + assert_eq!(field.get_field_value("name"), Some(&Value::string("description"))); assert_eq!( - field.get("description"), + field.get_field_value("description"), Some(&Value::string("Field description")) ); - assert_eq!(field.get("isDeprecated"), Some(&Value::boolean(false))); - assert_eq!(field.get("deprecationReason"), Some(&Value::null())); + assert_eq!(field.get_field_value("isDeprecated"), Some(&Value::boolean(false))); + assert_eq!(field.get_field_value("deprecationReason"), Some(&Value::null())); }); } #[test] fn introspect_object_field_deprecated() { run_field_info_query("Root", "deprecated", |field| { - assert_eq!(field.get("name"), Some(&Value::string("deprecated"))); - assert_eq!(field.get("description"), Some(&Value::null())); - assert_eq!(field.get("isDeprecated"), Some(&Value::boolean(true))); + assert_eq!(field.get_field_value("name"), Some(&Value::string("deprecated"))); + assert_eq!(field.get_field_value("description"), Some(&Value::null())); + assert_eq!(field.get_field_value("isDeprecated"), Some(&Value::boolean(true))); assert_eq!( - field.get("deprecationReason"), + field.get_field_value("deprecationReason"), Some(&Value::string("Deprecation reason")) ); }); @@ -180,11 +199,11 @@ fn introspect_object_field_deprecated() { #[test] fn introspect_interface_field_deprecated() { run_field_info_query("Interface", "deprecated", |field| { - assert_eq!(field.get("name"), Some(&Value::string("deprecated"))); - assert_eq!(field.get("description"), Some(&Value::null())); - assert_eq!(field.get("isDeprecated"), Some(&Value::boolean(true))); + assert_eq!(field.get_field_value("name"), Some(&Value::string("deprecated"))); + assert_eq!(field.get_field_value("description"), Some(&Value::null())); + assert_eq!(field.get_field_value("isDeprecated"), Some(&Value::boolean(true))); assert_eq!( - field.get("deprecationReason"), + field.get_field_value("deprecationReason"), Some(&Value::string("Deprecation reason")) ); }); @@ -193,14 +212,14 @@ fn introspect_interface_field_deprecated() { #[test] fn introspect_object_field_deprecated_descr() { run_field_info_query("Root", "deprecatedDescr", |field| { - assert_eq!(field.get("name"), Some(&Value::string("deprecatedDescr"))); + assert_eq!(field.get_field_value("name"), Some(&Value::string("deprecatedDescr"))); assert_eq!( - field.get("description"), + field.get_field_value("description"), Some(&Value::string("Field description")) ); - assert_eq!(field.get("isDeprecated"), Some(&Value::boolean(true))); + assert_eq!(field.get_field_value("isDeprecated"), Some(&Value::boolean(true))); assert_eq!( - field.get("deprecationReason"), + field.get_field_value("deprecationReason"), Some(&Value::string("Deprecation reason")) ); }); @@ -209,14 +228,14 @@ fn introspect_object_field_deprecated_descr() { #[test] fn introspect_interface_field_deprecated_descr() { run_field_info_query("Interface", "deprecatedDescr", |field| { - assert_eq!(field.get("name"), Some(&Value::string("deprecatedDescr"))); + assert_eq!(field.get_field_value("name"), Some(&Value::string("deprecatedDescr"))); assert_eq!( - field.get("description"), + field.get_field_value("description"), Some(&Value::string("Field description")) ); - assert_eq!(field.get("isDeprecated"), Some(&Value::boolean(true))); + assert_eq!(field.get_field_value("isDeprecated"), Some(&Value::boolean(true))); assert_eq!( - field.get("deprecationReason"), + field.get_field_value("deprecationReason"), Some(&Value::string("Deprecation reason")) ); }); diff --git a/juniper/src/macros/tests/interface.rs b/juniper/src/macros/tests/interface.rs index 0c2427f8..7700f25d 100644 --- a/juniper/src/macros/tests/interface.rs +++ b/juniper/src/macros/tests/interface.rs @@ -1,10 +1,9 @@ -use indexmap::IndexMap; use std::marker::PhantomData; use ast::InputValue; use schema::model::RootNode; use types::scalars::EmptyMutation; -use value::Value; +use value::{Value, Object}; /* @@ -130,7 +129,7 @@ graphql_object!(<'a> Root: () as "Root" |&self| { fn run_type_info_query(type_name: &str, f: F) where - F: Fn(&IndexMap, &Vec) -> (), + F: Fn(&Object, &Vec) -> (), { let doc = r#" query ($typeName: String!) { @@ -157,13 +156,13 @@ where let type_info = result .as_object_value() .expect("Result is not an object") - .get("__type") + .get_field_value("__type") .expect("__type field missing") .as_object_value() .expect("__type field not an object value"); let fields = type_info - .get("fields") + .get_field_value("fields") .expect("fields field missing") .as_list_value() .expect("fields field not a list value"); @@ -175,10 +174,10 @@ where fn introspect_custom_name() { run_type_info_query("ACustomNamedInterface", |object, fields| { assert_eq!( - object.get("name"), + object.get_field_value("name"), Some(&Value::string("ACustomNamedInterface")) ); - assert_eq!(object.get("description"), Some(&Value::null())); + assert_eq!(object.get_field_value("description"), Some(&Value::null())); assert!( fields.contains(&Value::object( @@ -193,8 +192,8 @@ fn introspect_custom_name() { #[test] fn introspect_with_lifetime() { run_type_info_query("WithLifetime", |object, fields| { - assert_eq!(object.get("name"), Some(&Value::string("WithLifetime"))); - assert_eq!(object.get("description"), Some(&Value::null())); + assert_eq!(object.get_field_value("name"), Some(&Value::string("WithLifetime"))); + assert_eq!(object.get_field_value("description"), Some(&Value::null())); assert!( fields.contains(&Value::object( @@ -209,8 +208,8 @@ fn introspect_with_lifetime() { #[test] fn introspect_with_generics() { run_type_info_query("WithGenerics", |object, fields| { - assert_eq!(object.get("name"), Some(&Value::string("WithGenerics"))); - assert_eq!(object.get("description"), Some(&Value::null())); + assert_eq!(object.get_field_value("name"), Some(&Value::string("WithGenerics"))); + assert_eq!(object.get_field_value("description"), Some(&Value::null())); assert!( fields.contains(&Value::object( @@ -225,9 +224,9 @@ fn introspect_with_generics() { #[test] fn introspect_description_first() { run_type_info_query("DescriptionFirst", |object, fields| { - assert_eq!(object.get("name"), Some(&Value::string("DescriptionFirst"))); + assert_eq!(object.get_field_value("name"), Some(&Value::string("DescriptionFirst"))); assert_eq!( - object.get("description"), + object.get_field_value("description"), Some(&Value::string("A description")) ); @@ -244,9 +243,9 @@ fn introspect_description_first() { #[test] fn introspect_fields_first() { run_type_info_query("FieldsFirst", |object, fields| { - assert_eq!(object.get("name"), Some(&Value::string("FieldsFirst"))); + assert_eq!(object.get_field_value("name"), Some(&Value::string("FieldsFirst"))); assert_eq!( - object.get("description"), + object.get_field_value("description"), Some(&Value::string("A description")) ); @@ -263,9 +262,9 @@ fn introspect_fields_first() { #[test] fn introspect_interfaces_first() { run_type_info_query("InterfacesFirst", |object, fields| { - assert_eq!(object.get("name"), Some(&Value::string("InterfacesFirst"))); + assert_eq!(object.get_field_value("name"), Some(&Value::string("InterfacesFirst"))); assert_eq!( - object.get("description"), + object.get_field_value("description"), Some(&Value::string("A description")) ); @@ -283,11 +282,11 @@ fn introspect_interfaces_first() { fn introspect_commas_with_trailing() { run_type_info_query("CommasWithTrailing", |object, fields| { assert_eq!( - object.get("name"), + object.get_field_value("name"), Some(&Value::string("CommasWithTrailing")) ); assert_eq!( - object.get("description"), + object.get_field_value("description"), Some(&Value::string("A description")) ); @@ -304,9 +303,9 @@ fn introspect_commas_with_trailing() { #[test] fn introspect_commas_on_meta() { run_type_info_query("CommasOnMeta", |object, fields| { - assert_eq!(object.get("name"), Some(&Value::string("CommasOnMeta"))); + assert_eq!(object.get_field_value("name"), Some(&Value::string("CommasOnMeta"))); assert_eq!( - object.get("description"), + object.get_field_value("description"), Some(&Value::string("A description")) ); @@ -324,11 +323,11 @@ fn introspect_commas_on_meta() { fn introspect_resolvers_with_trailing_comma() { run_type_info_query("ResolversWithTrailingComma", |object, fields| { assert_eq!( - object.get("name"), + object.get_field_value("name"), Some(&Value::string("ResolversWithTrailingComma")) ); assert_eq!( - object.get("description"), + object.get_field_value("description"), Some(&Value::string("A description")) ); diff --git a/juniper/src/macros/tests/object.rs b/juniper/src/macros/tests/object.rs index 16b1430c..54d2da4b 100644 --- a/juniper/src/macros/tests/object.rs +++ b/juniper/src/macros/tests/object.rs @@ -1,11 +1,10 @@ -use indexmap::IndexMap; use std::marker::PhantomData; use ast::InputValue; use executor::{Context, FieldResult}; use schema::model::RootNode; use types::scalars::EmptyMutation; -use value::Value; +use value::{Object, Value}; /* @@ -144,7 +143,7 @@ graphql_object!(<'a> Root: InnerContext as "Root" |&self| { fn run_type_info_query(type_name: &str, f: F) where - F: Fn(&IndexMap, &Vec) -> (), + F: Fn(&Object, &Vec) -> (), { let doc = r#" query ($typeName: String!) { @@ -184,13 +183,13 @@ where let type_info = result .as_object_value() .expect("Result is not an object") - .get("__type") + .get_field_value("__type") .expect("__type field missing") .as_object_value() .expect("__type field not an object value"); let fields = type_info - .get("fields") + .get_field_value("fields") .expect("fields field missing") .as_list_value() .expect("fields field not a list value"); @@ -201,12 +200,18 @@ where #[test] fn introspect_custom_name() { run_type_info_query("ACustomNamedType", |object, fields| { - assert_eq!(object.get("name"), Some(&Value::string("ACustomNamedType"))); - assert_eq!(object.get("description"), Some(&Value::null())); - assert_eq!(object.get("interfaces"), Some(&Value::list(vec![]))); + assert_eq!( + object.get_field_value("name"), + Some(&Value::string("ACustomNamedType")) + ); + assert_eq!(object.get_field_value("description"), Some(&Value::null())); + assert_eq!( + object.get_field_value("interfaces"), + Some(&Value::list(vec![])) + ); assert!(fields.contains(&graphql_value!({ - "name": "simple", + "name": "simple", "type": { "kind": "NON_NULL", "name": None, "ofType": { "kind": "SCALAR", "name": "Int" } } }))); }); @@ -215,12 +220,18 @@ fn introspect_custom_name() { #[test] fn introspect_with_lifetime() { run_type_info_query("WithLifetime", |object, fields| { - assert_eq!(object.get("name"), Some(&Value::string("WithLifetime"))); - assert_eq!(object.get("description"), Some(&Value::null())); - assert_eq!(object.get("interfaces"), Some(&Value::list(vec![]))); + assert_eq!( + object.get_field_value("name"), + Some(&Value::string("WithLifetime")) + ); + assert_eq!(object.get_field_value("description"), Some(&Value::null())); + assert_eq!( + object.get_field_value("interfaces"), + Some(&Value::list(vec![])) + ); assert!(fields.contains(&graphql_value!({ - "name": "simple", + "name": "simple", "type": { "kind": "NON_NULL", "name": None, "ofType": { "kind": "SCALAR", "name": "Int" } } }))); }); @@ -229,12 +240,18 @@ fn introspect_with_lifetime() { #[test] fn introspect_with_generics() { run_type_info_query("WithGenerics", |object, fields| { - assert_eq!(object.get("name"), Some(&Value::string("WithGenerics"))); - assert_eq!(object.get("description"), Some(&Value::null())); - assert_eq!(object.get("interfaces"), Some(&Value::list(vec![]))); + assert_eq!( + object.get_field_value("name"), + Some(&Value::string("WithGenerics")) + ); + assert_eq!(object.get_field_value("description"), Some(&Value::null())); + assert_eq!( + object.get_field_value("interfaces"), + Some(&Value::list(vec![])) + ); assert!(fields.contains(&graphql_value!({ - "name": "simple", + "name": "simple", "type": { "kind": "NON_NULL", "name": None, "ofType": { "kind": "SCALAR", "name": "Int" } } }))); }); @@ -243,13 +260,16 @@ fn introspect_with_generics() { #[test] fn introspect_description_first() { run_type_info_query("DescriptionFirst", |object, fields| { - assert_eq!(object.get("name"), Some(&Value::string("DescriptionFirst"))); assert_eq!( - object.get("description"), + object.get_field_value("name"), + Some(&Value::string("DescriptionFirst")) + ); + assert_eq!( + object.get_field_value("description"), Some(&Value::string("A description")) ); assert_eq!( - object.get("interfaces"), + object.get_field_value("interfaces"), Some(&Value::list(vec![Value::object( vec![ ("name", Value::string("Interface")), @@ -260,7 +280,7 @@ fn introspect_description_first() { ); assert!(fields.contains(&graphql_value!({ - "name": "simple", + "name": "simple", "type": { "kind": "NON_NULL", "name": None, "ofType": { "kind": "SCALAR", "name": "Int" } } }))); }); @@ -269,13 +289,16 @@ fn introspect_description_first() { #[test] fn introspect_fields_first() { run_type_info_query("FieldsFirst", |object, fields| { - assert_eq!(object.get("name"), Some(&Value::string("FieldsFirst"))); assert_eq!( - object.get("description"), + object.get_field_value("name"), + Some(&Value::string("FieldsFirst")) + ); + assert_eq!( + object.get_field_value("description"), Some(&Value::string("A description")) ); assert_eq!( - object.get("interfaces"), + object.get_field_value("interfaces"), Some(&Value::list(vec![Value::object( vec![ ("name", Value::string("Interface")), @@ -286,7 +309,7 @@ fn introspect_fields_first() { ); assert!(fields.contains(&graphql_value!({ - "name": "simple", + "name": "simple", "type": { "kind": "NON_NULL", "name": None, "ofType": { "kind": "SCALAR", "name": "Int" } } }))); }); @@ -295,13 +318,16 @@ fn introspect_fields_first() { #[test] fn introspect_interfaces_first() { run_type_info_query("InterfacesFirst", |object, fields| { - assert_eq!(object.get("name"), Some(&Value::string("InterfacesFirst"))); assert_eq!( - object.get("description"), + object.get_field_value("name"), + Some(&Value::string("InterfacesFirst")) + ); + assert_eq!( + object.get_field_value("description"), Some(&Value::string("A description")) ); assert_eq!( - object.get("interfaces"), + object.get_field_value("interfaces"), Some(&Value::list(vec![Value::object( vec![ ("name", Value::string("Interface")), @@ -312,7 +338,7 @@ fn introspect_interfaces_first() { ); assert!(fields.contains(&graphql_value!({ - "name": "simple", + "name": "simple", "type": { "kind": "NON_NULL", "name": None, "ofType": { "kind": "SCALAR", "name": "Int" } } }))); }); @@ -322,15 +348,15 @@ fn introspect_interfaces_first() { fn introspect_commas_with_trailing() { run_type_info_query("CommasWithTrailing", |object, fields| { assert_eq!( - object.get("name"), + object.get_field_value("name"), Some(&Value::string("CommasWithTrailing")) ); assert_eq!( - object.get("description"), + object.get_field_value("description"), Some(&Value::string("A description")) ); assert_eq!( - object.get("interfaces"), + object.get_field_value("interfaces"), Some(&Value::list(vec![Value::object( vec![ ("name", Value::string("Interface")), @@ -341,7 +367,7 @@ fn introspect_commas_with_trailing() { ); assert!(fields.contains(&graphql_value!({ - "name": "simple", + "name": "simple", "type": { "kind": "NON_NULL", "name": None, "ofType": { "kind": "SCALAR", "name": "Int" } } }))); }); @@ -350,13 +376,16 @@ fn introspect_commas_with_trailing() { #[test] fn introspect_commas_on_meta() { run_type_info_query("CommasOnMeta", |object, fields| { - assert_eq!(object.get("name"), Some(&Value::string("CommasOnMeta"))); assert_eq!( - object.get("description"), + object.get_field_value("name"), + Some(&Value::string("CommasOnMeta")) + ); + assert_eq!( + object.get_field_value("description"), Some(&Value::string("A description")) ); assert_eq!( - object.get("interfaces"), + object.get_field_value("interfaces"), Some(&Value::list(vec![Value::object( vec![ ("name", Value::string("Interface")), @@ -367,7 +396,7 @@ fn introspect_commas_on_meta() { ); assert!(fields.contains(&graphql_value!({ - "name": "simple", + "name": "simple", "type": { "kind": "NON_NULL", "name": None, "ofType": { "kind": "SCALAR", "name": "Int" } } }))); }); diff --git a/juniper/src/macros/tests/scalar.rs b/juniper/src/macros/tests/scalar.rs index c7c0ef5a..1818b2d5 100644 --- a/juniper/src/macros/tests/scalar.rs +++ b/juniper/src/macros/tests/scalar.rs @@ -1,9 +1,7 @@ -use indexmap::IndexMap; - use executor::Variables; use schema::model::RootNode; use types::scalars::EmptyMutation; -use value::Value; +use value::{Value, Object}; struct DefaultName(i32); struct OtherOrder(i32); @@ -72,7 +70,7 @@ graphql_object!(Root: () |&self| { fn run_type_info_query(doc: &str, f: F) where - F: Fn(&IndexMap) -> (), + F: Fn(&Object) -> (), { let schema = RootNode::new(Root {}, EmptyMutation::<()>::new()); @@ -86,7 +84,7 @@ where let type_info = result .as_object_value() .expect("Result is not an object") - .get("__type") + .get_field_value("__type") .expect("__type field missing") .as_object_value() .expect("__type field not an object value"); @@ -106,8 +104,8 @@ fn default_name_introspection() { "#; run_type_info_query(doc, |type_info| { - assert_eq!(type_info.get("name"), Some(&Value::string("DefaultName"))); - assert_eq!(type_info.get("description"), Some(&Value::null())); + assert_eq!(type_info.get_field_value("name"), Some(&Value::string("DefaultName"))); + assert_eq!(type_info.get_field_value("description"), Some(&Value::null())); }); } @@ -123,8 +121,8 @@ fn other_order_introspection() { "#; run_type_info_query(doc, |type_info| { - assert_eq!(type_info.get("name"), Some(&Value::string("OtherOrder"))); - assert_eq!(type_info.get("description"), Some(&Value::null())); + assert_eq!(type_info.get_field_value("name"), Some(&Value::string("OtherOrder"))); + assert_eq!(type_info.get_field_value("description"), Some(&Value::null())); }); } @@ -140,8 +138,8 @@ fn named_introspection() { "#; run_type_info_query(doc, |type_info| { - assert_eq!(type_info.get("name"), Some(&Value::string("ANamedScalar"))); - assert_eq!(type_info.get("description"), Some(&Value::null())); + assert_eq!(type_info.get_field_value("name"), Some(&Value::string("ANamedScalar"))); + assert_eq!(type_info.get_field_value("description"), Some(&Value::null())); }); } @@ -158,11 +156,11 @@ fn scalar_description_introspection() { run_type_info_query(doc, |type_info| { assert_eq!( - type_info.get("name"), + type_info.get_field_value("name"), Some(&Value::string("ScalarDescription")) ); assert_eq!( - type_info.get("description"), + type_info.get_field_value("description"), Some(&Value::string("A sample scalar, represented as an integer")) ); }); diff --git a/juniper/src/macros/tests/union.rs b/juniper/src/macros/tests/union.rs index fd1db109..c4850b73 100644 --- a/juniper/src/macros/tests/union.rs +++ b/juniper/src/macros/tests/union.rs @@ -1,10 +1,9 @@ -use indexmap::IndexMap; use std::marker::PhantomData; use ast::InputValue; use schema::model::RootNode; use types::scalars::EmptyMutation; -use value::Value; +use value::{Value, Object}; /* @@ -111,7 +110,7 @@ graphql_object!(<'a> Root: () as "Root" |&self| { fn run_type_info_query(type_name: &str, f: F) where - F: Fn(&IndexMap, &Vec) -> (), + F: Fn(&Object, &Vec) -> (), { let doc = r#" query ($typeName: String!) { @@ -138,13 +137,13 @@ where let type_info = result .as_object_value() .expect("Result is not an object") - .get("__type") + .get_field_value("__type") .expect("__type field missing") .as_object_value() .expect("__type field not an object value"); let possible_types = type_info - .get("possibleTypes") + .get_field_value("possibleTypes") .expect("possibleTypes field missing") .as_list_value() .expect("possibleTypes field not a list value"); @@ -155,8 +154,8 @@ where #[test] fn introspect_custom_name() { run_type_info_query("ACustomNamedUnion", |union, possible_types| { - assert_eq!(union.get("name"), Some(&Value::string("ACustomNamedUnion"))); - assert_eq!(union.get("description"), Some(&Value::null())); + assert_eq!(union.get_field_value("name"), Some(&Value::string("ACustomNamedUnion"))); + assert_eq!(union.get_field_value("description"), Some(&Value::null())); assert!( possible_types.contains(&Value::object( @@ -171,8 +170,8 @@ fn introspect_custom_name() { #[test] fn introspect_with_lifetime() { run_type_info_query("WithLifetime", |union, possible_types| { - assert_eq!(union.get("name"), Some(&Value::string("WithLifetime"))); - assert_eq!(union.get("description"), Some(&Value::null())); + assert_eq!(union.get_field_value("name"), Some(&Value::string("WithLifetime"))); + assert_eq!(union.get_field_value("description"), Some(&Value::null())); assert!( possible_types.contains(&Value::object( @@ -187,8 +186,8 @@ fn introspect_with_lifetime() { #[test] fn introspect_with_generics() { run_type_info_query("WithGenerics", |union, possible_types| { - assert_eq!(union.get("name"), Some(&Value::string("WithGenerics"))); - assert_eq!(union.get("description"), Some(&Value::null())); + assert_eq!(union.get_field_value("name"), Some(&Value::string("WithGenerics"))); + assert_eq!(union.get_field_value("description"), Some(&Value::null())); assert!( possible_types.contains(&Value::object( @@ -203,9 +202,9 @@ fn introspect_with_generics() { #[test] fn introspect_description_first() { run_type_info_query("DescriptionFirst", |union, possible_types| { - assert_eq!(union.get("name"), Some(&Value::string("DescriptionFirst"))); + assert_eq!(union.get_field_value("name"), Some(&Value::string("DescriptionFirst"))); assert_eq!( - union.get("description"), + union.get_field_value("description"), Some(&Value::string("A description")) ); @@ -222,9 +221,9 @@ fn introspect_description_first() { #[test] fn introspect_resolvers_first() { run_type_info_query("ResolversFirst", |union, possible_types| { - assert_eq!(union.get("name"), Some(&Value::string("ResolversFirst"))); + assert_eq!(union.get_field_value("name"), Some(&Value::string("ResolversFirst"))); assert_eq!( - union.get("description"), + union.get_field_value("description"), Some(&Value::string("A description")) ); @@ -242,11 +241,11 @@ fn introspect_resolvers_first() { fn introspect_commas_with_trailing() { run_type_info_query("CommasWithTrailing", |union, possible_types| { assert_eq!( - union.get("name"), + union.get_field_value("name"), Some(&Value::string("CommasWithTrailing")) ); assert_eq!( - union.get("description"), + union.get_field_value("description"), Some(&Value::string("A description")) ); @@ -264,11 +263,11 @@ fn introspect_commas_with_trailing() { fn introspect_resolvers_with_trailing_comma() { run_type_info_query("ResolversWithTrailingComma", |union, possible_types| { assert_eq!( - union.get("name"), + union.get_field_value("name"), Some(&Value::string("ResolversWithTrailingComma")) ); assert_eq!( - union.get("description"), + union.get_field_value("description"), Some(&Value::string("A description")) ); diff --git a/juniper/src/parser/utils.rs b/juniper/src/parser/utils.rs index a1decf68..6f141f25 100644 --- a/juniper/src/parser/utils.rs +++ b/juniper/src/parser/utils.rs @@ -2,7 +2,7 @@ use std::fmt; use std::hash::{Hash, Hasher}; /// A reference to a line and column in an input source file -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)] pub struct SourcePosition { index: usize, line: usize, diff --git a/juniper/src/schema/model.rs b/juniper/src/schema/model.rs index 9e286f0a..7822a093 100644 --- a/juniper/src/schema/model.rs +++ b/juniper/src/schema/model.rs @@ -319,6 +319,7 @@ impl<'a> SchemaType<'a> { } impl<'a> TypeType<'a> { + #[inline] pub fn to_concrete(&self) -> Option<&'a MetaType> { match *self { TypeType::Concrete(t) => Some(t), @@ -326,6 +327,7 @@ impl<'a> TypeType<'a> { } } + #[inline] pub fn innermost_concrete(&self) -> &'a MetaType { match *self { TypeType::Concrete(t) => t, @@ -333,6 +335,7 @@ impl<'a> TypeType<'a> { } } + #[inline] pub fn list_contents(&self) -> Option<&TypeType<'a>> { match *self { TypeType::List(ref n) => Some(n), @@ -341,6 +344,7 @@ impl<'a> TypeType<'a> { } } + #[inline] pub fn is_non_null(&self) -> bool { match *self { TypeType::NonNull(_) => true, diff --git a/juniper/src/schema/schema.rs b/juniper/src/schema/schema.rs index 6934ced2..fa67894c 100644 --- a/juniper/src/schema/schema.rs +++ b/juniper/src/schema/schema.rs @@ -1,5 +1,7 @@ use executor::{ExecutionResult, Executor, Registry}; use types::base::{Arguments, GraphQLType, TypeKind}; +use value::Value; +use ast::Selection; use schema::meta::{ Argument, EnumMeta, EnumValue, Field, InputObjectMeta, InterfaceMeta, MetaType, ObjectMeta, @@ -43,6 +45,26 @@ where _ => self.query_type.resolve_field(info, field, args, executor), } } + + fn resolve( + &self, + info: &Self::TypeInfo, + selection_set: Option<&[Selection]>, + executor: &Executor, + ) -> Value { + use value::Object; + use types::base::resolve_selection_set_into; + if let Some(selection_set) = selection_set { + let mut result = Object::with_capacity(selection_set.len()); + if resolve_selection_set_into(self, info, selection_set, executor, &mut result) { + Value::Object(result) + } else { + Value::null() + } + } else { + panic!("resolve() must be implemented by non-object output types"); + } + } } graphql_object!(<'a> SchemaType<'a>: SchemaType<'a> as "__Schema" |&self| { diff --git a/juniper/src/tests/introspection_tests.rs b/juniper/src/tests/introspection_tests.rs index 6b60d351..fe2e527f 100644 --- a/juniper/src/tests/introspection_tests.rs +++ b/juniper/src/tests/introspection_tests.rs @@ -198,11 +198,11 @@ fn test_possible_types() { let possible_types = result .as_object_value() .expect("execution result not an object") - .get("__type") + .get_field_value("__type") .expect("'__type' not present in result") .as_object_value() .expect("'__type' not an object") - .get("possibleTypes") + .get_field_value("possibleTypes") .expect("'possibleTypes' not present in '__type'") .as_list_value() .expect("'possibleTypes' not a list") @@ -210,7 +210,7 @@ fn test_possible_types() { .map(|t| { t.as_object_value() .expect("possible type not an object") - .get("name") + .get_field_value("name") .expect("'name' not present in type") .as_string_value() .expect("'name' not a string") diff --git a/juniper/src/types/base.rs b/juniper/src/types/base.rs index 80ff52b0..970843b3 100644 --- a/juniper/src/types/base.rs +++ b/juniper/src/types/base.rs @@ -1,9 +1,8 @@ -use indexmap::map::Entry; use indexmap::IndexMap; use ast::{Directive, FromInputValue, InputValue, Selection}; use executor::Variables; -use value::Value; +use value::{Object, Value}; use executor::{ExecutionResult, Executor, Registry}; use parser::Spanning; @@ -310,9 +309,9 @@ pub trait GraphQLType: Sized { executor: &Executor, ) -> Value { if let Some(selection_set) = selection_set { - let mut result = IndexMap::new(); + let mut result = Object::with_capacity(selection_set.len()); if resolve_selection_set_into(self, info, selection_set, executor, &mut result) { - Value::object(result) + Value::Object(result) } else { Value::null() } @@ -322,12 +321,12 @@ pub trait GraphQLType: Sized { } } -fn resolve_selection_set_into( +pub(crate) fn resolve_selection_set_into( instance: &T, info: &T::TypeInfo, selection_set: &[Selection], executor: &Executor, - result: &mut IndexMap, + result: &mut Object, ) -> bool where T: GraphQLType, @@ -352,11 +351,11 @@ where continue; } - let response_name = &f.alias.as_ref().unwrap_or(&f.name).item; + let response_name = f.alias.as_ref().unwrap_or(&f.name).item; if f.name.item == "__typename" { - result.insert( - (*response_name).to_owned(), + result.add_field( + response_name, Value::string(instance.concrete_type_name(executor.context(), info)), ); continue; @@ -406,7 +405,7 @@ where return false; } - result.insert((*response_name).to_owned(), Value::null()); + result.add_field(response_name, Value::null()); } } } @@ -453,8 +452,8 @@ where &sub_exec, ); - if let Ok(Value::Object(mut hash_map)) = sub_result { - for (k, v) in hash_map.drain(..) { + if let Ok(Value::Object(object)) = sub_result { + for (k, v) in object { merge_key_into(result, &k, v); } } else if let Err(e) = sub_result { @@ -503,15 +502,16 @@ fn is_excluded(directives: &Option>>, vars: &Variables) false } -fn merge_key_into(result: &mut IndexMap, response_name: &str, value: Value) { - match result.entry(response_name.to_owned()) { - Entry::Occupied(mut e) => match e.get_mut() { - &mut Value::Object(ref mut dest_obj) => { +fn merge_key_into(result: &mut Object, response_name: &str, value: Value) { + if let Some(&mut (_, ref mut e)) = result.iter_mut().find(|&&mut (ref key, _)| key == response_name) + { + match *e { + Value::Object(ref mut dest_obj) => { if let Value::Object(src_obj) = value { merge_maps(dest_obj, src_obj); } } - &mut Value::List(ref mut dest_list) => { + Value::List(ref mut dest_list) => { if let Value::List(src_list) = value { dest_list .iter_mut() @@ -527,19 +527,18 @@ fn merge_key_into(result: &mut IndexMap, response_name: &str, val } } _ => {} - }, - Entry::Vacant(e) => { - e.insert(value); } + return; } + result.add_field(response_name, value); } -fn merge_maps(dest: &mut IndexMap, src: IndexMap) { +fn merge_maps(dest: &mut Object, src: Object) { for (key, value) in src { - if dest.contains_key(&key) { + if dest.contains_field(&key) { merge_key_into(dest, &key, value); } else { - dest.insert(key, value); + dest.add_field(key, value); } } } diff --git a/juniper/src/types/containers.rs b/juniper/src/types/containers.rs index b47b6540..d73c32d8 100644 --- a/juniper/src/types/containers.rs +++ b/juniper/src/types/containers.rs @@ -152,18 +152,18 @@ where } } -fn resolve_into_list>( - executor: &Executor, - info: &T::TypeInfo, - iter: I, -) -> Value { +fn resolve_into_list(executor: &Executor, info: &T::TypeInfo, iter: I) -> Value +where + I: Iterator + ExactSizeIterator, + T: GraphQLType, +{ let stop_on_null = executor .current_type() .list_contents() .expect("Current type is not a list type") .is_non_null(); - let mut result = Vec::new(); + let mut result = Vec::with_capacity(iter.len()); for o in iter { let value = executor.resolve_into_value(info, &o); diff --git a/juniper/src/value.rs b/juniper/src/value.rs index ececbfb6..0f482db6 100644 --- a/juniper/src/value.rs +++ b/juniper/src/value.rs @@ -1,8 +1,7 @@ -use indexmap::IndexMap; -use std::hash::Hash; - use ast::{InputValue, ToInputValue}; use parser::Spanning; +use std::iter::FromIterator; +use std::vec::IntoIter; /// Serializable value returned from query and field execution. /// @@ -22,7 +21,152 @@ pub enum Value { String(String), Boolean(bool), List(Vec), - Object(IndexMap), + Object(Object), +} + +/// A Object value +#[derive(Debug, PartialEq, Clone)] +pub struct Object { + key_value_list: Vec<(String, Value)>, +} + +impl Object { + /// Create a new Object value with a fixed number of + /// preallocated slots for field-value pairs + pub fn with_capacity(size: usize) -> Self { + Object { + key_value_list: Vec::with_capacity(size), + } + } + + /// Add a new field with a value + /// + /// If there is already a field with the same name the old value + /// is returned + pub fn add_field(&mut self, k: K, value: Value) -> Option + where + K: Into, + for<'a> &'a str: PartialEq, + { + if let Some(item) = self + .key_value_list + .iter_mut() + .find(|&&mut (ref key, _)| (key as &str) == k) + { + return Some(::std::mem::replace(&mut item.1, value)); + } + self.key_value_list.push((k.into(), value)); + None + } + + /// Check if the object already contains a field with the given name + pub fn contains_field(&self, f: K) -> bool + where + for<'a> &'a str: PartialEq, + { + self.key_value_list + .iter() + .any(|&(ref key, _)| (key as &str) == f) + } + + /// Get a iterator over all field value pairs + /// + /// This method returns a iterator over `&'a (String, Value)` + // TODO: change this to `-> impl Iterator` + // as soon as juniper bumps the minimal supported rust verion to 1.26 + pub fn iter(&self) -> FieldIter { + FieldIter { + inner: self.key_value_list.iter(), + } + } + + /// Get a iterator over all mutable field value pairs + /// + /// This method returns a iterator over `&mut 'a (String, Value)` + // TODO: change this to `-> impl Iterator` + // as soon as juniper bumps the minimal supported rust verion to 1.26 + pub fn iter_mut(&mut self) -> FieldIterMut { + FieldIterMut { + inner: self.key_value_list.iter_mut(), + } + } + + /// Get the current number of fields + pub fn field_count(&self) -> usize { + self.key_value_list.len() + } + + /// Get the value for a given field + pub fn get_field_value(&self, key: K) -> Option<&Value> + where + for<'a> &'a str: PartialEq, + { + self.key_value_list + .iter() + .find(|&&(ref k, _)| (k as &str) == key) + .map(|&(_, ref value)| value) + } +} + +impl IntoIterator for Object { + type Item = (String, Value); + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.key_value_list.into_iter() + } +} + +impl From for Value { + fn from(o: Object) -> Self { + Value::Object(o) + } +} + +impl FromIterator<(K, Value)> for Object +where + K: Into, + for<'a> &'a str: PartialEq, +{ + fn from_iter(iter: I) -> Self + where + I: IntoIterator, + { + let iter = iter.into_iter(); + let mut ret = Self { + key_value_list: Vec::with_capacity(iter.size_hint().0), + }; + for (k, v) in iter { + ret.add_field(k, v); + } + ret + } +} + +#[doc(hidden)] +pub struct FieldIter<'a> { + inner: ::std::slice::Iter<'a, (String, Value)>, +} + +impl<'a> Iterator for FieldIter<'a> { + type Item = &'a (String, Value); + + fn next(&mut self) -> Option { + self.inner.next() + } +} + +#[doc(hidden)] +pub struct FieldIterMut<'a> { + inner: ::std::slice::IterMut<'a, (String, Value)>, +} + +impl<'a> Iterator for FieldIterMut<'a> { + type Item = &'a mut (String, Value); + + fn next(&mut self) -> Option { + self.inner.next() + } } impl Value { @@ -59,11 +203,8 @@ impl Value { } /// Construct an object value. - pub fn object(o: IndexMap) -> Value - where - K: Into + Eq + Hash, - { - Value::Object(o.into_iter().map(|(k, v)| (k.into(), v)).collect()) + pub fn object(o: Object) -> Value { + Value::Object(o) } // DISCRIMINATORS @@ -85,7 +226,7 @@ impl Value { } /// View the underlying object value, if present. - pub fn as_object_value(&self) -> Option<&IndexMap> { + pub fn as_object_value(&self) -> Option<&Object> { match *self { Value::Object(ref o) => Some(o), _ => None, @@ -93,7 +234,7 @@ impl Value { } /// Mutable view into the underlying object value, if present. - pub fn as_mut_object_value(&mut self) -> Option<&mut IndexMap> { + pub fn as_mut_object_value(&mut self) -> Option<&mut Object> { match *self { Value::Object(ref mut o) => Some(o), _ => None, @@ -132,7 +273,7 @@ impl ToInputValue for Value { ), Value::Object(ref o) => InputValue::Object( o.iter() - .map(|(k, v)| { + .map(|&(ref k, ref v)| { ( Spanning::unlocated(k.clone()), Spanning::unlocated(v.to_input_value()), diff --git a/juniper_tests/src/codegen/derive_object.rs b/juniper_tests/src/codegen/derive_object.rs index e35474b4..cb44a90a 100644 --- a/juniper_tests/src/codegen/derive_object.rs +++ b/juniper_tests/src/codegen/derive_object.rs @@ -1,7 +1,7 @@ #[cfg(test)] use fnv::FnvHashMap; #[cfg(test)] -use indexmap::IndexMap; +use juniper::Object; #[cfg(test)] use juniper::{self, execute, EmptyMutation, GraphQLType, RootNode, Value, Variables}; @@ -235,8 +235,8 @@ fn check_descriptions( object_name ); run_type_info_query(&doc, |(type_info, values)| { - assert_eq!(type_info.get("name"), Some(&Value::string(object_name))); - assert_eq!(type_info.get("description"), Some(object_description)); + assert_eq!(type_info.get_field_value("name"), Some(&Value::string(object_name))); + assert_eq!(type_info.get_field_value("description"), Some(object_description)); assert!( values.contains(&Value::object( vec![ @@ -252,7 +252,7 @@ fn check_descriptions( #[cfg(test)] fn run_type_info_query(doc: &str, f: F) where - F: Fn((&IndexMap, &Vec)) -> (), + F: Fn((&Object, &Vec)) -> (), { let schema = RootNode::new(Query, EmptyMutation::<()>::new()); @@ -266,13 +266,13 @@ where let type_info = result .as_object_value() .expect("Result is not an object") - .get("__type") + .get_field_value("__type") .expect("__type field missing") .as_object_value() .expect("__type field not an object value"); let fields = type_info - .get("fields") + .get_field_value("fields") .expect("fields field missing") .as_list_value() .expect("fields not a list");