diff --git a/CHANGELOG.md b/CHANGELOG.md index 509a16c6..1fe6711d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +- Add derived for object & simple object & complex object. [#667](https://github.com/async-graphql/async-graphql/pull/667) [#670](https://github.com/async-graphql/async-graphql/pull/670) +- Respect query object field order. [#612](https://github.com/async-graphql/async-graphql/issues/612) + ## [2.10.5] 2021-10-22 - Bump poem from `0.6.6` to `1.0.7`. diff --git a/derive/src/complex_object.rs b/derive/src/complex_object.rs index 105d2ee8..8d282690 100644 --- a/derive/src/complex_object.rs +++ b/derive/src/complex_object.rs @@ -85,8 +85,7 @@ pub fn generate( _ => Some(Err(Error::new_spanned( &pat, "Must be a simple argument", - ) - .into())), + ))), }, FnArg::Receiver(_) => None, }) diff --git a/derive/src/input_object.rs b/derive/src/input_object.rs index cd7680d7..228938e3 100644 --- a/derive/src/input_object.rs +++ b/derive/src/input_object.rs @@ -231,7 +231,7 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult } fn to_value(&self) -> #crate_name::Value { - let mut map = ::std::collections::BTreeMap::new(); + let mut map = #crate_name::indexmap::IndexMap::new(); #(#put_fields)* #crate_name::Value::Object(map) } @@ -272,7 +272,7 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult } fn __internal_to_value(&self) -> #crate_name::Value where Self: #crate_name::InputType { - let mut map = ::std::collections::BTreeMap::new(); + let mut map = #crate_name::indexmap::IndexMap::new(); #(#put_fields)* #crate_name::Value::Object(map) } diff --git a/derive/src/object.rs b/derive/src/object.rs index 940ac4f4..6cfe0b79 100644 --- a/derive/src/object.rs +++ b/derive/src/object.rs @@ -100,8 +100,7 @@ pub fn generate( _ => Some(Err(Error::new_spanned( &pat, "Must be a simple argument", - ) - .into())), + ))), }, FnArg::Receiver(_) => None, }) diff --git a/derive/src/simple_object.rs b/derive/src/simple_object.rs index 1750e0b8..784f46d9 100644 --- a/derive/src/simple_object.rs +++ b/derive/src/simple_object.rs @@ -53,7 +53,7 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult GeneratorResult GraphQLPlaygroundConfig<'a> { #[cfg(test)] mod tests { use super::*; - use std::collections::BTreeMap; + use indexmap::IndexMap; #[test] fn test_with_setting_can_use_any_json_value() { @@ -634,7 +634,7 @@ mod tests { .with_setting("number", 10) .with_setting("null", Value::Null) .with_setting("array", Vec::from([1, 2, 3])) - .with_setting("object", BTreeMap::new()); + .with_setting("object", IndexMap::new()); let json = serde_json::to_value(settings).unwrap(); let settings = json["settings"].as_object().unwrap(); diff --git a/src/resolver_utils/container.rs b/src/resolver_utils/container.rs index dfe30795..d0dc3048 100644 --- a/src/resolver_utils/container.rs +++ b/src/resolver_utils/container.rs @@ -1,7 +1,8 @@ -use std::collections::BTreeMap; use std::future::Future; use std::pin::Pin; +use indexmap::IndexMap; + use crate::extensions::ResolveInfo; use crate::parser::types::Selection; use crate::registry::MetaType; @@ -74,7 +75,7 @@ pub async fn resolve_container_serial<'a, T: ContainerType + ?Sized>( resolve_container_inner(ctx, root, false).await } -fn insert_value(target: &mut BTreeMap, name: Name, value: Value) { +fn insert_value(target: &mut IndexMap, name: Name, value: Value) { if let Some(prev_value) = target.get_mut(&name) { if let Value::Object(target_map) = prev_value { if let Value::Object(obj) = value { @@ -118,7 +119,7 @@ async fn resolve_container_inner<'a, T: ContainerType + ?Sized>( results }; - let mut map = BTreeMap::new(); + let mut map = IndexMap::new(); for (name, value) in res { insert_value(&mut map, name, value); } diff --git a/src/response.rs b/src/response.rs index 1a3fea0a..d851cca9 100644 --- a/src/response.rs +++ b/src/response.rs @@ -98,6 +98,7 @@ impl Response { } /// Response for batchable queries +#[allow(clippy::large_enum_variant)] #[derive(Debug, Serialize)] #[serde(untagged)] pub enum BatchResponse { diff --git a/src/types/external/json_object/btreemap.rs b/src/types/external/json_object/btreemap.rs index c3f14660..bfaff892 100644 --- a/src/types/external/json_object/btreemap.rs +++ b/src/types/external/json_object/btreemap.rs @@ -2,6 +2,8 @@ use std::collections::BTreeMap; use std::fmt::Display; use std::str::FromStr; +use indexmap::IndexMap; + use crate::{ InputType, InputValueError, InputValueResult, Name, OutputType, Scalar, ScalarType, Value, }; @@ -33,7 +35,7 @@ where } fn to_value(&self) -> Value { - let mut map = BTreeMap::new(); + let mut map = IndexMap::new(); for (name, value) in self { map.insert(Name::new(name.to_string()), value.to_value()); } diff --git a/src/types/external/json_object/hashmap.rs b/src/types/external/json_object/hashmap.rs index d805f6ca..9fcc1f05 100644 --- a/src/types/external/json_object/hashmap.rs +++ b/src/types/external/json_object/hashmap.rs @@ -1,8 +1,10 @@ -use std::collections::{BTreeMap, HashMap}; +use std::collections::HashMap; use std::fmt::Display; use std::hash::Hash; use std::str::FromStr; +use indexmap::IndexMap; + use crate::{ InputType, InputValueError, InputValueResult, Name, OutputType, Scalar, ScalarType, Value, }; @@ -34,7 +36,7 @@ where } fn to_value(&self) -> Value { - let mut map = BTreeMap::new(); + let mut map = IndexMap::new(); for (name, value) in self { map.insert(Name::new(name.to_string()), value.to_value()); } diff --git a/tests/preserve_order.rs b/tests/preserve_order.rs new file mode 100644 index 00000000..f4aa96bd --- /dev/null +++ b/tests/preserve_order.rs @@ -0,0 +1,60 @@ +use async_graphql::*; + +#[tokio::test] +pub async fn test_preserve_order() { + #[derive(SimpleObject)] + struct Root { + a: i32, + b: i32, + c: i32, + } + + let schema = Schema::new(Root { a: 1, b: 2, c: 3 }, EmptyMutation, EmptySubscription); + assert_eq!( + schema + .execute("{ a c b }") + .await + .into_result() + .unwrap() + .data, + value!({ + "a": 1, "c": 3, "b": 2 + }) + ); + assert_eq!( + serde_json::to_string( + &schema + .execute("{ a c b }") + .await + .into_result() + .unwrap() + .data + ) + .unwrap(), + r#"{"a":1,"c":3,"b":2}"# + ); + + assert_eq!( + schema + .execute("{ c b a }") + .await + .into_result() + .unwrap() + .data, + value!({ + "c": 3, "b": 2, "a": 1 + }) + ); + assert_eq!( + serde_json::to_string( + &schema + .execute("{ c b a }") + .await + .into_result() + .unwrap() + .data + ) + .unwrap(), + r#"{"c":3,"b":2,"a":1}"# + ); +} diff --git a/value/Cargo.toml b/value/Cargo.toml index f3949a73..0d0f1374 100644 --- a/value/Cargo.toml +++ b/value/Cargo.toml @@ -15,3 +15,4 @@ categories = ["network-programming", "asynchronous"] serde_json = "1.0.64" serde = { version = "1.0.125", features = ["derive"] } bytes = { version = "1.0.1", features = ["serde"] } +indexmap = { version = "1.7.0", features = ["serde"] } diff --git a/value/src/deserializer.rs b/value/src/deserializer.rs index e473cae1..e0cf01e7 100644 --- a/value/src/deserializer.rs +++ b/value/src/deserializer.rs @@ -1,6 +1,7 @@ -use std::collections::BTreeMap; use std::{fmt, vec}; +use indexmap::IndexMap; + use crate::{ConstValue, Name}; use serde::de::{ @@ -78,7 +79,7 @@ where } fn visit_object<'de, V>( - object: BTreeMap, + object: IndexMap, visitor: V, ) -> Result where @@ -365,13 +366,13 @@ impl<'de> SeqAccess<'de> for SeqDeserializer { } struct MapDeserializer { - iter: as IntoIterator>::IntoIter, + iter: as IntoIterator>::IntoIter, value: Option, } impl MapDeserializer { #[inline] - fn new(map: BTreeMap) -> Self { + fn new(map: IndexMap) -> Self { MapDeserializer { iter: map.into_iter(), value: None, diff --git a/value/src/lib.rs b/value/src/lib.rs index fd44e9e6..c04da07f 100644 --- a/value/src/lib.rs +++ b/value/src/lib.rs @@ -10,7 +10,6 @@ mod value_serde; mod variables; use std::borrow::{Borrow, Cow}; -use std::collections::BTreeMap; use std::convert::{TryFrom, TryInto}; use std::fmt::{self, Display, Formatter, Write}; use std::iter::FromIterator; @@ -18,9 +17,12 @@ use std::ops::Deref; use std::sync::Arc; use bytes::Bytes; +use indexmap::IndexMap; use serde::{Deserialize, Deserializer, Serialize, Serializer}; pub use deserializer::{from_value, DeserializerError}; +#[doc(hidden)] +pub use indexmap; pub use serde_json::Number; pub use serializer::{to_value, SerializerError}; @@ -139,7 +141,7 @@ pub enum ConstValue { /// A list of values. List(Vec), /// An object. This is a map of keys to values. - Object(BTreeMap), + Object(IndexMap), } impl PartialEq for ConstValue { @@ -254,8 +256,8 @@ impl> From> for ConstValue { } } -impl From> for ConstValue { - fn from(f: BTreeMap) -> Self { +impl From> for ConstValue { + fn from(f: IndexMap) -> Self { ConstValue::Object(f) } } @@ -364,7 +366,7 @@ pub enum Value { /// A list of values. List(Vec), /// An object. This is a map of keys to values. - Object(BTreeMap), + Object(IndexMap), } impl Value { diff --git a/value/src/macros.rs b/value/src/macros.rs index d39da636..4be7cc12 100644 --- a/value/src/macros.rs +++ b/value/src/macros.rs @@ -191,7 +191,7 @@ macro_rules! value_internal { ({ $($tt:tt)+ }) => { $crate::ConstValue::Object({ - let mut object = std::collections::BTreeMap::new(); + let mut object = $crate::indexmap::IndexMap::new(); $crate::value_internal!(@object object () ($($tt)+) ($($tt)+)); object }) @@ -227,7 +227,7 @@ macro_rules! value_expect_expr_comma { #[cfg(test)] mod tests { use crate::{ConstValue, Name}; - use std::collections::BTreeMap; + use indexmap::IndexMap; #[test] fn test_macro() { @@ -254,7 +254,7 @@ mod tests { ) ); assert_eq!(value!({"a": 10, "b": true}), { - let mut map = BTreeMap::new(); + let mut map = IndexMap::new(); map.insert(Name::new("a"), ConstValue::Number(10.into())); map.insert(Name::new("b"), ConstValue::Boolean(true)); ConstValue::Object(map) diff --git a/value/src/serializer.rs b/value/src/serializer.rs index 1db14dfe..ac198822 100644 --- a/value/src/serializer.rs +++ b/value/src/serializer.rs @@ -1,7 +1,7 @@ -use std::collections::BTreeMap; use std::error::Error; use std::fmt; +use indexmap::IndexMap; use serde::ser::{self, Impossible}; use serde::Serialize; @@ -180,7 +180,7 @@ impl ser::Serializer for Serializer { T: ser::Serialize, { value.serialize(self).map(|v| { - let mut map = BTreeMap::new(); + let mut map = IndexMap::new(); map.insert(Name::new(variant), v); ConstValue::Object(map) }) @@ -222,7 +222,7 @@ impl ser::Serializer for Serializer { #[inline] fn serialize_map(self, _len: Option) -> Result { Ok(SerializeMap { - map: BTreeMap::new(), + map: IndexMap::new(), key: None, }) } @@ -233,7 +233,7 @@ impl ser::Serializer for Serializer { _name: &'static str, _len: usize, ) -> Result { - Ok(SerializeStruct(BTreeMap::new())) + Ok(SerializeStruct(IndexMap::new())) } #[inline] @@ -244,7 +244,7 @@ impl ser::Serializer for Serializer { variant: &'static str, _len: usize, ) -> Result { - Ok(SerializeStructVariant(Name::new(variant), BTreeMap::new())) + Ok(SerializeStructVariant(Name::new(variant), IndexMap::new())) } #[inline] @@ -337,14 +337,14 @@ impl ser::SerializeTupleVariant for SerializeTupleVariant { #[inline] fn end(self) -> Result { - let mut map = BTreeMap::new(); + let mut map = IndexMap::new(); map.insert(self.0, ConstValue::List(self.1)); Ok(ConstValue::Object(map)) } } struct SerializeMap { - map: BTreeMap, + map: IndexMap, key: Option, } @@ -378,7 +378,7 @@ impl ser::SerializeMap for SerializeMap { } } -struct SerializeStruct(BTreeMap); +struct SerializeStruct(IndexMap); impl ser::SerializeStruct for SerializeStruct { type Ok = ConstValue; @@ -405,7 +405,7 @@ impl ser::SerializeStruct for SerializeStruct { } } -struct SerializeStructVariant(Name, BTreeMap); +struct SerializeStructVariant(Name, IndexMap); impl ser::SerializeStructVariant for SerializeStructVariant { type Ok = ConstValue; @@ -428,7 +428,7 @@ impl ser::SerializeStructVariant for SerializeStructVariant { #[inline] fn end(self) -> Result { - let mut map = BTreeMap::new(); + let mut map = IndexMap::new(); map.insert(self.0, ConstValue::Object(self.1)); Ok(ConstValue::Object(map)) } diff --git a/value/src/value_serde.rs b/value/src/value_serde.rs index 7e2ffb7a..3d6aa76a 100644 --- a/value/src/value_serde.rs +++ b/value/src/value_serde.rs @@ -1,6 +1,6 @@ -use std::collections::BTreeMap; use std::fmt::{self, Formatter}; +use indexmap::IndexMap; use serde::de::{Error as DeError, MapAccess, SeqAccess, Visitor}; use serde::ser::Error as SerError; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -137,7 +137,7 @@ impl<'de> Deserialize<'de> for ConstValue { where A: MapAccess<'de>, { - let mut map = BTreeMap::new(); + let mut map = IndexMap::new(); while let Some((name, value)) = visitor.next_entry()? { map.insert(name, value); } @@ -280,7 +280,7 @@ impl<'de> Deserialize<'de> for Value { where A: MapAccess<'de>, { - let mut map = BTreeMap::new(); + let mut map = IndexMap::new(); while let Some((name, value)) = visitor.next_entry()? { map.insert(name, value); } diff --git a/value/src/variables.rs b/value/src/variables.rs index 7e9d7d1e..8599085c 100644 --- a/value/src/variables.rs +++ b/value/src/variables.rs @@ -36,7 +36,7 @@ impl Variables { #[must_use] pub fn from_value(value: ConstValue) -> Self { match value { - ConstValue::Object(obj) => Self(obj), + ConstValue::Object(obj) => Self(obj.into_iter().collect()), _ => Self::default(), } } @@ -55,7 +55,7 @@ impl Variables { /// Get the variables as a GraphQL value. #[must_use] pub fn into_value(self) -> ConstValue { - ConstValue::Object(self.0) + ConstValue::Object(self.0.into_iter().collect()) } }