Merge branch 'async-graphql-v4' into master
This commit is contained in:
commit
f417068c27
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -4,6 +4,17 @@ 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).
|
||||
|
||||
# [3.0.38] 2022-4-8
|
||||
|
||||
- Update Axum integration to Axum 0.5.1 [#883](https://github.com/async-graphql/async-graphql/pull/883)
|
||||
- Support macro type in enum variant. [#884](https://github.com/async-graphql/async-graphql/pull/884)
|
||||
- Introduce process_with for input object [#817](https://github.com/async-graphql/async-graphql/pull/817)
|
||||
- Add `MaybeUndefined::update_to` method. [#881](https://github.com/async-graphql/async-graphql/issues/881)
|
||||
|
||||
# [3.0.37] 2022-3-30
|
||||
|
||||
- Panics when the same Rust type has the same name. [#880](https://github.com/async-graphql/async-graphql/issues/880)
|
||||
|
||||
# [3.0.36] 2022-3-22
|
||||
|
||||
- Generate `@deprecated` to SDL. [#874](https://github.com/async-graphql/async-graphql/issues/874)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "async-graphql"
|
||||
version = "3.0.37"
|
||||
version = "3.0.38"
|
||||
authors = ["sunli <scott_s829@163.com>", "Koxiaet"]
|
||||
edition = "2021"
|
||||
description = "A GraphQL server library implemented in Rust"
|
||||
|
@ -26,9 +26,9 @@ chrono-duration = ["chrono", "iso8601-duration"]
|
|||
password-strength-validator = ["zxcvbn"]
|
||||
|
||||
[dependencies]
|
||||
async-graphql-derive = { path = "derive", version = "3.0.36" }
|
||||
async-graphql-value = { path = "value", version = "3.0.36" }
|
||||
async-graphql-parser = { path = "parser", version = "3.0.36" }
|
||||
async-graphql-derive = { path = "derive", version = "3.0.38" }
|
||||
async-graphql-value = { path = "value", version = "3.0.38" }
|
||||
async-graphql-parser = { path = "parser", version = "3.0.38" }
|
||||
|
||||
async-stream = "0.3.0"
|
||||
async-trait = "0.1.48"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "async-graphql-derive"
|
||||
version = "3.0.36"
|
||||
version = "3.0.38"
|
||||
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 = "3.0.36" }
|
||||
async-graphql-parser = { path = "../parser", version = "3.0.38" }
|
||||
proc-macro2 = "1.0.24"
|
||||
syn = { version = "1.0.64", features = ["full", "extra-traits", "visit-mut", "visit"] }
|
||||
quote = "1.0.9"
|
||||
|
|
|
@ -154,6 +154,8 @@ pub struct SimpleObjectField {
|
|||
pub visible: Option<Visible>,
|
||||
#[darling(default, multiple)]
|
||||
pub derived: Vec<DerivedField>,
|
||||
#[darling(default)]
|
||||
pub process_with: Option<String>,
|
||||
// for InputObject
|
||||
#[darling(default)]
|
||||
pub default: Option<DefaultValue>,
|
||||
|
@ -212,6 +214,8 @@ pub struct Argument {
|
|||
pub default: Option<DefaultValue>,
|
||||
pub default_with: Option<LitStr>,
|
||||
pub validator: Option<Validators>,
|
||||
#[darling(default)]
|
||||
pub process_with: Option<String>,
|
||||
pub key: bool, // for entity
|
||||
pub visible: Option<Visible>,
|
||||
pub secret: bool,
|
||||
|
@ -372,6 +376,8 @@ pub struct InputObjectField {
|
|||
pub skip: bool,
|
||||
#[darling(default)]
|
||||
pub skip_input: bool,
|
||||
#[darling(default)]
|
||||
pub process_with: Option<String>,
|
||||
// for SimpleObject
|
||||
#[darling(default)]
|
||||
pub skip_output: bool,
|
||||
|
@ -547,6 +553,8 @@ pub struct SubscriptionFieldArgument {
|
|||
pub default: Option<DefaultValue>,
|
||||
pub default_with: Option<LitStr>,
|
||||
pub validator: Option<Validators>,
|
||||
#[darling(default)]
|
||||
pub process_with: Option<String>,
|
||||
pub visible: Option<Visible>,
|
||||
pub secret: bool,
|
||||
}
|
||||
|
|
|
@ -215,6 +215,17 @@ pub fn generate(
|
|||
});
|
||||
use_params.push(quote! { #ident });
|
||||
|
||||
let param_ident = &ident.ident;
|
||||
let process_with = match argument.process_with.as_ref() {
|
||||
Some(fn_path) => {
|
||||
let fn_path: syn::ExprPath = syn::parse_str(fn_path)?;
|
||||
quote! {
|
||||
#fn_path(&mut #param_ident);
|
||||
}
|
||||
}
|
||||
None => Default::default(),
|
||||
};
|
||||
|
||||
let validators = argument
|
||||
.validator
|
||||
.clone()
|
||||
|
@ -225,10 +236,15 @@ pub fn generate(
|
|||
quote!(#ty),
|
||||
Some(quote!(.map_err(|err| err.into_server_error(__pos)))),
|
||||
)?;
|
||||
let mut non_mut_ident = ident.clone();
|
||||
non_mut_ident.mutability = None;
|
||||
get_params.push(quote! {
|
||||
#[allow(non_snake_case, unused_variables)]
|
||||
let (__pos, #ident) = ctx.oneof_param_value::<#ty>()?;
|
||||
#[allow(non_snake_case, unused_variables, unused_mut)]
|
||||
let (__pos, mut #non_mut_ident) = ctx.oneof_param_value::<#ty>()?;
|
||||
#process_with
|
||||
#validators
|
||||
#[allow(non_snake_case, unused_variables)]
|
||||
let #ident = #non_mut_ident;
|
||||
});
|
||||
} else {
|
||||
for (
|
||||
|
@ -240,6 +256,7 @@ pub fn generate(
|
|||
default,
|
||||
default_with,
|
||||
validator,
|
||||
process_with,
|
||||
visible,
|
||||
secret,
|
||||
..
|
||||
|
@ -289,6 +306,17 @@ pub fn generate(
|
|||
None => quote! { ::std::option::Option::None },
|
||||
};
|
||||
|
||||
let param_ident = &ident.ident;
|
||||
let process_with = match process_with.as_ref() {
|
||||
Some(fn_path) => {
|
||||
let fn_path: syn::ExprPath = syn::parse_str(fn_path)?;
|
||||
quote! {
|
||||
#fn_path(&mut #param_ident);
|
||||
}
|
||||
}
|
||||
None => Default::default(),
|
||||
};
|
||||
|
||||
let validators = validator.clone().unwrap_or_default().create_validators(
|
||||
&crate_name,
|
||||
quote!(&#ident),
|
||||
|
@ -296,10 +324,15 @@ pub fn generate(
|
|||
Some(quote!(.map_err(|err| err.into_server_error(__pos)))),
|
||||
)?;
|
||||
|
||||
let mut non_mut_ident = ident.clone();
|
||||
non_mut_ident.mutability = None;
|
||||
get_params.push(quote! {
|
||||
#[allow(non_snake_case)]
|
||||
let (__pos, #ident) = ctx.param_value::<#ty>(#name, #default)?;
|
||||
#[allow(non_snake_case, unused_mut)]
|
||||
let (__pos, mut #non_mut_ident) = ctx.param_value::<#ty>(#name, #default)?;
|
||||
#process_with
|
||||
#validators
|
||||
#[allow(non_snake_case)]
|
||||
let #ident = #non_mut_ident;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,7 +130,7 @@ pub fn generate(enum_args: &args::Enum) -> GeneratorResult<TokenStream> {
|
|||
}
|
||||
|
||||
fn __create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String {
|
||||
registry.create_input_type::<Self, _>(|registry| {
|
||||
registry.create_input_type::<Self, _>(#crate_name::registry::MetaTypeId::Enum, |registry| {
|
||||
#crate_name::registry::MetaType::Enum {
|
||||
name: ::std::borrow::ToOwned::to_owned(#gql_typename),
|
||||
description: #desc,
|
||||
|
|
|
@ -75,6 +75,16 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
|
|||
|
||||
federation_fields.push((ty, name.clone()));
|
||||
|
||||
let process_with = match field.process_with.as_ref() {
|
||||
Some(fn_path) => {
|
||||
let fn_path: syn::ExprPath = syn::parse_str(fn_path)?;
|
||||
quote! {
|
||||
#fn_path(&mut #ident);
|
||||
}
|
||||
}
|
||||
None => Default::default(),
|
||||
};
|
||||
|
||||
let validators = field
|
||||
.validator
|
||||
.clone()
|
||||
|
@ -99,9 +109,11 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
|
|||
});
|
||||
|
||||
get_fields.push(quote! {
|
||||
let #ident: #ty = #crate_name::InputType::parse(
|
||||
#[allow(unused_mut)]
|
||||
let mut #ident: #ty = #crate_name::InputType::parse(
|
||||
::std::option::Option::Some(#crate_name::Value::Object(::std::clone::Clone::clone(&obj)))
|
||||
).map_err(#crate_name::InputValueError::propagate)?;
|
||||
#process_with
|
||||
#validators
|
||||
});
|
||||
|
||||
|
@ -137,8 +149,12 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
|
|||
let #ident: #ty = {
|
||||
match obj.get(#name) {
|
||||
::std::option::Option::Some(value) => {
|
||||
#crate_name::InputType::parse(::std::option::Option::Some(::std::clone::Clone::clone(&value)))
|
||||
.map_err(#crate_name::InputValueError::propagate)?
|
||||
#[allow(unused_mut)]
|
||||
let mut #ident = #crate_name::InputType::parse(::std::option::Option::Some(::std::clone::Clone::clone(&value)))
|
||||
.map_err(#crate_name::InputValueError::propagate)?;
|
||||
#process_with
|
||||
#ident
|
||||
|
||||
},
|
||||
::std::option::Option::None => #default,
|
||||
}
|
||||
|
@ -147,9 +163,10 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
|
|||
});
|
||||
} else {
|
||||
get_fields.push(quote! {
|
||||
#[allow(non_snake_case)]
|
||||
let #ident: #ty = #crate_name::InputType::parse(obj.get(#name).cloned())
|
||||
#[allow(non_snake_case, unused_mut)]
|
||||
let mut #ident: #ty = #crate_name::InputType::parse(obj.get(#name).cloned())
|
||||
.map_err(#crate_name::InputValueError::propagate)?;
|
||||
#process_with
|
||||
#validators
|
||||
});
|
||||
}
|
||||
|
@ -213,7 +230,7 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
|
|||
}
|
||||
|
||||
fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String {
|
||||
registry.create_input_type::<Self, _>(|registry| #crate_name::registry::MetaType::InputObject {
|
||||
registry.create_input_type::<Self, _>(#crate_name::registry::MetaTypeId::InputObject, |registry| #crate_name::registry::MetaType::InputObject {
|
||||
name: ::std::borrow::ToOwned::to_owned(#gql_typename),
|
||||
description: #desc,
|
||||
input_fields: {
|
||||
|
@ -260,7 +277,7 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
|
|||
#[allow(clippy::all, clippy::pedantic)]
|
||||
impl #impl_generics #ident #ty_generics #where_clause {
|
||||
fn __internal_create_type_info(registry: &mut #crate_name::registry::Registry, name: &str) -> ::std::string::String where Self: #crate_name::InputType {
|
||||
registry.create_input_type::<Self, _>(|registry| #crate_name::registry::MetaType::InputObject {
|
||||
registry.create_input_type::<Self, _>(#crate_name::registry::MetaTypeId::InputObject, |registry| #crate_name::registry::MetaType::InputObject {
|
||||
name: ::std::borrow::ToOwned::to_owned(name),
|
||||
description: #desc,
|
||||
input_fields: {
|
||||
|
|
|
@ -374,7 +374,7 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
|
|||
}
|
||||
|
||||
fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String {
|
||||
registry.create_output_type::<Self, _>(|registry| {
|
||||
registry.create_output_type::<Self, _>(#crate_name::registry::MetaTypeId::Interface, |registry| {
|
||||
#(#registry_types)*
|
||||
|
||||
#crate_name::registry::MetaType::Interface {
|
||||
|
|
|
@ -82,7 +82,7 @@ pub fn generate(object_args: &args::MergedObject) -> GeneratorResult<TokenStream
|
|||
}
|
||||
|
||||
fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String {
|
||||
registry.create_output_type::<Self, _>(|registry| {
|
||||
registry.create_output_type::<Self, _>(#crate_name::registry::MetaTypeId::Object, |registry| {
|
||||
let mut fields = ::std::default::Default::default();
|
||||
let mut cache_control = ::std::default::Default::default();
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ pub fn generate(newtype_args: &args::NewType) -> GeneratorResult<TokenStream> {
|
|||
};
|
||||
|
||||
quote! {
|
||||
registry.create_input_type::<#ident, _>(|_| #crate_name::registry::MetaType::Scalar {
|
||||
registry.create_input_type::<#ident, _>(#crate_name::registry::MetaTypeId::Scalar, |_| #crate_name::registry::MetaType::Scalar {
|
||||
name: ::std::borrow::ToOwned::to_owned(#name),
|
||||
description: #desc,
|
||||
is_valid: |value| <#ident as #crate_name::ScalarType>::is_valid(value),
|
||||
|
|
|
@ -360,6 +360,17 @@ pub fn generate(
|
|||
});
|
||||
use_params.push(quote! { #ident });
|
||||
|
||||
let param_ident = &ident.ident;
|
||||
let process_with = match argument.process_with.as_ref() {
|
||||
Some(fn_path) => {
|
||||
let fn_path: syn::ExprPath = syn::parse_str(fn_path)?;
|
||||
quote! {
|
||||
#fn_path(&mut #param_ident);
|
||||
}
|
||||
}
|
||||
None => Default::default(),
|
||||
};
|
||||
|
||||
let validators = argument
|
||||
.validator
|
||||
.clone()
|
||||
|
@ -370,10 +381,15 @@ pub fn generate(
|
|||
quote!(#ty),
|
||||
Some(quote!(.map_err(|err| err.into_server_error(__pos)))),
|
||||
)?;
|
||||
let mut non_mut_ident = ident.clone();
|
||||
non_mut_ident.mutability = None;
|
||||
get_params.push(quote! {
|
||||
#[allow(non_snake_case, unused_variables)]
|
||||
let (__pos, #ident) = ctx.oneof_param_value::<#ty>()?;
|
||||
#[allow(non_snake_case, unused_variables, unused_mut)]
|
||||
let (__pos, mut #non_mut_ident) = ctx.oneof_param_value::<#ty>()?;
|
||||
#process_with
|
||||
#validators
|
||||
#[allow(non_snake_case, unused_variables)]
|
||||
let #ident = #non_mut_ident;
|
||||
});
|
||||
} else {
|
||||
for (
|
||||
|
@ -384,6 +400,7 @@ pub fn generate(
|
|||
desc,
|
||||
default,
|
||||
default_with,
|
||||
process_with,
|
||||
validator,
|
||||
visible,
|
||||
secret,
|
||||
|
@ -434,6 +451,16 @@ pub fn generate(
|
|||
None => quote! { ::std::option::Option::None },
|
||||
};
|
||||
|
||||
let process_with = match process_with.as_ref() {
|
||||
Some(fn_path) => {
|
||||
let fn_path: syn::ExprPath = syn::parse_str(fn_path)?;
|
||||
quote! {
|
||||
#fn_path(&mut #param_ident);
|
||||
}
|
||||
}
|
||||
None => Default::default(),
|
||||
};
|
||||
|
||||
let validators = validator.clone().unwrap_or_default().create_validators(
|
||||
&crate_name,
|
||||
quote!(&#ident),
|
||||
|
@ -441,10 +468,15 @@ pub fn generate(
|
|||
Some(quote!(.map_err(|err| err.into_server_error(__pos)))),
|
||||
)?;
|
||||
|
||||
let mut non_mut_ident = ident.clone();
|
||||
non_mut_ident.mutability = None;
|
||||
get_params.push(quote! {
|
||||
#[allow(non_snake_case, unused_variables)]
|
||||
let (__pos, #ident) = ctx.param_value::<#ty>(#name, #default)?;
|
||||
#[allow(non_snake_case, unused_variables, unused_mut)]
|
||||
let (__pos, mut #non_mut_ident) = ctx.param_value::<#ty>(#name, #default)?;
|
||||
#process_with
|
||||
#validators
|
||||
#[allow(non_snake_case, unused_variables)]
|
||||
let #ident = #non_mut_ident;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -653,7 +685,7 @@ pub fn generate(
|
|||
}
|
||||
|
||||
fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String {
|
||||
let ty = registry.create_output_type::<Self, _>(|registry| #crate_name::registry::MetaType::Object {
|
||||
let ty = registry.create_output_type::<Self, _>(#crate_name::registry::MetaTypeId::Object, |registry| #crate_name::registry::MetaType::Object {
|
||||
name: ::std::borrow::ToOwned::to_owned(#gql_typename),
|
||||
description: #desc,
|
||||
fields: {
|
||||
|
@ -692,7 +724,7 @@ pub fn generate(
|
|||
|
||||
impl #impl_generics #self_ty #where_clause {
|
||||
fn __internal_create_type_info(registry: &mut #crate_name::registry::Registry, name: &str) -> ::std::string::String where Self: #crate_name::OutputType {
|
||||
let ty = registry.create_output_type::<Self, _>(|registry| #crate_name::registry::MetaType::Object {
|
||||
let ty = registry.create_output_type::<Self, _>(#crate_name::registry::MetaTypeId::Object, |registry| #crate_name::registry::MetaType::Object {
|
||||
name: ::std::borrow::ToOwned::to_owned(name),
|
||||
description: #desc,
|
||||
fields: {
|
||||
|
|
|
@ -131,7 +131,7 @@ pub fn generate(object_args: &args::OneofObject) -> GeneratorResult<TokenStream>
|
|||
}
|
||||
|
||||
fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String {
|
||||
registry.create_input_type::<Self, _>(|registry| #crate_name::registry::MetaType::InputObject {
|
||||
registry.create_input_type::<Self, _>(#crate_name::registry::MetaTypeId::InputObject, |registry| #crate_name::registry::MetaType::InputObject {
|
||||
name: ::std::borrow::ToOwned::to_owned(#gql_typename),
|
||||
description: #desc,
|
||||
input_fields: {
|
||||
|
@ -181,7 +181,7 @@ pub fn generate(object_args: &args::OneofObject) -> GeneratorResult<TokenStream>
|
|||
#[allow(clippy::all, clippy::pedantic)]
|
||||
impl #impl_generics #ident #ty_generics #where_clause {
|
||||
fn __internal_create_type_info(registry: &mut #crate_name::registry::Registry, name: &str) -> ::std::string::String where Self: #crate_name::InputType {
|
||||
registry.create_input_type::<Self, _>(|registry| #crate_name::registry::MetaType::InputObject {
|
||||
registry.create_input_type::<Self, _>(#crate_name::registry::MetaTypeId::InputObject, |registry| #crate_name::registry::MetaType::InputObject {
|
||||
name: ::std::borrow::ToOwned::to_owned(name),
|
||||
description: #desc,
|
||||
input_fields: {
|
||||
|
|
|
@ -47,7 +47,7 @@ pub fn generate(
|
|||
}
|
||||
|
||||
fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String {
|
||||
registry.create_input_type::<#self_ty, _>(|_| #crate_name::registry::MetaType::Scalar {
|
||||
registry.create_input_type::<#self_ty, _>(#crate_name::registry::MetaTypeId::Scalar, |_| #crate_name::registry::MetaType::Scalar {
|
||||
name: ::std::borrow::ToOwned::to_owned(#gql_typename),
|
||||
description: #desc,
|
||||
is_valid: |value| <#self_ty as #crate_name::ScalarType>::is_valid(value),
|
||||
|
@ -77,7 +77,7 @@ pub fn generate(
|
|||
}
|
||||
|
||||
fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String {
|
||||
registry.create_output_type::<#self_ty, _>(|_| #crate_name::registry::MetaType::Scalar {
|
||||
registry.create_output_type::<#self_ty, _>(#crate_name::registry::MetaTypeId::Scalar, |_| #crate_name::registry::MetaType::Scalar {
|
||||
name: ::std::borrow::ToOwned::to_owned(#gql_typename),
|
||||
description: #desc,
|
||||
is_valid: |value| <#self_ty as #crate_name::ScalarType>::is_valid(value),
|
||||
|
|
|
@ -311,7 +311,7 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
|
|||
}
|
||||
|
||||
fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String {
|
||||
registry.create_output_type::<Self, _>(|registry| #crate_name::registry::MetaType::Object {
|
||||
registry.create_output_type::<Self, _>(#crate_name::registry::MetaTypeId::Object, |registry| #crate_name::registry::MetaType::Object {
|
||||
name: ::std::borrow::ToOwned::to_owned(#gql_typename),
|
||||
description: #desc,
|
||||
fields: {
|
||||
|
@ -375,7 +375,7 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
|
|||
name: &str,
|
||||
complex_fields: #crate_name::indexmap::IndexMap<::std::string::String, #crate_name::registry::MetaField>,
|
||||
) -> ::std::string::String where Self: #crate_name::OutputType {
|
||||
registry.create_output_type::<Self, _>(|registry| #crate_name::registry::MetaType::Object {
|
||||
registry.create_output_type::<Self, _>(#crate_name::registry::MetaTypeId::Object, |registry| #crate_name::registry::MetaType::Object {
|
||||
name: ::std::borrow::ToOwned::to_owned(name),
|
||||
description: #desc,
|
||||
fields: {
|
||||
|
|
|
@ -92,6 +92,17 @@ pub fn generate(
|
|||
});
|
||||
use_params.push(quote! { #ident });
|
||||
|
||||
let param_ident = &ident.ident;
|
||||
let process_with = match argument.process_with.as_ref() {
|
||||
Some(fn_path) => {
|
||||
let fn_path: syn::ExprPath = syn::parse_str(fn_path)?;
|
||||
quote! {
|
||||
#fn_path(&mut #param_ident);
|
||||
}
|
||||
}
|
||||
None => Default::default(),
|
||||
};
|
||||
|
||||
let validators = argument
|
||||
.validator
|
||||
.clone()
|
||||
|
@ -102,10 +113,15 @@ pub fn generate(
|
|||
quote!(#ty),
|
||||
Some(quote!(.map_err(|err| err.into_server_error(__pos)))),
|
||||
)?;
|
||||
let mut non_mut_ident = ident.clone();
|
||||
non_mut_ident.mutability = None;
|
||||
get_params.push(quote! {
|
||||
#[allow(non_snake_case, unused_variables)]
|
||||
let (__pos, #ident) = ctx.oneof_param_value::<#ty>()?;
|
||||
#[allow(non_snake_case, unused_variables, unused_mut)]
|
||||
let (__pos, mut #non_mut_ident) = ctx.oneof_param_value::<#ty>()?;
|
||||
#process_with
|
||||
#validators
|
||||
#[allow(non_snake_case, unused_variables)]
|
||||
let #ident = #non_mut_ident;
|
||||
});
|
||||
} else {
|
||||
for (
|
||||
|
@ -117,6 +133,7 @@ pub fn generate(
|
|||
default,
|
||||
default_with,
|
||||
validator,
|
||||
process_with,
|
||||
visible: arg_visible,
|
||||
secret,
|
||||
},
|
||||
|
@ -164,6 +181,18 @@ pub fn generate(
|
|||
}
|
||||
None => quote! { ::std::option::Option::None },
|
||||
};
|
||||
|
||||
let param_ident = &ident.ident;
|
||||
let process_with = match process_with.as_ref() {
|
||||
Some(fn_path) => {
|
||||
let fn_path: syn::ExprPath = syn::parse_str(fn_path)?;
|
||||
quote! {
|
||||
#fn_path(&mut #param_ident);
|
||||
}
|
||||
}
|
||||
None => Default::default(),
|
||||
};
|
||||
|
||||
let validators = validator.clone().unwrap_or_default().create_validators(
|
||||
&crate_name,
|
||||
quote!(&#ident),
|
||||
|
@ -171,10 +200,15 @@ pub fn generate(
|
|||
Some(quote!(.map_err(|err| err.into_server_error(__pos)))),
|
||||
)?;
|
||||
|
||||
let mut non_mut_ident = ident.clone();
|
||||
non_mut_ident.mutability = None;
|
||||
get_params.push(quote! {
|
||||
#[allow(non_snake_case)]
|
||||
let (__pos, #ident) = ctx.param_value::<#ty>(#name, #default)?;
|
||||
#[allow(non_snake_case, unused_mut)]
|
||||
let (__pos, mut #non_mut_ident) = ctx.param_value::<#ty>(#name, #default)?;
|
||||
#process_with
|
||||
#validators
|
||||
#[allow(non_snake_case)]
|
||||
let #ident = #non_mut_ident;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,9 +60,14 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
|
|||
}
|
||||
};
|
||||
|
||||
if let Type::Path(p) = &ty {
|
||||
let mut ty = ty;
|
||||
while let Type::Group(group) = ty {
|
||||
ty = &*group.elem;
|
||||
}
|
||||
|
||||
if matches!(ty, Type::Path(_) | Type::Macro(_)) {
|
||||
// This validates that the field type wasn't already used
|
||||
if !enum_items.insert(p) {
|
||||
if !enum_items.insert(ty) {
|
||||
return Err(
|
||||
Error::new_spanned(&ty, "This type already used in another variant").into(),
|
||||
);
|
||||
|
@ -70,16 +75,16 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
|
|||
|
||||
enum_names.push(enum_name);
|
||||
|
||||
let mut assert_ty = p.clone();
|
||||
RemoveLifetime.visit_type_path_mut(&mut assert_ty);
|
||||
let mut assert_ty = ty.clone();
|
||||
RemoveLifetime.visit_type_mut(&mut assert_ty);
|
||||
|
||||
if !variant.flatten {
|
||||
type_into_impls.push(quote! {
|
||||
#crate_name::static_assertions::assert_impl_one!(#assert_ty: #crate_name::ObjectType);
|
||||
|
||||
#[allow(clippy::all, clippy::pedantic)]
|
||||
impl #impl_generics ::std::convert::From<#p> for #ident #ty_generics #where_clause {
|
||||
fn from(obj: #p) -> Self {
|
||||
impl #impl_generics ::std::convert::From<#ty> for #ident #ty_generics #where_clause {
|
||||
fn from(obj: #ty) -> Self {
|
||||
#ident::#enum_name(obj)
|
||||
}
|
||||
}
|
||||
|
@ -89,8 +94,8 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
|
|||
#crate_name::static_assertions::assert_impl_one!(#assert_ty: #crate_name::UnionType);
|
||||
|
||||
#[allow(clippy::all, clippy::pedantic)]
|
||||
impl #impl_generics ::std::convert::From<#p> for #ident #ty_generics #where_clause {
|
||||
fn from(obj: #p) -> Self {
|
||||
impl #impl_generics ::std::convert::From<#ty> for #ident #ty_generics #where_clause {
|
||||
fn from(obj: #ty) -> Self {
|
||||
#ident::#enum_name(obj)
|
||||
}
|
||||
}
|
||||
|
@ -99,15 +104,15 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
|
|||
|
||||
if !variant.flatten {
|
||||
registry_types.push(quote! {
|
||||
<#p as #crate_name::OutputType>::create_type_info(registry);
|
||||
<#ty as #crate_name::OutputType>::create_type_info(registry);
|
||||
});
|
||||
possible_types.push(quote! {
|
||||
possible_types.insert(<#p as #crate_name::OutputType>::type_name().into_owned());
|
||||
possible_types.insert(<#ty as #crate_name::OutputType>::type_name().into_owned());
|
||||
});
|
||||
} else {
|
||||
possible_types.push(quote! {
|
||||
if let #crate_name::registry::MetaType::Union { possible_types: possible_types2, .. } =
|
||||
registry.create_fake_output_type::<#p>() {
|
||||
registry.create_fake_output_type::<#ty>() {
|
||||
possible_types.extend(possible_types2);
|
||||
}
|
||||
});
|
||||
|
@ -115,11 +120,11 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
|
|||
|
||||
if !variant.flatten {
|
||||
get_introspection_typename.push(quote! {
|
||||
#ident::#enum_name(obj) => <#p as #crate_name::OutputType>::type_name()
|
||||
#ident::#enum_name(obj) => <#ty as #crate_name::OutputType>::type_name()
|
||||
});
|
||||
} else {
|
||||
get_introspection_typename.push(quote! {
|
||||
#ident::#enum_name(obj) => <#p as #crate_name::OutputType>::introspection_type_name(obj)
|
||||
#ident::#enum_name(obj) => <#ty as #crate_name::OutputType>::introspection_type_name(obj)
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -172,7 +177,7 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
|
|||
}
|
||||
|
||||
fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String {
|
||||
registry.create_output_type::<Self, _>(|registry| {
|
||||
registry.create_output_type::<Self, _>(#crate_name::registry::MetaTypeId::Union, |registry| {
|
||||
#(#registry_types)*
|
||||
|
||||
#crate_name::registry::MetaType::Union {
|
||||
|
|
|
@ -25,3 +25,12 @@ impl Query {
|
|||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Errors in subscriptions
|
||||
|
||||
Errors can be returned from subscription resolvers as well, using a return type of the form:
|
||||
```rust
|
||||
async fn my_subscription_resolver(&self) -> impl Stream<Item = Result<MyItem, MyError>> { ... }
|
||||
```
|
||||
|
||||
Note however that the `MyError` struct must have `Clone` implemented, due to the restrictions placed by the `Subscription` macro. One way to accomplish this is by creating a custom error type, with `#[derive(Clone)]`, as [seen here](https://github.com/async-graphql/async-graphql/issues/845#issuecomment-1090933464).
|
||||
|
|
2
examples
2
examples
|
@ -1 +1 @@
|
|||
Subproject commit 6a63808523f7e4995f9111a3497f06921cd3b11b
|
||||
Subproject commit ffb8cb9518e0f23ce17f171c957d3f7948bb1e00
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "async-graphql-actix-web"
|
||||
version = "3.0.36"
|
||||
version = "3.0.38"
|
||||
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 = "3.0.36" }
|
||||
async-graphql = { path = "../..", version = "3.0.38" }
|
||||
|
||||
actix = "0.13.0"
|
||||
actix-http = "3.0.1"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "async-graphql-axum"
|
||||
version = "3.0.36"
|
||||
version = "3.0.38"
|
||||
authors = ["sunli <scott_s829@163.com>"]
|
||||
edition = "2021"
|
||||
description = "async-graphql for axum"
|
||||
|
@ -12,14 +12,14 @@ keywords = ["futures", "async", "graphql", "axum"]
|
|||
categories = ["network-programming", "asynchronous"]
|
||||
|
||||
[dependencies]
|
||||
async-graphql = { path = "../..", version = "3.0.36" }
|
||||
async-graphql = { path = "../..", version = "3.0.38" }
|
||||
|
||||
async-trait = "0.1.51"
|
||||
axum = { version = "0.4", features = ["ws", "headers"] }
|
||||
axum = { version = "0.5.1", features = ["ws", "headers"] }
|
||||
bytes = "1.0.1"
|
||||
http-body = "0.4.2"
|
||||
serde_json = "1.0.66"
|
||||
serde_urlencoded = "0.7.0"
|
||||
tokio-util = { version = "0.6.7", features = ["io", "compat"] }
|
||||
tokio-util = { version = "0.7.1", features = ["io", "compat"] }
|
||||
futures-util = "0.3.0"
|
||||
tower-service = "0.3"
|
||||
|
|
|
@ -106,7 +106,7 @@ where
|
|||
} else {
|
||||
let content_type = req
|
||||
.headers()
|
||||
.and_then(|headers| headers.get(http::header::CONTENT_TYPE))
|
||||
.get(http::header::CONTENT_TYPE)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.map(ToString::to_string);
|
||||
let body_stream = BodyStream::from_request(req)
|
||||
|
|
|
@ -29,7 +29,7 @@ impl<B: Send> FromRequest<B> for GraphQLProtocol {
|
|||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
req.headers()
|
||||
.and_then(|headers| headers.get(http::header::SEC_WEBSOCKET_PROTOCOL))
|
||||
.get(http::header::SEC_WEBSOCKET_PROTOCOL)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.and_then(|protocols| {
|
||||
protocols
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "async-graphql-poem"
|
||||
version = "3.0.36"
|
||||
version = "3.0.38"
|
||||
authors = ["sunli <scott_s829@163.com>"]
|
||||
edition = "2021"
|
||||
description = "async-graphql for poem"
|
||||
|
@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql", "poem"]
|
|||
categories = ["network-programming", "asynchronous"]
|
||||
|
||||
[dependencies]
|
||||
async-graphql = { path = "../..", version = "3.0.36" }
|
||||
async-graphql = { path = "../..", version = "3.0.38" }
|
||||
|
||||
poem = { version = "1.2.2", features = ["websocket"] }
|
||||
futures-util = { version = "0.3.0", default-features = false }
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "async-graphql-rocket"
|
||||
version = "3.0.36"
|
||||
version = "3.0.38"
|
||||
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 = "3.0.36" }
|
||||
async-graphql = { path = "../..", version = "3.0.38" }
|
||||
|
||||
rocket = { version = "0.5.0-rc.1", default-features = false }
|
||||
serde = "1.0.126"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "async-graphql-tide"
|
||||
version = "3.0.36"
|
||||
version = "3.0.38"
|
||||
authors = ["vkill <vkill.net@gmail.com>", "sunli <scott_s829@163.com>"]
|
||||
edition = "2021"
|
||||
description = "async-graphql for tide"
|
||||
|
@ -16,7 +16,7 @@ default = ["websocket"]
|
|||
websocket = ["tide-websockets"]
|
||||
|
||||
[dependencies]
|
||||
async-graphql = { path = "../..", version = "3.0.36" }
|
||||
async-graphql = { path = "../..", version = "3.0.38" }
|
||||
|
||||
async-trait = "0.1.48"
|
||||
futures-util = "0.3.0"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "async-graphql-warp"
|
||||
version = "3.0.36"
|
||||
version = "3.0.38"
|
||||
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 = "3.0.36" }
|
||||
async-graphql = { path = "../..", version = "3.0.38" }
|
||||
|
||||
warp = { version = "0.3.0", default-features = false, features = ["websocket"] }
|
||||
futures-util = { version = "0.3.0", default-features = false, features = ["sink"] }
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "async-graphql-parser"
|
||||
version = "3.0.36"
|
||||
version = "3.0.38"
|
||||
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 = "3.0.36" }
|
||||
async-graphql-value = { path = "../value", version = "3.0.38" }
|
||||
pest = "2.1.3"
|
||||
pest_derive = "2.1.0"
|
||||
serde_json = "1.0.64"
|
||||
|
|
|
@ -13,11 +13,11 @@ use http::HeaderValue;
|
|||
use serde::ser::{SerializeSeq, Serializer};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::extensions::Extensions;
|
||||
use crate::parser::types::{
|
||||
Directive, Field, FragmentDefinition, OperationDefinition, Selection, SelectionSet,
|
||||
};
|
||||
use crate::schema::SchemaEnv;
|
||||
use crate::{extensions::Extensions, schema::IntrospectionMode};
|
||||
use crate::{
|
||||
Error, InputType, Lookahead, Name, OneofObjectType, PathSegment, Pos, Positioned, Result,
|
||||
ServerError, ServerResult, UploadValue, Value,
|
||||
|
@ -244,7 +244,7 @@ pub struct QueryEnvInner {
|
|||
pub session_data: Arc<Data>,
|
||||
pub ctx_data: Arc<Data>,
|
||||
pub http_headers: Mutex<HeaderMap>,
|
||||
pub disable_introspection: bool,
|
||||
pub introspection_mode: IntrospectionMode,
|
||||
pub errors: Mutex<Vec<ServerError>>,
|
||||
}
|
||||
|
||||
|
|
|
@ -52,6 +52,7 @@ some simple fields, and use the `ComplexObject` macro to define some other field
|
|||
| 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 |
|
||||
| secret | Mark this field as a secret, it will not output the actual value in the log. | bool | Y |
|
||||
| process_with | Upon successful parsing, invokes specified function. Its signature must be `fn(&mut T)`. | code path | Y |
|
||||
|
||||
# Examples
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ Define a GraphQL input object
|
|||
| flatten | Similar to serde (flatten) | boolean | Y |
|
||||
| skip | Skip this field, use `Default::default` to get a default value for this field. | bool | Y |
|
||||
| skip_input | Skip this field, similar to `skip`, but avoids conflicts when this macro is used with `SimpleObject`. | bool | Y |
|
||||
| process_with | Upon successful parsing, invokes specified function. Its signature must be `fn(&mut T)`. | code path | Y |
|
||||
| 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 |
|
||||
| secret | Mark this field as a secret, it will not output the actual value in the log. | bool | Y |
|
||||
|
|
|
@ -56,6 +56,7 @@ All methods are converted to camelCase.
|
|||
| 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 |
|
||||
| key | Is entity key(for Federation) | bool | Y |
|
||||
| process_with | Upon successful parsing, invokes specified function. Its signature must be `fn(&mut T)`. | code path | Y |
|
||||
|
||||
# Derived argument attributes
|
||||
|
||||
|
|
|
@ -47,6 +47,7 @@ The filter function should be synchronous.
|
|||
| validator | Input value validator *[See also the Book](https://async-graphql.github.io/async-graphql/en/input_value_validators.html)* | object | Y |
|
||||
| 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 |
|
||||
| process_with | Upon successful parsing, invokes specified function. Its signature must be `fn(&mut T)`. | code path | Y |
|
||||
|
||||
# Examples
|
||||
|
||||
|
|
|
@ -91,8 +91,8 @@ pub async fn receive_batch_json(body: impl AsyncRead) -> Result<BatchRequest, Pa
|
|||
body.read_to_end(&mut data)
|
||||
.await
|
||||
.map_err(ParseRequestError::Io)?;
|
||||
Ok(serde_json::from_slice::<BatchRequest>(&data)
|
||||
.map_err(|e| ParseRequestError::InvalidRequest(Box::new(e)))?)
|
||||
serde_json::from_slice::<BatchRequest>(&data)
|
||||
.map_err(|e| ParseRequestError::InvalidRequest(Box::new(e)))
|
||||
}
|
||||
|
||||
/// Receive a GraphQL request from a body as CBOR.
|
||||
|
@ -111,6 +111,6 @@ pub async fn receive_batch_cbor(body: impl AsyncRead) -> Result<BatchRequest, Pa
|
|||
body.read_to_end(&mut data)
|
||||
.await
|
||||
.map_err(ParseRequestError::Io)?;
|
||||
Ok(serde_cbor::from_slice::<BatchRequest>(&data)
|
||||
.map_err(|e| ParseRequestError::InvalidRequest(Box::new(e)))?)
|
||||
serde_cbor::from_slice::<BatchRequest>(&data)
|
||||
.map_err(|e| ParseRequestError::InvalidRequest(Box::new(e)))
|
||||
}
|
||||
|
|
|
@ -365,6 +365,7 @@ impl std::str::FromStr for Protocols {
|
|||
/// A websocket message received from the client
|
||||
#[derive(Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
#[allow(clippy::large_enum_variant)] //Request is at fault
|
||||
pub enum ClientMessage {
|
||||
/// A new connection
|
||||
ConnectionInit {
|
||||
|
|
|
@ -33,13 +33,7 @@ impl<'a> Lookahead<'a> {
|
|||
pub fn field(&self, name: &str) -> Self {
|
||||
let mut fields = Vec::new();
|
||||
for field in &self.fields {
|
||||
filter(
|
||||
&mut fields,
|
||||
self.fragments,
|
||||
&field.selection_set.node,
|
||||
name,
|
||||
self.context,
|
||||
)
|
||||
filter(&mut fields, self.fragments, &field.selection_set.node, name)
|
||||
}
|
||||
|
||||
Self {
|
||||
|
@ -108,7 +102,6 @@ fn filter<'a>(
|
|||
fragments: &'a HashMap<Name, Positioned<FragmentDefinition>>,
|
||||
selection_set: &'a SelectionSet,
|
||||
name: &str,
|
||||
context: &'a Context<'a>,
|
||||
) {
|
||||
for item in &selection_set.items {
|
||||
match &item.node {
|
||||
|
@ -117,22 +110,12 @@ fn filter<'a>(
|
|||
fields.push(&field.node)
|
||||
}
|
||||
}
|
||||
Selection::InlineFragment(fragment) => filter(
|
||||
fields,
|
||||
fragments,
|
||||
&fragment.node.selection_set.node,
|
||||
name,
|
||||
context,
|
||||
),
|
||||
Selection::InlineFragment(fragment) => {
|
||||
filter(fields, fragments, &fragment.node.selection_set.node, name)
|
||||
}
|
||||
Selection::FragmentSpread(spread) => {
|
||||
if let Some(fragment) = fragments.get(&spread.node.fragment_name.node) {
|
||||
filter(
|
||||
fields,
|
||||
fragments,
|
||||
&fragment.node.selection_set.node,
|
||||
name,
|
||||
context,
|
||||
)
|
||||
filter(fields, fragments, &fragment.node.selection_set.node, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,18 +3,20 @@ mod export_sdl;
|
|||
mod stringify_exec_doc;
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use indexmap::map::IndexMap;
|
||||
use indexmap::set::IndexSet;
|
||||
|
||||
pub use crate::model::__DirectiveLocation;
|
||||
use crate::parser::types::{
|
||||
BaseType as ParsedBaseType, Field, Type as ParsedType, VariableDefinition,
|
||||
};
|
||||
use crate::{
|
||||
model, Any, Context, InputType, OutputType, Positioned, ServerResult, SubscriptionType, Value,
|
||||
VisitorContext,
|
||||
};
|
||||
use crate::{
|
||||
parser::types::{BaseType as ParsedBaseType, Field, Type as ParsedType, VariableDefinition},
|
||||
schema::IntrospectionMode,
|
||||
};
|
||||
|
||||
pub use cache_control::CacheControl;
|
||||
|
||||
|
@ -179,6 +181,29 @@ pub struct MetaEnumValue {
|
|||
|
||||
type MetaVisibleFn = fn(&Context<'_>) -> bool;
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum MetaTypeId {
|
||||
Scalar,
|
||||
Object,
|
||||
Interface,
|
||||
Union,
|
||||
Enum,
|
||||
InputObject,
|
||||
}
|
||||
|
||||
impl Display for MetaTypeId {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(match self {
|
||||
MetaTypeId::Scalar => "Scalar",
|
||||
MetaTypeId::Object => "Object",
|
||||
MetaTypeId::Interface => "Interface",
|
||||
MetaTypeId::Union => "Union",
|
||||
MetaTypeId::Enum => "Enum",
|
||||
MetaTypeId::InputObject => "InputObject",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum MetaType {
|
||||
Scalar {
|
||||
|
@ -234,6 +259,18 @@ pub enum MetaType {
|
|||
}
|
||||
|
||||
impl MetaType {
|
||||
#[inline]
|
||||
pub fn type_id(&self) -> MetaTypeId {
|
||||
match self {
|
||||
MetaType::Scalar { .. } => MetaTypeId::Scalar,
|
||||
MetaType::Object { .. } => MetaTypeId::Object,
|
||||
MetaType::Interface { .. } => MetaTypeId::Interface,
|
||||
MetaType::Union { .. } => MetaTypeId::Union,
|
||||
MetaType::Enum { .. } => MetaTypeId::Enum,
|
||||
MetaType::InputObject { .. } => MetaTypeId::InputObject,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn field_by_name(&self, name: &str) -> Option<&MetaField> {
|
||||
self.fields().and_then(|fields| fields.get(name))
|
||||
|
@ -365,36 +402,51 @@ pub struct Registry {
|
|||
pub query_type: String,
|
||||
pub mutation_type: Option<String>,
|
||||
pub subscription_type: Option<String>,
|
||||
pub disable_introspection: bool,
|
||||
pub introspection_mode: IntrospectionMode,
|
||||
pub enable_federation: bool,
|
||||
pub federation_subscription: bool,
|
||||
}
|
||||
|
||||
impl Registry {
|
||||
pub fn create_input_type<T: InputType + ?Sized, F: FnMut(&mut Registry) -> MetaType>(
|
||||
&mut self,
|
||||
mut f: F,
|
||||
) -> String {
|
||||
self.create_type(&mut f, &*T::type_name(), std::any::type_name::<T>());
|
||||
pub fn create_input_type<T, F>(&mut self, type_id: MetaTypeId, mut f: F) -> String
|
||||
where
|
||||
T: InputType + ?Sized,
|
||||
F: FnMut(&mut Registry) -> MetaType,
|
||||
{
|
||||
self.create_type(
|
||||
&mut f,
|
||||
&*T::type_name(),
|
||||
std::any::type_name::<T>(),
|
||||
type_id,
|
||||
);
|
||||
T::qualified_type_name()
|
||||
}
|
||||
|
||||
pub fn create_output_type<T: OutputType + ?Sized, F: FnMut(&mut Registry) -> MetaType>(
|
||||
&mut self,
|
||||
mut f: F,
|
||||
) -> String {
|
||||
self.create_type(&mut f, &*T::type_name(), std::any::type_name::<T>());
|
||||
pub fn create_output_type<T, F>(&mut self, type_id: MetaTypeId, mut f: F) -> String
|
||||
where
|
||||
T: OutputType + ?Sized,
|
||||
F: FnMut(&mut Registry) -> MetaType,
|
||||
{
|
||||
self.create_type(
|
||||
&mut f,
|
||||
&*T::type_name(),
|
||||
std::any::type_name::<T>(),
|
||||
type_id,
|
||||
);
|
||||
T::qualified_type_name()
|
||||
}
|
||||
|
||||
pub fn create_subscription_type<
|
||||
pub fn create_subscription_type<T, F>(&mut self, mut f: F) -> String
|
||||
where
|
||||
T: SubscriptionType + ?Sized,
|
||||
F: FnMut(&mut Registry) -> MetaType,
|
||||
>(
|
||||
&mut self,
|
||||
mut f: F,
|
||||
) -> String {
|
||||
self.create_type(&mut f, &*T::type_name(), std::any::type_name::<T>());
|
||||
{
|
||||
self.create_type(
|
||||
&mut f,
|
||||
&*T::type_name(),
|
||||
std::any::type_name::<T>(),
|
||||
MetaTypeId::Object,
|
||||
);
|
||||
T::qualified_type_name()
|
||||
}
|
||||
|
||||
|
@ -403,16 +455,30 @@ impl Registry {
|
|||
f: &mut F,
|
||||
name: &str,
|
||||
rust_typename: &str,
|
||||
type_id: MetaTypeId,
|
||||
) {
|
||||
match self.types.get(name) {
|
||||
Some(ty) => {
|
||||
if let Some(prev_typename) = ty.rust_typename() {
|
||||
if prev_typename != "__fake_type__" && rust_typename != prev_typename {
|
||||
if prev_typename == "__fake_type__" {
|
||||
return;
|
||||
}
|
||||
|
||||
if rust_typename != prev_typename {
|
||||
panic!(
|
||||
"`{}` and `{}` have the same GraphQL name `{}`",
|
||||
prev_typename, rust_typename, name,
|
||||
);
|
||||
}
|
||||
|
||||
if ty.type_id() != type_id {
|
||||
panic!(
|
||||
"Register `{}` as `{}`, but it is already registered as `{}`",
|
||||
name,
|
||||
type_id,
|
||||
ty.type_id()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
|
|
|
@ -4,7 +4,10 @@ use std::fmt::{self, Debug, Formatter};
|
|||
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
|
||||
use crate::{Data, ParseRequestError, UploadValue, Value, Variables};
|
||||
use crate::parser::parse_query;
|
||||
use crate::parser::types::ExecutableDocument;
|
||||
use crate::schema::IntrospectionMode;
|
||||
use crate::{Data, ParseRequestError, ServerError, UploadValue, Value, Variables};
|
||||
|
||||
/// GraphQL request.
|
||||
///
|
||||
|
@ -12,6 +15,7 @@ use crate::{Data, ParseRequestError, UploadValue, Value, Variables};
|
|||
/// variables. The names are all in `camelCase` (e.g. `operationName`).
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[non_exhaustive]
|
||||
pub struct Request {
|
||||
/// The query source of the request.
|
||||
#[serde(default)]
|
||||
|
@ -40,8 +44,17 @@ pub struct Request {
|
|||
pub extensions: HashMap<String, Value>,
|
||||
|
||||
/// Disable introspection queries for this request.
|
||||
/// This option has priority over `introspection_mode` when set to true.
|
||||
/// `introspection_mode` has priority when `disable_introspection` set to `false`.
|
||||
#[serde(skip)]
|
||||
pub disable_introspection: bool,
|
||||
|
||||
#[serde(skip)]
|
||||
pub(crate) parsed_query: Option<ExecutableDocument>,
|
||||
|
||||
/// Sets the introspection mode for this request (defaults to [IntrospectionMode::Enabled]).
|
||||
#[serde(skip)]
|
||||
pub introspection_mode: IntrospectionMode,
|
||||
}
|
||||
|
||||
impl Request {
|
||||
|
@ -55,6 +68,8 @@ impl Request {
|
|||
data: Data::default(),
|
||||
extensions: Default::default(),
|
||||
disable_introspection: false,
|
||||
parsed_query: None,
|
||||
introspection_mode: IntrospectionMode::Enabled,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,9 +99,36 @@ impl Request {
|
|||
#[must_use]
|
||||
pub fn disable_introspection(mut self) -> Self {
|
||||
self.disable_introspection = true;
|
||||
self.introspection_mode = IntrospectionMode::Disabled;
|
||||
self
|
||||
}
|
||||
|
||||
/// Only allow introspection queries for this request.
|
||||
#[must_use]
|
||||
pub fn only_introspection(mut self) -> Self {
|
||||
self.disable_introspection = false;
|
||||
self.introspection_mode = IntrospectionMode::IntrospectionOnly;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Performs parsing of query ahead of execution.
|
||||
///
|
||||
/// This effectively allows to inspect query information, before passing request to schema for
|
||||
/// execution as long as query is valid.
|
||||
pub fn parsed_query(&mut self) -> Result<&ExecutableDocument, ServerError> {
|
||||
if self.parsed_query.is_none() {
|
||||
match parse_query(&self.query) {
|
||||
Ok(parsed) => self.parsed_query = Some(parsed),
|
||||
Err(error) => return Err(error.into()),
|
||||
}
|
||||
}
|
||||
|
||||
//forbid_unsafe effectively bans optimize away else branch here so use unwrap
|
||||
//but this unwrap never panics
|
||||
Ok(self.parsed_query.as_ref().unwrap())
|
||||
}
|
||||
|
||||
/// Set a variable to an upload value.
|
||||
///
|
||||
/// `var_path` is a dot-separated path to the item that begins with `variables`, for example
|
||||
|
@ -141,6 +183,7 @@ impl Debug for Request {
|
|||
/// **Reference:** <https://www.apollographql.com/blog/batching-client-graphql-queries-a685f5bcd41b/>
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
#[allow(clippy::large_enum_variant)] //Request is at fault
|
||||
pub enum BatchRequest {
|
||||
/// Single query
|
||||
Single(Request),
|
||||
|
@ -207,6 +250,17 @@ impl BatchRequest {
|
|||
pub fn disable_introspection(mut self) -> Self {
|
||||
for request in self.iter_mut() {
|
||||
request.disable_introspection = true;
|
||||
request.introspection_mode = IntrospectionMode::Disabled;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Only allow introspection queries for each request.
|
||||
#[must_use]
|
||||
pub fn introspection_only(mut self) -> Self {
|
||||
for request in self.iter_mut() {
|
||||
request.disable_introspection = false;
|
||||
request.introspection_mode = IntrospectionMode::IntrospectionOnly;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
|
|
@ -154,12 +154,14 @@ macro_rules! scalar_internal {
|
|||
fn create_type_info(
|
||||
registry: &mut $crate::registry::Registry,
|
||||
) -> ::std::string::String {
|
||||
registry.create_input_type::<$ty, _>(|_| $crate::registry::MetaType::Scalar {
|
||||
name: ::std::borrow::ToOwned::to_owned($name),
|
||||
description: $desc,
|
||||
is_valid: |value| <$ty as $crate::ScalarType>::is_valid(value),
|
||||
visible: ::std::option::Option::None,
|
||||
specified_by_url: $specified_by_url,
|
||||
registry.create_input_type::<$ty, _>($crate::registry::MetaTypeId::Scalar, |_| {
|
||||
$crate::registry::MetaType::Scalar {
|
||||
name: ::std::borrow::ToOwned::to_owned($name),
|
||||
description: $desc,
|
||||
is_valid: |value| <$ty as $crate::ScalarType>::is_valid(value),
|
||||
visible: ::std::option::Option::None,
|
||||
specified_by_url: $specified_by_url,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -187,12 +189,14 @@ macro_rules! scalar_internal {
|
|||
fn create_type_info(
|
||||
registry: &mut $crate::registry::Registry,
|
||||
) -> ::std::string::String {
|
||||
registry.create_output_type::<$ty, _>(|_| $crate::registry::MetaType::Scalar {
|
||||
name: ::std::borrow::ToOwned::to_owned($name),
|
||||
description: $desc,
|
||||
is_valid: |value| <$ty as $crate::ScalarType>::is_valid(value),
|
||||
visible: ::std::option::Option::None,
|
||||
specified_by_url: $specified_by_url,
|
||||
registry.create_output_type::<$ty, _>($crate::registry::MetaTypeId::Scalar, |_| {
|
||||
$crate::registry::MetaType::Scalar {
|
||||
name: ::std::borrow::ToOwned::to_owned($name),
|
||||
description: $desc,
|
||||
is_valid: |value| <$ty as $crate::ScalarType>::is_valid(value),
|
||||
visible: ::std::option::Option::None,
|
||||
specified_by_url: $specified_by_url,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ use std::sync::Arc;
|
|||
use futures_util::stream::{self, Stream, StreamExt};
|
||||
use indexmap::map::IndexMap;
|
||||
|
||||
use crate::context::{Data, QueryEnvInner};
|
||||
use crate::custom_directive::CustomDirectiveFactory;
|
||||
use crate::extensions::{ExtensionFactory, Extensions};
|
||||
use crate::model::__DirectiveLocation;
|
||||
|
@ -17,11 +16,29 @@ use crate::resolver_utils::{resolve_container, resolve_container_serial};
|
|||
use crate::subscription::collect_subscription_streams;
|
||||
use crate::types::QueryRoot;
|
||||
use crate::validation::{check_rules, ValidationMode};
|
||||
use crate::{
|
||||
context::{Data, QueryEnvInner},
|
||||
EmptyMutation, EmptySubscription,
|
||||
};
|
||||
use crate::{
|
||||
BatchRequest, BatchResponse, CacheControl, ContextBase, InputType, ObjectType, OutputType,
|
||||
QueryEnv, Request, Response, ServerError, SubscriptionType, Variables, ID,
|
||||
};
|
||||
|
||||
/// Introspection mode
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum IntrospectionMode {
|
||||
IntrospectionOnly,
|
||||
Enabled,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
impl Default for IntrospectionMode {
|
||||
fn default() -> Self {
|
||||
IntrospectionMode::Enabled
|
||||
}
|
||||
}
|
||||
|
||||
/// Schema builder
|
||||
pub struct SchemaBuilder<Query, Mutation, Subscription> {
|
||||
validation_mode: ValidationMode,
|
||||
|
@ -58,7 +75,14 @@ impl<Query, Mutation, Subscription> SchemaBuilder<Query, Mutation, Subscription>
|
|||
/// Disable introspection queries.
|
||||
#[must_use]
|
||||
pub fn disable_introspection(mut self) -> Self {
|
||||
self.registry.disable_introspection = true;
|
||||
self.registry.introspection_mode = IntrospectionMode::Disabled;
|
||||
self
|
||||
}
|
||||
|
||||
/// Only process introspection queries, everything else is processed as an error.
|
||||
#[must_use]
|
||||
pub fn introspection_only(mut self) -> Self {
|
||||
self.registry.introspection_mode = IntrospectionMode::IntrospectionOnly;
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -303,7 +327,7 @@ where
|
|||
} else {
|
||||
Some(Subscription::type_name().to_string())
|
||||
},
|
||||
disable_introspection: false,
|
||||
introspection_mode: IntrospectionMode::Enabled,
|
||||
enable_federation: false,
|
||||
federation_subscription: false,
|
||||
};
|
||||
|
@ -427,13 +451,19 @@ where
|
|||
let query_data = Arc::new(std::mem::take(&mut request.data));
|
||||
extensions.attach_query_data(query_data.clone());
|
||||
|
||||
let request = extensions.prepare_request(request).await?;
|
||||
let mut request = extensions.prepare_request(request).await?;
|
||||
let mut document = {
|
||||
let query = &request.query;
|
||||
let fut_parse = async { parse_query(&query).map_err(Into::<ServerError>::into) };
|
||||
let parsed_doc = request.parsed_query.take();
|
||||
let fut_parse = async move {
|
||||
match parsed_doc {
|
||||
Some(parsed_doc) => Ok(parsed_doc),
|
||||
None => parse_query(query).map_err(Into::into),
|
||||
}
|
||||
};
|
||||
futures_util::pin_mut!(fut_parse);
|
||||
extensions
|
||||
.parse_query(&query, &request.variables, &mut fut_parse)
|
||||
.parse_query(query, &request.variables, &mut fut_parse)
|
||||
.await?
|
||||
};
|
||||
|
||||
|
@ -509,7 +539,11 @@ where
|
|||
session_data,
|
||||
ctx_data: query_data,
|
||||
http_headers: Default::default(),
|
||||
disable_introspection: request.disable_introspection,
|
||||
introspection_mode: if request.disable_introspection {
|
||||
IntrospectionMode::Disabled
|
||||
} else {
|
||||
request.introspection_mode
|
||||
},
|
||||
errors: Default::default(),
|
||||
};
|
||||
Ok((QueryEnv::new(env), validation_result.cache_control))
|
||||
|
@ -526,7 +560,15 @@ where
|
|||
|
||||
let res = match &env.operation.node.ty {
|
||||
OperationType::Query => resolve_container(&ctx, &self.query).await,
|
||||
OperationType::Mutation => resolve_container_serial(&ctx, &self.mutation).await,
|
||||
OperationType::Mutation => {
|
||||
if self.env.registry.introspection_mode == IntrospectionMode::IntrospectionOnly
|
||||
|| env.introspection_mode == IntrospectionMode::IntrospectionOnly
|
||||
{
|
||||
resolve_container_serial(&ctx, &EmptyMutation).await
|
||||
} else {
|
||||
resolve_container_serial(&ctx, &self.mutation).await
|
||||
}
|
||||
}
|
||||
OperationType::Subscription => Err(ServerError::new(
|
||||
"Subscriptions are not supported on this transport.",
|
||||
None,
|
||||
|
@ -621,7 +663,15 @@ where
|
|||
);
|
||||
|
||||
let mut streams = Vec::new();
|
||||
if let Err(err) = collect_subscription_streams(&ctx, &schema.subscription, &mut streams) {
|
||||
let collect_result = if schema.env.registry.introspection_mode
|
||||
== IntrospectionMode::IntrospectionOnly
|
||||
|| env.introspection_mode == IntrospectionMode::IntrospectionOnly
|
||||
{
|
||||
collect_subscription_streams(&ctx, &EmptySubscription, &mut streams)
|
||||
} else {
|
||||
collect_subscription_streams(&ctx, &schema.subscription, &mut streams)
|
||||
};
|
||||
if let Err(err) = collect_result {
|
||||
yield Response::from_errors(vec![err]);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ use indexmap::map::IndexMap;
|
|||
use crate::connection::edge::Edge;
|
||||
use crate::connection::page_info::PageInfo;
|
||||
use crate::parser::types::Field;
|
||||
use crate::registry::MetaTypeId;
|
||||
use crate::resolver_utils::{resolve_container, ContainerType};
|
||||
use crate::types::connection::{CursorType, EmptyFields};
|
||||
use crate::{
|
||||
|
@ -163,7 +164,7 @@ where
|
|||
}
|
||||
|
||||
fn create_type_info(registry: &mut registry::Registry) -> String {
|
||||
registry.create_output_type::<Self, _>(|registry| {
|
||||
registry.create_output_type::<Self, _>(MetaTypeId::Object,|registry| {
|
||||
EC::create_type_info(registry);
|
||||
let additional_fields = if let Some(registry::MetaType::Object { fields, .. }) =
|
||||
registry.types.remove(EC::type_name().as_ref())
|
||||
|
|
|
@ -4,6 +4,7 @@ use indexmap::map::IndexMap;
|
|||
|
||||
use crate::connection::EmptyFields;
|
||||
use crate::parser::types::Field;
|
||||
use crate::registry::MetaTypeId;
|
||||
use crate::resolver_utils::{resolve_container, ContainerType};
|
||||
use crate::types::connection::CursorType;
|
||||
use crate::{
|
||||
|
@ -72,7 +73,7 @@ where
|
|||
}
|
||||
|
||||
fn create_type_info(registry: &mut registry::Registry) -> String {
|
||||
registry.create_output_type::<Self, _>(|registry| {
|
||||
registry.create_output_type::<Self, _>(MetaTypeId::Object, |registry| {
|
||||
let additional_fields = if let registry::MetaType::Object { fields, .. } =
|
||||
registry.create_fake_output_type::<E>()
|
||||
{
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use crate::parser::types::Field;
|
||||
use crate::registry::MetaTypeId;
|
||||
use crate::resolver_utils::ContainerType;
|
||||
use crate::{
|
||||
registry, Context, ContextSelectionSet, ObjectType, OutputType, Positioned, ServerError,
|
||||
|
@ -49,7 +50,7 @@ impl OutputType for EmptyMutation {
|
|||
}
|
||||
|
||||
fn create_type_info(registry: &mut registry::Registry) -> String {
|
||||
registry.create_output_type::<Self, _>(|_| registry::MetaType::Object {
|
||||
registry.create_output_type::<Self, _>(MetaTypeId::Object, |_| registry::MetaType::Object {
|
||||
name: "EmptyMutation".to_string(),
|
||||
description: None,
|
||||
fields: Default::default(),
|
||||
|
|
6
src/types/external/json_object/btreemap.rs
vendored
6
src/types/external/json_object/btreemap.rs
vendored
|
@ -10,7 +10,7 @@ use indexmap::IndexMap;
|
|||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::registry::{MetaType, Registry};
|
||||
use crate::registry::{MetaType, MetaTypeId, Registry};
|
||||
use crate::{
|
||||
ContextSelectionSet, InputType, InputValueError, InputValueResult, Name, OutputType,
|
||||
ServerResult, Value,
|
||||
|
@ -29,7 +29,7 @@ where
|
|||
}
|
||||
|
||||
fn create_type_info(registry: &mut Registry) -> String {
|
||||
registry.create_input_type::<Self, _>(|_| MetaType::Scalar {
|
||||
registry.create_input_type::<Self, _>(MetaTypeId::Scalar, |_| MetaType::Scalar {
|
||||
name: <Self as InputType>::type_name().to_string(),
|
||||
description: Some("A scalar that can represent any JSON Object value."),
|
||||
is_valid: |_| true,
|
||||
|
@ -84,7 +84,7 @@ where
|
|||
}
|
||||
|
||||
fn create_type_info(registry: &mut Registry) -> String {
|
||||
registry.create_output_type::<Self, _>(|_| MetaType::Scalar {
|
||||
registry.create_output_type::<Self, _>(MetaTypeId::Scalar, |_| MetaType::Scalar {
|
||||
name: <Self as OutputType>::type_name().to_string(),
|
||||
description: Some("A scalar that can represent any JSON Object value."),
|
||||
is_valid: |_| true,
|
||||
|
|
6
src/types/external/json_object/hashmap.rs
vendored
6
src/types/external/json_object/hashmap.rs
vendored
|
@ -11,7 +11,7 @@ use indexmap::IndexMap;
|
|||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::registry::{MetaType, Registry};
|
||||
use crate::registry::{MetaType, MetaTypeId, Registry};
|
||||
use crate::{
|
||||
ContextSelectionSet, InputType, InputValueError, InputValueResult, Name, OutputType,
|
||||
ServerResult, Value,
|
||||
|
@ -31,7 +31,7 @@ where
|
|||
}
|
||||
|
||||
fn create_type_info(registry: &mut Registry) -> String {
|
||||
registry.create_input_type::<Self, _>(|_| MetaType::Scalar {
|
||||
registry.create_input_type::<Self, _>(MetaTypeId::Scalar, |_| MetaType::Scalar {
|
||||
name: <Self as InputType>::type_name().to_string(),
|
||||
description: Some("A scalar that can represent any JSON Object value."),
|
||||
is_valid: |_| true,
|
||||
|
@ -87,7 +87,7 @@ where
|
|||
}
|
||||
|
||||
fn create_type_info(registry: &mut Registry) -> String {
|
||||
registry.create_output_type::<Self, _>(|_| MetaType::Scalar {
|
||||
registry.create_output_type::<Self, _>(MetaTypeId::Scalar, |_| MetaType::Scalar {
|
||||
name: <Self as OutputType>::type_name().to_string(),
|
||||
description: Some("A scalar that can represent any JSON Object value."),
|
||||
is_valid: |_| true,
|
||||
|
|
|
@ -5,7 +5,7 @@ use serde::de::DeserializeOwned;
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::parser::types::Field;
|
||||
use crate::registry::{MetaType, Registry};
|
||||
use crate::registry::{MetaType, MetaTypeId, Registry};
|
||||
use crate::{
|
||||
from_value, to_value, ContextSelectionSet, InputType, InputValueResult, OutputType, Positioned,
|
||||
ServerResult, Value,
|
||||
|
@ -46,7 +46,7 @@ impl<T: DeserializeOwned + Serialize + Send + Sync> InputType for Json<T> {
|
|||
}
|
||||
|
||||
fn create_type_info(registry: &mut Registry) -> String {
|
||||
registry.create_input_type::<Json<T>, _>(|_| MetaType::Scalar {
|
||||
registry.create_input_type::<Json<T>, _>(MetaTypeId::Scalar, |_| MetaType::Scalar {
|
||||
name: <Self as InputType>::type_name().to_string(),
|
||||
description: Some("A scalar that can represent any JSON value."),
|
||||
is_valid: |_| true,
|
||||
|
@ -75,7 +75,7 @@ impl<T: Serialize + Send + Sync> OutputType for Json<T> {
|
|||
}
|
||||
|
||||
fn create_type_info(registry: &mut Registry) -> String {
|
||||
registry.create_output_type::<Json<T>, _>(|_| MetaType::Scalar {
|
||||
registry.create_output_type::<Json<T>, _>(MetaTypeId::Scalar, |_| MetaType::Scalar {
|
||||
name: <Self as OutputType>::type_name().to_string(),
|
||||
description: Some("A scalar that can represent any JSON value."),
|
||||
is_valid: |_| true,
|
||||
|
@ -101,12 +101,14 @@ impl InputType for serde_json::Value {
|
|||
}
|
||||
|
||||
fn create_type_info(registry: &mut Registry) -> String {
|
||||
registry.create_input_type::<serde_json::Value, _>(|_| MetaType::Scalar {
|
||||
name: <Self as InputType>::type_name().to_string(),
|
||||
description: Some("A scalar that can represent any JSON value."),
|
||||
is_valid: |_| true,
|
||||
visible: None,
|
||||
specified_by_url: None,
|
||||
registry.create_input_type::<serde_json::Value, _>(MetaTypeId::Scalar, |_| {
|
||||
MetaType::Scalar {
|
||||
name: <Self as InputType>::type_name().to_string(),
|
||||
description: Some("A scalar that can represent any JSON value."),
|
||||
is_valid: |_| true,
|
||||
visible: None,
|
||||
specified_by_url: None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -130,12 +132,14 @@ impl OutputType for serde_json::Value {
|
|||
}
|
||||
|
||||
fn create_type_info(registry: &mut Registry) -> String {
|
||||
registry.create_output_type::<serde_json::Value, _>(|_| MetaType::Scalar {
|
||||
name: <Self as OutputType>::type_name().to_string(),
|
||||
description: Some("A scalar that can represent any JSON value."),
|
||||
is_valid: |_| true,
|
||||
visible: None,
|
||||
specified_by_url: None,
|
||||
registry.create_output_type::<serde_json::Value, _>(MetaTypeId::Scalar, |_| {
|
||||
MetaType::Scalar {
|
||||
name: <Self as OutputType>::type_name().to_string(),
|
||||
description: Some("A scalar that can represent any JSON value."),
|
||||
is_valid: |_| true,
|
||||
visible: None,
|
||||
specified_by_url: None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -172,6 +172,32 @@ impl<T> MaybeUndefined<T> {
|
|||
MaybeUndefined::Undefined => MaybeUndefined::Undefined,
|
||||
}
|
||||
}
|
||||
|
||||
/// Update `value` if the `MaybeUndefined<T>` is not undefined.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use async_graphql::MaybeUndefined;
|
||||
///
|
||||
/// let mut value = None;
|
||||
///
|
||||
/// MaybeUndefined::Value(10i32).update_to(&mut value);
|
||||
/// assert_eq!(value, Some(10));
|
||||
///
|
||||
/// MaybeUndefined::Undefined.update_to(&mut value);
|
||||
/// assert_eq!(value, Some(10));
|
||||
///
|
||||
/// MaybeUndefined::Null.update_to(&mut value);
|
||||
/// assert_eq!(value, None);
|
||||
/// ```
|
||||
pub fn update_to(self, value: &mut Option<T>) {
|
||||
match self {
|
||||
MaybeUndefined::Value(new) => *value = Some(new),
|
||||
MaybeUndefined::Null => *value = None,
|
||||
MaybeUndefined::Undefined => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: InputType> InputType for MaybeUndefined<T> {
|
||||
|
|
|
@ -5,7 +5,7 @@ use indexmap::IndexMap;
|
|||
|
||||
use crate::futures_util::Stream;
|
||||
use crate::parser::types::Field;
|
||||
use crate::registry::{MetaType, Registry};
|
||||
use crate::registry::{MetaType, MetaTypeId, Registry};
|
||||
use crate::{
|
||||
CacheControl, ContainerType, Context, ContextSelectionSet, OutputType, Positioned, Response,
|
||||
ServerResult, SimpleObject, SubscriptionType, Value,
|
||||
|
@ -48,7 +48,7 @@ where
|
|||
}
|
||||
|
||||
fn create_type_info(registry: &mut Registry) -> String {
|
||||
registry.create_output_type::<Self, _>(|registry| {
|
||||
registry.create_output_type::<Self, _>(MetaTypeId::Object, |registry| {
|
||||
let mut fields = IndexMap::new();
|
||||
let mut cc = CacheControl::default();
|
||||
|
||||
|
|
|
@ -2,9 +2,12 @@ use std::borrow::Cow;
|
|||
|
||||
use indexmap::map::IndexMap;
|
||||
|
||||
use crate::model::{__Schema, __Type};
|
||||
use crate::parser::types::Field;
|
||||
use crate::resolver_utils::{resolve_container, ContainerType};
|
||||
use crate::{
|
||||
model::{__Schema, __Type},
|
||||
schema::IntrospectionMode,
|
||||
};
|
||||
use crate::{
|
||||
registry, Any, Context, ContextSelectionSet, ObjectType, OutputType, Positioned, ServerError,
|
||||
ServerResult, SimpleObject, Value,
|
||||
|
@ -24,7 +27,13 @@ pub(crate) struct QueryRoot<T> {
|
|||
#[async_trait::async_trait]
|
||||
impl<T: ObjectType> ContainerType for QueryRoot<T> {
|
||||
async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult<Option<Value>> {
|
||||
if !ctx.schema_env.registry.disable_introspection && !ctx.query_env.disable_introspection {
|
||||
if matches!(
|
||||
ctx.schema_env.registry.introspection_mode,
|
||||
IntrospectionMode::Enabled | IntrospectionMode::IntrospectionOnly
|
||||
) && matches!(
|
||||
ctx.query_env.introspection_mode,
|
||||
IntrospectionMode::Enabled | IntrospectionMode::IntrospectionOnly,
|
||||
) {
|
||||
if ctx.item.node.name.node == "__schema" {
|
||||
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
||||
let visible_types = ctx.schema_env.registry.find_visible_types(ctx);
|
||||
|
@ -54,6 +63,12 @@ impl<T: ObjectType> ContainerType for QueryRoot<T> {
|
|||
}
|
||||
}
|
||||
|
||||
if ctx.schema_env.registry.introspection_mode == IntrospectionMode::IntrospectionOnly
|
||||
|| ctx.query_env.introspection_mode == IntrospectionMode::IntrospectionOnly
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if ctx.schema_env.registry.enable_federation || ctx.schema_env.registry.has_entities() {
|
||||
if ctx.item.node.name.node == "_entities" {
|
||||
let (_, representations) = ctx.param_value::<Vec<Any>>("representations", None)?;
|
||||
|
@ -93,7 +108,10 @@ impl<T: ObjectType> OutputType for QueryRoot<T> {
|
|||
fn create_type_info(registry: &mut registry::Registry) -> String {
|
||||
let root = T::create_type_info(registry);
|
||||
|
||||
if !registry.disable_introspection {
|
||||
if matches!(
|
||||
registry.introspection_mode,
|
||||
IntrospectionMode::Enabled | IntrospectionMode::IntrospectionOnly
|
||||
) {
|
||||
let schema_type = __Schema::create_type_info(registry);
|
||||
if let Some(registry::MetaType::Object { fields, .. }) =
|
||||
registry.types.get_mut(T::type_name().as_ref())
|
||||
|
|
|
@ -5,6 +5,7 @@ use std::io::Read;
|
|||
#[cfg(feature = "unblock")]
|
||||
use futures_util::io::AsyncRead;
|
||||
|
||||
use crate::registry::MetaTypeId;
|
||||
use crate::{registry, Context, InputType, InputValueError, InputValueResult, Value};
|
||||
|
||||
/// A file upload value.
|
||||
|
@ -109,7 +110,7 @@ impl InputType for Upload {
|
|||
}
|
||||
|
||||
fn create_type_info(registry: &mut registry::Registry) -> String {
|
||||
registry.create_input_type::<Self, _>(|_| registry::MetaType::Scalar {
|
||||
registry.create_input_type::<Self, _>(MetaTypeId::Scalar, |_| registry::MetaType::Scalar {
|
||||
name: Self::type_name().to_string(),
|
||||
description: None,
|
||||
is_valid: |value| matches!(value, Value::String(_)),
|
||||
|
|
|
@ -1,6 +1,45 @@
|
|||
use async_graphql::*;
|
||||
use core::marker::PhantomData;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_complex_object_process_with_method_field() {
|
||||
#[derive(SimpleObject)]
|
||||
#[graphql(complex)]
|
||||
struct MyObj {
|
||||
a: i32,
|
||||
}
|
||||
|
||||
#[ComplexObject]
|
||||
impl MyObj {
|
||||
async fn test(
|
||||
&self,
|
||||
#[graphql(process_with = "str::make_ascii_uppercase")] processed_complex_arg: String,
|
||||
) -> String {
|
||||
processed_complex_arg
|
||||
}
|
||||
}
|
||||
|
||||
struct Query;
|
||||
|
||||
#[Object]
|
||||
impl Query {
|
||||
async fn obj(&self) -> MyObj {
|
||||
MyObj { a: 10 }
|
||||
}
|
||||
}
|
||||
|
||||
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
|
||||
let query = "{ obj { test(processedComplexArg: \"smol\") } }";
|
||||
assert_eq!(
|
||||
schema.execute(query).await.into_result().unwrap().data,
|
||||
value!({
|
||||
"obj": {
|
||||
"test": "SMOL"
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
pub async fn test_complex_object() {
|
||||
#[derive(SimpleObject)]
|
||||
|
|
|
@ -407,6 +407,31 @@ pub async fn test_both_input_output() {
|
|||
assert_eq!(<MyObject as OutputType>::type_name(), "MyObject");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
pub fn test_both_input_output_with_same_name() {
|
||||
#[derive(SimpleObject, InputObject)]
|
||||
#[allow(dead_code)]
|
||||
struct MyObject {
|
||||
#[graphql(default = 10)]
|
||||
a: i32,
|
||||
b: bool,
|
||||
#[graphql(skip)]
|
||||
c: String,
|
||||
}
|
||||
|
||||
struct Query;
|
||||
|
||||
#[Object]
|
||||
impl Query {
|
||||
async fn obj(&self, input: MyObject) -> MyObject {
|
||||
input
|
||||
}
|
||||
}
|
||||
|
||||
Schema::new(Query, EmptyMutation, EmptySubscription);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
pub async fn test_skip_input() {
|
||||
#[derive(SimpleObject, InputObject)]
|
||||
|
@ -521,3 +546,88 @@ pub async fn test_complex_output() {
|
|||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
pub async fn test_input_object_process_with() {
|
||||
mod processor {
|
||||
pub fn string(input: &mut String) {
|
||||
while let Some(ch) = input.pop() {
|
||||
if !ch.is_whitespace() {
|
||||
input.push(ch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(InputObject)]
|
||||
struct MyInput {
|
||||
//processor does nothing on default value
|
||||
#[graphql(default = " ", process_with = "processor::string")]
|
||||
a: String,
|
||||
|
||||
#[graphql(process_with = "processor::string")]
|
||||
b: String,
|
||||
}
|
||||
|
||||
struct MyOutput {
|
||||
a: String,
|
||||
b: String,
|
||||
}
|
||||
|
||||
#[Object]
|
||||
impl MyOutput {
|
||||
async fn a(&self) -> &String {
|
||||
&self.a
|
||||
}
|
||||
|
||||
async fn b(&self) -> &String {
|
||||
&self.b
|
||||
}
|
||||
}
|
||||
|
||||
struct Root;
|
||||
|
||||
#[Object]
|
||||
impl Root {
|
||||
async fn a(&self, input: MyInput) -> MyOutput {
|
||||
MyOutput {
|
||||
a: input.a,
|
||||
b: input.b,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let schema = Schema::new(Root, EmptyMutation, EmptySubscription);
|
||||
let query = r#"{
|
||||
a(input:{b: "test b "}) {
|
||||
a b
|
||||
}
|
||||
}"#
|
||||
.to_owned();
|
||||
assert_eq!(
|
||||
schema.execute(&query).await.data,
|
||||
value!({
|
||||
"a": {
|
||||
"a": " ",
|
||||
"b": "test b",
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
let schema = Schema::new(Root, EmptyMutation, EmptySubscription);
|
||||
let query = r#"{
|
||||
a(input:{a: "test a ", b: "test"}) {
|
||||
a b
|
||||
}
|
||||
}"#
|
||||
.to_owned();
|
||||
assert_eq!(
|
||||
schema.execute(&query).await.data,
|
||||
value!({
|
||||
"a": {
|
||||
"a": "test a",
|
||||
"b": "test",
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1272,3 +1272,109 @@ pub async fn test_disable_introspection() {
|
|||
value!({ "__type": null })
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
pub async fn test_introspection_only() {
|
||||
let schema = Schema::build(Query, Mutation, EmptySubscription)
|
||||
.introspection_only()
|
||||
.finish();
|
||||
|
||||
// Test whether introspection works.
|
||||
let query = r#"
|
||||
{
|
||||
__type(name: "Mutation") {
|
||||
name
|
||||
kind
|
||||
description
|
||||
fields {
|
||||
description
|
||||
name
|
||||
type { kind name }
|
||||
args { name }
|
||||
}
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let res_json = value!({
|
||||
"__type": {
|
||||
"name": "Mutation",
|
||||
"kind": "OBJECT",
|
||||
"description": "Global mutation",
|
||||
"fields": [
|
||||
{
|
||||
"description": "simple_mutation description\nline2\nline3",
|
||||
"name": "simpleMutation",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null
|
||||
},
|
||||
"args": [
|
||||
{
|
||||
"name": "input"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
let res = schema.execute(query).await.into_result().unwrap().data;
|
||||
assert_eq!(res, res_json);
|
||||
|
||||
// Test whether introspection works.
|
||||
let query = r#"
|
||||
{
|
||||
__type(name: "Query") {
|
||||
name
|
||||
kind
|
||||
description
|
||||
fields {
|
||||
description
|
||||
name
|
||||
type { kind name }
|
||||
args { name }
|
||||
}
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let res_json = value!({
|
||||
"__type": {
|
||||
"name": "Query",
|
||||
"kind": "OBJECT",
|
||||
"description": "Global query",
|
||||
"fields": [
|
||||
{
|
||||
"description": "Get a simple object",
|
||||
"name": "simpleObject",
|
||||
"type": { "kind": "NON_NULL", "name": null },
|
||||
"args": []
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
let res = schema.execute(query).await.into_result().unwrap().data;
|
||||
assert_eq!(res, res_json);
|
||||
|
||||
// Queries shouldn't work in introspection only mode.
|
||||
let query = r#"
|
||||
{
|
||||
simpleObject {
|
||||
a
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let res_json = value!({ "simpleObject": null });
|
||||
let res = schema.execute(query).await.into_result().unwrap().data;
|
||||
assert_eq!(res, res_json);
|
||||
|
||||
// Mutations shouldn't work in introspection only mode.
|
||||
let query = r#"
|
||||
mutation {
|
||||
simpleMutation(input: { a: "" }) {
|
||||
a
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let res_json = value!({ "simpleMutation": null });
|
||||
let res = schema.execute(query).await.into_result().unwrap().data;
|
||||
assert_eq!(res, res_json);
|
||||
}
|
||||
|
|
|
@ -118,6 +118,30 @@ async fn test_flatten_with_context() {
|
|||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_object_process_with_field() {
|
||||
struct Query;
|
||||
|
||||
#[Object]
|
||||
impl Query {
|
||||
async fn test(
|
||||
&self,
|
||||
#[graphql(process_with = "str::make_ascii_uppercase")] processed_arg: String,
|
||||
) -> String {
|
||||
processed_arg
|
||||
}
|
||||
}
|
||||
|
||||
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
|
||||
let query = "{ test(processedArg: \"smol\") }";
|
||||
assert_eq!(
|
||||
schema.execute(query).await.into_result().unwrap().data,
|
||||
value!({
|
||||
"test": "SMOL"
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_oneof_field() {
|
||||
#[derive(OneofObject)]
|
||||
|
|
|
@ -419,3 +419,24 @@ pub async fn test_trait_object_in_union() {
|
|||
})
|
||||
);
|
||||
}
|
||||
|
||||
macro_rules! generate_union {
|
||||
($name:ident, $variant_ty:ty) => {
|
||||
#[derive(Union)]
|
||||
pub enum $name {
|
||||
Val($variant_ty),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_macro_generated_union() {
|
||||
#[derive(SimpleObject)]
|
||||
pub struct IntObj {
|
||||
pub val: i32,
|
||||
}
|
||||
|
||||
generate_union!(MyEnum, IntObj);
|
||||
|
||||
let _ = MyEnum::Val(IntObj { val: 1 });
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "async-graphql-value"
|
||||
version = "3.0.36"
|
||||
version = "3.0.38"
|
||||
authors = ["sunli <scott_s829@163.com>", "Koxiaet"]
|
||||
edition = "2021"
|
||||
description = "GraphQL value for async-graphql"
|
||||
|
|
Loading…
Reference in New Issue
Block a user