Merge branch 'master' into master
This commit is contained in:
commit
2f74cd4641
31
CHANGELOG.md
31
CHANGELOG.md
|
@ -4,6 +4,37 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
# [3.0.33] 2022-3-4
|
||||||
|
|
||||||
|
- Add support for oneof field on object. [#766](https://github.com/async-graphql/async-graphql/issues/766)
|
||||||
|
|
||||||
|
# [3.0.32] 2022-3-4
|
||||||
|
|
||||||
|
- Bump `Actix-web` from `4.0.0-rc.3` to `4.0.1`.
|
||||||
|
|
||||||
|
# [3.0.31] 2022-02-17
|
||||||
|
|
||||||
|
- Add `OneOfObject` macro to support for oneof input object.
|
||||||
|
- Bump actix-web from `4.0.0-rc.2` to `4.0.0-rc.3`.
|
||||||
|
|
||||||
|
# [3.0.30] 2022-2-15
|
||||||
|
|
||||||
|
- Implement `ScalarType` for `time::Date`. [#822](https://github.com/async-graphql/async-graphql/pull/822)
|
||||||
|
|
||||||
|
# [3.0.29] 2022-2-6
|
||||||
|
|
||||||
|
- Pass context to resolvers with flatten attribute. [#813](https://github.com/async-graphql/async-graphql/pull/813)
|
||||||
|
- Add support for using both `ComplexObject` and `InputObject`.
|
||||||
|
- Bump `Actix-web` from `4.0.0-beta.19` to `4.0.0-rc.2`.
|
||||||
|
|
||||||
|
# [3.0.28] 2022-1-30
|
||||||
|
|
||||||
|
- Implement `InputType` and `OutputType` for `Box<[T]>` and `Arc<[T]>`. [#805](https://github.com/async-graphql/async-graphql/issues/805)
|
||||||
|
|
||||||
|
# [3.0.27] 2022-1-28
|
||||||
|
|
||||||
|
- Fix possible stack overflow in validator, thanks @quapka.
|
||||||
|
|
||||||
# [3.0.26] 2022-1-26
|
# [3.0.26] 2022-1-26
|
||||||
|
|
||||||
- Add `skip_input` attribute to `InputObject` macro, `skip_output` attribute to `SimpleObject` macro.
|
- Add `skip_input` attribute to `InputObject` macro, `skip_output` attribute to `SimpleObject` macro.
|
||||||
|
|
16
Cargo.toml
16
Cargo.toml
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "async-graphql"
|
name = "async-graphql"
|
||||||
version = "3.0.26"
|
version = "3.0.33"
|
||||||
authors = ["sunli <scott_s829@163.com>", "Koxiaet"]
|
authors = ["sunli <scott_s829@163.com>", "Koxiaet"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "A GraphQL server library implemented in Rust"
|
description = "A GraphQL server library implemented in Rust"
|
||||||
|
@ -26,9 +26,9 @@ chrono-duration = ["chrono", "iso8601-duration"]
|
||||||
password-strength-validator = ["zxcvbn"]
|
password-strength-validator = ["zxcvbn"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-graphql-derive = { path = "derive", version = "3.0.26" }
|
async-graphql-derive = { path = "derive", version = "3.0.33" }
|
||||||
async-graphql-value = { path = "value", version = "3.0.26" }
|
async-graphql-value = { path = "value", version = "3.0.33" }
|
||||||
async-graphql-parser = { path = "parser", version = "3.0.26" }
|
async-graphql-parser = { path = "parser", version = "3.0.33" }
|
||||||
|
|
||||||
async-stream = "0.3.0"
|
async-stream = "0.3.0"
|
||||||
async-trait = "0.1.48"
|
async-trait = "0.1.48"
|
||||||
|
@ -52,15 +52,15 @@ fast_chemail = "0.9.6"
|
||||||
|
|
||||||
# Feature optional dependencies
|
# Feature optional dependencies
|
||||||
bson = { version = "2.0.0", optional = true, features = ["chrono-0_4"] }
|
bson = { version = "2.0.0", optional = true, features = ["chrono-0_4"] }
|
||||||
chrono = { version = "0.4.19", optional = true }
|
chrono = { version = "0.4.19", optional = true, default-features = false, features = ["clock", "std"] }
|
||||||
chrono-tz = { version = "0.5.3", optional = true }
|
chrono-tz = { version = "0.5.3", optional = true }
|
||||||
hashbrown = { version = "0.12.0", optional = true }
|
hashbrown = { version = "0.12.0", optional = true }
|
||||||
iso8601-duration = { version = "0.1.0", optional = true }
|
iso8601-duration = { version = "0.1.0", optional = true }
|
||||||
log = { version = "0.4.14", optional = true }
|
log = { version = "0.4.14", optional = true }
|
||||||
secrecy = { version = "0.7.0", optional = true }
|
secrecy = { version = "0.8.0", optional = true }
|
||||||
tracinglib = { version = "0.1.25", optional = true, package = "tracing" }
|
tracinglib = { version = "0.1.25", optional = true, package = "tracing" }
|
||||||
tracing-futures = { version = "0.2.5", optional = true, features = ["std-future", "futures-03"] }
|
tracing-futures = { version = "0.2.5", optional = true, features = ["std-future", "futures-03"] }
|
||||||
opentelemetry = { version = "0.16.0", optional = true, default-features = false, features = ["trace"] }
|
opentelemetry = { version = "0.17.0", optional = true, default-features = false, features = ["trace"] }
|
||||||
uuid = { version = "0.8.2", optional = true, features = ["v4", "serde"] }
|
uuid = { version = "0.8.2", optional = true, features = ["v4", "serde"] }
|
||||||
rust_decimal = { version = "1.14.3", optional = true }
|
rust_decimal = { version = "1.14.3", optional = true }
|
||||||
url = { version = "2.2.1", optional = true }
|
url = { version = "2.2.1", optional = true }
|
||||||
|
@ -70,7 +70,7 @@ time = { version = "0.3.5", optional = true, features = ["parsing", "formatting"
|
||||||
# Non-feature optional dependencies
|
# Non-feature optional dependencies
|
||||||
blocking = { version = "1.0.2", optional = true }
|
blocking = { version = "1.0.2", optional = true }
|
||||||
lru = { version = "0.7.1", optional = true }
|
lru = { version = "0.7.1", optional = true }
|
||||||
sha2 = { version = "0.9.3", optional = true }
|
sha2 = { version = "0.10.2", optional = true }
|
||||||
futures-timer = { version = "3.0.2", optional = true }
|
futures-timer = { version = "3.0.2", optional = true }
|
||||||
futures-channel = { version = "0.3.13", optional = true }
|
futures-channel = { version = "0.3.13", optional = true }
|
||||||
serde_cbor = { version = "0.11.1", optional = true }
|
serde_cbor = { version = "0.11.1", optional = true }
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
## Supported Versions
|
## Supported Versions
|
||||||
|
|
||||||
| Version | Supported |
|
| Version | Supported |
|
||||||
| ------- | ------------------ |
|
|----------|--------------------|
|
||||||
| >= 3.0.0 | :white_check_mark: |
|
| >= 3.0.0 | :white_check_mark: |
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "async-graphql-derive"
|
name = "async-graphql-derive"
|
||||||
version = "3.0.26"
|
version = "3.0.33"
|
||||||
authors = ["sunli <scott_s829@163.com>", "Koxiaet"]
|
authors = ["sunli <scott_s829@163.com>", "Koxiaet"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Macros for async-graphql"
|
description = "Macros for async-graphql"
|
||||||
|
@ -15,7 +15,7 @@ categories = ["network-programming", "asynchronous"]
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-graphql-parser = { path = "../parser", version = "3.0.26" }
|
async-graphql-parser = { path = "../parser", version = "3.0.33" }
|
||||||
proc-macro2 = "1.0.24"
|
proc-macro2 = "1.0.24"
|
||||||
syn = { version = "1.0.64", features = ["full", "extra-traits", "visit-mut", "visit"] }
|
syn = { version = "1.0.64", features = ["full", "extra-traits", "visit-mut", "visit"] }
|
||||||
quote = "1.0.9"
|
quote = "1.0.9"
|
||||||
|
|
|
@ -271,6 +271,7 @@ pub struct ObjectField {
|
||||||
#[darling(default, multiple)]
|
#[darling(default, multiple)]
|
||||||
pub derived: Vec<DerivedField>,
|
pub derived: Vec<DerivedField>,
|
||||||
pub flatten: bool,
|
pub flatten: bool,
|
||||||
|
pub oneof: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(FromMeta, Default, Clone)]
|
#[derive(FromMeta, Default, Clone)]
|
||||||
|
@ -396,6 +397,46 @@ pub struct InputObject {
|
||||||
pub visible: Option<Visible>,
|
pub visible: Option<Visible>,
|
||||||
#[darling(default, multiple, rename = "concrete")]
|
#[darling(default, multiple, rename = "concrete")]
|
||||||
pub concretes: Vec<ConcreteType>,
|
pub concretes: Vec<ConcreteType>,
|
||||||
|
// for SimpleObject
|
||||||
|
#[darling(default)]
|
||||||
|
pub complex: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromVariant)]
|
||||||
|
#[darling(attributes(graphql), forward_attrs(doc))]
|
||||||
|
pub struct OneofObjectField {
|
||||||
|
pub ident: Ident,
|
||||||
|
pub attrs: Vec<Attribute>,
|
||||||
|
pub fields: Fields<syn::Type>,
|
||||||
|
|
||||||
|
#[darling(default)]
|
||||||
|
pub name: Option<String>,
|
||||||
|
#[darling(default)]
|
||||||
|
pub validator: Option<Validators>,
|
||||||
|
#[darling(default)]
|
||||||
|
pub visible: Option<Visible>,
|
||||||
|
#[darling(default)]
|
||||||
|
pub secret: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromDeriveInput)]
|
||||||
|
#[darling(attributes(graphql), forward_attrs(doc))]
|
||||||
|
pub struct OneofObject {
|
||||||
|
pub ident: Ident,
|
||||||
|
pub generics: Generics,
|
||||||
|
pub attrs: Vec<Attribute>,
|
||||||
|
pub data: Data<OneofObjectField, Ignored>,
|
||||||
|
|
||||||
|
#[darling(default)]
|
||||||
|
pub internal: bool,
|
||||||
|
#[darling(default)]
|
||||||
|
pub name: Option<String>,
|
||||||
|
#[darling(default)]
|
||||||
|
pub rename_fields: Option<RenameRule>,
|
||||||
|
#[darling(default)]
|
||||||
|
pub visible: Option<Visible>,
|
||||||
|
#[darling(default, multiple, rename = "concrete")]
|
||||||
|
pub concretes: Vec<ConcreteType>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(FromMeta)]
|
#[derive(FromMeta)]
|
||||||
|
@ -417,7 +458,7 @@ pub struct InterfaceFieldArgument {
|
||||||
|
|
||||||
#[derive(FromMeta)]
|
#[derive(FromMeta)]
|
||||||
pub struct InterfaceField {
|
pub struct InterfaceField {
|
||||||
pub name: String,
|
pub name: SpannedValue<String>,
|
||||||
#[darling(rename = "type")]
|
#[darling(rename = "type")]
|
||||||
pub ty: LitStr,
|
pub ty: LitStr,
|
||||||
#[darling(default)]
|
#[darling(default)]
|
||||||
|
@ -436,6 +477,8 @@ pub struct InterfaceField {
|
||||||
pub requires: Option<String>,
|
pub requires: Option<String>,
|
||||||
#[darling(default)]
|
#[darling(default)]
|
||||||
pub visible: Option<Visible>,
|
pub visible: Option<Visible>,
|
||||||
|
#[darling(default)]
|
||||||
|
pub oneof: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(FromVariant)]
|
#[derive(FromVariant)]
|
||||||
|
@ -511,6 +554,7 @@ pub struct SubscriptionField {
|
||||||
pub guard: Option<SpannedValue<String>>,
|
pub guard: Option<SpannedValue<String>>,
|
||||||
pub visible: Option<Visible>,
|
pub visible: Option<Visible>,
|
||||||
pub complexity: Option<ComplexityType>,
|
pub complexity: Option<ComplexityType>,
|
||||||
|
pub oneof: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(FromField)]
|
#[derive(FromField)]
|
||||||
|
@ -712,6 +756,7 @@ pub struct ComplexObjectField {
|
||||||
#[darling(multiple)]
|
#[darling(multiple)]
|
||||||
pub derived: Vec<DerivedField>,
|
pub derived: Vec<DerivedField>,
|
||||||
pub flatten: bool,
|
pub flatten: bool,
|
||||||
|
pub oneof: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(FromMeta, Default)]
|
#[derive(FromMeta, Default)]
|
||||||
|
|
|
@ -125,6 +125,9 @@ pub fn generate(
|
||||||
let cfg_attrs = get_cfg_attrs(&method.attrs);
|
let cfg_attrs = get_cfg_attrs(&method.attrs);
|
||||||
|
|
||||||
if method_args.flatten {
|
if method_args.flatten {
|
||||||
|
// Only used to inject the context placeholder if required.
|
||||||
|
extract_input_args::<args::Argument>(&crate_name, method)?;
|
||||||
|
|
||||||
let ty = match &method.sig.output {
|
let ty = match &method.sig.output {
|
||||||
ReturnType::Type(_, ty) => OutputType::parse(ty)?,
|
ReturnType::Type(_, ty) => OutputType::parse(ty)?,
|
||||||
ReturnType::Default => {
|
ReturnType::Default => {
|
||||||
|
@ -149,7 +152,7 @@ pub fn generate(
|
||||||
|
|
||||||
resolvers.push(quote! {
|
resolvers.push(quote! {
|
||||||
#(#cfg_attrs)*
|
#(#cfg_attrs)*
|
||||||
if let ::std::option::Option::Some(value) = #crate_name::ContainerType::resolve_field(&self.#ident().await, ctx).await? {
|
if let ::std::option::Option::Some(value) = #crate_name::ContainerType::resolve_field(&self.#ident(ctx).await, ctx).await? {
|
||||||
return ::std::result::Result::Ok(std::option::Option::Some(value));
|
return ::std::result::Result::Ok(std::option::Option::Some(value));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -187,7 +190,120 @@ pub fn generate(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let args = extract_input_args(&crate_name, method)?;
|
let mut args = extract_input_args::<args::Argument>(&crate_name, method)?;
|
||||||
|
let mut schema_args = Vec::new();
|
||||||
|
let mut use_params = Vec::new();
|
||||||
|
let mut get_params = Vec::new();
|
||||||
|
let mut is_oneof_field = false;
|
||||||
|
|
||||||
|
if method_args.oneof {
|
||||||
|
is_oneof_field = true;
|
||||||
|
|
||||||
|
if args.len() != 1 {
|
||||||
|
return Err(Error::new_spanned(
|
||||||
|
&method,
|
||||||
|
"The `oneof` field requires exactly one argument.",
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
let (ident, ty, argument) = args.pop().unwrap();
|
||||||
|
schema_args.push(quote! {
|
||||||
|
#crate_name::static_assertions::assert_impl_one!(#ty: #crate_name::OneofObjectType);
|
||||||
|
if let #crate_name::registry::MetaType::InputObject { input_fields, .. } = registry.create_fake_input_type::<#ty>() {
|
||||||
|
args.extend(input_fields);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
use_params.push(quote! { #ident });
|
||||||
|
|
||||||
|
let validators = argument
|
||||||
|
.validator
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.create_validators(
|
||||||
|
&crate_name,
|
||||||
|
quote!(&#ident),
|
||||||
|
quote!(#ty),
|
||||||
|
Some(quote!(.map_err(|err| err.into_server_error(__pos)))),
|
||||||
|
)?;
|
||||||
|
get_params.push(quote! {
|
||||||
|
#[allow(non_snake_case, unused_variables)]
|
||||||
|
let (__pos, #ident) = ctx.oneof_param_value::<#ty>()?;
|
||||||
|
#validators
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
for (
|
||||||
|
ident,
|
||||||
|
ty,
|
||||||
|
args::Argument {
|
||||||
|
name,
|
||||||
|
desc,
|
||||||
|
default,
|
||||||
|
default_with,
|
||||||
|
validator,
|
||||||
|
visible,
|
||||||
|
secret,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
) in &args
|
||||||
|
{
|
||||||
|
let name = name.clone().unwrap_or_else(|| {
|
||||||
|
object_args
|
||||||
|
.rename_args
|
||||||
|
.rename(ident.ident.unraw().to_string(), RenameTarget::Argument)
|
||||||
|
});
|
||||||
|
let desc = desc
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| quote! {::std::option::Option::Some(#s)})
|
||||||
|
.unwrap_or_else(|| quote! {::std::option::Option::None});
|
||||||
|
let default = generate_default(default, default_with)?;
|
||||||
|
let schema_default = default
|
||||||
|
.as_ref()
|
||||||
|
.map(|value| {
|
||||||
|
quote! {
|
||||||
|
::std::option::Option::Some(::std::string::ToString::to_string(
|
||||||
|
&<#ty as #crate_name::InputType>::to_value(&#value)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| quote! {::std::option::Option::None});
|
||||||
|
|
||||||
|
let visible = visible_fn(visible);
|
||||||
|
schema_args.push(quote! {
|
||||||
|
args.insert(::std::borrow::ToOwned::to_owned(#name), #crate_name::registry::MetaInputValue {
|
||||||
|
name: #name,
|
||||||
|
description: #desc,
|
||||||
|
ty: <#ty as #crate_name::InputType>::create_type_info(registry),
|
||||||
|
default_value: #schema_default,
|
||||||
|
visible: #visible,
|
||||||
|
is_secret: #secret,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let param_ident = &ident.ident;
|
||||||
|
use_params.push(quote! { #param_ident });
|
||||||
|
|
||||||
|
let default = match default {
|
||||||
|
Some(default) => {
|
||||||
|
quote! { ::std::option::Option::Some(|| -> #ty { #default }) }
|
||||||
|
}
|
||||||
|
None => quote! { ::std::option::Option::None },
|
||||||
|
};
|
||||||
|
|
||||||
|
let validators = validator.clone().unwrap_or_default().create_validators(
|
||||||
|
&crate_name,
|
||||||
|
quote!(&#ident),
|
||||||
|
quote!(ty),
|
||||||
|
Some(quote!(.map_err(|err| err.into_server_error(__pos)))),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
get_params.push(quote! {
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
let (__pos, #ident) = ctx.param_value::<#ty>(#name, #default)?;
|
||||||
|
#validators
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let ty = match &method.sig.output {
|
let ty = match &method.sig.output {
|
||||||
ReturnType::Type(_, ty) => OutputType::parse(ty)?,
|
ReturnType::Type(_, ty) => OutputType::parse(ty)?,
|
||||||
ReturnType::Default => {
|
ReturnType::Default => {
|
||||||
|
@ -198,83 +314,6 @@ pub fn generate(
|
||||||
.into())
|
.into())
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut schema_args = Vec::new();
|
|
||||||
let mut use_params = Vec::new();
|
|
||||||
let mut get_params = Vec::new();
|
|
||||||
|
|
||||||
for (
|
|
||||||
ident,
|
|
||||||
ty,
|
|
||||||
args::Argument {
|
|
||||||
name,
|
|
||||||
desc,
|
|
||||||
default,
|
|
||||||
default_with,
|
|
||||||
validator,
|
|
||||||
visible,
|
|
||||||
secret,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
) in &args
|
|
||||||
{
|
|
||||||
let name = name.clone().unwrap_or_else(|| {
|
|
||||||
object_args
|
|
||||||
.rename_args
|
|
||||||
.rename(ident.ident.unraw().to_string(), RenameTarget::Argument)
|
|
||||||
});
|
|
||||||
let desc = desc
|
|
||||||
.as_ref()
|
|
||||||
.map(|s| quote! {::std::option::Option::Some(#s)})
|
|
||||||
.unwrap_or_else(|| quote! {::std::option::Option::None});
|
|
||||||
let default = generate_default(default, default_with)?;
|
|
||||||
let schema_default = default
|
|
||||||
.as_ref()
|
|
||||||
.map(|value| {
|
|
||||||
quote! {
|
|
||||||
::std::option::Option::Some(::std::string::ToString::to_string(
|
|
||||||
&<#ty as #crate_name::InputType>::to_value(&#value)
|
|
||||||
))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| quote! {::std::option::Option::None});
|
|
||||||
|
|
||||||
let visible = visible_fn(visible);
|
|
||||||
schema_args.push(quote! {
|
|
||||||
args.insert(#name, #crate_name::registry::MetaInputValue {
|
|
||||||
name: #name,
|
|
||||||
description: #desc,
|
|
||||||
ty: <#ty as #crate_name::InputType>::create_type_info(registry),
|
|
||||||
default_value: #schema_default,
|
|
||||||
visible: #visible,
|
|
||||||
is_secret: #secret,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
let param_ident = &ident.ident;
|
|
||||||
use_params.push(quote! { #param_ident });
|
|
||||||
|
|
||||||
let default = match default {
|
|
||||||
Some(default) => {
|
|
||||||
quote! { ::std::option::Option::Some(|| -> #ty { #default }) }
|
|
||||||
}
|
|
||||||
None => quote! { ::std::option::Option::None },
|
|
||||||
};
|
|
||||||
|
|
||||||
let validators = validator.clone().unwrap_or_default().create_validators(
|
|
||||||
&crate_name,
|
|
||||||
quote!(&#ident),
|
|
||||||
quote!(ty),
|
|
||||||
Some(quote!(.map_err(|err| err.into_server_error(__pos)))),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
get_params.push(quote! {
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
let (__pos, #ident) = ctx.param_value::<#ty>(#name, #default)?;
|
|
||||||
#validators
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let schema_ty = ty.value_type();
|
let schema_ty = ty.value_type();
|
||||||
let visible = visible_fn(&method_args.visible);
|
let visible = visible_fn(&method_args.visible);
|
||||||
|
|
||||||
|
@ -347,6 +386,7 @@ pub fn generate(
|
||||||
requires: #requires,
|
requires: #requires,
|
||||||
visible: #visible,
|
visible: #visible,
|
||||||
compute_complexity: #complexity,
|
compute_complexity: #complexity,
|
||||||
|
oneof: #is_oneof_field,
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -82,7 +82,7 @@ pub fn generate(
|
||||||
let visible = visible_fn(&visible);
|
let visible = visible_fn(&visible);
|
||||||
|
|
||||||
schema_args.push(quote! {
|
schema_args.push(quote! {
|
||||||
args.insert(#name, #crate_name::registry::MetaInputValue {
|
args.insert(::std::borrow::ToOwned::to_owned(#name), #crate_name::registry::MetaInputValue {
|
||||||
name: #name,
|
name: #name,
|
||||||
description: #desc,
|
description: #desc,
|
||||||
ty: <#arg_ty as #crate_name::InputType>::create_type_info(registry),
|
ty: <#arg_ty as #crate_name::InputType>::create_type_info(registry),
|
||||||
|
|
|
@ -223,6 +223,7 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
|
||||||
},
|
},
|
||||||
visible: #visible,
|
visible: #visible,
|
||||||
rust_typename: ::std::any::type_name::<Self>(),
|
rust_typename: ::std::any::type_name::<Self>(),
|
||||||
|
oneof: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,6 +270,7 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
|
||||||
},
|
},
|
||||||
visible: #visible,
|
visible: #visible,
|
||||||
rust_typename: ::std::any::type_name::<Self>(),
|
rust_typename: ::std::any::type_name::<Self>(),
|
||||||
|
oneof: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,12 +5,13 @@ use proc_macro::TokenStream;
|
||||||
use proc_macro2::{Ident, Span};
|
use proc_macro2::{Ident, Span};
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::visit_mut::VisitMut;
|
use syn::visit_mut::VisitMut;
|
||||||
use syn::{visit_mut, Error, Lifetime, Type};
|
use syn::{Error, Type};
|
||||||
|
|
||||||
use crate::args::{self, InterfaceField, InterfaceFieldArgument, RenameRuleExt, RenameTarget};
|
use crate::args::{self, InterfaceField, InterfaceFieldArgument, RenameRuleExt, RenameTarget};
|
||||||
use crate::output_type::OutputType;
|
use crate::output_type::OutputType;
|
||||||
use crate::utils::{
|
use crate::utils::{
|
||||||
gen_deprecation, generate_default, get_crate_name, get_rustdoc, visible_fn, GeneratorResult,
|
gen_deprecation, generate_default, get_crate_name, get_rustdoc, visible_fn, GeneratorResult,
|
||||||
|
RemoveLifetime,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream> {
|
pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream> {
|
||||||
|
@ -76,14 +77,6 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RemoveLifetime;
|
|
||||||
impl VisitMut for RemoveLifetime {
|
|
||||||
fn visit_lifetime_mut(&mut self, i: &mut Lifetime) {
|
|
||||||
i.ident = Ident::new("_", Span::call_site());
|
|
||||||
visit_mut::visit_lifetime_mut(self, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut assert_ty = p.clone();
|
let mut assert_ty = p.clone();
|
||||||
RemoveLifetime.visit_type_path_mut(&mut assert_ty);
|
RemoveLifetime.visit_type_path_mut(&mut assert_ty);
|
||||||
|
|
||||||
|
@ -143,8 +136,10 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
|
||||||
provides,
|
provides,
|
||||||
requires,
|
requires,
|
||||||
visible,
|
visible,
|
||||||
|
oneof,
|
||||||
} in &interface_args.fields
|
} in &interface_args.fields
|
||||||
{
|
{
|
||||||
|
let name_span = name.span();
|
||||||
let (name, method_name) = if let Some(method) = method {
|
let (name, method_name) = if let Some(method) = method {
|
||||||
(name.to_string(), Ident::new(method, Span::call_site()))
|
(name.to_string(), Ident::new(method, Span::call_site()))
|
||||||
} else {
|
} else {
|
||||||
|
@ -152,7 +147,7 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
|
||||||
(
|
(
|
||||||
interface_args
|
interface_args
|
||||||
.rename_fields
|
.rename_fields
|
||||||
.rename(name, RenameTarget::Field),
|
.rename(name.as_ref(), RenameTarget::Field),
|
||||||
method_name,
|
method_name,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
@ -177,61 +172,93 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
|
||||||
decl_params.push(quote! { ctx: &'ctx #crate_name::Context<'ctx> });
|
decl_params.push(quote! { ctx: &'ctx #crate_name::Context<'ctx> });
|
||||||
use_params.push(quote! { ctx });
|
use_params.push(quote! { ctx });
|
||||||
|
|
||||||
for InterfaceFieldArgument {
|
let mut is_oneof_field = false;
|
||||||
name,
|
|
||||||
desc,
|
if *oneof {
|
||||||
ty,
|
is_oneof_field = true;
|
||||||
default,
|
|
||||||
default_with,
|
if args.len() != 1 {
|
||||||
visible,
|
return Err(
|
||||||
secret,
|
Error::new(name_span, "The `oneof` field requires one parameter.").into(),
|
||||||
} in args
|
);
|
||||||
{
|
}
|
||||||
|
|
||||||
|
let InterfaceFieldArgument { name, ty, .. } = &args[0];
|
||||||
let ident = Ident::new(name, Span::call_site());
|
let ident = Ident::new(name, Span::call_site());
|
||||||
let name = interface_args
|
|
||||||
.rename_args
|
|
||||||
.rename(name, RenameTarget::Argument);
|
|
||||||
let ty = match syn::parse_str::<syn::Type>(&ty.value()) {
|
let ty = match syn::parse_str::<syn::Type>(&ty.value()) {
|
||||||
Ok(ty) => ty,
|
Ok(ty) => ty,
|
||||||
Err(_) => return Err(Error::new_spanned(&ty, "Expect type").into()),
|
Err(_) => return Err(Error::new_spanned(&ty, "Expect type").into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
decl_params.push(quote! { #ident: #ty });
|
decl_params.push(quote! { #ident: #ty });
|
||||||
use_params.push(quote! { #ident });
|
use_params.push(quote! { #ident });
|
||||||
|
|
||||||
let default = generate_default(default, default_with)?;
|
|
||||||
let get_default = match &default {
|
|
||||||
Some(default) => quote! { ::std::option::Option::Some(|| -> #ty { #default }) },
|
|
||||||
None => quote! { ::std::option::Option::None },
|
|
||||||
};
|
|
||||||
get_params.push(quote! {
|
get_params.push(quote! {
|
||||||
let (_, #ident) = ctx.param_value::<#ty>(#name, #get_default)?;
|
#[allow(non_snake_case, unused_variables)]
|
||||||
|
let (_, #ident) = ctx.oneof_param_value::<#ty>()?;
|
||||||
});
|
});
|
||||||
|
|
||||||
let desc = desc
|
|
||||||
.as_ref()
|
|
||||||
.map(|s| quote! {::std::option::Option::Some(#s)})
|
|
||||||
.unwrap_or_else(|| quote! {::std::option::Option::None});
|
|
||||||
let schema_default = default
|
|
||||||
.as_ref()
|
|
||||||
.map(|value| {
|
|
||||||
quote! {
|
|
||||||
::std::option::Option::Some(::std::string::ToString::to_string(
|
|
||||||
&<#ty as #crate_name::InputType>::to_value(&#value)
|
|
||||||
))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| quote! {::std::option::Option::None});
|
|
||||||
let visible = visible_fn(visible);
|
|
||||||
schema_args.push(quote! {
|
schema_args.push(quote! {
|
||||||
args.insert(#name, #crate_name::registry::MetaInputValue {
|
#crate_name::static_assertions::assert_impl_one!(#ty: #crate_name::OneofObjectType);
|
||||||
name: #name,
|
if let #crate_name::registry::MetaType::InputObject { input_fields, .. } = registry.create_fake_input_type::<#ty>() {
|
||||||
description: #desc,
|
args.extend(input_fields);
|
||||||
ty: <#ty as #crate_name::InputType>::create_type_info(registry),
|
}
|
||||||
default_value: #schema_default,
|
|
||||||
visible: #visible,
|
|
||||||
is_secret: #secret,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
for InterfaceFieldArgument {
|
||||||
|
name,
|
||||||
|
desc,
|
||||||
|
ty,
|
||||||
|
default,
|
||||||
|
default_with,
|
||||||
|
visible,
|
||||||
|
secret,
|
||||||
|
} in args
|
||||||
|
{
|
||||||
|
let ident = Ident::new(name, Span::call_site());
|
||||||
|
let name = interface_args
|
||||||
|
.rename_args
|
||||||
|
.rename(name, RenameTarget::Argument);
|
||||||
|
let ty = match syn::parse_str::<syn::Type>(&ty.value()) {
|
||||||
|
Ok(ty) => ty,
|
||||||
|
Err(_) => return Err(Error::new_spanned(&ty, "Expect type").into()),
|
||||||
|
};
|
||||||
|
decl_params.push(quote! { #ident: #ty });
|
||||||
|
use_params.push(quote! { #ident });
|
||||||
|
|
||||||
|
let default = generate_default(default, default_with)?;
|
||||||
|
let get_default = match &default {
|
||||||
|
Some(default) => quote! { ::std::option::Option::Some(|| -> #ty { #default }) },
|
||||||
|
None => quote! { ::std::option::Option::None },
|
||||||
|
};
|
||||||
|
get_params.push(quote! {
|
||||||
|
let (_, #ident) = ctx.param_value::<#ty>(#name, #get_default)?;
|
||||||
|
});
|
||||||
|
|
||||||
|
let desc = desc
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| quote! {::std::option::Option::Some(#s)})
|
||||||
|
.unwrap_or_else(|| quote! {::std::option::Option::None});
|
||||||
|
let schema_default = default
|
||||||
|
.as_ref()
|
||||||
|
.map(|value| {
|
||||||
|
quote! {
|
||||||
|
::std::option::Option::Some(::std::string::ToString::to_string(
|
||||||
|
&<#ty as #crate_name::InputType>::to_value(&#value)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| quote! {::std::option::Option::None});
|
||||||
|
let visible = visible_fn(visible);
|
||||||
|
schema_args.push(quote! {
|
||||||
|
args.insert(::std::borrow::ToOwned::to_owned(#name), #crate_name::registry::MetaInputValue {
|
||||||
|
name: #name,
|
||||||
|
description: #desc,
|
||||||
|
ty: <#ty as #crate_name::InputType>::create_type_info(registry),
|
||||||
|
default_value: #schema_default,
|
||||||
|
visible: #visible,
|
||||||
|
is_secret: #secret,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for enum_name in &enum_names {
|
for enum_name in &enum_names {
|
||||||
|
@ -282,6 +309,7 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
|
||||||
requires: #requires,
|
requires: #requires,
|
||||||
visible: #visible,
|
visible: #visible,
|
||||||
compute_complexity: ::std::option::Option::None,
|
compute_complexity: ::std::option::Option::None,
|
||||||
|
oneof: #is_oneof_field,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ mod merged_object;
|
||||||
mod merged_subscription;
|
mod merged_subscription;
|
||||||
mod newtype;
|
mod newtype;
|
||||||
mod object;
|
mod object;
|
||||||
|
mod oneof_object;
|
||||||
mod output_type;
|
mod output_type;
|
||||||
mod scalar;
|
mod scalar;
|
||||||
mod simple_object;
|
mod simple_object;
|
||||||
|
@ -217,3 +218,16 @@ pub fn Directive(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
Err(err) => err.write_errors().into(),
|
Err(err) => err.write_errors().into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[proc_macro_derive(OneofObject, attributes(graphql))]
|
||||||
|
pub fn derive_oneof_object(input: TokenStream) -> TokenStream {
|
||||||
|
let object_args =
|
||||||
|
match args::OneofObject::from_derive_input(&parse_macro_input!(input as DeriveInput)) {
|
||||||
|
Ok(object_args) => object_args,
|
||||||
|
Err(err) => return TokenStream::from(err.write_errors()),
|
||||||
|
};
|
||||||
|
match oneof_object::generate(&object_args) {
|
||||||
|
Ok(expanded) => expanded,
|
||||||
|
Err(err) => err.write_errors().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -141,7 +141,15 @@ pub fn generate(
|
||||||
return Err(Error::new_spanned(&method, "Must be asynchronous").into());
|
return Err(Error::new_spanned(&method, "Must be asynchronous").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let args = extract_input_args(&crate_name, method)?;
|
if method_args.oneof {
|
||||||
|
return Err(Error::new_spanned(
|
||||||
|
&method,
|
||||||
|
"The `entity` and `oneof` attributes cannot be specified at the same time.",
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let args = extract_input_args::<args::Argument>(&crate_name, method)?;
|
||||||
|
|
||||||
let ty = match &method.sig.output {
|
let ty = match &method.sig.output {
|
||||||
ReturnType::Type(_, ty) => OutputType::parse(ty)?,
|
ReturnType::Type(_, ty) => OutputType::parse(ty)?,
|
||||||
|
@ -261,6 +269,9 @@ pub fn generate(
|
||||||
let cfg_attrs = get_cfg_attrs(&method.attrs);
|
let cfg_attrs = get_cfg_attrs(&method.attrs);
|
||||||
|
|
||||||
if method_args.flatten {
|
if method_args.flatten {
|
||||||
|
// Only used to inject the context placeholder if required.
|
||||||
|
extract_input_args::<args::Argument>(&crate_name, method)?;
|
||||||
|
|
||||||
let ty = match &method.sig.output {
|
let ty = match &method.sig.output {
|
||||||
ReturnType::Type(_, ty) => OutputType::parse(ty)?,
|
ReturnType::Type(_, ty) => OutputType::parse(ty)?,
|
||||||
ReturnType::Default => {
|
ReturnType::Default => {
|
||||||
|
@ -285,7 +296,7 @@ pub fn generate(
|
||||||
|
|
||||||
resolvers.push(quote! {
|
resolvers.push(quote! {
|
||||||
#(#cfg_attrs)*
|
#(#cfg_attrs)*
|
||||||
if let ::std::option::Option::Some(value) = #crate_name::ContainerType::resolve_field(&self.#ident().await, ctx).await? {
|
if let ::std::option::Option::Some(value) = #crate_name::ContainerType::resolve_field(&self.#ident(ctx).await, ctx).await? {
|
||||||
return ::std::result::Result::Ok(std::option::Option::Some(value));
|
return ::std::result::Result::Ok(std::option::Option::Some(value));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -323,10 +334,121 @@ pub fn generate(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let args = extract_input_args(&crate_name, method)?;
|
let mut args = extract_input_args::<args::Argument>(&crate_name, method)?;
|
||||||
let mut schema_args = Vec::new();
|
let mut schema_args = Vec::new();
|
||||||
let mut use_params = Vec::new();
|
let mut use_params = Vec::new();
|
||||||
let mut get_params = Vec::new();
|
let mut get_params = Vec::new();
|
||||||
|
let mut is_oneof_field = false;
|
||||||
|
|
||||||
|
if method_args.oneof {
|
||||||
|
is_oneof_field = true;
|
||||||
|
|
||||||
|
if args.len() != 1 {
|
||||||
|
return Err(Error::new_spanned(
|
||||||
|
&method,
|
||||||
|
"The `oneof` field requires exactly one argument.",
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
let (ident, ty, argument) = args.pop().unwrap();
|
||||||
|
|
||||||
|
schema_args.push(quote! {
|
||||||
|
#crate_name::static_assertions::assert_impl_one!(#ty: #crate_name::OneofObjectType);
|
||||||
|
if let #crate_name::registry::MetaType::InputObject { input_fields, .. } = registry.create_fake_input_type::<#ty>() {
|
||||||
|
args.extend(input_fields);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
use_params.push(quote! { #ident });
|
||||||
|
|
||||||
|
let validators = argument
|
||||||
|
.validator
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.create_validators(
|
||||||
|
&crate_name,
|
||||||
|
quote!(&#ident),
|
||||||
|
quote!(#ty),
|
||||||
|
Some(quote!(.map_err(|err| err.into_server_error(__pos)))),
|
||||||
|
)?;
|
||||||
|
get_params.push(quote! {
|
||||||
|
#[allow(non_snake_case, unused_variables)]
|
||||||
|
let (__pos, #ident) = ctx.oneof_param_value::<#ty>()?;
|
||||||
|
#validators
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
for (
|
||||||
|
ident,
|
||||||
|
ty,
|
||||||
|
args::Argument {
|
||||||
|
name,
|
||||||
|
desc,
|
||||||
|
default,
|
||||||
|
default_with,
|
||||||
|
validator,
|
||||||
|
visible,
|
||||||
|
secret,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
) in &args
|
||||||
|
{
|
||||||
|
let name = name.clone().unwrap_or_else(|| {
|
||||||
|
object_args
|
||||||
|
.rename_args
|
||||||
|
.rename(ident.ident.unraw().to_string(), RenameTarget::Argument)
|
||||||
|
});
|
||||||
|
let desc = desc
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| quote! {::std::option::Option::Some(#s)})
|
||||||
|
.unwrap_or_else(|| quote! {::std::option::Option::None});
|
||||||
|
let default = generate_default(default, default_with)?;
|
||||||
|
let schema_default = default
|
||||||
|
.as_ref()
|
||||||
|
.map(|value| {
|
||||||
|
quote! {
|
||||||
|
::std::option::Option::Some(::std::string::ToString::to_string(
|
||||||
|
&<#ty as #crate_name::InputType>::to_value(&#value)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| quote! {::std::option::Option::None});
|
||||||
|
|
||||||
|
let visible = visible_fn(visible);
|
||||||
|
schema_args.push(quote! {
|
||||||
|
args.insert(::std::borrow::ToOwned::to_owned(#name), #crate_name::registry::MetaInputValue {
|
||||||
|
name: #name,
|
||||||
|
description: #desc,
|
||||||
|
ty: <#ty as #crate_name::InputType>::create_type_info(registry),
|
||||||
|
default_value: #schema_default,
|
||||||
|
visible: #visible,
|
||||||
|
is_secret: #secret,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let param_ident = &ident.ident;
|
||||||
|
use_params.push(quote! { #param_ident });
|
||||||
|
|
||||||
|
let default = match default {
|
||||||
|
Some(default) => {
|
||||||
|
quote! { ::std::option::Option::Some(|| -> #ty { #default }) }
|
||||||
|
}
|
||||||
|
None => quote! { ::std::option::Option::None },
|
||||||
|
};
|
||||||
|
|
||||||
|
let validators = validator.clone().unwrap_or_default().create_validators(
|
||||||
|
&crate_name,
|
||||||
|
quote!(&#ident),
|
||||||
|
quote!(#ty),
|
||||||
|
Some(quote!(.map_err(|err| err.into_server_error(__pos)))),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
get_params.push(quote! {
|
||||||
|
#[allow(non_snake_case, unused_variables)]
|
||||||
|
let (__pos, #ident) = ctx.param_value::<#ty>(#name, #default)?;
|
||||||
|
#validators
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let ty = match &method.sig.output {
|
let ty = match &method.sig.output {
|
||||||
ReturnType::Type(_, ty) => OutputType::parse(ty)?,
|
ReturnType::Type(_, ty) => OutputType::parse(ty)?,
|
||||||
ReturnType::Default => {
|
ReturnType::Default => {
|
||||||
|
@ -337,79 +459,6 @@ pub fn generate(
|
||||||
.into())
|
.into())
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (
|
|
||||||
ident,
|
|
||||||
ty,
|
|
||||||
args::Argument {
|
|
||||||
name,
|
|
||||||
desc,
|
|
||||||
default,
|
|
||||||
default_with,
|
|
||||||
validator,
|
|
||||||
visible,
|
|
||||||
secret,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
) in &args
|
|
||||||
{
|
|
||||||
let name = name.clone().unwrap_or_else(|| {
|
|
||||||
object_args
|
|
||||||
.rename_args
|
|
||||||
.rename(ident.ident.unraw().to_string(), RenameTarget::Argument)
|
|
||||||
});
|
|
||||||
let desc = desc
|
|
||||||
.as_ref()
|
|
||||||
.map(|s| quote! {::std::option::Option::Some(#s)})
|
|
||||||
.unwrap_or_else(|| quote! {::std::option::Option::None});
|
|
||||||
let default = generate_default(default, default_with)?;
|
|
||||||
let schema_default = default
|
|
||||||
.as_ref()
|
|
||||||
.map(|value| {
|
|
||||||
quote! {
|
|
||||||
::std::option::Option::Some(::std::string::ToString::to_string(
|
|
||||||
&<#ty as #crate_name::InputType>::to_value(&#value)
|
|
||||||
))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| quote! {::std::option::Option::None});
|
|
||||||
|
|
||||||
let visible = visible_fn(visible);
|
|
||||||
schema_args.push(quote! {
|
|
||||||
args.insert(#name, #crate_name::registry::MetaInputValue {
|
|
||||||
name: #name,
|
|
||||||
description: #desc,
|
|
||||||
ty: <#ty as #crate_name::InputType>::create_type_info(registry),
|
|
||||||
default_value: #schema_default,
|
|
||||||
visible: #visible,
|
|
||||||
is_secret: #secret,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
let param_ident = &ident.ident;
|
|
||||||
use_params.push(quote! { #param_ident });
|
|
||||||
|
|
||||||
let default = match default {
|
|
||||||
Some(default) => {
|
|
||||||
quote! { ::std::option::Option::Some(|| -> #ty { #default }) }
|
|
||||||
}
|
|
||||||
None => quote! { ::std::option::Option::None },
|
|
||||||
};
|
|
||||||
|
|
||||||
let validators = validator.clone().unwrap_or_default().create_validators(
|
|
||||||
&crate_name,
|
|
||||||
quote!(&#ident),
|
|
||||||
quote!(#ty),
|
|
||||||
Some(quote!(.map_err(|err| err.into_server_error(__pos)))),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
get_params.push(quote! {
|
|
||||||
#[allow(non_snake_case, unused_variables)]
|
|
||||||
let (__pos, #ident) = ctx.param_value::<#ty>(#name, #default)?;
|
|
||||||
#validators
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let schema_ty = ty.value_type();
|
let schema_ty = ty.value_type();
|
||||||
let visible = visible_fn(&method_args.visible);
|
let visible = visible_fn(&method_args.visible);
|
||||||
|
|
||||||
|
@ -482,6 +531,7 @@ pub fn generate(
|
||||||
requires: #requires,
|
requires: #requires,
|
||||||
visible: #visible,
|
visible: #visible,
|
||||||
compute_complexity: #complexity,
|
compute_complexity: #complexity,
|
||||||
|
oneof: #is_oneof_field,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
261
derive/src/oneof_object.rs
Normal file
261
derive/src/oneof_object.rs
Normal file
|
@ -0,0 +1,261 @@
|
||||||
|
use darling::ast::{Data, Style};
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use syn::{Error, Type};
|
||||||
|
|
||||||
|
use crate::args;
|
||||||
|
use crate::args::{RenameRuleExt, RenameTarget};
|
||||||
|
use crate::utils::{get_crate_name, get_rustdoc, visible_fn, GeneratorResult};
|
||||||
|
|
||||||
|
pub fn generate(object_args: &args::OneofObject) -> GeneratorResult<TokenStream> {
|
||||||
|
let crate_name = get_crate_name(object_args.internal);
|
||||||
|
let (impl_generics, ty_generics, where_clause) = object_args.generics.split_for_impl();
|
||||||
|
let ident = &object_args.ident;
|
||||||
|
let desc = get_rustdoc(&object_args.attrs)?
|
||||||
|
.map(|s| quote! { ::std::option::Option::Some(#s) })
|
||||||
|
.unwrap_or_else(|| quote! {::std::option::Option::None});
|
||||||
|
let gql_typename = object_args
|
||||||
|
.name
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| RenameTarget::Type.rename(ident.to_string()));
|
||||||
|
let s = match &object_args.data {
|
||||||
|
Data::Enum(s) => s,
|
||||||
|
_ => {
|
||||||
|
return Err(
|
||||||
|
Error::new_spanned(ident, "InputObject can only be applied to an enum.").into(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut enum_items = HashSet::new();
|
||||||
|
let mut enum_names = Vec::new();
|
||||||
|
let mut schema_fields = Vec::new();
|
||||||
|
let mut parse_item = Vec::new();
|
||||||
|
let mut put_fields = Vec::new();
|
||||||
|
|
||||||
|
for variant in s {
|
||||||
|
let enum_name = &variant.ident;
|
||||||
|
let field_name = variant.name.clone().unwrap_or_else(|| {
|
||||||
|
object_args
|
||||||
|
.rename_fields
|
||||||
|
.rename(enum_name.to_string(), RenameTarget::Field)
|
||||||
|
});
|
||||||
|
let desc = get_rustdoc(&object_args.attrs)?
|
||||||
|
.map(|s| quote! { ::std::option::Option::Some(#s) })
|
||||||
|
.unwrap_or_else(|| quote! {::std::option::Option::None});
|
||||||
|
let ty = match variant.fields.style {
|
||||||
|
Style::Tuple if variant.fields.fields.len() == 1 => &variant.fields.fields[0],
|
||||||
|
Style::Tuple => {
|
||||||
|
return Err(Error::new_spanned(
|
||||||
|
enum_name,
|
||||||
|
"Only single value variants are supported",
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
Style::Unit => {
|
||||||
|
return Err(
|
||||||
|
Error::new_spanned(enum_name, "Empty variants are not supported").into(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Style::Struct => {
|
||||||
|
return Err(Error::new_spanned(
|
||||||
|
enum_name,
|
||||||
|
"Variants with named fields are not supported",
|
||||||
|
)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Type::Path(p) = ty {
|
||||||
|
// This validates that the field type wasn't already used
|
||||||
|
if !enum_items.insert(p) {
|
||||||
|
return Err(
|
||||||
|
Error::new_spanned(ty, "This type already used in another variant").into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum_names.push(enum_name);
|
||||||
|
|
||||||
|
let secret = variant.secret;
|
||||||
|
let visible = visible_fn(&variant.visible);
|
||||||
|
|
||||||
|
schema_fields.push(quote! {
|
||||||
|
fields.insert(::std::borrow::ToOwned::to_owned(#field_name), #crate_name::registry::MetaInputValue {
|
||||||
|
name: #field_name,
|
||||||
|
description: #desc,
|
||||||
|
ty: <::std::option::Option<#ty> as #crate_name::InputType>::create_type_info(registry),
|
||||||
|
default_value: ::std::option::Option::None,
|
||||||
|
visible: #visible,
|
||||||
|
is_secret: #secret,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let validators = variant
|
||||||
|
.validator
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.create_validators(
|
||||||
|
&crate_name,
|
||||||
|
quote!(&value),
|
||||||
|
quote!(#ty),
|
||||||
|
Some(quote!(.map_err(#crate_name::InputValueError::propagate))),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
parse_item.push(quote! {
|
||||||
|
if obj.contains_key(#field_name) && obj.len() == 1 {
|
||||||
|
let value = #crate_name::InputType::parse(obj.remove(#field_name)).map_err(#crate_name::InputValueError::propagate)?;
|
||||||
|
#validators
|
||||||
|
return ::std::result::Result::Ok(Self::#enum_name(value));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
put_fields.push(quote! {
|
||||||
|
Self::#enum_name(value) => {
|
||||||
|
map.insert(#crate_name::Name::new(#field_name), #crate_name::InputType::to_value(value));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return Err(Error::new_spanned(ty, "Invalid type").into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let visible = visible_fn(&object_args.visible);
|
||||||
|
let expanded = if object_args.concretes.is_empty() {
|
||||||
|
quote! {
|
||||||
|
impl #crate_name::InputType for #ident {
|
||||||
|
type RawValueType = Self;
|
||||||
|
|
||||||
|
fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> {
|
||||||
|
::std::borrow::Cow::Borrowed(#gql_typename)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String {
|
||||||
|
registry.create_input_type::<Self, _>(|registry| #crate_name::registry::MetaType::InputObject {
|
||||||
|
name: ::std::borrow::ToOwned::to_owned(#gql_typename),
|
||||||
|
description: #desc,
|
||||||
|
input_fields: {
|
||||||
|
let mut fields = #crate_name::indexmap::IndexMap::new();
|
||||||
|
#(#schema_fields)*
|
||||||
|
fields
|
||||||
|
},
|
||||||
|
visible: #visible,
|
||||||
|
rust_typename: ::std::any::type_name::<Self>(),
|
||||||
|
oneof: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(value: ::std::option::Option<#crate_name::Value>) -> #crate_name::InputValueResult<Self> {
|
||||||
|
if let ::std::option::Option::Some(#crate_name::Value::Object(mut obj)) = value {
|
||||||
|
#(#parse_item)*
|
||||||
|
::std::result::Result::Err(#crate_name::InputValueError::expected_type(#crate_name::Value::Object(obj)))
|
||||||
|
} else {
|
||||||
|
::std::result::Result::Err(#crate_name::InputValueError::expected_type(value.unwrap_or_default()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_value(&self) -> #crate_name::Value {
|
||||||
|
let mut map = #crate_name::indexmap::IndexMap::new();
|
||||||
|
match self {
|
||||||
|
#(#put_fields)*
|
||||||
|
}
|
||||||
|
#crate_name::Value::Object(map)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn federation_fields() -> ::std::option::Option<::std::string::String> {
|
||||||
|
::std::option::Option::None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_raw_value(&self) -> ::std::option::Option<&Self::RawValueType> {
|
||||||
|
::std::option::Option::Some(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl #crate_name::InputObjectType for #ident {}
|
||||||
|
impl #crate_name::OneofObjectType for #ident {}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut code = Vec::new();
|
||||||
|
|
||||||
|
code.push(quote! {
|
||||||
|
#[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 {
|
||||||
|
name: ::std::borrow::ToOwned::to_owned(name),
|
||||||
|
description: #desc,
|
||||||
|
input_fields: {
|
||||||
|
let mut fields = #crate_name::indexmap::IndexMap::new();
|
||||||
|
#(#schema_fields)*
|
||||||
|
fields
|
||||||
|
},
|
||||||
|
visible: #visible,
|
||||||
|
rust_typename: ::std::any::type_name::<Self>(),
|
||||||
|
oneof: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __internal_parse(value: ::std::option::Option<#crate_name::Value>) -> #crate_name::InputValueResult<Self> where Self: #crate_name::InputType {
|
||||||
|
if let ::std::option::Option::Some(#crate_name::Value::Object(mut obj)) = value {
|
||||||
|
#(#parse_item)*
|
||||||
|
::std::result::Result::Err(#crate_name::InputValueError::expected_type(#crate_name::Value::Object(obj)))
|
||||||
|
} else {
|
||||||
|
::std::result::Result::Err(#crate_name::InputValueError::expected_type(value.unwrap_or_default()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __internal_to_value(&self) -> #crate_name::Value where Self: #crate_name::InputType {
|
||||||
|
let mut map = #crate_name::indexmap::IndexMap::new();
|
||||||
|
match self {
|
||||||
|
#(#put_fields)*
|
||||||
|
}
|
||||||
|
#crate_name::Value::Object(map)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for concrete in &object_args.concretes {
|
||||||
|
let gql_typename = &concrete.name;
|
||||||
|
let params = &concrete.params.0;
|
||||||
|
let concrete_type = quote! { #ident<#(#params),*> };
|
||||||
|
|
||||||
|
let expanded = quote! {
|
||||||
|
#[allow(clippy::all, clippy::pedantic)]
|
||||||
|
impl #crate_name::InputType for #concrete_type {
|
||||||
|
type RawValueType = Self;
|
||||||
|
|
||||||
|
fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> {
|
||||||
|
::std::borrow::Cow::Borrowed(#gql_typename)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_type_info(registry: &mut #crate_name::registry::Registry) -> ::std::string::String {
|
||||||
|
Self::__internal_create_type_info(registry, #gql_typename)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(value: ::std::option::Option<#crate_name::Value>) -> #crate_name::InputValueResult<Self> {
|
||||||
|
Self::__internal_parse(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_value(&self) -> #crate_name::Value {
|
||||||
|
self.__internal_to_value()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn federation_fields() -> ::std::option::Option<::std::string::String> {
|
||||||
|
::std::option::Option::None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_raw_value(&self) -> ::std::option::Option<&Self::RawValueType> {
|
||||||
|
::std::option::Option::Some(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl #crate_name::InputObjectType for #concrete_type {}
|
||||||
|
impl #crate_name::OneofObjectType for #concrete_type {}
|
||||||
|
};
|
||||||
|
code.push(expanded);
|
||||||
|
}
|
||||||
|
quote!(#(#code)*)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(expanded.into())
|
||||||
|
}
|
|
@ -167,6 +167,7 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
|
||||||
requires: #requires,
|
requires: #requires,
|
||||||
visible: #visible,
|
visible: #visible,
|
||||||
compute_complexity: ::std::option::Option::None,
|
compute_complexity: ::std::option::Option::None,
|
||||||
|
oneof: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,17 +1,14 @@
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::ext::IdentExt;
|
use syn::ext::IdentExt;
|
||||||
use syn::{
|
use syn::{Block, Error, ImplItem, ItemImpl, ReturnType, Type, TypeImplTrait, TypeParamBound};
|
||||||
Block, Error, FnArg, ImplItem, ItemImpl, Pat, ReturnType, Type, TypeImplTrait, TypeParamBound,
|
|
||||||
TypeReference,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::args::{self, ComplexityType, RenameRuleExt, RenameTarget, SubscriptionField};
|
use crate::args::{self, ComplexityType, RenameRuleExt, RenameTarget, SubscriptionField};
|
||||||
use crate::output_type::OutputType;
|
use crate::output_type::OutputType;
|
||||||
use crate::utils::{
|
use crate::utils::{
|
||||||
gen_deprecation, generate_default, generate_guards, get_cfg_attrs, get_crate_name, get_rustdoc,
|
extract_input_args, gen_deprecation, generate_default, generate_guards, get_cfg_attrs,
|
||||||
get_type_path_and_name, parse_complexity_expr, parse_graphql_attrs, remove_graphql_attrs,
|
get_crate_name, get_rustdoc, get_type_path_and_name, parse_complexity_expr,
|
||||||
visible_fn, GeneratorResult,
|
parse_graphql_attrs, remove_graphql_attrs, visible_fn, GeneratorResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn generate(
|
pub fn generate(
|
||||||
|
@ -48,7 +45,7 @@ pub fn generate(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ident = &method.sig.ident;
|
let ident = method.sig.ident.clone();
|
||||||
let field_name = field.name.clone().unwrap_or_else(|| {
|
let field_name = field.name.clone().unwrap_or_else(|| {
|
||||||
subscription_args
|
subscription_args
|
||||||
.rename_fields
|
.rename_fields
|
||||||
|
@ -68,6 +65,120 @@ pub fn generate(
|
||||||
.into());
|
.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut schema_args = Vec::new();
|
||||||
|
let mut use_params = Vec::new();
|
||||||
|
let mut get_params = Vec::new();
|
||||||
|
let mut is_oneof_field = false;
|
||||||
|
let mut args =
|
||||||
|
extract_input_args::<args::SubscriptionFieldArgument>(&crate_name, method)?;
|
||||||
|
|
||||||
|
if field.oneof {
|
||||||
|
is_oneof_field = true;
|
||||||
|
|
||||||
|
if args.len() != 1 {
|
||||||
|
return Err(Error::new_spanned(
|
||||||
|
&method,
|
||||||
|
"The `oneof` field requires exactly one argument.",
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
let (ident, ty, argument) = args.pop().unwrap();
|
||||||
|
|
||||||
|
schema_args.push(quote! {
|
||||||
|
#crate_name::static_assertions::assert_impl_one!(#ty: #crate_name::OneofObjectType);
|
||||||
|
if let #crate_name::registry::MetaType::InputObject { input_fields, .. } = registry.create_fake_input_type::<#ty>() {
|
||||||
|
args.extend(input_fields);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
use_params.push(quote! { #ident });
|
||||||
|
|
||||||
|
let validators = argument
|
||||||
|
.validator
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.create_validators(
|
||||||
|
&crate_name,
|
||||||
|
quote!(&#ident),
|
||||||
|
quote!(#ty),
|
||||||
|
Some(quote!(.map_err(|err| err.into_server_error(__pos)))),
|
||||||
|
)?;
|
||||||
|
get_params.push(quote! {
|
||||||
|
#[allow(non_snake_case, unused_variables)]
|
||||||
|
let (__pos, #ident) = ctx.oneof_param_value::<#ty>()?;
|
||||||
|
#validators
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
for (
|
||||||
|
ident,
|
||||||
|
ty,
|
||||||
|
args::SubscriptionFieldArgument {
|
||||||
|
name,
|
||||||
|
desc,
|
||||||
|
default,
|
||||||
|
default_with,
|
||||||
|
validator,
|
||||||
|
visible: arg_visible,
|
||||||
|
secret,
|
||||||
|
},
|
||||||
|
) in &args
|
||||||
|
{
|
||||||
|
let name = name.clone().unwrap_or_else(|| {
|
||||||
|
subscription_args
|
||||||
|
.rename_args
|
||||||
|
.rename(ident.ident.unraw().to_string(), RenameTarget::Argument)
|
||||||
|
});
|
||||||
|
let desc = desc
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| quote! {::std::option::Option::Some(#s)})
|
||||||
|
.unwrap_or_else(|| quote! {::std::option::Option::None});
|
||||||
|
let default = generate_default(default, default_with)?;
|
||||||
|
|
||||||
|
let schema_default = default
|
||||||
|
.as_ref()
|
||||||
|
.map(|value| {
|
||||||
|
quote! {
|
||||||
|
::std::option::Option::Some(::std::string::ToString::to_string(
|
||||||
|
&<#ty as #crate_name::InputType>::to_value(&#value)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| quote! {::std::option::Option::None});
|
||||||
|
|
||||||
|
let visible = visible_fn(arg_visible);
|
||||||
|
schema_args.push(quote! {
|
||||||
|
args.insert(::std::borrow::ToOwned::to_owned(#name), #crate_name::registry::MetaInputValue {
|
||||||
|
name: #name,
|
||||||
|
description: #desc,
|
||||||
|
ty: <#ty as #crate_name::InputType>::create_type_info(registry),
|
||||||
|
default_value: #schema_default,
|
||||||
|
visible: #visible,
|
||||||
|
is_secret: #secret,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
use_params.push(quote! { #ident });
|
||||||
|
|
||||||
|
let default = match default {
|
||||||
|
Some(default) => {
|
||||||
|
quote! { ::std::option::Option::Some(|| -> #ty { #default }) }
|
||||||
|
}
|
||||||
|
None => quote! { ::std::option::Option::None },
|
||||||
|
};
|
||||||
|
let validators = validator.clone().unwrap_or_default().create_validators(
|
||||||
|
&crate_name,
|
||||||
|
quote!(&#ident),
|
||||||
|
quote!(#ty),
|
||||||
|
Some(quote!(.map_err(|err| err.into_server_error(__pos)))),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
get_params.push(quote! {
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
let (__pos, #ident) = ctx.param_value::<#ty>(#name, #default)?;
|
||||||
|
#validators
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let ty = match &method.sig.output {
|
let ty = match &method.sig.output {
|
||||||
ReturnType::Type(_, ty) => OutputType::parse(ty)?,
|
ReturnType::Type(_, ty) => OutputType::parse(ty)?,
|
||||||
ReturnType::Default => {
|
ReturnType::Default => {
|
||||||
|
@ -78,138 +189,6 @@ pub fn generate(
|
||||||
.into())
|
.into())
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut create_ctx = true;
|
|
||||||
let mut args = Vec::new();
|
|
||||||
|
|
||||||
for (idx, arg) in method.sig.inputs.iter_mut().enumerate() {
|
|
||||||
if let FnArg::Receiver(receiver) = arg {
|
|
||||||
if idx != 0 {
|
|
||||||
return Err(Error::new_spanned(
|
|
||||||
receiver,
|
|
||||||
"The self receiver must be the first parameter.",
|
|
||||||
)
|
|
||||||
.into());
|
|
||||||
}
|
|
||||||
} else if let FnArg::Typed(pat) = arg {
|
|
||||||
if idx == 0 {
|
|
||||||
return Err(Error::new_spanned(
|
|
||||||
pat,
|
|
||||||
"The self receiver must be the first parameter.",
|
|
||||||
)
|
|
||||||
.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
match (&*pat.pat, &*pat.ty) {
|
|
||||||
(Pat::Ident(arg_ident), Type::Path(arg_ty)) => {
|
|
||||||
args.push((
|
|
||||||
arg_ident.clone(),
|
|
||||||
arg_ty.clone(),
|
|
||||||
parse_graphql_attrs::<args::SubscriptionFieldArgument>(&pat.attrs)?
|
|
||||||
.unwrap_or_default(),
|
|
||||||
));
|
|
||||||
remove_graphql_attrs(&mut pat.attrs);
|
|
||||||
}
|
|
||||||
(arg, Type::Reference(TypeReference { elem, .. })) => {
|
|
||||||
if let Type::Path(path) = elem.as_ref() {
|
|
||||||
if idx != 1 || path.path.segments.last().unwrap().ident != "Context"
|
|
||||||
{
|
|
||||||
return Err(Error::new_spanned(
|
|
||||||
arg,
|
|
||||||
"Only types that implement `InputType` can be used as input arguments.",
|
|
||||||
)
|
|
||||||
.into());
|
|
||||||
} else {
|
|
||||||
create_ctx = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err(Error::new_spanned(arg, "Incorrect argument type").into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(Error::new_spanned(arg, "Incorrect argument type").into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if create_ctx {
|
|
||||||
let arg = syn::parse2::<FnArg>(quote! { _: &#crate_name::Context<'_> }).unwrap();
|
|
||||||
method.sig.inputs.insert(1, arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut schema_args = Vec::new();
|
|
||||||
let mut use_params = Vec::new();
|
|
||||||
let mut get_params = Vec::new();
|
|
||||||
|
|
||||||
for (
|
|
||||||
ident,
|
|
||||||
ty,
|
|
||||||
args::SubscriptionFieldArgument {
|
|
||||||
name,
|
|
||||||
desc,
|
|
||||||
default,
|
|
||||||
default_with,
|
|
||||||
validator,
|
|
||||||
visible: arg_visible,
|
|
||||||
secret,
|
|
||||||
},
|
|
||||||
) in &args
|
|
||||||
{
|
|
||||||
let name = name.clone().unwrap_or_else(|| {
|
|
||||||
subscription_args
|
|
||||||
.rename_args
|
|
||||||
.rename(ident.ident.unraw().to_string(), RenameTarget::Argument)
|
|
||||||
});
|
|
||||||
let desc = desc
|
|
||||||
.as_ref()
|
|
||||||
.map(|s| quote! {::std::option::Option::Some(#s)})
|
|
||||||
.unwrap_or_else(|| quote! {::std::option::Option::None});
|
|
||||||
let default = generate_default(default, default_with)?;
|
|
||||||
|
|
||||||
let schema_default = default
|
|
||||||
.as_ref()
|
|
||||||
.map(|value| {
|
|
||||||
quote! {
|
|
||||||
::std::option::Option::Some(::std::string::ToString::to_string(
|
|
||||||
&<#ty as #crate_name::InputType>::to_value(&#value)
|
|
||||||
))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| quote! {::std::option::Option::None});
|
|
||||||
|
|
||||||
let visible = visible_fn(arg_visible);
|
|
||||||
schema_args.push(quote! {
|
|
||||||
args.insert(#name, #crate_name::registry::MetaInputValue {
|
|
||||||
name: #name,
|
|
||||||
description: #desc,
|
|
||||||
ty: <#ty as #crate_name::InputType>::create_type_info(registry),
|
|
||||||
default_value: #schema_default,
|
|
||||||
visible: #visible,
|
|
||||||
is_secret: #secret,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
use_params.push(quote! { #ident });
|
|
||||||
|
|
||||||
let default = match default {
|
|
||||||
Some(default) => quote! { ::std::option::Option::Some(|| -> #ty { #default }) },
|
|
||||||
None => quote! { ::std::option::Option::None },
|
|
||||||
};
|
|
||||||
let validators = validator.clone().unwrap_or_default().create_validators(
|
|
||||||
&crate_name,
|
|
||||||
quote!(&#ident),
|
|
||||||
quote!(#ty),
|
|
||||||
Some(quote!(.map_err(|err| err.into_server_error(__pos)))),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
get_params.push(quote! {
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
let (__pos, #ident) = ctx.param_value::<#ty>(#name, #default)?;
|
|
||||||
#validators
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let res_ty = ty.value_type();
|
let res_ty = ty.value_type();
|
||||||
let stream_ty = if let Type::ImplTrait(TypeImplTrait { bounds, .. }) = &res_ty {
|
let stream_ty = if let Type::ImplTrait(TypeImplTrait { bounds, .. }) = &res_ty {
|
||||||
let mut r = None;
|
let mut r = None;
|
||||||
|
@ -307,6 +286,7 @@ pub fn generate(
|
||||||
provides: ::std::option::Option::None,
|
provides: ::std::option::Option::None,
|
||||||
visible: #visible,
|
visible: #visible,
|
||||||
compute_complexity: #complexity,
|
compute_complexity: #complexity,
|
||||||
|
oneof: #is_oneof_field,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
use darling::ast::{Data, Style};
|
use darling::ast::{Data, Style};
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro2::{Ident, Span};
|
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use syn::visit_mut::VisitMut;
|
use syn::visit_mut::VisitMut;
|
||||||
use syn::{visit_mut, Error, Lifetime, Type};
|
use syn::{Error, Type};
|
||||||
|
|
||||||
use crate::args::{self, RenameTarget};
|
use crate::args::{self, RenameTarget};
|
||||||
use crate::utils::{get_crate_name, get_rustdoc, visible_fn, GeneratorResult};
|
use crate::utils::{get_crate_name, get_rustdoc, visible_fn, GeneratorResult, RemoveLifetime};
|
||||||
|
|
||||||
pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
|
pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
|
||||||
let crate_name = get_crate_name(union_args.internal);
|
let crate_name = get_crate_name(union_args.internal);
|
||||||
|
@ -71,14 +70,6 @@ pub fn generate(union_args: &args::Union) -> GeneratorResult<TokenStream> {
|
||||||
|
|
||||||
enum_names.push(enum_name);
|
enum_names.push(enum_name);
|
||||||
|
|
||||||
struct RemoveLifetime;
|
|
||||||
impl VisitMut for RemoveLifetime {
|
|
||||||
fn visit_lifetime_mut(&mut self, i: &mut Lifetime) {
|
|
||||||
i.ident = Ident::new("_", Span::call_site());
|
|
||||||
visit_mut::visit_lifetime_mut(self, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut assert_ty = p.clone();
|
let mut assert_ty = p.clone();
|
||||||
RemoveLifetime.visit_type_path_mut(&mut assert_ty);
|
RemoveLifetime.visit_type_path_mut(&mut assert_ty);
|
||||||
|
|
||||||
|
|
|
@ -6,13 +6,14 @@ use proc_macro2::{Span, TokenStream, TokenTree};
|
||||||
use proc_macro_crate::{crate_name, FoundCrate};
|
use proc_macro_crate::{crate_name, FoundCrate};
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::visit::Visit;
|
use syn::visit::Visit;
|
||||||
|
use syn::visit_mut::VisitMut;
|
||||||
use syn::{
|
use syn::{
|
||||||
Attribute, Error, Expr, ExprPath, FnArg, Ident, ImplItemMethod, Lit, LitStr, Meta, Pat,
|
visit_mut, Attribute, Error, Expr, ExprPath, FnArg, Ident, ImplItemMethod, Lifetime, Lit,
|
||||||
PatIdent, Type, TypeGroup, TypeParamBound, TypeReference,
|
LitStr, Meta, Pat, PatIdent, Type, TypeGroup, TypeParamBound, TypeReference,
|
||||||
};
|
};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::args::{self, Argument, Deprecation, Visible};
|
use crate::args::{self, Deprecation, Visible};
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum GeneratorError {
|
pub enum GeneratorError {
|
||||||
|
@ -141,7 +142,9 @@ pub fn get_cfg_attrs(attrs: &[Attribute]) -> Vec<Attribute> {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_graphql_attrs<T: FromMeta>(attrs: &[Attribute]) -> GeneratorResult<Option<T>> {
|
pub fn parse_graphql_attrs<T: FromMeta + Default>(
|
||||||
|
attrs: &[Attribute],
|
||||||
|
) -> GeneratorResult<Option<T>> {
|
||||||
for attr in attrs {
|
for attr in attrs {
|
||||||
if attr.path.is_ident("graphql") {
|
if attr.path.is_ident("graphql") {
|
||||||
let meta = attr.parse_meta()?;
|
let meta = attr.parse_meta()?;
|
||||||
|
@ -237,10 +240,10 @@ pub fn gen_deprecation(deprecation: &Deprecation, crate_name: &TokenStream) -> T
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_input_args(
|
pub fn extract_input_args<T: FromMeta + Default>(
|
||||||
crate_name: &proc_macro2::TokenStream,
|
crate_name: &proc_macro2::TokenStream,
|
||||||
method: &mut ImplItemMethod,
|
method: &mut ImplItemMethod,
|
||||||
) -> GeneratorResult<Vec<(PatIdent, Type, Argument)>> {
|
) -> GeneratorResult<Vec<(PatIdent, Type, T)>> {
|
||||||
let mut args = Vec::new();
|
let mut args = Vec::new();
|
||||||
let mut create_ctx = true;
|
let mut create_ctx = true;
|
||||||
|
|
||||||
|
@ -277,8 +280,7 @@ pub fn extract_input_args(
|
||||||
args.push((
|
args.push((
|
||||||
arg_ident.clone(),
|
arg_ident.clone(),
|
||||||
pat.ty.as_ref().clone(),
|
pat.ty.as_ref().clone(),
|
||||||
parse_graphql_attrs::<args::Argument>(&pat.attrs)?
|
parse_graphql_attrs::<T>(&pat.attrs)?.unwrap_or_default(),
|
||||||
.unwrap_or_default(),
|
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
create_ctx = false;
|
create_ctx = false;
|
||||||
|
@ -289,7 +291,7 @@ pub fn extract_input_args(
|
||||||
args.push((
|
args.push((
|
||||||
arg_ident.clone(),
|
arg_ident.clone(),
|
||||||
ty.clone(),
|
ty.clone(),
|
||||||
parse_graphql_attrs::<args::Argument>(&pat.attrs)?.unwrap_or_default(),
|
parse_graphql_attrs::<T>(&pat.attrs)?.unwrap_or_default(),
|
||||||
));
|
));
|
||||||
remove_graphql_attrs(&mut pat.attrs);
|
remove_graphql_attrs(&mut pat.attrs);
|
||||||
}
|
}
|
||||||
|
@ -307,3 +309,12 @@ pub fn extract_input_args(
|
||||||
|
|
||||||
Ok(args)
|
Ok(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct RemoveLifetime;
|
||||||
|
|
||||||
|
impl VisitMut for RemoveLifetime {
|
||||||
|
fn visit_lifetime_mut(&mut self, i: &mut Lifetime) {
|
||||||
|
i.ident = Ident::new("_", Span::call_site());
|
||||||
|
visit_mut::visit_lifetime_mut(self, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ impl Query {
|
||||||
let mut connection = Connection::new(start > 0, end < 10000);
|
let mut connection = Connection::new(start > 0, end < 10000);
|
||||||
connection.append(
|
connection.append(
|
||||||
(start..end).into_iter().map(|n|
|
(start..end).into_iter().map(|n|
|
||||||
Ok(Edge::new_with_additional_fields(n, n as i32, EmptyFields)),
|
Ok(Edge::with_additional_fields(n, n as i32, EmptyFields)),
|
||||||
))?;
|
))?;
|
||||||
Ok(connection)
|
Ok(connection)
|
||||||
})
|
})
|
||||||
|
|
|
@ -4,7 +4,7 @@ In `Async-graphql` most common scalar types are built in, but you can also creat
|
||||||
|
|
||||||
Using `async-graphql::Scalar`, you can add support for a scalar when you implement it. You only need to implement parsing and output functions.
|
Using `async-graphql::Scalar`, you can add support for a scalar when you implement it. You only need to implement parsing and output functions.
|
||||||
|
|
||||||
The following example defines a 64-bit integer scalar where its input and output are strings. (Note: `Async-graphql` already supports 64-bit integers and uses strings as input and output.)
|
The following example defines a 64-bit integer scalar where its input and output are strings.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use async_graphql::*;
|
use async_graphql::*;
|
||||||
|
|
|
@ -4,7 +4,7 @@ Different from `SimpleObject`, `Object` must have a resolver defined for each fi
|
||||||
|
|
||||||
**A resolver function has to be asynchronous. The first argument has to be `&self`, the second is an optional `Context` and it is followed by field arguments.**
|
**A resolver function has to be asynchronous. The first argument has to be `&self`, the second is an optional `Context` and it is followed by field arguments.**
|
||||||
|
|
||||||
The resolvers is used to get the value of the field. For example, you can query a database and return the result. **The return type of the function is the type of the field.** You can also return a `async_graphql::Result` to return an error if it occurs. The error message will then be sent to query result.
|
The resolver is used to get the value of the field. For example, you can query a database and return the result. **The return type of the function is the type of the field.** You can also return a `async_graphql::Result` to return an error if it occurs. The error message will then be sent as query result.
|
||||||
|
|
||||||
You may need access to global data in your query, for example a database connection pool.
|
You may need access to global data in your query, for example a database connection pool.
|
||||||
When creating your `Schema`, you can use `SchemaBuilder::data` to configure the global data, and `Context::data` to configure `Context` data.
|
When creating your `Schema`, you can use `SchemaBuilder::data` to configure the global data, and `Context::data` to configure `Context` data.
|
||||||
|
|
2
examples
2
examples
|
@ -1 +1 @@
|
||||||
Subproject commit c8219078a4b7aa6d84d22e9b79f033088897be4b
|
Subproject commit 61384c8fa50e1395e50015b2be77b4bc6f5de4d2
|
|
@ -4,29 +4,31 @@ Comparing Features of Other Rust GraphQL Implementations
|
||||||
|
|
||||||
**Please let me know if there is anything wrong.**
|
**Please let me know if there is anything wrong.**
|
||||||
|
|
||||||
| | async-graphql | juniper(0.15.1) |
|
| | async-graphql | juniper(0.15.1) |
|
||||||
|----------------|---------------|-----------------|
|
|------------------------------------------------------------------------|---------------|-----------------|
|
||||||
| async/await | 👍 | 👍️ |
|
| async/await | 👍 | 👍️ |
|
||||||
| Rustfmt friendly(No DSL) | 👍 | ⛔️ |
|
| Rustfmt friendly(No DSL) | 👍 | ⛔️ |
|
||||||
| Boilerplate | Less | Some |
|
| Boilerplate | Less | Some |
|
||||||
| Type Safety | 👍 | 👍 |
|
| Type Safety | 👍 | 👍 |
|
||||||
| Query | 👍 | 👍 |
|
| Query | 👍 | 👍 |
|
||||||
| Mutation | 👍 | 👍 |
|
| Mutation | 👍 | 👍 |
|
||||||
| Interfaces | 👍 | 👍 |
|
| Interfaces | 👍 | 👍 |
|
||||||
| Union | 👍 | 👍 |
|
| Union | 👍 | 👍 |
|
||||||
| Dataloading | 👍 | 👍 |
|
| Dataloading | 👍 | 👍 |
|
||||||
| Custom Scalar | 👍 | 👍 |
|
| Custom Scalar | 👍 | 👍 |
|
||||||
| Custom Error | 👍 | 👍 |
|
| Custom Error | 👍 | 👍 |
|
||||||
| Custom Directive | 👍 | ⛔ |
|
| Custom Directive | 👍 | ⛔ |
|
||||||
| Extensions | 👍 | ⛔️ |
|
| [Oneof Input Object](https://github.com/graphql/graphql-spec/pull/825) | 👍 | ⛔ |
|
||||||
| Cursor Connections | 👍 | ⛔️ |
|
| [Oneof Field](https://github.com/graphql/graphql-spec/pull/825) | 👍 | ⛔ |
|
||||||
| Query complexity/depth | 👍 | ⛔️ |
|
| Extensions | 👍 | ⛔️ |
|
||||||
| Input validators | 👍 | ⛔️ |
|
| Cursor Connections | 👍 | ⛔️ |
|
||||||
| Field guard | 👍 | ⛔️ |
|
| Query complexity/depth | 👍 | ⛔️ |
|
||||||
| Multipart request(upload file) | 👍 | ⛔️ |
|
| Input validators | 👍 | ⛔️ |
|
||||||
| Subscription | 👍 | 👍️ |
|
| Field guard | 👍 | ⛔️ |
|
||||||
| Opentracing | 👍 | ⛔️ |
|
| Multipart request(upload file) | 👍 | ⛔️ |
|
||||||
| Apollo Federation | 👍 | ⛔️ |
|
| Subscription | 👍 | 👍️ |
|
||||||
| Apollo Tracing | 👍 | ⛔️ |
|
| Opentracing | 👍 | ⛔️ |
|
||||||
| Apollo Persisted Queries | 👍 | ⛔️ |
|
| Apollo Federation | 👍 | ⛔️ |
|
||||||
| Disabling introspection | 👍 | ⛔️ |
|
| Apollo Tracing | 👍 | ⛔️ |
|
||||||
|
| Apollo Persisted Queries | 👍 | ⛔️ |
|
||||||
|
| Disabling introspection | 👍 | ⛔️ |
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "async-graphql-actix-web"
|
name = "async-graphql-actix-web"
|
||||||
version = "3.0.26"
|
version = "3.0.33"
|
||||||
authors = ["sunli <scott_s829@163.com>", "Koxiaet"]
|
authors = ["sunli <scott_s829@163.com>", "Koxiaet"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "async-graphql for actix-web"
|
description = "async-graphql for actix-web"
|
||||||
|
@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql"]
|
||||||
categories = ["network-programming", "asynchronous"]
|
categories = ["network-programming", "asynchronous"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-graphql = { path = "../..", version = "3.0.26" }
|
async-graphql = { path = "../..", version = "3.0.33" }
|
||||||
|
|
||||||
actix = "0.13.0"
|
actix = "0.13.0"
|
||||||
actix-http = "3.0.1"
|
actix-http = "3.0.1"
|
||||||
|
@ -31,6 +31,6 @@ default = []
|
||||||
cbor = ["serde_cbor"]
|
cbor = ["serde_cbor"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "2.2.0"
|
actix-rt = "2.6.0"
|
||||||
async-mutex = "1.4.0"
|
async-mutex = "1.4.0"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "async-graphql-axum"
|
name = "async-graphql-axum"
|
||||||
version = "3.0.26"
|
version = "3.0.33"
|
||||||
authors = ["sunli <scott_s829@163.com>"]
|
authors = ["sunli <scott_s829@163.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "async-graphql for axum"
|
description = "async-graphql for axum"
|
||||||
|
@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql", "axum"]
|
||||||
categories = ["network-programming", "asynchronous"]
|
categories = ["network-programming", "asynchronous"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-graphql = { path = "../..", version = "3.0.26" }
|
async-graphql = { path = "../..", version = "3.0.33" }
|
||||||
|
|
||||||
async-trait = "0.1.51"
|
async-trait = "0.1.51"
|
||||||
axum = { version = "0.4", features = ["ws", "headers"] }
|
axum = { version = "0.4", features = ["ws", "headers"] }
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "async-graphql-poem"
|
name = "async-graphql-poem"
|
||||||
version = "3.0.26"
|
version = "3.0.33"
|
||||||
authors = ["sunli <scott_s829@163.com>"]
|
authors = ["sunli <scott_s829@163.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "async-graphql for poem"
|
description = "async-graphql for poem"
|
||||||
|
@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql", "poem"]
|
||||||
categories = ["network-programming", "asynchronous"]
|
categories = ["network-programming", "asynchronous"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-graphql = { path = "../..", version = "3.0.26" }
|
async-graphql = { path = "../..", version = "3.0.33" }
|
||||||
|
|
||||||
poem = { version = "1.2.2", features = ["websocket"] }
|
poem = { version = "1.2.2", features = ["websocket"] }
|
||||||
futures-util = { version = "0.3.0", default-features = false }
|
futures-util = { version = "0.3.0", default-features = false }
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "async-graphql-rocket"
|
name = "async-graphql-rocket"
|
||||||
version = "3.0.26"
|
version = "3.0.33"
|
||||||
authors = ["Daniel Wiesenberg <daniel@simplificAR.io>"]
|
authors = ["Daniel Wiesenberg <daniel@simplificAR.io>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "async-graphql for Rocket.rs"
|
description = "async-graphql for Rocket.rs"
|
||||||
|
@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql", "rocket"]
|
||||||
categories = ["network-programming", "asynchronous"]
|
categories = ["network-programming", "asynchronous"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-graphql = { path = "../..", version = "3.0.26" }
|
async-graphql = { path = "../..", version = "3.0.33" }
|
||||||
|
|
||||||
rocket = { version = "0.5.0-rc.1", default-features = false }
|
rocket = { version = "0.5.0-rc.1", default-features = false }
|
||||||
serde = "1.0.126"
|
serde = "1.0.126"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "async-graphql-tide"
|
name = "async-graphql-tide"
|
||||||
version = "3.0.26"
|
version = "3.0.33"
|
||||||
authors = ["vkill <vkill.net@gmail.com>", "sunli <scott_s829@163.com>"]
|
authors = ["vkill <vkill.net@gmail.com>", "sunli <scott_s829@163.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "async-graphql for tide"
|
description = "async-graphql for tide"
|
||||||
|
@ -16,7 +16,7 @@ default = ["websocket"]
|
||||||
websocket = ["tide-websockets"]
|
websocket = ["tide-websockets"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-graphql = { path = "../..", version = "3.0.26" }
|
async-graphql = { path = "../..", version = "3.0.33" }
|
||||||
|
|
||||||
async-trait = "0.1.48"
|
async-trait = "0.1.48"
|
||||||
futures-util = "0.3.0"
|
futures-util = "0.3.0"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "async-graphql-warp"
|
name = "async-graphql-warp"
|
||||||
version = "3.0.26"
|
version = "3.0.33"
|
||||||
authors = ["sunli <scott_s829@163.com>", "Koxiaet"]
|
authors = ["sunli <scott_s829@163.com>", "Koxiaet"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "async-graphql for warp"
|
description = "async-graphql for warp"
|
||||||
|
@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql"]
|
||||||
categories = ["network-programming", "asynchronous"]
|
categories = ["network-programming", "asynchronous"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-graphql = { path = "../..", version = "3.0.26" }
|
async-graphql = { path = "../..", version = "3.0.33" }
|
||||||
|
|
||||||
warp = { version = "0.3.0", default-features = false, features = ["websocket"] }
|
warp = { version = "0.3.0", default-features = false, features = ["websocket"] }
|
||||||
futures-util = { version = "0.3.0", default-features = false, features = ["sink"] }
|
futures-util = { version = "0.3.0", default-features = false, features = ["sink"] }
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "async-graphql-parser"
|
name = "async-graphql-parser"
|
||||||
version = "3.0.26"
|
version = "3.0.33"
|
||||||
authors = ["sunli <scott_s829@163.com>", "Koxiaet"]
|
authors = ["sunli <scott_s829@163.com>", "Koxiaet"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "GraphQL query parser for async-graphql"
|
description = "GraphQL query parser for async-graphql"
|
||||||
|
@ -12,7 +12,7 @@ keywords = ["futures", "async", "graphql"]
|
||||||
categories = ["network-programming", "asynchronous"]
|
categories = ["network-programming", "asynchronous"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-graphql-value = { path = "../value", version = "3.0.26" }
|
async-graphql-value = { path = "../value", version = "3.0.33" }
|
||||||
pest = "2.1.3"
|
pest = "2.1.3"
|
||||||
pest_derive = "2.1.0"
|
pest_derive = "2.1.0"
|
||||||
serde_json = "1.0.64"
|
serde_json = "1.0.64"
|
||||||
|
|
|
@ -148,6 +148,9 @@ pub trait UnionType: ContainerType {}
|
||||||
/// A GraphQL input object.
|
/// A GraphQL input object.
|
||||||
pub trait InputObjectType: InputType {}
|
pub trait InputObjectType: InputType {}
|
||||||
|
|
||||||
|
/// A GraphQL oneof input object.
|
||||||
|
pub trait OneofObjectType: InputObjectType {}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<T: OutputType + ?Sized> OutputType for Box<T> {
|
impl<T: OutputType + ?Sized> OutputType for Box<T> {
|
||||||
fn type_name() -> Cow<'static, str> {
|
fn type_name() -> Cow<'static, str> {
|
||||||
|
|
|
@ -19,8 +19,8 @@ use crate::parser::types::{
|
||||||
};
|
};
|
||||||
use crate::schema::SchemaEnv;
|
use crate::schema::SchemaEnv;
|
||||||
use crate::{
|
use crate::{
|
||||||
Error, InputType, Lookahead, Name, PathSegment, Pos, Positioned, Result, ServerError,
|
Error, InputType, Lookahead, Name, OneofObjectType, PathSegment, Pos, Positioned, Result,
|
||||||
ServerResult, UploadValue, Value,
|
ServerError, ServerResult, UploadValue, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Data related functions of the context.
|
/// Data related functions of the context.
|
||||||
|
@ -604,6 +604,22 @@ impl<'a> ContextBase<'a, &'a Positioned<Field>> {
|
||||||
self.get_param_value(&self.item.node.arguments, name, default)
|
self.get_param_value(&self.item.node.arguments, name, default)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn oneof_param_value<T: OneofObjectType>(&self) -> ServerResult<(Pos, T)> {
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
|
||||||
|
let mut map = IndexMap::new();
|
||||||
|
|
||||||
|
for (name, value) in &self.item.node.arguments {
|
||||||
|
let value = self.resolve_input_value(value.clone())?;
|
||||||
|
map.insert(name.node.clone(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
InputType::parse(Some(Value::Object(map)))
|
||||||
|
.map(|value| (self.item.pos, value))
|
||||||
|
.map_err(|e| e.into_server_error(self.item.pos))
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a uniform interface to inspect the forthcoming selections.
|
/// Creates a uniform interface to inspect the forthcoming selections.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
|
|
|
@ -36,6 +36,7 @@ some simple fields, and use the `ComplexObject` macro to define some other field
|
||||||
| complexity | Custom field complexity. | string | Y |
|
| complexity | Custom field complexity. | string | Y |
|
||||||
| derived | Generate derived fields *[See also the Book](https://async-graphql.github.io/async-graphql/en/derived_fields.html).* | object | Y |
|
| derived | Generate derived fields *[See also the Book](https://async-graphql.github.io/async-graphql/en/derived_fields.html).* | object | Y |
|
||||||
| flatten | Similar to serde (flatten) | boolean | Y |
|
| flatten | Similar to serde (flatten) | boolean | Y |
|
||||||
|
| oneof | Oneof field | bool | Y |
|
||||||
|
|
||||||
# Field argument attributes
|
# Field argument attributes
|
||||||
|
|
||||||
|
|
|
@ -4,12 +4,13 @@ Define a GraphQL input object
|
||||||
|
|
||||||
# Macro attributes
|
# Macro attributes
|
||||||
|
|
||||||
| Attribute | description | Type | Optional |
|
| Attribute | description | Type | Optional |
|
||||||
|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------|
|
|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|----------|
|
||||||
| name | Object name | string | Y |
|
| name | Object name | string | Y |
|
||||||
| rename_fields | Rename all the fields according to the given case convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase", "snake_case", "SCREAMING_SNAKE_CASE". | string | Y |
|
| rename_fields | Rename all the fields according to the given case convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase", "snake_case", "SCREAMING_SNAKE_CASE". | string | 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 | 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 |
|
| visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y |
|
||||||
|
| concretes | Specify how the concrete type of the generic SimpleObject should be implemented. | ConcreteType | Y |
|
||||||
|
|
||||||
# Field attributes
|
# Field attributes
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ Define a GraphQL interface
|
||||||
| requires | Annotate the required input fieldset from a base type for a resolver. It is used to develop a query plan where the required fields may not be needed by the client, but the service may need additional information from other services. | string | Y |
|
| requires | Annotate the required input fieldset from a base type for a resolver. It is used to develop a query plan where the required fields may not be needed by the client, but the service may need additional information from other services. | string | 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 | 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 |
|
| visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y |
|
||||||
|
| oneof | Oneof field | bool | Y |
|
||||||
|
|
||||||
# Field argument attributes
|
# Field argument attributes
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ All methods are converted to camelCase.
|
||||||
| 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 | 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 |
|
| visible | Call the specified function. If the return value is `false`, it will not be displayed in introspection. | string | Y |
|
||||||
| serial | Resolve each field sequentially. | bool | Y |
|
| serial | Resolve each field sequentially. | bool | Y |
|
||||||
|
| concretes | Specify how the concrete type of the generic SimpleObject should be implemented. | ConcreteType | Y |
|
||||||
|
|
||||||
# Field attributes
|
# Field attributes
|
||||||
|
|
||||||
|
@ -38,6 +39,7 @@ All methods are converted to camelCase.
|
||||||
| complexity | Custom field complexity. | string | Y |
|
| complexity | Custom field complexity. | string | Y |
|
||||||
| derived | Generate derived fields *[See also the Book](https://async-graphql.github.io/async-graphql/en/derived_fields.html).* | object | Y |
|
| derived | Generate derived fields *[See also the Book](https://async-graphql.github.io/async-graphql/en/derived_fields.html).* | object | Y |
|
||||||
| flatten | Similar to serde (flatten) | boolean | Y |
|
| flatten | Similar to serde (flatten) | boolean | Y |
|
||||||
|
| oneof | Oneof field | bool | Y |
|
||||||
|
|
||||||
# Field argument attributes
|
# Field argument attributes
|
||||||
|
|
||||||
|
|
56
src/docs/oneof_object.md
Normal file
56
src/docs/oneof_object.md
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
Define a GraphQL oneof input object
|
||||||
|
|
||||||
|
# Macro attributes
|
||||||
|
|
||||||
|
| Attribute | description | Type | Optional |
|
||||||
|
|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|----------|
|
||||||
|
| name | Object name | string | Y |
|
||||||
|
| rename_fields | Rename all the fields according to the given case convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase", "snake_case", "SCREAMING_SNAKE_CASE". | string | 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 |
|
||||||
|
| concretes | Specify how the concrete type of the generic SimpleObject should be implemented. | ConcreteType | Y |
|
||||||
|
|
||||||
|
# Field attributes
|
||||||
|
|
||||||
|
| Attribute | description | Type | Optional |
|
||||||
|
|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------|-------------|----------|
|
||||||
|
| name | Field name | string | Y |
|
||||||
|
| 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 |
|
||||||
|
| secret | Mark this field as a secret, it will not output the actual value in the log. | bool | Y |
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use async_graphql::*;
|
||||||
|
|
||||||
|
#[derive(OneofObject)]
|
||||||
|
enum MyInputObject {
|
||||||
|
A(i32),
|
||||||
|
B(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Query;
|
||||||
|
|
||||||
|
#[Object]
|
||||||
|
impl Query {
|
||||||
|
/// value
|
||||||
|
async fn value(&self, input: MyInputObject) -> String {
|
||||||
|
match input {
|
||||||
|
MyInputObject::A(value) => format!("a:{}", value),
|
||||||
|
MyInputObject::B(value) => format!("b:{}", value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# tokio::runtime::Runtime::new().unwrap().block_on(async move {
|
||||||
|
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
|
||||||
|
let res = schema.execute(r#"
|
||||||
|
{
|
||||||
|
value1: value(input:{a:100})
|
||||||
|
value2: value(input:{b:"abc"})
|
||||||
|
}"#).await.into_result().unwrap().data;
|
||||||
|
assert_eq!(res, value!({ "value1": "a:100", "value2": "b:abc" }));
|
||||||
|
# });
|
||||||
|
```
|
|
@ -32,6 +32,7 @@ The filter function should be synchronous.
|
||||||
| 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. *[See also the Book](https://async-graphql.github.io/async-graphql/en/depth_and_complexity.html).* | bool | Y |
|
||||||
| complexity | Custom field complexity. | string | Y |
|
| complexity | Custom field complexity. | string | Y |
|
||||||
| secret | Mark this field as a secret, it will not output the actual value in the log. | bool | Y |
|
| secret | Mark this field as a secret, it will not output the actual value in the log. | bool | Y |
|
||||||
|
| oneof | Oneof field | bool | Y |
|
||||||
|
|
||||||
# Field argument attributes
|
# Field argument attributes
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,8 @@ impl<T> OpenTelemetry<T> {
|
||||||
/// Use `tracer` to create an OpenTelemetry extension.
|
/// Use `tracer` to create an OpenTelemetry extension.
|
||||||
pub fn new(tracer: T) -> OpenTelemetry<T>
|
pub fn new(tracer: T) -> OpenTelemetry<T>
|
||||||
where
|
where
|
||||||
T: Tracer + Send + Sync,
|
T: Tracer + Send + Sync + 'static,
|
||||||
|
<T as Tracer>::Span: Sync + Send,
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
tracer: Arc::new(tracer),
|
tracer: Arc::new(tracer),
|
||||||
|
@ -39,7 +40,11 @@ impl<T> OpenTelemetry<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Tracer + Send + Sync> ExtensionFactory for OpenTelemetry<T> {
|
impl<T> ExtensionFactory for OpenTelemetry<T>
|
||||||
|
where
|
||||||
|
T: Tracer + Send + Sync + 'static,
|
||||||
|
<T as Tracer>::Span: Sync + Send,
|
||||||
|
{
|
||||||
fn create(&self) -> Arc<dyn Extension> {
|
fn create(&self) -> Arc<dyn Extension> {
|
||||||
Arc::new(OpenTelemetryExtension {
|
Arc::new(OpenTelemetryExtension {
|
||||||
tracer: self.tracer.clone(),
|
tracer: self.tracer.clone(),
|
||||||
|
@ -52,7 +57,11 @@ struct OpenTelemetryExtension<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<T: Tracer + Send + Sync> Extension for OpenTelemetryExtension<T> {
|
impl<T> Extension for OpenTelemetryExtension<T>
|
||||||
|
where
|
||||||
|
T: Tracer + Send + Sync + 'static,
|
||||||
|
<T as Tracer>::Span: Sync + Send,
|
||||||
|
{
|
||||||
async fn request(&self, ctx: &ExtensionContext<'_>, next: NextRequest<'_>) -> Response {
|
async fn request(&self, ctx: &ExtensionContext<'_>, next: NextRequest<'_>) -> Response {
|
||||||
next.run(ctx)
|
next.run(ctx)
|
||||||
.with_context(OpenTelemetryContext::current_with_span(
|
.with_context(OpenTelemetryContext::current_with_span(
|
||||||
|
|
|
@ -213,8 +213,8 @@ pub use async_graphql_value::{
|
||||||
SerializerError, Variables,
|
SerializerError, Variables,
|
||||||
};
|
};
|
||||||
pub use base::{
|
pub use base::{
|
||||||
ComplexObject, Description, InputObjectType, InputType, InterfaceType, ObjectType, OutputType,
|
ComplexObject, Description, InputObjectType, InputType, InterfaceType, ObjectType,
|
||||||
UnionType,
|
OneofObjectType, OutputType, UnionType,
|
||||||
};
|
};
|
||||||
pub use custom_directive::{CustomDirective, CustomDirectiveFactory};
|
pub use custom_directive::{CustomDirective, CustomDirectiveFactory};
|
||||||
pub use error::{
|
pub use error::{
|
||||||
|
@ -266,6 +266,8 @@ pub use async_graphql_derive::MergedSubscription;
|
||||||
pub use async_graphql_derive::NewType;
|
pub use async_graphql_derive::NewType;
|
||||||
#[doc = include_str!("docs/object.md")]
|
#[doc = include_str!("docs/object.md")]
|
||||||
pub use async_graphql_derive::Object;
|
pub use async_graphql_derive::Object;
|
||||||
|
#[doc = include_str!("docs/oneof_object.md")]
|
||||||
|
pub use async_graphql_derive::OneofObject;
|
||||||
#[doc = include_str!("docs/scalar.md")]
|
#[doc = include_str!("docs/scalar.md")]
|
||||||
pub use async_graphql_derive::Scalar;
|
pub use async_graphql_derive::Scalar;
|
||||||
#[doc = include_str!("docs/simple_object.md")]
|
#[doc = include_str!("docs/simple_object.md")]
|
||||||
|
|
|
@ -50,4 +50,8 @@ impl<'a> __Field<'a> {
|
||||||
async fn deprecation_reason(&self) -> Option<&str> {
|
async fn deprecation_reason(&self) -> Option<&str> {
|
||||||
self.field.deprecation.reason()
|
self.field.deprecation.reason()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn one_of(&self) -> bool {
|
||||||
|
self.field.oneof
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -228,4 +228,12 @@ impl<'a> __Type<'a> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn one_of(&self) -> Option<bool> {
|
||||||
|
if let TypeDetail::Named(registry::MetaType::InputObject { oneof, .. }) = &self.detail {
|
||||||
|
Some(*oneof)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,6 +69,10 @@ impl Registry {
|
||||||
write!(sdl, "\t{}: {}", field.name, field.ty).ok();
|
write!(sdl, "\t{}: {}", field.name, field.ty).ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if field.oneof {
|
||||||
|
write!(sdl, " @oneof").ok();
|
||||||
|
}
|
||||||
|
|
||||||
if federation {
|
if federation {
|
||||||
if field.external {
|
if field.external {
|
||||||
write!(sdl, " @external").ok();
|
write!(sdl, " @external").ok();
|
||||||
|
@ -202,12 +206,16 @@ impl Registry {
|
||||||
name,
|
name,
|
||||||
input_fields,
|
input_fields,
|
||||||
description,
|
description,
|
||||||
|
oneof,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
if description.is_some() {
|
if description.is_some() {
|
||||||
writeln!(sdl, "\"\"\"\n{}\n\"\"\"", description.unwrap()).ok();
|
writeln!(sdl, "\"\"\"\n{}\n\"\"\"", description.unwrap()).ok();
|
||||||
}
|
}
|
||||||
write!(sdl, "input {} ", name).ok();
|
write!(sdl, "input {} ", name).ok();
|
||||||
|
if *oneof {
|
||||||
|
write!(sdl, "@oneof ").ok();
|
||||||
|
}
|
||||||
writeln!(sdl, "{{").ok();
|
writeln!(sdl, "{{").ok();
|
||||||
for field in input_fields.values() {
|
for field in input_fields.values() {
|
||||||
if let Some(description) = field.description {
|
if let Some(description) = field.description {
|
||||||
|
|
|
@ -157,7 +157,7 @@ impl Deprecation {
|
||||||
pub struct MetaField {
|
pub struct MetaField {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: Option<&'static str>,
|
pub description: Option<&'static str>,
|
||||||
pub args: IndexMap<&'static str, MetaInputValue>,
|
pub args: IndexMap<String, MetaInputValue>,
|
||||||
pub ty: String,
|
pub ty: String,
|
||||||
pub deprecation: Deprecation,
|
pub deprecation: Deprecation,
|
||||||
pub cache_control: CacheControl,
|
pub cache_control: CacheControl,
|
||||||
|
@ -166,6 +166,7 @@ pub struct MetaField {
|
||||||
pub provides: Option<&'static str>,
|
pub provides: Option<&'static str>,
|
||||||
pub visible: Option<MetaVisibleFn>,
|
pub visible: Option<MetaVisibleFn>,
|
||||||
pub compute_complexity: Option<ComplexityType>,
|
pub compute_complexity: Option<ComplexityType>,
|
||||||
|
pub oneof: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -228,6 +229,7 @@ pub enum MetaType {
|
||||||
input_fields: IndexMap<String, MetaInputValue>,
|
input_fields: IndexMap<String, MetaInputValue>,
|
||||||
visible: Option<MetaVisibleFn>,
|
visible: Option<MetaVisibleFn>,
|
||||||
rust_typename: &'static str,
|
rust_typename: &'static str,
|
||||||
|
oneof: bool,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -350,7 +352,7 @@ pub struct MetaDirective {
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
pub description: Option<&'static str>,
|
pub description: Option<&'static str>,
|
||||||
pub locations: Vec<model::__DirectiveLocation>,
|
pub locations: Vec<model::__DirectiveLocation>,
|
||||||
pub args: IndexMap<&'static str, MetaInputValue>,
|
pub args: IndexMap<String, MetaInputValue>,
|
||||||
pub is_repeatable: bool,
|
pub is_repeatable: bool,
|
||||||
pub visible: Option<MetaVisibleFn>,
|
pub visible: Option<MetaVisibleFn>,
|
||||||
}
|
}
|
||||||
|
@ -565,6 +567,7 @@ impl Registry {
|
||||||
provides: None,
|
provides: None,
|
||||||
visible: None,
|
visible: None,
|
||||||
compute_complexity: None,
|
compute_complexity: None,
|
||||||
|
oneof: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -576,7 +579,7 @@ impl Registry {
|
||||||
args: {
|
args: {
|
||||||
let mut args = IndexMap::new();
|
let mut args = IndexMap::new();
|
||||||
args.insert(
|
args.insert(
|
||||||
"representations",
|
"representations".to_string(),
|
||||||
MetaInputValue {
|
MetaInputValue {
|
||||||
name: "representations",
|
name: "representations",
|
||||||
description: None,
|
description: None,
|
||||||
|
@ -596,6 +599,7 @@ impl Registry {
|
||||||
provides: None,
|
provides: None,
|
||||||
visible: None,
|
visible: None,
|
||||||
compute_complexity: None,
|
compute_complexity: None,
|
||||||
|
oneof: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -626,6 +630,7 @@ impl Registry {
|
||||||
provides: None,
|
provides: None,
|
||||||
visible: None,
|
visible: None,
|
||||||
compute_complexity: None,
|
compute_complexity: None,
|
||||||
|
oneof: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
fields
|
fields
|
||||||
|
|
|
@ -318,7 +318,7 @@ where
|
||||||
],
|
],
|
||||||
args: {
|
args: {
|
||||||
let mut args = IndexMap::new();
|
let mut args = IndexMap::new();
|
||||||
args.insert("if", MetaInputValue {
|
args.insert("if".to_string(), MetaInputValue {
|
||||||
name: "if",
|
name: "if",
|
||||||
description: Some("Included when true."),
|
description: Some("Included when true."),
|
||||||
ty: "Boolean!".to_string(),
|
ty: "Boolean!".to_string(),
|
||||||
|
@ -342,7 +342,7 @@ where
|
||||||
],
|
],
|
||||||
args: {
|
args: {
|
||||||
let mut args = IndexMap::new();
|
let mut args = IndexMap::new();
|
||||||
args.insert("if", MetaInputValue {
|
args.insert("if".to_string(), MetaInputValue {
|
||||||
name: "if",
|
name: "if",
|
||||||
description: Some("Skipped when true."),
|
description: Some("Skipped when true."),
|
||||||
ty: "Boolean!".to_string(),
|
ty: "Boolean!".to_string(),
|
||||||
|
|
|
@ -193,6 +193,7 @@ where
|
||||||
provides: None,
|
provides: None,
|
||||||
visible: None,
|
visible: None,
|
||||||
compute_complexity: None,
|
compute_complexity: None,
|
||||||
|
oneof: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -212,6 +213,7 @@ where
|
||||||
provides: None,
|
provides: None,
|
||||||
visible: None,
|
visible: None,
|
||||||
compute_complexity: None,
|
compute_complexity: None,
|
||||||
|
oneof: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -101,6 +101,7 @@ where
|
||||||
provides: None,
|
provides: None,
|
||||||
visible: None,
|
visible: None,
|
||||||
compute_complexity: None,
|
compute_complexity: None,
|
||||||
|
oneof: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -118,6 +119,7 @@ where
|
||||||
provides: None,
|
provides: None,
|
||||||
visible: None,
|
visible: None,
|
||||||
compute_complexity: None,
|
compute_complexity: None,
|
||||||
|
oneof: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
86
src/types/external/list/slice.rs
vendored
86
src/types/external/list/slice.rs
vendored
|
@ -1,8 +1,12 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::parser::types::Field;
|
use crate::parser::types::Field;
|
||||||
use crate::resolver_utils::resolve_list;
|
use crate::resolver_utils::resolve_list;
|
||||||
use crate::{registry, ContextSelectionSet, OutputType, Positioned, ServerResult, Value};
|
use crate::{
|
||||||
|
registry, ContextSelectionSet, InputType, InputValueError, InputValueResult, OutputType,
|
||||||
|
Positioned, ServerResult, Value,
|
||||||
|
};
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<'a, T: OutputType + 'a> OutputType for &'a [T] {
|
impl<'a, T: OutputType + 'a> OutputType for &'a [T] {
|
||||||
|
@ -27,3 +31,83 @@ impl<'a, T: OutputType + 'a> OutputType for &'a [T] {
|
||||||
resolve_list(ctx, field, self.iter(), Some(self.len())).await
|
resolve_list(ctx, field, self.iter(), Some(self.len())).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_output_slice_for_smart_ptr {
|
||||||
|
($ty:ty) => {
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl<T: OutputType> OutputType for $ty {
|
||||||
|
fn type_name() -> Cow<'static, str> {
|
||||||
|
Cow::Owned(format!("[{}]", T::qualified_type_name()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn qualified_type_name() -> String {
|
||||||
|
format!("[{}]!", T::qualified_type_name())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_type_info(registry: &mut registry::Registry) -> String {
|
||||||
|
T::create_type_info(registry);
|
||||||
|
Self::qualified_type_name()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn resolve(
|
||||||
|
&self,
|
||||||
|
ctx: &ContextSelectionSet<'_>,
|
||||||
|
field: &Positioned<Field>,
|
||||||
|
) -> ServerResult<Value> {
|
||||||
|
resolve_list(ctx, field, self.iter(), Some(self.len())).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_output_slice_for_smart_ptr!(Box<[T]>);
|
||||||
|
impl_output_slice_for_smart_ptr!(Arc<[T]>);
|
||||||
|
|
||||||
|
macro_rules! impl_input_slice_for_smart_ptr {
|
||||||
|
($ty:ty) => {
|
||||||
|
impl<T: InputType> InputType for $ty {
|
||||||
|
type RawValueType = Self;
|
||||||
|
|
||||||
|
fn type_name() -> Cow<'static, str> {
|
||||||
|
Cow::Owned(format!("[{}]", T::qualified_type_name()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn qualified_type_name() -> String {
|
||||||
|
format!("[{}]!", T::qualified_type_name())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_type_info(registry: &mut registry::Registry) -> String {
|
||||||
|
T::create_type_info(registry);
|
||||||
|
Self::qualified_type_name()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(value: Option<Value>) -> InputValueResult<Self> {
|
||||||
|
match value.unwrap_or_default() {
|
||||||
|
Value::List(values) => values
|
||||||
|
.into_iter()
|
||||||
|
.map(|value| InputType::parse(Some(value)))
|
||||||
|
.collect::<Result<_, _>>()
|
||||||
|
.map_err(InputValueError::propagate),
|
||||||
|
value => {
|
||||||
|
Ok(
|
||||||
|
vec![InputType::parse(Some(value))
|
||||||
|
.map_err(InputValueError::propagate)?]
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_value(&self) -> Value {
|
||||||
|
Value::List(self.iter().map(InputType::to_value).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_raw_value(&self) -> Option<&Self::RawValueType> {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_input_slice_for_smart_ptr!(Box<[T]>);
|
||||||
|
impl_input_slice_for_smart_ptr!(Arc<[T]>);
|
||||||
|
|
2
src/types/external/mod.rs
vendored
2
src/types/external/mod.rs
vendored
|
@ -29,6 +29,8 @@ mod secrecy;
|
||||||
#[cfg(feature = "smol_str")]
|
#[cfg(feature = "smol_str")]
|
||||||
mod smol_str;
|
mod smol_str;
|
||||||
#[cfg(feature = "time")]
|
#[cfg(feature = "time")]
|
||||||
|
mod time_date;
|
||||||
|
#[cfg(feature = "time")]
|
||||||
mod time_offset_date_time;
|
mod time_offset_date_time;
|
||||||
#[cfg(feature = "time")]
|
#[cfg(feature = "time")]
|
||||||
mod time_primitive_date_time;
|
mod time_primitive_date_time;
|
||||||
|
|
67
src/types/external/time_date.rs
vendored
Normal file
67
src/types/external/time_date.rs
vendored
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
use crate::{InputValueError, InputValueResult, Scalar, ScalarType, Value};
|
||||||
|
use time::{format_description::FormatItem, macros::format_description, Date};
|
||||||
|
|
||||||
|
const DATE_FORMAT: &[FormatItem<'_>] = format_description!("[year]-[month]-[day]");
|
||||||
|
|
||||||
|
/// ISO 8601 calendar date without timezone.
|
||||||
|
/// Format: %Y-%m-%d
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// * `1994-11-13`
|
||||||
|
/// * `2000-02-24`
|
||||||
|
#[Scalar(internal, name = "Date")]
|
||||||
|
impl ScalarType for Date {
|
||||||
|
fn parse(value: Value) -> InputValueResult<Self> {
|
||||||
|
match &value {
|
||||||
|
Value::String(s) => Ok(Self::parse(s, &DATE_FORMAT)?),
|
||||||
|
_ => Err(InputValueError::expected_type(value)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_value(&self) -> Value {
|
||||||
|
Value::String(
|
||||||
|
self.format(&DATE_FORMAT)
|
||||||
|
.unwrap_or_else(|e| panic!("Failed to format `Date`: {}", e)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::{ScalarType, Value};
|
||||||
|
use time::{macros::date, Date};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_date_to_value() {
|
||||||
|
let cases = [
|
||||||
|
(date!(1994 - 11 - 13), "1994-11-13"),
|
||||||
|
(date!(2000 - 01 - 24), "2000-01-24"),
|
||||||
|
];
|
||||||
|
for (value, expected) in cases {
|
||||||
|
let value = value.to_value();
|
||||||
|
|
||||||
|
if let Value::String(s) = value {
|
||||||
|
assert_eq!(s, expected);
|
||||||
|
} else {
|
||||||
|
panic!(
|
||||||
|
"Unexpected Value type when formatting PrimitiveDateTime: {:?}",
|
||||||
|
value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_date_parse() {
|
||||||
|
let cases = [
|
||||||
|
("1994-11-13", date!(1994 - 11 - 13)),
|
||||||
|
("2000-01-24", date!(2000 - 01 - 24)),
|
||||||
|
];
|
||||||
|
for (value, expected) in cases {
|
||||||
|
let value = Value::String(value.to_string());
|
||||||
|
let parsed = <Date as ScalarType>::parse(value).unwrap();
|
||||||
|
assert_eq!(parsed, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -112,6 +112,7 @@ impl<T: ObjectType> OutputType for QueryRoot<T> {
|
||||||
provides: None,
|
provides: None,
|
||||||
visible: None,
|
visible: None,
|
||||||
compute_complexity: None,
|
compute_complexity: None,
|
||||||
|
oneof: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -123,7 +124,7 @@ impl<T: ObjectType> OutputType for QueryRoot<T> {
|
||||||
args: {
|
args: {
|
||||||
let mut args = IndexMap::new();
|
let mut args = IndexMap::new();
|
||||||
args.insert(
|
args.insert(
|
||||||
"name",
|
"name".to_string(),
|
||||||
registry::MetaInputValue {
|
registry::MetaInputValue {
|
||||||
name: "name",
|
name: "name",
|
||||||
description: None,
|
description: None,
|
||||||
|
@ -143,6 +144,7 @@ impl<T: ObjectType> OutputType for QueryRoot<T> {
|
||||||
provides: None,
|
provides: None,
|
||||||
visible: None,
|
visible: None,
|
||||||
compute_complexity: None,
|
compute_complexity: None,
|
||||||
|
oneof: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,6 +73,7 @@ pub fn check_rules(
|
||||||
.with(rules::KnownDirectives::default())
|
.with(rules::KnownDirectives::default())
|
||||||
.with(rules::DirectivesUnique::default())
|
.with(rules::DirectivesUnique::default())
|
||||||
.with(rules::OverlappingFieldsCanBeMerged)
|
.with(rules::OverlappingFieldsCanBeMerged)
|
||||||
|
.with(rules::OneofFieldsHaveExactlyOneArgument)
|
||||||
.with(rules::UploadFile)
|
.with(rules::UploadFile)
|
||||||
.with(visitors::CacheControlCalculate {
|
.with(visitors::CacheControlCalculate {
|
||||||
cache_control: &mut cache_control,
|
cache_control: &mut cache_control,
|
||||||
|
|
|
@ -11,7 +11,7 @@ use crate::{Name, Positioned, QueryPathSegment};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ArgumentsOfCorrectType<'a> {
|
pub struct ArgumentsOfCorrectType<'a> {
|
||||||
current_args: Option<&'a IndexMap<&'static str, MetaInputValue>>,
|
current_args: Option<&'a IndexMap<String, MetaInputValue>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Visitor<'a> for ArgumentsOfCorrectType<'a> {
|
impl<'a> Visitor<'a> for ArgumentsOfCorrectType<'a> {
|
||||||
|
@ -983,4 +983,82 @@ mod tests {
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn oneof() {
|
||||||
|
expect_passes_rule!(
|
||||||
|
factory,
|
||||||
|
r#"
|
||||||
|
{
|
||||||
|
oneofArg(arg: {a: 10})
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect_passes_rule!(
|
||||||
|
factory,
|
||||||
|
r#"
|
||||||
|
{
|
||||||
|
oneofArg(arg: {b: "abc"})
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect_fails_rule!(
|
||||||
|
factory,
|
||||||
|
r#"
|
||||||
|
{
|
||||||
|
oneofArg(arg: {a: 10, b: "abc"})
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn oneof_opt() {
|
||||||
|
expect_passes_rule!(
|
||||||
|
factory,
|
||||||
|
r#"
|
||||||
|
{
|
||||||
|
oneofOpt(arg: {a: 10})
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect_passes_rule!(
|
||||||
|
factory,
|
||||||
|
r#"
|
||||||
|
{
|
||||||
|
oneofOpt(arg: {b: "abc"})
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect_passes_rule!(
|
||||||
|
factory,
|
||||||
|
r#"
|
||||||
|
{
|
||||||
|
oneofOpt
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect_passes_rule!(
|
||||||
|
factory,
|
||||||
|
r#"
|
||||||
|
{
|
||||||
|
oneofOpt(arg: null)
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect_fails_rule!(
|
||||||
|
factory,
|
||||||
|
r#"
|
||||||
|
{
|
||||||
|
oneofOpt(arg: {a: 10, b: "abc"})
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ enum ArgsType<'a> {
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct KnownArgumentNames<'a> {
|
pub struct KnownArgumentNames<'a> {
|
||||||
current_args: Option<(&'a IndexMap<&'static str, MetaInputValue>, ArgsType<'a>)>,
|
current_args: Option<(&'a IndexMap<String, MetaInputValue>, ArgsType<'a>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> KnownArgumentNames<'a> {
|
impl<'a> KnownArgumentNames<'a> {
|
||||||
|
@ -26,7 +26,7 @@ impl<'a> KnownArgumentNames<'a> {
|
||||||
" Did you mean",
|
" Did you mean",
|
||||||
self.current_args
|
self.current_args
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(args, _)| args.iter().map(|arg| *arg.0))
|
.map(|(args, _)| args.iter().map(|arg| arg.0.as_str()))
|
||||||
.flatten(),
|
.flatten(),
|
||||||
name,
|
name,
|
||||||
)
|
)
|
||||||
|
|
|
@ -11,6 +11,7 @@ mod no_fragment_cycles;
|
||||||
mod no_undefined_variables;
|
mod no_undefined_variables;
|
||||||
mod no_unused_fragments;
|
mod no_unused_fragments;
|
||||||
mod no_unused_variables;
|
mod no_unused_variables;
|
||||||
|
mod oneof_fields_have_exactly_one_argument;
|
||||||
mod overlapping_fields_can_be_merged;
|
mod overlapping_fields_can_be_merged;
|
||||||
mod possible_fragment_spreads;
|
mod possible_fragment_spreads;
|
||||||
mod provided_non_null_arguments;
|
mod provided_non_null_arguments;
|
||||||
|
@ -34,6 +35,7 @@ pub use no_fragment_cycles::NoFragmentCycles;
|
||||||
pub use no_undefined_variables::NoUndefinedVariables;
|
pub use no_undefined_variables::NoUndefinedVariables;
|
||||||
pub use no_unused_fragments::NoUnusedFragments;
|
pub use no_unused_fragments::NoUnusedFragments;
|
||||||
pub use no_unused_variables::NoUnusedVariables;
|
pub use no_unused_variables::NoUnusedVariables;
|
||||||
|
pub use oneof_fields_have_exactly_one_argument::OneofFieldsHaveExactlyOneArgument;
|
||||||
pub use overlapping_fields_can_be_merged::OverlappingFieldsCanBeMerged;
|
pub use overlapping_fields_can_be_merged::OverlappingFieldsCanBeMerged;
|
||||||
pub use possible_fragment_spreads::PossibleFragmentSpreads;
|
pub use possible_fragment_spreads::PossibleFragmentSpreads;
|
||||||
pub use provided_non_null_arguments::ProvidedNonNullArguments;
|
pub use provided_non_null_arguments::ProvidedNonNullArguments;
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
use crate::validation::visitor::{RuleError, Visitor};
|
||||||
|
use crate::VisitorContext;
|
||||||
|
use async_graphql_parser::types::Field;
|
||||||
|
use async_graphql_parser::Positioned;
|
||||||
|
|
||||||
|
pub struct OneofFieldsHaveExactlyOneArgument;
|
||||||
|
|
||||||
|
impl<'a> Visitor<'a> for OneofFieldsHaveExactlyOneArgument {
|
||||||
|
fn enter_field(&mut self, ctx: &mut VisitorContext<'a>, field: &'a Positioned<Field>) {
|
||||||
|
if let Some(parent_type) = ctx.parent_type() {
|
||||||
|
if let Some(field_def) = parent_type
|
||||||
|
.fields()
|
||||||
|
.and_then(|fields| fields.get(field.node.name.node.as_str()))
|
||||||
|
{
|
||||||
|
if field_def.oneof && field.node.arguments.len() != 1 {
|
||||||
|
ctx.errors.push(RuleError::new(
|
||||||
|
vec![field.pos],
|
||||||
|
"Oneof fields requires have exactly one argument".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn factory() -> OneofFieldsHaveExactlyOneArgument {
|
||||||
|
OneofFieldsHaveExactlyOneArgument
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn oneof_field() {
|
||||||
|
expect_passes_rule!(
|
||||||
|
factory,
|
||||||
|
r#"
|
||||||
|
query Foo {
|
||||||
|
oneofField(a: 10)
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn oneof_not_exactly_one_argument() {
|
||||||
|
expect_fails_rule!(
|
||||||
|
factory,
|
||||||
|
r#"
|
||||||
|
query Foo {
|
||||||
|
oneofField(a: 10, b: "abc")
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect_fails_rule!(
|
||||||
|
factory,
|
||||||
|
r#"
|
||||||
|
query Foo {
|
||||||
|
oneofField
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use crate::parser::types::{Field, Selection, SelectionSet};
|
use crate::parser::types::{Field, Selection, SelectionSet};
|
||||||
use crate::validation::visitor::{Visitor, VisitorContext};
|
use crate::validation::visitor::{Visitor, VisitorContext};
|
||||||
|
@ -15,6 +15,7 @@ impl<'a> Visitor<'a> for OverlappingFieldsCanBeMerged {
|
||||||
) {
|
) {
|
||||||
let mut find_conflicts = FindConflicts {
|
let mut find_conflicts = FindConflicts {
|
||||||
outputs: Default::default(),
|
outputs: Default::default(),
|
||||||
|
visited: Default::default(),
|
||||||
ctx,
|
ctx,
|
||||||
};
|
};
|
||||||
find_conflicts.find(selection_set);
|
find_conflicts.find(selection_set);
|
||||||
|
@ -23,6 +24,7 @@ impl<'a> Visitor<'a> for OverlappingFieldsCanBeMerged {
|
||||||
|
|
||||||
struct FindConflicts<'a, 'ctx> {
|
struct FindConflicts<'a, 'ctx> {
|
||||||
outputs: HashMap<&'a str, &'a Positioned<Field>>,
|
outputs: HashMap<&'a str, &'a Positioned<Field>>,
|
||||||
|
visited: HashSet<&'a str>,
|
||||||
ctx: &'a mut VisitorContext<'ctx>,
|
ctx: &'a mut VisitorContext<'ctx>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +48,13 @@ impl<'a, 'ctx> FindConflicts<'a, 'ctx> {
|
||||||
if let Some(fragment) =
|
if let Some(fragment) =
|
||||||
self.ctx.fragment(&fragment_spread.node.fragment_name.node)
|
self.ctx.fragment(&fragment_spread.node.fragment_name.node)
|
||||||
{
|
{
|
||||||
|
if !self
|
||||||
|
.visited
|
||||||
|
.insert(fragment_spread.node.fragment_name.node.as_str())
|
||||||
|
{
|
||||||
|
// To avoid recursing itself, this error is detected by the `NoFragmentCycles` validator.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
self.find(&fragment.node.selection_set);
|
self.find(&fragment.node.selection_set);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,18 +18,20 @@ fn levenshtein_distance(s1: &str, s2: &str) -> usize {
|
||||||
column[s1.len()]
|
column[s1.len()]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn make_suggestion<'a, I>(prefix: &str, options: I, input: &str) -> Option<String>
|
pub fn make_suggestion<I, A>(prefix: &str, options: I, input: &str) -> Option<String>
|
||||||
where
|
where
|
||||||
I: Iterator<Item = &'a str>,
|
I: IntoIterator<Item = A>,
|
||||||
|
A: AsRef<str>,
|
||||||
{
|
{
|
||||||
let mut selected = Vec::new();
|
let mut selected = Vec::new();
|
||||||
let mut distances = HashMap::new();
|
let mut distances = HashMap::new();
|
||||||
|
|
||||||
for opt in options {
|
for opt in options {
|
||||||
let distance = levenshtein_distance(input, opt);
|
let opt = opt.as_ref().to_string();
|
||||||
|
let distance = levenshtein_distance(input, &opt);
|
||||||
let threshold = (input.len() / 2).max((opt.len() / 2).max(1));
|
let threshold = (input.len() / 2).max((opt.len() / 2).max(1));
|
||||||
if distance < threshold {
|
if distance < threshold {
|
||||||
selected.push(opt);
|
selected.push(opt.clone());
|
||||||
distances.insert(opt, distance);
|
distances.insert(opt, distance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -288,6 +288,13 @@ impl ComplicatedArgs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(OneofObject)]
|
||||||
|
#[graphql(internal)]
|
||||||
|
enum OneofArg {
|
||||||
|
A(i32),
|
||||||
|
B(String),
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Query;
|
pub struct Query;
|
||||||
|
|
||||||
#[Object(internal)]
|
#[Object(internal)]
|
||||||
|
@ -335,6 +342,19 @@ impl Query {
|
||||||
async fn complicated_args(&self) -> Option<ComplicatedArgs> {
|
async fn complicated_args(&self) -> Option<ComplicatedArgs> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn oneof_arg(&self, arg: OneofArg) -> String {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn oneof_opt(&self, arg: Option<OneofArg>) -> String {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql(oneof)]
|
||||||
|
async fn oneof_field(&self, arg: OneofArg) -> String {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Mutation;
|
pub struct Mutation;
|
||||||
|
|
|
@ -120,9 +120,17 @@ pub fn is_valid_input_value(
|
||||||
registry::MetaType::InputObject {
|
registry::MetaType::InputObject {
|
||||||
input_fields,
|
input_fields,
|
||||||
name: object_name,
|
name: object_name,
|
||||||
|
oneof,
|
||||||
..
|
..
|
||||||
} => match value {
|
} => match value {
|
||||||
ConstValue::Object(values) => {
|
ConstValue::Object(values) => {
|
||||||
|
if *oneof && values.len() != 1 {
|
||||||
|
return Some(valid_error(
|
||||||
|
&path_node,
|
||||||
|
"Oneof input objects requires have exactly one field".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let mut input_names =
|
let mut input_names =
|
||||||
values.keys().map(AsRef::as_ref).collect::<HashSet<_>>();
|
values.keys().map(AsRef::as_ref).collect::<HashSet<_>>();
|
||||||
|
|
||||||
|
|
|
@ -365,6 +365,70 @@ async fn test_flatten() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_flatten_with_context() {
|
||||||
|
#[derive(SimpleObject)]
|
||||||
|
struct A {
|
||||||
|
a: i32,
|
||||||
|
b: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(SimpleObject)]
|
||||||
|
#[graphql(complex)]
|
||||||
|
struct B {
|
||||||
|
#[graphql(skip)]
|
||||||
|
a: A,
|
||||||
|
c: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[ComplexObject]
|
||||||
|
impl B {
|
||||||
|
#[graphql(flatten)]
|
||||||
|
async fn a(&self, _ctx: &Context<'_>) -> &A {
|
||||||
|
&self.a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Query;
|
||||||
|
|
||||||
|
#[Object]
|
||||||
|
impl Query {
|
||||||
|
async fn obj(&self) -> B {
|
||||||
|
B {
|
||||||
|
a: A { a: 100, b: 200 },
|
||||||
|
c: 300,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
|
||||||
|
let query = "{ __type(name: \"B\") { fields { name } } }";
|
||||||
|
assert_eq!(
|
||||||
|
schema.execute(query).await.data,
|
||||||
|
value!({
|
||||||
|
"__type": {
|
||||||
|
"fields": [
|
||||||
|
{"name": "c"},
|
||||||
|
{"name": "a"},
|
||||||
|
{"name": "b"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
let query = "{ obj { a b c } }";
|
||||||
|
assert_eq!(
|
||||||
|
schema.execute(query).await.data,
|
||||||
|
value!({
|
||||||
|
"obj": {
|
||||||
|
"a": 100,
|
||||||
|
"b": 200,
|
||||||
|
"c": 300,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_flatten_with_result() {
|
async fn test_flatten_with_result() {
|
||||||
#[derive(SimpleObject)]
|
#[derive(SimpleObject)]
|
||||||
|
@ -428,3 +492,87 @@ async fn test_flatten_with_result() {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_oneof_field() {
|
||||||
|
#[derive(OneofObject)]
|
||||||
|
enum TestArg {
|
||||||
|
A(i32),
|
||||||
|
B(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(SimpleObject)]
|
||||||
|
#[graphql(complex)]
|
||||||
|
struct Query {
|
||||||
|
a: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[ComplexObject]
|
||||||
|
impl Query {
|
||||||
|
#[graphql(oneof)]
|
||||||
|
async fn test(&self, arg: TestArg) -> String {
|
||||||
|
match arg {
|
||||||
|
TestArg::A(a) => format!("a:{}", a),
|
||||||
|
TestArg::B(b) => format!("b:{}", b),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let schema = Schema::new(Query { a: 10 }, EmptyMutation, EmptySubscription);
|
||||||
|
let query = "{ test(a: 10) }";
|
||||||
|
assert_eq!(
|
||||||
|
schema.execute(query).await.into_result().unwrap().data,
|
||||||
|
value!({
|
||||||
|
"test": "a:10"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
let query = r#"{ test(b: "abc") }"#;
|
||||||
|
assert_eq!(
|
||||||
|
schema.execute(query).await.into_result().unwrap().data,
|
||||||
|
value!({
|
||||||
|
"test": "b:abc"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
let query = r#"{
|
||||||
|
__type(name: "Query") {
|
||||||
|
fields {
|
||||||
|
name
|
||||||
|
args {
|
||||||
|
name
|
||||||
|
type {
|
||||||
|
kind
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
assert_eq!(
|
||||||
|
schema.execute(query).await.into_result().unwrap().data,
|
||||||
|
value!({
|
||||||
|
"__type": {
|
||||||
|
"fields": [{
|
||||||
|
"name": "a",
|
||||||
|
"args": []
|
||||||
|
}, {
|
||||||
|
"name": "test",
|
||||||
|
"args": [{
|
||||||
|
"name": "a",
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "Int"
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"name": "b",
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -479,3 +479,45 @@ pub async fn test_skip_output() {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
pub async fn test_complex_output() {
|
||||||
|
#[derive(SimpleObject, InputObject)]
|
||||||
|
#[graphql(input_name = "MyObjectInput")]
|
||||||
|
#[graphql(complex)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
struct MyObject {
|
||||||
|
a: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[ComplexObject]
|
||||||
|
impl MyObject {
|
||||||
|
async fn double(&self) -> i32 {
|
||||||
|
self.a * 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Query;
|
||||||
|
#[Object]
|
||||||
|
impl Query {
|
||||||
|
async fn obj(&self, input: MyObject) -> MyObject {
|
||||||
|
input
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
|
||||||
|
assert_eq!(
|
||||||
|
schema
|
||||||
|
.execute("{ obj(input: { a: 1 }) { a, double } }")
|
||||||
|
.await
|
||||||
|
.into_result()
|
||||||
|
.unwrap()
|
||||||
|
.data,
|
||||||
|
value!({
|
||||||
|
"obj": {
|
||||||
|
"a": 1,
|
||||||
|
"double": 2,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -55,3 +55,29 @@ pub async fn test_input_box_str() {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
pub async fn test_input_box_slice() {
|
||||||
|
struct Query;
|
||||||
|
|
||||||
|
#[Object]
|
||||||
|
impl Query {
|
||||||
|
async fn box_slice(&self, s: Box<[i32]>) -> Box<[i32]> {
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn arc_slice(&self, s: Arc<[i32]>) -> Arc<[i32]> {
|
||||||
|
s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
|
||||||
|
let query = r#"{ boxSlice(s: [1, 2, 3]) arcSlice(s: [4, 5, 6]) }"#;
|
||||||
|
assert_eq!(
|
||||||
|
schema.execute(query).await.into_result().unwrap().data,
|
||||||
|
value!({
|
||||||
|
"boxSlice": [1, 2, 3],
|
||||||
|
"arcSlice": [4, 5, 6],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -473,3 +473,118 @@ pub async fn test_issue_330() {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
pub async fn test_oneof() {
|
||||||
|
#[derive(OneofObject)]
|
||||||
|
enum TestArg {
|
||||||
|
A(i32),
|
||||||
|
B(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct A;
|
||||||
|
|
||||||
|
#[Object]
|
||||||
|
impl A {
|
||||||
|
async fn test(&self, arg: TestArg) -> String {
|
||||||
|
match arg {
|
||||||
|
TestArg::A(a) => format!("A:a:{}", a),
|
||||||
|
TestArg::B(b) => format!("A:b:{}", b),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct B;
|
||||||
|
|
||||||
|
#[Object]
|
||||||
|
impl B {
|
||||||
|
async fn test(&self, arg: TestArg) -> String {
|
||||||
|
match arg {
|
||||||
|
TestArg::A(a) => format!("B:a:{}", a),
|
||||||
|
TestArg::B(b) => format!("B:b:{}", b),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Interface)]
|
||||||
|
#[graphql(field(
|
||||||
|
name = "test",
|
||||||
|
type = "String",
|
||||||
|
oneof,
|
||||||
|
arg(name = "arg", type = "TestArg")
|
||||||
|
))]
|
||||||
|
enum Obj {
|
||||||
|
A(A),
|
||||||
|
B(B),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Query;
|
||||||
|
|
||||||
|
#[Object]
|
||||||
|
impl Query {
|
||||||
|
async fn a(&self) -> Obj {
|
||||||
|
A.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn b(&self) -> Obj {
|
||||||
|
B.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
|
||||||
|
assert_eq!(
|
||||||
|
schema
|
||||||
|
.execute("{ a { test(a: 10) } }")
|
||||||
|
.await
|
||||||
|
.into_result()
|
||||||
|
.unwrap()
|
||||||
|
.data,
|
||||||
|
value!({
|
||||||
|
"a": {
|
||||||
|
"test": "A:a:10"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
schema
|
||||||
|
.execute("{ a { test(b: \"abc\") } }")
|
||||||
|
.await
|
||||||
|
.into_result()
|
||||||
|
.unwrap()
|
||||||
|
.data,
|
||||||
|
value!({
|
||||||
|
"a": {
|
||||||
|
"test": "A:b:abc"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
schema
|
||||||
|
.execute("{ b { test(a: 10) } }")
|
||||||
|
.await
|
||||||
|
.into_result()
|
||||||
|
.unwrap()
|
||||||
|
.data,
|
||||||
|
value!({
|
||||||
|
"b": {
|
||||||
|
"test": "B:a:10"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
schema
|
||||||
|
.execute("{ b { test(b: \"def\") } }")
|
||||||
|
.await
|
||||||
|
.into_result()
|
||||||
|
.unwrap()
|
||||||
|
.data,
|
||||||
|
value!({
|
||||||
|
"b": {
|
||||||
|
"test": "B:b:def"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
136
tests/object.rs
136
tests/object.rs
|
@ -58,3 +58,139 @@ async fn test_flatten() {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_flatten_with_context() {
|
||||||
|
#[derive(SimpleObject)]
|
||||||
|
struct A {
|
||||||
|
a: i32,
|
||||||
|
b: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct B;
|
||||||
|
|
||||||
|
#[Object]
|
||||||
|
impl B {
|
||||||
|
#[graphql(flatten)]
|
||||||
|
async fn a(&self, _ctx: &Context<'_>) -> A {
|
||||||
|
A { a: 100, b: 200 }
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn c(&self) -> i32 {
|
||||||
|
300
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Query;
|
||||||
|
|
||||||
|
#[Object]
|
||||||
|
impl Query {
|
||||||
|
async fn obj(&self) -> B {
|
||||||
|
B
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
|
||||||
|
let query = "{ __type(name: \"B\") { fields { name } } }";
|
||||||
|
assert_eq!(
|
||||||
|
schema.execute(query).await.data,
|
||||||
|
value!({
|
||||||
|
"__type": {
|
||||||
|
"fields": [
|
||||||
|
{"name": "a"},
|
||||||
|
{"name": "b"},
|
||||||
|
{"name": "c"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
let query = "{ obj { a b c } }";
|
||||||
|
assert_eq!(
|
||||||
|
schema.execute(query).await.data,
|
||||||
|
value!({
|
||||||
|
"obj": {
|
||||||
|
"a": 100,
|
||||||
|
"b": 200,
|
||||||
|
"c": 300,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_oneof_field() {
|
||||||
|
#[derive(OneofObject)]
|
||||||
|
enum TestArg {
|
||||||
|
A(i32),
|
||||||
|
B(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Query;
|
||||||
|
|
||||||
|
#[Object]
|
||||||
|
impl Query {
|
||||||
|
#[graphql(oneof)]
|
||||||
|
async fn test(&self, arg: TestArg) -> String {
|
||||||
|
match arg {
|
||||||
|
TestArg::A(a) => format!("a:{}", a),
|
||||||
|
TestArg::B(b) => format!("b:{}", b),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
|
||||||
|
let query = "{ test(a: 10) }";
|
||||||
|
assert_eq!(
|
||||||
|
schema.execute(query).await.into_result().unwrap().data,
|
||||||
|
value!({
|
||||||
|
"test": "a:10"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
let query = r#"{ test(b: "abc") }"#;
|
||||||
|
assert_eq!(
|
||||||
|
schema.execute(query).await.into_result().unwrap().data,
|
||||||
|
value!({
|
||||||
|
"test": "b:abc"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
let query = r#"{
|
||||||
|
__type(name: "Query") {
|
||||||
|
fields {
|
||||||
|
name
|
||||||
|
args {
|
||||||
|
name
|
||||||
|
type {
|
||||||
|
kind
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
assert_eq!(
|
||||||
|
schema.execute(query).await.into_result().unwrap().data,
|
||||||
|
value!({
|
||||||
|
"__type": {
|
||||||
|
"fields": [{
|
||||||
|
"name": "test",
|
||||||
|
"args": [{
|
||||||
|
"name": "a",
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "Int"
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"name": "b",
|
||||||
|
"type": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
263
tests/oneof_object.rs
Normal file
263
tests/oneof_object.rs
Normal file
|
@ -0,0 +1,263 @@
|
||||||
|
use async_graphql::registry::{MetaType, Registry};
|
||||||
|
use async_graphql::*;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_oneof_object() {
|
||||||
|
#[derive(Debug, InputObject, PartialEq)]
|
||||||
|
struct MyInput {
|
||||||
|
a: i32,
|
||||||
|
b: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, OneofObject, PartialEq)]
|
||||||
|
enum MyOneofObj {
|
||||||
|
A(i32),
|
||||||
|
B(MyInput),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
MyOneofObj::parse(Some(value!({
|
||||||
|
"a": 100,
|
||||||
|
})))
|
||||||
|
.unwrap(),
|
||||||
|
MyOneofObj::A(100)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
MyOneofObj::A(100).to_value(),
|
||||||
|
value!({
|
||||||
|
"a": 100,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
MyOneofObj::parse(Some(value!({
|
||||||
|
"b": {
|
||||||
|
"a": 200,
|
||||||
|
"b": "abc",
|
||||||
|
},
|
||||||
|
})))
|
||||||
|
.unwrap(),
|
||||||
|
MyOneofObj::B(MyInput {
|
||||||
|
a: 200,
|
||||||
|
b: "abc".to_string()
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
MyOneofObj::B(MyInput {
|
||||||
|
a: 200,
|
||||||
|
b: "abc".to_string()
|
||||||
|
})
|
||||||
|
.to_value(),
|
||||||
|
value!({
|
||||||
|
"b": {
|
||||||
|
"a": 200,
|
||||||
|
"b": "abc",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
struct Query;
|
||||||
|
|
||||||
|
#[Object]
|
||||||
|
impl Query {
|
||||||
|
async fn test(&self, obj: MyOneofObj) -> String {
|
||||||
|
match obj {
|
||||||
|
MyOneofObj::A(value) => format!("a:{}", value),
|
||||||
|
MyOneofObj::B(MyInput { a, b }) => format!("b:{}/{}", a, b),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
schema
|
||||||
|
.execute("{ test(obj: {a: 100}) }")
|
||||||
|
.await
|
||||||
|
.into_result()
|
||||||
|
.unwrap()
|
||||||
|
.data,
|
||||||
|
value!({
|
||||||
|
"test": "a:100"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
schema
|
||||||
|
.execute(r#"{ test(obj: {b: {a: 200, b: "abc"}}) }"#)
|
||||||
|
.await
|
||||||
|
.into_result()
|
||||||
|
.unwrap()
|
||||||
|
.data,
|
||||||
|
value!({
|
||||||
|
"test": "b:200/abc"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
schema
|
||||||
|
.execute(r#"{ __type(name: "MyOneofObj") { name oneOf } }"#)
|
||||||
|
.await
|
||||||
|
.into_result()
|
||||||
|
.unwrap()
|
||||||
|
.data,
|
||||||
|
value!({
|
||||||
|
"__type": { "name": "MyOneofObj", "oneOf": true }
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_oneof_object_concrete() {
|
||||||
|
#[derive(Debug, OneofObject, PartialEq)]
|
||||||
|
#[graphql(
|
||||||
|
concrete(name = "MyObjI32", params(i32)),
|
||||||
|
concrete(name = "MyObjString", params(String))
|
||||||
|
)]
|
||||||
|
enum MyObj<T: InputType> {
|
||||||
|
A(i32),
|
||||||
|
B(T),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(MyObj::<i32>::type_name(), "MyObjI32");
|
||||||
|
assert_eq!(MyObj::<String>::type_name(), "MyObjString");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
MyObj::<String>::parse(Some(value!({
|
||||||
|
"a": 100,
|
||||||
|
})))
|
||||||
|
.unwrap(),
|
||||||
|
MyObj::A(100)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
MyObj::<i32>::A(100).to_value(),
|
||||||
|
value!({
|
||||||
|
"a": 100,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
MyObj::<String>::B("abc".to_string()).to_value(),
|
||||||
|
value!({
|
||||||
|
"b": "abc",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
struct Query;
|
||||||
|
|
||||||
|
#[Object]
|
||||||
|
impl Query {
|
||||||
|
async fn test(&self, obj: MyObj<String>) -> String {
|
||||||
|
match obj {
|
||||||
|
MyObj::A(value) => format!("a:{}", value),
|
||||||
|
MyObj::B(value) => format!("b:{}", value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
schema
|
||||||
|
.execute("{ test(obj: {a: 100}) }")
|
||||||
|
.await
|
||||||
|
.into_result()
|
||||||
|
.unwrap()
|
||||||
|
.data,
|
||||||
|
value!({
|
||||||
|
"test": "a:100"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
schema
|
||||||
|
.execute(r#"{ test(obj: {b: "abc"}) }"#)
|
||||||
|
.await
|
||||||
|
.into_result()
|
||||||
|
.unwrap()
|
||||||
|
.data,
|
||||||
|
value!({
|
||||||
|
"test": "b:abc"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_oneof_object_rename_fields() {
|
||||||
|
#[derive(OneofObject)]
|
||||||
|
#[graphql(rename_fields = "lowercase")]
|
||||||
|
enum MyInput {
|
||||||
|
Name(i32),
|
||||||
|
CreateAt(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut registry = Registry::default();
|
||||||
|
MyInput::create_type_info(&mut registry);
|
||||||
|
|
||||||
|
let ty: &MetaType = registry.types.get("MyInput").unwrap();
|
||||||
|
match ty {
|
||||||
|
MetaType::InputObject { input_fields, .. } => {
|
||||||
|
assert_eq!(
|
||||||
|
input_fields.keys().collect::<Vec<_>>(),
|
||||||
|
vec!["name", "createat"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_oneof_object_rename_field() {
|
||||||
|
#[derive(OneofObject)]
|
||||||
|
enum MyInput {
|
||||||
|
Name(i32),
|
||||||
|
#[graphql(name = "create_At")]
|
||||||
|
CreateAt(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut registry = Registry::default();
|
||||||
|
MyInput::create_type_info(&mut registry);
|
||||||
|
|
||||||
|
let ty: &MetaType = registry.types.get("MyInput").unwrap();
|
||||||
|
match ty {
|
||||||
|
MetaType::InputObject { input_fields, .. } => {
|
||||||
|
assert_eq!(
|
||||||
|
input_fields.keys().collect::<Vec<_>>(),
|
||||||
|
vec!["name", "create_At"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_oneof_object_validation() {
|
||||||
|
#[derive(Debug, OneofObject, PartialEq)]
|
||||||
|
enum MyOneofObj {
|
||||||
|
#[graphql(validator(maximum = 10))]
|
||||||
|
A(i32),
|
||||||
|
#[graphql(validator(max_length = 3))]
|
||||||
|
B(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
MyOneofObj::parse(Some(value!({
|
||||||
|
"a": 5,
|
||||||
|
})))
|
||||||
|
.unwrap(),
|
||||||
|
MyOneofObj::A(5)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
MyOneofObj::parse(Some(value!({
|
||||||
|
"a": 20,
|
||||||
|
})))
|
||||||
|
.unwrap_err()
|
||||||
|
.into_server_error(Default::default())
|
||||||
|
.message,
|
||||||
|
r#"Failed to parse "Int": the value is 20, must be less than or equal to 10 (occurred while parsing "MyOneofObj")"#
|
||||||
|
);
|
||||||
|
}
|
|
@ -54,3 +54,51 @@ async fn test_flatten() {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn recursive_fragment_definition() {
|
||||||
|
#[derive(SimpleObject)]
|
||||||
|
struct Hello {
|
||||||
|
world: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Query;
|
||||||
|
|
||||||
|
// this setup is actually completely irrelevant we just need to be able ot execute a query
|
||||||
|
#[Object]
|
||||||
|
impl Query {
|
||||||
|
async fn obj(&self) -> Hello {
|
||||||
|
Hello {
|
||||||
|
world: "Hello World".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
|
||||||
|
let query = "fragment f on Query {...f} { __typename }";
|
||||||
|
assert!(schema.execute(query).await.into_result().is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn recursive_fragment_definition_nested() {
|
||||||
|
#[derive(SimpleObject)]
|
||||||
|
struct Hello {
|
||||||
|
world: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Query;
|
||||||
|
|
||||||
|
// this setup is actually completely irrelevant we just need to be able ot execute a query
|
||||||
|
#[Object]
|
||||||
|
impl Query {
|
||||||
|
async fn obj(&self) -> Hello {
|
||||||
|
Hello {
|
||||||
|
world: "Hello World".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
|
||||||
|
let query = "fragment f on Query { a { ...f a { ...f } } } { __typename }";
|
||||||
|
assert!(schema.execute(query).await.into_result().is_err());
|
||||||
|
}
|
||||||
|
|
|
@ -425,3 +425,46 @@ pub async fn test_subscription_fieldresult() {
|
||||||
|
|
||||||
assert!(stream.next().await.is_none());
|
assert!(stream.next().await.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
pub async fn test_oneof_field() {
|
||||||
|
#[derive(OneofObject)]
|
||||||
|
enum TestArg {
|
||||||
|
A(i32),
|
||||||
|
B(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Subscription;
|
||||||
|
|
||||||
|
#[Subscription]
|
||||||
|
impl Subscription {
|
||||||
|
#[graphql(oneof)]
|
||||||
|
async fn test(&self, arg: TestArg) -> impl Stream<Item = String> {
|
||||||
|
let value = match arg {
|
||||||
|
TestArg::A(a) => format!("a:{}", a),
|
||||||
|
TestArg::B(b) => format!("b:{}", b),
|
||||||
|
};
|
||||||
|
futures_util::stream::once(async move { value })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let schema = Schema::new(Query, EmptyMutation, Subscription);
|
||||||
|
|
||||||
|
let mut stream = schema.execute_stream(r#"subscription { test(a: 10) }"#);
|
||||||
|
assert_eq!(
|
||||||
|
Response::new(value!({
|
||||||
|
"test": "a:10"
|
||||||
|
})),
|
||||||
|
stream.next().await.unwrap()
|
||||||
|
);
|
||||||
|
assert!(stream.next().await.is_none());
|
||||||
|
|
||||||
|
let mut stream = schema.execute_stream(r#"subscription { test(b: "abc") }"#);
|
||||||
|
assert_eq!(
|
||||||
|
Response::new(value!({
|
||||||
|
"test": "b:abc"
|
||||||
|
})),
|
||||||
|
stream.next().await.unwrap()
|
||||||
|
);
|
||||||
|
assert!(stream.next().await.is_none());
|
||||||
|
}
|
||||||
|
|
|
@ -789,3 +789,103 @@ pub async fn test_list_both_max_items_and_max_length() {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
pub async fn test_custom_validator_on_oneof_field() {
|
||||||
|
struct MyValidator;
|
||||||
|
|
||||||
|
impl CustomValidator<TestArg> for MyValidator {
|
||||||
|
fn check(&self, value: &TestArg) -> Result<(), String> {
|
||||||
|
match value {
|
||||||
|
TestArg::A(a) => {
|
||||||
|
if *a < 100 {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err("invalid a".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TestArg::B(b) => {
|
||||||
|
if b.len() < 5 {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err("invalid b".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(OneofObject)]
|
||||||
|
enum TestArg {
|
||||||
|
A(i32),
|
||||||
|
B(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Query;
|
||||||
|
|
||||||
|
#[Object]
|
||||||
|
impl Query {
|
||||||
|
#[graphql(oneof)]
|
||||||
|
async fn test(&self, #[graphql(validator(custom = "MyValidator"))] arg: TestArg) -> String {
|
||||||
|
match arg {
|
||||||
|
TestArg::A(a) => format!("a:{}", a),
|
||||||
|
TestArg::B(b) => format!("b:{}", b),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
|
||||||
|
assert_eq!(
|
||||||
|
schema
|
||||||
|
.execute("{ test(a: 10) }")
|
||||||
|
.await
|
||||||
|
.into_result()
|
||||||
|
.unwrap()
|
||||||
|
.data,
|
||||||
|
value!({
|
||||||
|
"test": "a:10"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
schema
|
||||||
|
.execute("{ test(a: 200) }")
|
||||||
|
.await
|
||||||
|
.into_result()
|
||||||
|
.unwrap_err(),
|
||||||
|
vec![ServerError {
|
||||||
|
message: r#"Failed to parse "TestArg": invalid a"#.to_string(),
|
||||||
|
source: None,
|
||||||
|
locations: vec![Pos { column: 3, line: 1 }],
|
||||||
|
path: vec![PathSegment::Field("test".to_string())],
|
||||||
|
extensions: None
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
schema
|
||||||
|
.execute(r#"{ test(b: "abcd") }"#)
|
||||||
|
.await
|
||||||
|
.into_result()
|
||||||
|
.unwrap()
|
||||||
|
.data,
|
||||||
|
value!({
|
||||||
|
"test": "b:abcd"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
schema
|
||||||
|
.execute(r#"{ test(b: "abcde") }"#)
|
||||||
|
.await
|
||||||
|
.into_result()
|
||||||
|
.unwrap_err(),
|
||||||
|
vec![ServerError {
|
||||||
|
message: r#"Failed to parse "TestArg": invalid b"#.to_string(),
|
||||||
|
source: None,
|
||||||
|
locations: vec![Pos { column: 3, line: 1 }],
|
||||||
|
path: vec![PathSegment::Field("test".to_string())],
|
||||||
|
extensions: None
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "async-graphql-value"
|
name = "async-graphql-value"
|
||||||
version = "3.0.26"
|
version = "3.0.33"
|
||||||
authors = ["sunli <scott_s829@163.com>", "Koxiaet"]
|
authors = ["sunli <scott_s829@163.com>", "Koxiaet"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "GraphQL value for async-graphql"
|
description = "GraphQL value for async-graphql"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user