Merge branch 'master' into update-to-rust-edition-2021
This commit is contained in:
commit
abac47fc50
13
CHANGELOG.md
13
CHANGELOG.md
|
@ -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.
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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! {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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 {
|
||||
quote! {
|
||||
#[inline]
|
||||
#[allow(missing_docs)]
|
||||
#vis async fn #ident(&self, ctx: &#crate_name::Context<'_>) -> #crate_name::Result<&#ty> {
|
||||
::std::result::Result::Ok(&self.#ident)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
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(::std::clone::Clone::clone(&self.#ident))
|
||||
::std::result::Result::Ok(#block)
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
resolvers.push(quote! {
|
||||
if ctx.item.node.name.node == #field_name {
|
||||
|
|
|
@ -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))
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
|
|
103
docs/en/src/derived_fields.md
Normal file
103
docs/en/src/derived_fields.md
Normal 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>,
|
||||
}
|
||||
```
|
2
examples
2
examples
|
@ -1 +1 @@
|
|||
Subproject commit df8cfdff35d25d1cef3c9fda4d98e639a55fffc2
|
||||
Subproject commit 1683aac1883a6acf27747bf3e22491fa13aa538f
|
|
@ -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"
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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();
|
||||
|
|
29
src/lib.rs
29
src/lib.rs
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -98,6 +98,7 @@ impl Response {
|
|||
}
|
||||
|
||||
/// Response for batchable queries
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum BatchResponse {
|
||||
|
|
4
src/types/external/json_object/btreemap.rs
vendored
4
src/types/external/json_object/btreemap.rs
vendored
|
@ -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());
|
||||
}
|
||||
|
|
6
src/types/external/json_object/hashmap.rs
vendored
6
src/types/external/json_object/hashmap.rs
vendored
|
@ -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
382
tests/derived_field.rs
Normal 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
60
tests/preserve_order.rs
Normal 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}"#
|
||||
);
|
||||
}
|
|
@ -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"] }
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user