Merge branch 'master' into update-to-rust-edition-2021

This commit is contained in:
Sunli 2021-11-02 20:08:41 +08:00 committed by GitHub
commit abac47fc50
38 changed files with 981 additions and 103 deletions

View File

@ -4,6 +4,19 @@ 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).
## [2.10.8] 2021-10-26
- [async-graphql-poem] Bump poem to `1.0.13`.
## [2.10.6] 2021-10-26
- 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`.
## [2.10.4] 2021-10-22
- Implement `Default` for ID #659.

View File

@ -1,6 +1,6 @@
[package]
name = "async-graphql"
version = "2.10.4"
version = "2.10.8"
authors = ["sunli <scott_s829@163.com>", "Koxiaet"]
edition = "2021"
description = "A GraphQL server library implemented in Rust"
@ -24,9 +24,9 @@ decimal = ["rust_decimal"]
cbor = ["serde_cbor"]
[dependencies]
async-graphql-derive = { path = "derive", version = "=2.10.4" }
async-graphql-value = { path = "value", version = "=2.10.4" }
async-graphql-parser = { path = "parser", version = "=2.10.4" }
async-graphql-derive = { path = "derive", version = "2.10.8" }
async-graphql-value = { path = "value", version = "2.10.8" }
async-graphql-parser = { path = "parser", version = "2.10.8" }
async-stream = "0.3.0"
async-trait = "0.1.48"

View File

@ -1,6 +1,6 @@
[package]
name = "async-graphql-derive"
version = "2.10.4"
version = "2.10.8"
authors = ["sunli <scott_s829@163.com>", "Koxiaet"]
edition = "2021"
description = "Macros for async-graphql"
@ -15,7 +15,7 @@ categories = ["network-programming", "asynchronous"]
proc-macro = true
[dependencies]
async-graphql-parser = { path = "../parser", version = "=2.10.4" }
async-graphql-parser = { path = "../parser", version = "2.10.8" }
proc-macro2 = "1.0.24"
syn = { version = "1.0.64", features = ["full", "extra-traits", "visit-mut", "visit"] }
quote = "1.0.9"

View File

@ -6,7 +6,7 @@ use syn::{
Attribute, Generics, Ident, Lit, LitBool, LitStr, Meta, NestedMeta, Path, Type, Visibility,
};
#[derive(FromMeta)]
#[derive(FromMeta, Clone)]
#[darling(default)]
pub struct CacheControl {
public: bool,
@ -46,7 +46,7 @@ impl FromMeta for DefaultValue {
}
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum Visible {
None,
HiddenAlways,
@ -86,7 +86,7 @@ pub struct ConcreteType {
pub params: PathList,
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum Deprecation {
NoDeprecated,
Deprecated { reason: Option<String> },
@ -115,7 +115,7 @@ impl FromMeta for Deprecation {
}
}
#[derive(FromField)]
#[derive(FromField, Clone)]
#[darling(attributes(graphql), forward_attrs(doc))]
pub struct SimpleObjectField {
pub ident: Option<Ident>,
@ -143,6 +143,8 @@ pub struct SimpleObjectField {
pub guard: Option<Meta>,
#[darling(default)]
pub visible: Option<Visible>,
#[darling(default, multiple)]
pub derived: Vec<DerivedField>,
}
#[derive(FromDeriveInput)]
@ -243,6 +245,19 @@ pub struct ObjectField {
pub guard: Option<Meta>,
pub visible: Option<Visible>,
pub complexity: Option<ComplexityType>,
#[darling(default, multiple)]
pub derived: Vec<DerivedField>,
}
#[derive(FromMeta, Default, Clone)]
#[darling(default)]
/// Derivied fields arguments: are used to generate derivied fields.
pub struct DerivedField {
pub name: Option<Ident>,
pub into: Option<String>,
pub with: Option<Path>,
#[darling(default)]
pub owned: Option<bool>,
}
#[derive(FromDeriveInput)]

View File

@ -1,7 +1,13 @@
use proc_macro::TokenStream;
use proc_macro2::Ident;
use quote::quote;
use std::iter::FromIterator;
use std::str::FromStr;
use syn::ext::IdentExt;
use syn::{Block, Error, ImplItem, ItemImpl, ReturnType};
use syn::{
punctuated::Punctuated, Block, Error, FnArg, ImplItem, ItemImpl, Pat, ReturnType, Token, Type,
TypeReference,
};
use crate::args::{self, ComplexityType, RenameRuleExt, RenameTarget};
use crate::output_type::OutputType;
@ -23,6 +29,91 @@ pub fn generate(
let mut resolvers = Vec::new();
let mut schema_fields = Vec::new();
// Computation of the derivated fields
let mut derived_impls = vec![];
for item in &mut item_impl.items {
if let ImplItem::Method(method) = item {
let method_args: args::ObjectField =
parse_graphql_attrs(&method.attrs)?.unwrap_or_default();
for derived in method_args.derived {
if derived.name.is_some() && derived.into.is_some() {
let base_function_name = &method.sig.ident;
let name = derived.name.unwrap();
let with = derived.with;
let into = Type::Verbatim(
proc_macro2::TokenStream::from_str(&derived.into.unwrap()).unwrap(),
);
let mut new_impl = method.clone();
new_impl.sig.ident = name;
new_impl.sig.output =
syn::parse2::<ReturnType>(quote! { -> #crate_name::Result<#into> })
.expect("invalid result type");
let should_create_context = new_impl
.sig
.inputs
.iter()
.nth(1)
.map(|x| {
if let FnArg::Typed(pat) = x {
if let Type::Reference(TypeReference { elem, .. }) = &*pat.ty {
if let Type::Path(path) = elem.as_ref() {
return path.path.segments.last().unwrap().ident
!= "Context";
}
}
};
true
})
.unwrap_or(true);
if should_create_context {
let arg_ctx = syn::parse2::<FnArg>(quote! { ctx: &Context<'_> })
.expect("invalid arg type");
new_impl.sig.inputs.insert(1, arg_ctx);
}
let other_atts: Punctuated<Ident, Token![,]> = Punctuated::from_iter(
new_impl
.sig
.inputs
.iter()
.filter_map(|x| match x {
FnArg::Typed(pat) => match &*pat.pat {
Pat::Ident(ident) => Some(Ok(ident.ident.clone())),
_ => Some(Err(Error::new_spanned(
&pat,
"Must be a simple argument",
))),
},
FnArg::Receiver(_) => None,
})
.collect::<Result<Vec<Ident>, Error>>()?
.into_iter(),
);
let new_block = match with {
Some(with) => quote!({
::std::result::Result::Ok(#with(#self_ty::#base_function_name(&self, #other_atts).await?))
}),
None => quote!({
{
::std::result::Result::Ok(#self_ty::#base_function_name(&self, #other_atts).await?.into())
}
}),
};
new_impl.block = syn::parse2::<Block>(new_block).expect("invalid block");
derived_impls.push(ImplItem::Method(new_impl));
}
}
}
}
item_impl.items.append(&mut derived_impls);
for item in &mut item_impl.items {
if let ImplItem::Method(method) = item {
let method_args: args::ObjectField =

View File

@ -71,13 +71,9 @@ pub fn generate(enum_args: &args::Enum) -> GeneratorResult<TokenStream> {
}
let remote_conversion = if let Some(remote) = &enum_args.remote {
let remote_ty = if let Ok(ty) = syn::parse_str::<syn::Type>(remote) {
ty
} else {
return Err(
Error::new_spanned(remote, format!("Invalid remote type: '{}'", remote)).into(),
);
};
let remote_ty = syn::parse_str::<syn::Type>(remote).map_err(|_| {
Error::new_spanned(remote, format!("Invalid remote type: '{}'", remote))
})?;
let local_to_remote_items = enum_items.iter().map(|item| {
quote! {

View File

@ -231,7 +231,7 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
}
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<TokenStream>
}
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)
}

View File

@ -1,7 +1,13 @@
use proc_macro::TokenStream;
use proc_macro2::Ident;
use quote::quote;
use std::iter::FromIterator;
use std::str::FromStr;
use syn::ext::IdentExt;
use syn::{Block, Error, ImplItem, ItemImpl, ReturnType};
use syn::{
punctuated::Punctuated, Block, Error, FnArg, ImplItem, ItemImpl, Pat, ReturnType, Token, Type,
TypeReference,
};
use crate::args::{self, ComplexityType, RenameRuleExt, RenameTarget};
use crate::output_type::OutputType;
@ -38,6 +44,91 @@ pub fn generate(
let mut add_keys = Vec::new();
let mut create_entity_types = Vec::new();
// Computation of the derivated fields
let mut derived_impls = vec![];
for item in &mut item_impl.items {
if let ImplItem::Method(method) = item {
let method_args: args::ObjectField =
parse_graphql_attrs(&method.attrs)?.unwrap_or_default();
for derived in method_args.derived {
if derived.name.is_some() && derived.into.is_some() {
let base_function_name = &method.sig.ident;
let name = derived.name.unwrap();
let with = derived.with;
let into = Type::Verbatim(
proc_macro2::TokenStream::from_str(&derived.into.unwrap()).unwrap(),
);
let mut new_impl = method.clone();
new_impl.sig.ident = name;
new_impl.sig.output =
syn::parse2::<ReturnType>(quote! { -> #crate_name::Result<#into> })
.expect("invalid result type");
let should_create_context = new_impl
.sig
.inputs
.iter()
.nth(1)
.map(|x| {
if let FnArg::Typed(pat) = x {
if let Type::Reference(TypeReference { elem, .. }) = &*pat.ty {
if let Type::Path(path) = elem.as_ref() {
return path.path.segments.last().unwrap().ident
!= "Context";
}
}
};
true
})
.unwrap_or(true);
if should_create_context {
let arg_ctx = syn::parse2::<FnArg>(quote! { ctx: &Context<'_> })
.expect("invalid arg type");
new_impl.sig.inputs.insert(1, arg_ctx);
}
let other_atts: Punctuated<Ident, Token![,]> = Punctuated::from_iter(
new_impl
.sig
.inputs
.iter()
.filter_map(|x| match x {
FnArg::Typed(pat) => match &*pat.pat {
Pat::Ident(ident) => Some(Ok(ident.ident.clone())),
_ => Some(Err(Error::new_spanned(
&pat,
"Must be a simple argument",
))),
},
FnArg::Receiver(_) => None,
})
.collect::<Result<Vec<Ident>, Error>>()?
.into_iter(),
);
let new_block = match with {
Some(with) => quote!({
::std::result::Result::Ok(#with(#self_ty::#base_function_name(&self, #other_atts).await?))
}),
None => quote!({
{
::std::result::Result::Ok(#self_ty::#base_function_name(&self, #other_atts).await?.into())
}
}),
};
new_impl.block = syn::parse2::<Block>(new_block).expect("invalid block");
derived_impls.push(ImplItem::Method(new_impl));
}
}
}
}
item_impl.items.append(&mut derived_impls);
for item in &mut item_impl.items {
if let ImplItem::Method(method) = item {
let method_args: args::ObjectField =

View File

@ -1,14 +1,28 @@
use darling::ast::Data;
use proc_macro::TokenStream;
use quote::quote;
use std::str::FromStr;
use syn::ext::IdentExt;
use syn::Error;
use syn::{Error, Ident, Path, Type};
use crate::args::{self, RenameRuleExt, RenameTarget};
use crate::args::{self, RenameRuleExt, RenameTarget, SimpleObjectField};
use crate::utils::{
gen_deprecation, generate_guards, get_crate_name, get_rustdoc, visible_fn, GeneratorResult,
};
#[derive(Debug)]
struct DerivedFieldMetadata {
ident: Ident,
into: Type,
owned: Option<bool>,
with: Option<Path>,
}
struct SimpleObjectFieldGenerator<'a> {
field: &'a SimpleObjectField,
derived: Option<DerivedFieldMetadata>,
}
pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream> {
let crate_name = get_crate_name(object_args.internal);
let ident = &object_args.ident;
@ -37,15 +51,62 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
let mut resolvers = Vec::new();
let mut schema_fields = Vec::new();
let mut processed_fields: Vec<SimpleObjectFieldGenerator> = vec![];
// Before processing the fields, we generate the derivated fields
for field in &s.fields {
processed_fields.push(SimpleObjectFieldGenerator {
field,
derived: None,
});
for derived in &field.derived {
if derived.name.is_some() && derived.into.is_some() {
let name = derived.name.clone().unwrap();
let into = match syn::parse2::<Type>(
proc_macro2::TokenStream::from_str(&derived.into.clone().unwrap()).unwrap(),
) {
Ok(e) => e,
_ => {
return Err(Error::new_spanned(
&name,
"derived into must be a valid type.",
)
.into());
}
};
let derived = DerivedFieldMetadata {
ident: name,
into,
owned: derived.owned,
with: derived.with.clone(),
};
processed_fields.push(SimpleObjectFieldGenerator {
field,
derived: Some(derived),
})
}
}
}
for SimpleObjectFieldGenerator { field, derived } in &processed_fields {
if field.skip {
continue;
}
let ident = match &field.ident {
let base_ident = match &field.ident {
Some(ident) => ident,
None => return Err(Error::new_spanned(&ident, "All fields must be named.").into()),
};
let ident = if let Some(derived) = derived {
&derived.ident
} else {
base_ident
};
let field_name = field.name.clone().unwrap_or_else(|| {
object_args
.rename_fields
@ -65,7 +126,18 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
None => quote! { ::std::option::Option::None },
};
let vis = &field.vis;
let ty = &field.ty;
let ty = if let Some(derived) = derived {
&derived.into
} else {
&field.ty
};
let owned = if let Some(derived) = derived {
derived.owned.unwrap_or(field.owned)
} else {
field.owned
};
let cache_control = {
let public = field.cache_control.is_public();
@ -104,23 +176,41 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
|guard| quote! { #guard.check(ctx).await.map_err(|err| err.into_server_error(ctx.item.pos))?; },
);
getters.push(if !field.owned {
let with_function = derived.as_ref().and_then(|x| x.with.as_ref());
let mut block = match !owned {
true => quote! {
&self.#base_ident
},
false => quote! {
::std::clone::Clone::clone(&self.#base_ident)
},
};
block = match (derived, with_function) {
(Some(_), Some(with)) => quote! {
#with(#block)
},
(Some(_), None) => quote! {
::std::convert::Into::into(#block)
},
(_, _) => block,
};
let ty = match !owned {
true => quote! { &#ty },
false => quote! { #ty },
};
getters.push(
quote! {
#[inline]
#[allow(missing_docs)]
#vis async fn #ident(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::Result<&#ty> {
::std::result::Result::Ok(&self.#ident)
#vis async fn #ident(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::Result<#ty> {
::std::result::Result::Ok(#block)
}
}
} else {
quote! {
#[inline]
#[allow(missing_docs)]
#vis async fn #ident(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::Result<#ty> {
::std::result::Result::Ok(::std::clone::Clone::clone(&self.#ident))
}
}
});
);
resolvers.push(quote! {
if ctx.item.node.name.node == #field_name {

View File

@ -375,7 +375,7 @@ pub fn generate(
};
#crate_name::futures_util::pin_mut!(resolve_fut);
let mut resp = query_env.extensions.resolve(ri, &mut resolve_fut).await.map(|value| {
let mut map = ::std::collections::BTreeMap::new();
let mut map = #crate_name::indexmap::IndexMap::new();
map.insert(::std::clone::Clone::clone(&field_name), value.unwrap_or_default());
#crate_name::Response::new(#crate_name::Value::Object(map))
})

View File

@ -8,6 +8,7 @@
- [Context](context.md)
- [Error handling](error_handling.md)
- [Merging Objects / Subscriptions](merging_objects.md)
- [Derived fields](derived_fields.md)
- [Enum](define_enum.md)
- [Interface](define_interface.md)
- [Union](define_union.md)

View File

@ -0,0 +1,103 @@
# Derived fields
When you are working on a GraphQL project, you usually have to explain and share how your scalars should
be interpreted by your consumers. Sometimes, you event want to have the same data and the same logic exposing
the data in another type.
Within `async-graphql` you can create derived fields for objects to generate derived fields.
Consider you want to create a `Date` scalar, to represent an event of time.
How will you represent and format this date? You could create a scalar `Date` where you specified it's the RFCXXX
implemented to format it.
With derived fields there is a simple way to support multiple representation of a `Date` easily:
```rust
struct DateRFC3339(chrono::DateTime);
struct DateRFC2822(chrono::DateTime);
#[Scalar]
impl ScalarType for DateRFC3339 {
fn parse(value: Value) -> InputValueResult { ... }
fn to_value(&self) -> Value {
Value::String(self.0.to_rfc3339())
}
}
#[Scalar]
impl ScalarType for DateRFC2822 {
fn parse(value: Value) -> InputValueResult { ... }
fn to_value(&self) -> Value {
Value::String(self.0.to_rfc2822())
}
}
impl From<DateRFC2822> for DateRFC3339 {
fn from(value: DateRFC2822) -> Self {
DateRFC3339(value.0)
}
}
struct Query;
#[Object]
impl Query {
#[graphql(derived(name = "date_rfc3339", into = "DateRFC3339"))]
async fn duration_rfc2822(&self, arg: String) -> DateRFC2822 {
todo!()
}
}
```
It will render a GraphQL like:
```graphql
type Query {
duration_rfc2822(arg: String): DateRFC2822!
duration_rfc3339(arg: String): DateRFC3339!
}
```
## Wrapper types
A derived field won't be able to manage everythings easily: without the specialization from the Rust language, you won't be able to implement specialized trait like:
```
impl From<Vec<U>> for Vec<T> {
...
}
```
So you wouldn't be able to generate derived fields for existing wrapper type structures like `Vec` or `Option`. But when you implement a `From<U> for T` you should be able to derived a `From<Vec<U>> for Vec<T>` and a `From<Option<U>> for Option<T>`.
We included a `with` parameter to help you define a function to call instead of using the `Into` trait implementation between wrapper structures.
### Example
```rust
#[derive(Serialize, Deserialize, Clone)]
struct ValueDerived(String);
#[derive(Serialize, Deserialize, Clone)]
struct ValueDerived2(String);
scalar!(ValueDerived);
scalar!(ValueDerived2);
impl From<ValueDerived> for ValueDerived2 {
fn from(value: ValueDerived) -> Self {
ValueDerived2(value.0)
}
}
fn option_to_option<T, U: From<T>>(value: Option<T>) -> Option<U> {
value.map(|x| x.into())
}
#[derive(SimpleObject)]
struct TestObj {
#[graphql(derived(owned, name = "value2", into = "Option<ValueDerived2>", with = "option_to_option"))]
pub value1: Option<ValueDerived>,
}
```

@ -1 +1 @@
Subproject commit df8cfdff35d25d1cef3c9fda4d98e639a55fffc2
Subproject commit 1683aac1883a6acf27747bf3e22491fa13aa538f

View File

@ -1,6 +1,6 @@
[package]
name = "async-graphql-actix-web"
version = "2.10.4"
version = "2.10.8"
authors = ["sunli <scott_s829@163.com>", "Koxiaet"]
edition = "2021"
description = "async-graphql for actix-web"
@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql"]
categories = ["network-programming", "asynchronous"]
[dependencies]
async-graphql = { path = "../..", version = "=2.10.4" }
async-graphql = { path = "../..", version = "2.10.8" }
actix = "0.10.0"
actix-http = "2.2.0"

View File

@ -1,6 +1,6 @@
[package]
name = "async-graphql-axum"
version = "2.10.4"
version = "2.10.8"
authors = ["sunli <scott_s829@163.com>"]
edition = "2021"
description = "async-graphql for axum"
@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql", "axum"]
categories = ["network-programming", "asynchronous"]
[dependencies]
async-graphql = { path = "../..", version = "=2.10.4" }
async-graphql = { path = "../..", version = "2.10.8" }
async-trait = "0.1.51"
axum = { version = "0.2.5", features = ["ws", "headers"] }

View File

@ -1,6 +1,6 @@
[package]
name = "async-graphql-poem"
version = "2.10.4"
version = "2.10.8"
authors = ["sunli <scott_s829@163.com>"]
edition = "2021"
description = "async-graphql for poem"
@ -11,11 +11,10 @@ repository = "https://github.com/async-graphql/async-graphql"
keywords = ["futures", "async", "graphql", "poem"]
categories = ["network-programming", "asynchronous"]
[dependencies]
async-graphql = { path = "../..", version = "=2.10.4" }
async-graphql = { path = "../..", version = "2.10.8" }
poem = { version = "0.6.6", features = ["websocket"] }
poem = { version = "1.0.13", features = ["websocket"] }
futures-util = { version = "0.3.13", default-features = false }
serde_json = "1.0.66"
tokio-util = { version = "0.6.7", features = ["compat"] }

View File

@ -1,4 +1,5 @@
use async_graphql::http::MultipartOptions;
use poem::error::BadRequest;
use poem::http::{header, Method};
use poem::web::Query;
use poem::{async_trait, Error, FromRequest, Request, RequestBody, Result};
@ -12,7 +13,7 @@ use tokio_util::compat::TokioAsyncReadCompatExt;
/// # Example
///
/// ```
/// use poem::{handler, RouteMethod, route, EndpointExt};
/// use poem::{handler, Route, post, EndpointExt};
/// use poem::web::{Json, Data};
/// use poem::middleware::AddData;
/// use async_graphql_poem::GraphQLRequest;
@ -35,7 +36,7 @@ use tokio_util::compat::TokioAsyncReadCompatExt;
/// }
///
/// let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
/// let app = route().at("/", RouteMethod::new().post(index.with(AddData::new(schema))));
/// let app = Route::new().at("/", post(index.with(AddData::new(schema))));
/// ```
pub struct GraphQLRequest(pub async_graphql::Request);
@ -49,7 +50,7 @@ impl<'a> FromRequest<'a> for GraphQLRequest {
.await?
.0
.into_single()
.map_err(Error::bad_request)?,
.map_err(BadRequest)?,
))
}
}
@ -63,10 +64,7 @@ impl<'a> FromRequest<'a> for GraphQLBatchRequest {
async fn from_request(req: &'a Request, body: &mut RequestBody) -> Result<Self> {
if req.method() == Method::GET {
let req = Query::from_request(req, body)
.await
.map_err(Error::bad_request)?
.0;
let req = Query::from_request(req, body).await?.0;
Ok(Self(async_graphql::BatchRequest::Single(req)))
} else {
let content_type = req
@ -80,8 +78,7 @@ impl<'a> FromRequest<'a> for GraphQLBatchRequest {
body.take()?.into_async_read().compat(),
MultipartOptions::default(),
)
.await
.map_err(Error::bad_request)?,
.await?,
))
}
}

View File

@ -9,7 +9,7 @@ use crate::GraphQLBatchRequest;
/// # Example
///
/// ```
/// use poem::{route, RouteMethod};
/// use poem::{Route, post};
/// use async_graphql_poem::GraphQL;
/// use async_graphql::{EmptyMutation, EmptySubscription, Object, Schema};
///
@ -25,7 +25,7 @@ use crate::GraphQLBatchRequest;
/// type MySchema = Schema<Query, EmptyMutation, EmptySubscription>;
///
/// let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
/// let app = route().at("/", RouteMethod::new().post(GraphQL::new(schema)));
/// let app = Route::new().at("/", post(GraphQL::new(schema)));
/// ```
pub struct GraphQL<Query, Mutation, Subscription> {
schema: Schema<Query, Mutation, Subscription>,

View File

@ -12,7 +12,7 @@ use poem::{http, Endpoint, FromRequest, IntoResponse, Request, Response, Result}
/// # Example
///
/// ```
/// use poem::{route, RouteMethod};
/// use poem::{Route, get};
/// use async_graphql_poem::GraphQLSubscription;
/// use async_graphql::{EmptyMutation, Object, Schema, Subscription};
/// use futures_util::{Stream, stream};
@ -38,7 +38,7 @@ use poem::{http, Endpoint, FromRequest, IntoResponse, Request, Response, Result}
/// type MySchema = Schema<Query, EmptyMutation, Subscription>;
///
/// let schema = Schema::new(Query, EmptyMutation, Subscription);
/// let app = route().at("/ws", RouteMethod::new().get(GraphQLSubscription::new(schema)));
/// let app = Route::new().at("/ws", get(GraphQLSubscription::new(schema)));
/// ```
pub struct GraphQLSubscription<Query, Mutation, Subscription, F> {
schema: Schema<Query, Mutation, Subscription>,

View File

@ -1,6 +1,6 @@
[package]
name = "async-graphql-rocket"
version = "2.10.4"
version = "2.10.8"
authors = ["Daniel Wiesenberg <daniel@simplificAR.io>"]
edition = "2021"
description = "async-graphql for Rocket.rs"
@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql", "rocket"]
categories = ["network-programming", "asynchronous"]
[dependencies]
async-graphql = { path = "../..", version = "=2.10.4" }
async-graphql = { path = "../..", version = "2.10.8" }
rocket = { version = "0.5.0-rc.1", default-features = false }
serde = "1.0.126"

View File

@ -1,6 +1,6 @@
[package]
name = "async-graphql-tide"
version = "2.10.4"
version = "2.10.8"
authors = ["vkill <vkill.net@gmail.com>"]
edition = "2021"
description = "async-graphql for tide"
@ -16,7 +16,7 @@ default = ["websocket"]
websocket = ["tide-websockets"]
[dependencies]
async-graphql = { path = "../..", version = "=2.10.4" }
async-graphql = { path = "../..", version = "2.10.8" }
async-trait = "0.1.48"
futures-util = "0.3.13"
serde_json = "1.0.64"

View File

@ -1,6 +1,6 @@
[package]
name = "async-graphql-warp"
version = "2.10.4"
version = "2.10.8"
authors = ["sunli <scott_s829@163.com>", "Koxiaet"]
edition = "2021"
description = "async-graphql for warp"
@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql"]
categories = ["network-programming", "asynchronous"]
[dependencies]
async-graphql = { path = "../..", version = "=2.10.4" }
async-graphql = { path = "../..", version = "2.10.8" }
warp = { version = "0.3.0", default-features = false, features = ["websocket"] }
futures-util = { version = "0.3.13", default-features = false, features = ["sink"] }

View File

@ -1,6 +1,6 @@
[package]
name = "async-graphql-parser"
version = "2.10.4"
version = "2.10.8"
authors = ["sunli <scott_s829@163.com>", "Koxiaet"]
edition = "2021"
description = "GraphQL query parser for async-graphql"
@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql"]
categories = ["network-programming", "asynchronous"]
[dependencies]
async-graphql-value = { path = "../value", version = "=2.10.4" }
async-graphql-value = { path = "../value", version = "2.10.8" }
pest = "2.1.3"
pest_derive = "2.1.0"
serde_json = "1.0.64"

View File

@ -624,7 +624,7 @@ impl<'a> 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();

View File

@ -279,6 +279,7 @@ pub type FieldResult<T> = Result<T>;
/// | default | Use `Default::default` for default value | none | Y |
/// | default | Argument default value | literal | Y |
/// | default_with | Expression to generate default value | code string | Y |
/// | derived | Generate derived fields *[See also the Book](https://async-graphql.github.io/async-graphql/en/derived_fields.html).* | object | Y |
/// | validator | Input value validator | [`InputValueValidator`](validators/trait.InputValueValidator.html) | Y |
/// | complexity | Custom field complexity. *[See also the Book](https://async-graphql.github.io/async-graphql/en/depth_and_complexity.html).* | bool | Y |
/// | complexity | Custom field complexity. | string | Y |
@ -288,6 +289,14 @@ pub type FieldResult<T> = Result<T>;
/// | serial | Resolve each field sequentially. | bool | Y |
/// | key | Is entity key(for Federation) | bool | Y |
///
/// # Derived argument parameters
///
/// | Attribute | description | Type | Optional |
/// |--------------|------------------------------------------|------------ |----------|
/// | name | Generated derived field name | string | N |
/// | into | Type to derived an into | string | Y |
/// | with | Function to apply to manage advanced use cases | string| Y |
///
/// # Valid field return types
///
/// - Scalar values, such as `i32` and `bool`. `usize`, `isize`, `u128` and `i128` are not
@ -443,6 +452,7 @@ pub use async_graphql_derive::Object;
/// | name | Field name | string | Y |
/// | deprecation | Field deprecated | bool | Y |
/// | deprecation | Field deprecation reason | string | Y |
/// | derived | Generate derived fields *[See also the Book](https://async-graphql.github.io/async-graphql/en/derived_fields.html).* | object | Y |
/// | owned | Field resolver return a ownedship value | bool | Y |
/// | cache_control | Field cache control | [`CacheControl`](struct.CacheControl.html) | Y |
/// | external | Mark a field as owned by another service. This allows service A to use fields from service B while also knowing at runtime the types of that field. | bool | Y |
@ -452,6 +462,16 @@ pub use async_graphql_derive::Object;
/// | visible | If `false`, it will not be displayed in introspection. *[See also the Book](https://async-graphql.github.io/async-graphql/en/visibility.html).* | bool | Y |
/// | visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y |
///
/// # Derived argument parameters
///
/// | Attribute | description | Type | Optional |
/// |--------------|------------------------------------------|------------ |----------|
/// | name | Generated derived field name | string | N |
/// | into | Type to derived an into | string | Y |
/// | owned | Field resolver return a ownedship value | bool | Y |
/// | with | Function to apply to manage advanced use cases | string| Y |
///
///
/// # Examples
///
/// ```rust
@ -498,6 +518,7 @@ pub use async_graphql_derive::SimpleObject;
/// | name | Field name | string | Y |
/// | deprecation | Field deprecated | bool | Y |
/// | deprecation | Field deprecation reason | string | Y |
/// | derived | Generate derived fields *[See also the Book](https://async-graphql.github.io/async-graphql/en/derived_fields.html).* | object | Y |
/// | cache_control | Field cache control | [`CacheControl`](struct.CacheControl.html) | Y |
/// | external | Mark a field as owned by another service. This allows service A to use fields from service B while also knowing at runtime the types of that field. | bool | Y |
/// | provides | Annotate the expected returned fieldset from a field on a base type that is guaranteed to be selectable by the gateway. | string | Y |
@ -507,6 +528,14 @@ pub use async_graphql_derive::SimpleObject;
/// | visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y |
/// | secret | Mark this field as a secret, it will not output the actual value in the log. | bool | Y |
///
/// # Derived argument parameters
///
/// | Attribute | description | Type | Optional |
/// |--------------|------------------------------------------|------------ |----------|
/// | name | Generated derived field name | string | N |
/// | into | Type to derived an into | string | Y |
/// | with | Function to apply to manage advanced use cases | string| Y |
///
/// # Examples
///
/// ```rust

View File

@ -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, Value>, name: Name, value: Value) {
fn insert_value(target: &mut IndexMap<Name, Value>, 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);
}

View File

@ -98,6 +98,7 @@ impl Response {
}
/// Response for batchable queries
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum BatchResponse {

View File

@ -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());
}

View File

@ -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());
}

382
tests/derived_field.rs Normal file
View File

@ -0,0 +1,382 @@
use async_graphql::*;
#[tokio::test]
pub async fn test_derived_field_object() {
use serde::{Deserialize, Serialize};
struct Query;
#[derive(Serialize, Deserialize)]
struct ValueDerived(String);
scalar!(ValueDerived);
impl From<i32> for ValueDerived {
fn from(value: i32) -> Self {
ValueDerived(format!("{}", value))
}
}
#[Object]
impl Query {
#[graphql(derived(name = "value2", into = "ValueDerived"))]
async fn value1(&self, #[graphql(default = 100)] input: i32) -> i32 {
input
}
}
let query = "{ value1 value2 }";
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
assert_eq!(
schema.execute(query).await.data,
value!({
"value1": 100,
"value2": "100",
})
);
let query = "{ value1(input: 1) value2(input: 2) }";
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
assert_eq!(
schema.execute(query).await.data,
value!({
"value1": 1,
"value2": "2",
})
);
}
#[tokio::test]
pub async fn test_derived_field_object_with() {
use serde::{Deserialize, Serialize};
struct Query;
#[derive(Serialize, Deserialize)]
struct ValueDerived(String);
scalar!(ValueDerived);
impl From<i32> for ValueDerived {
fn from(value: i32) -> Self {
ValueDerived(format!("{}", value))
}
}
fn option_to_option<T, U: From<T>>(value: Option<T>) -> Option<U> {
value.map(|x| x.into())
}
#[Object]
impl Query {
#[graphql(derived(
name = "value2",
into = "Option<ValueDerived>",
with = "option_to_option"
))]
async fn value1(&self, #[graphql(default = 100)] input: i32) -> Option<i32> {
Some(input)
}
}
let query = "{ value1 value2 }";
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
assert_eq!(
schema.execute(query).await.data,
value!({
"value1": 100,
"value2": "100",
})
);
let query = "{ value1(input: 1) value2(input: 2) }";
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
assert_eq!(
schema.execute(query).await.data,
value!({
"value1": 1,
"value2": "2",
})
);
}
#[tokio::test]
pub async fn test_derived_field_simple_object() {
use serde::{Deserialize, Serialize};
struct Query;
#[derive(Serialize, Deserialize)]
struct ValueDerived(String);
scalar!(ValueDerived);
impl From<i32> for ValueDerived {
fn from(value: i32) -> Self {
ValueDerived(format!("{}", value))
}
}
#[derive(SimpleObject)]
struct TestObj {
#[graphql(owned, derived(name = "value2", into = "ValueDerived"))]
pub value1: i32,
}
#[Object]
impl Query {
async fn test(&self, #[graphql(default = 100)] input: i32) -> TestObj {
TestObj { value1: input }
}
}
let query = "{ test { value1 value2 } }";
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
assert_eq!(
schema.execute(query).await.data,
value!({
"test": {
"value1": 100,
"value2": "100",
}
})
);
let query = "{ test(input: 2) { value1 value2 }}";
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
dbg!(schema.execute(query).await);
assert_eq!(
schema.execute(query).await.data,
value!({
"test": {
"value1": 2,
"value2": "2",
}
})
);
}
#[tokio::test]
pub async fn test_derived_field_simple_object_option() {
use serde::{Deserialize, Serialize};
struct Query;
#[derive(Serialize, Deserialize, Clone)]
struct ValueDerived(String);
#[derive(Serialize, Deserialize, Clone)]
struct ValueDerived2(String);
scalar!(ValueDerived);
scalar!(ValueDerived2);
impl From<ValueDerived> for ValueDerived2 {
fn from(value: ValueDerived) -> Self {
ValueDerived2(value.0)
}
}
fn option_to_option<T, U: From<T>>(value: Option<T>) -> Option<U> {
value.map(|x| x.into())
}
fn vec_to_vec<T, U: From<T>>(value: Vec<T>) -> Vec<U> {
value.into_iter().map(|x| x.into()).collect()
}
fn vecopt_to_vecopt<T, U: From<T>>(value: Vec<Option<T>>) -> Vec<Option<U>> {
value.into_iter().map(|x| x.map(|opt| opt.into())).collect()
}
fn optvec_to_optvec<T, U: From<T>>(value: Option<Vec<T>>) -> Option<Vec<U>> {
value.map(|x| x.into_iter().map(|y| y.into()).collect())
}
#[derive(SimpleObject)]
struct TestObj {
#[graphql(derived(
owned,
name = "value2",
into = "Option<ValueDerived2>",
with = "option_to_option"
))]
pub value1: Option<ValueDerived>,
#[graphql(derived(
owned,
name = "value_vec_2",
into = "Vec<ValueDerived2>",
with = "vec_to_vec"
))]
pub value_vec_1: Vec<ValueDerived>,
#[graphql(derived(
owned,
name = "value_opt_vec_2",
into = "Option<Vec<ValueDerived2>>",
with = "optvec_to_optvec"
))]
pub value_opt_vec_1: Option<Vec<ValueDerived>>,
#[graphql(derived(
owned,
name = "value_vec_opt_2",
into = "Vec<Option<ValueDerived2>>",
with = "vecopt_to_vecopt"
))]
pub value_vec_opt_1: Vec<Option<ValueDerived>>,
}
#[Object]
impl Query {
async fn test(&self) -> TestObj {
TestObj {
value1: Some(ValueDerived("Test".to_string())),
value_vec_1: vec![ValueDerived("Test".to_string())],
value_opt_vec_1: Some(vec![ValueDerived("Test".to_string())]),
value_vec_opt_1: vec![Some(ValueDerived("Test".to_string()))],
}
}
}
let query = "{ test { value1 value2 valueVec1 valueVec2 valueOptVec1 valueOptVec2 } }";
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
assert_eq!(
schema.execute(query).await.data,
value!({
"test": {
"value1": "Test",
"value2": "Test",
"valueVec1": vec!["Test"],
"valueVec2": vec!["Test"],
"valueOptVec1": vec!["Test"],
"valueOptVec2": vec!["Test"],
}
})
);
}
#[tokio::test]
pub async fn test_derived_field_complex_object() {
use serde::{Deserialize, Serialize};
#[derive(SimpleObject)]
#[graphql(complex)]
struct MyObj {
a: i32,
#[graphql(owned, derived(name = "f", into = "ValueDerived"))]
b: i32,
}
#[derive(Serialize, Deserialize)]
struct ValueDerived(String);
scalar!(ValueDerived);
impl From<i32> for ValueDerived {
fn from(value: i32) -> Self {
ValueDerived(format!("{}", value))
}
}
#[ComplexObject]
impl MyObj {
async fn c(&self) -> i32 {
self.a + self.b
}
#[graphql(derived(name = "e", into = "ValueDerived"))]
async fn d(&self, v: i32) -> i32 {
self.a + self.b + v
}
}
struct Query;
#[Object]
impl Query {
async fn obj(&self) -> MyObj {
MyObj { a: 10, b: 20 }
}
}
let query = "{ obj { a b c d(v:100) e(v: 200) f } }";
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
dbg!(schema.execute(query).await);
assert_eq!(
schema.execute(query).await.data,
value!({
"obj": {
"a": 10,
"b": 20,
"c": 30,
"d": 130,
"e": "230",
"f": "20",
},
})
);
}
#[tokio::test]
pub async fn test_derived_field_complex_object_derived() {
use serde::{Deserialize, Serialize};
#[derive(SimpleObject)]
#[graphql(complex)]
struct MyObj {
a: i32,
#[graphql(owned, derived(name = "f", into = "ValueDerived"))]
b: i32,
}
#[derive(Serialize, Deserialize)]
struct ValueDerived(String);
scalar!(ValueDerived);
impl From<i32> for ValueDerived {
fn from(value: i32) -> Self {
ValueDerived(format!("{}", value))
}
}
fn option_to_option<T, U: From<T>>(value: Option<T>) -> Option<U> {
value.map(|x| x.into())
}
#[ComplexObject]
impl MyObj {
async fn c(&self) -> i32 {
self.a + self.b
}
#[graphql(derived(name = "e", into = "Option<ValueDerived>", with = "option_to_option"))]
async fn d(&self, v: i32) -> Option<i32> {
Some(self.a + self.b + v)
}
}
struct Query;
#[Object]
impl Query {
async fn obj(&self) -> MyObj {
MyObj { a: 10, b: 20 }
}
}
let query = "{ obj { a b c d(v:100) e(v: 200) f } }";
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
assert_eq!(
schema.execute(query).await.data,
value!({
"obj": {
"a": 10,
"b": 20,
"c": 30,
"d": 130,
"e": "230",
"f": "20",
},
})
);
}

60
tests/preserve_order.rs Normal file
View File

@ -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}"#
);
}

View File

@ -1,6 +1,6 @@
[package]
name = "async-graphql-value"
version = "2.10.4"
version = "2.10.8"
authors = ["sunli <scott_s829@163.com>", "Koxiaet"]
edition = "2021"
description = "GraphQL value for async-graphql"
@ -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"] }

View File

@ -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<Name, ConstValue>,
object: IndexMap<Name, ConstValue>,
visitor: V,
) -> Result<V::Value, DeserializerError>
where
@ -365,13 +366,13 @@ impl<'de> SeqAccess<'de> for SeqDeserializer {
}
struct MapDeserializer {
iter: <BTreeMap<Name, ConstValue> as IntoIterator>::IntoIter,
iter: <IndexMap<Name, ConstValue> as IntoIterator>::IntoIter,
value: Option<ConstValue>,
}
impl MapDeserializer {
#[inline]
fn new(map: BTreeMap<Name, ConstValue>) -> Self {
fn new(map: IndexMap<Name, ConstValue>) -> Self {
MapDeserializer {
iter: map.into_iter(),
value: None,

View File

@ -16,9 +16,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};
@ -137,7 +140,7 @@ pub enum ConstValue {
/// A list of values.
List(Vec<ConstValue>),
/// An object. This is a map of keys to values.
Object(BTreeMap<Name, ConstValue>),
Object(IndexMap<Name, ConstValue>),
}
impl PartialEq for ConstValue {
@ -252,8 +255,8 @@ impl<T: Into<ConstValue>> From<Vec<T>> for ConstValue {
}
}
impl From<BTreeMap<Name, ConstValue>> for ConstValue {
fn from(f: BTreeMap<Name, ConstValue>) -> Self {
impl From<IndexMap<Name, ConstValue>> for ConstValue {
fn from(f: IndexMap<Name, ConstValue>) -> Self {
ConstValue::Object(f)
}
}
@ -362,7 +365,7 @@ pub enum Value {
/// A list of values.
List(Vec<Value>),
/// An object. This is a map of keys to values.
Object(BTreeMap<Name, Value>),
Object(IndexMap<Name, Value>),
}
impl Value {

View File

@ -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)

View File

@ -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<usize>) -> Result<Self::SerializeMap, Self::Error> {
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<Self::SerializeStruct, Self::Error> {
Ok(SerializeStruct(BTreeMap::new()))
Ok(SerializeStruct(IndexMap::new()))
}
#[inline]
@ -244,7 +244,7 @@ impl ser::Serializer for Serializer {
variant: &'static str,
_len: usize,
) -> Result<Self::SerializeStructVariant, Self::Error> {
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<Self::Ok, Self::Error> {
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<Name, ConstValue>,
map: IndexMap<Name, ConstValue>,
key: Option<Name>,
}
@ -378,7 +378,7 @@ impl ser::SerializeMap for SerializeMap {
}
}
struct SerializeStruct(BTreeMap<Name, ConstValue>);
struct SerializeStruct(IndexMap<Name, ConstValue>);
impl ser::SerializeStruct for SerializeStruct {
type Ok = ConstValue;
@ -405,7 +405,7 @@ impl ser::SerializeStruct for SerializeStruct {
}
}
struct SerializeStructVariant(Name, BTreeMap<Name, ConstValue>);
struct SerializeStructVariant(Name, IndexMap<Name, ConstValue>);
impl ser::SerializeStructVariant for SerializeStructVariant {
type Ok = ConstValue;
@ -428,7 +428,7 @@ impl ser::SerializeStructVariant for SerializeStructVariant {
#[inline]
fn end(self) -> Result<Self::Ok, Self::Error> {
let mut map = BTreeMap::new();
let mut map = IndexMap::new();
map.insert(self.0, ConstValue::Object(self.1));
Ok(ConstValue::Object(map))
}

View File

@ -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);
}

View File

@ -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())
}
}