implemented interface
This commit is contained in:
parent
e50694cc36
commit
69b3dea88c
|
@ -1,6 +1,6 @@
|
||||||
use crate::utils::parse_value;
|
use crate::utils::parse_value;
|
||||||
use graphql_parser::query::Value;
|
use graphql_parser::query::Value;
|
||||||
use syn::{Attribute, AttributeArgs, Error, Meta, NestedMeta, Result};
|
use syn::{Attribute, AttributeArgs, Error, Meta, MetaList, NestedMeta, Result, Type};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Object {
|
pub struct Object {
|
||||||
|
@ -110,7 +110,7 @@ impl Argument {
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::new_spanned(
|
return Err(Error::new_spanned(
|
||||||
&nv.lit,
|
&nv.lit,
|
||||||
"Attribute 'deprecation' should be a string.",
|
"Attribute 'default' should be a string.",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -451,3 +451,257 @@ impl InputObject {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct InterfaceFieldArgument {
|
||||||
|
pub name: String,
|
||||||
|
pub desc: Option<String>,
|
||||||
|
pub ty: Type,
|
||||||
|
pub default: Option<Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InterfaceFieldArgument {
|
||||||
|
pub fn parse(ls: &MetaList) -> Result<Self> {
|
||||||
|
let mut name = None;
|
||||||
|
let mut desc = None;
|
||||||
|
let mut ty = None;
|
||||||
|
let mut default = None;
|
||||||
|
|
||||||
|
for meta in &ls.nested {
|
||||||
|
match meta {
|
||||||
|
NestedMeta::Meta(Meta::NameValue(nv)) => {
|
||||||
|
if nv.path.is_ident("name") {
|
||||||
|
if let syn::Lit::Str(lit) = &nv.lit {
|
||||||
|
name = Some(lit.value());
|
||||||
|
} else {
|
||||||
|
return Err(Error::new_spanned(
|
||||||
|
&nv.lit,
|
||||||
|
"Attribute 'name' should be a string.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else if nv.path.is_ident("desc") {
|
||||||
|
if let syn::Lit::Str(lit) = &nv.lit {
|
||||||
|
desc = Some(lit.value());
|
||||||
|
} else {
|
||||||
|
return Err(Error::new_spanned(
|
||||||
|
&nv.lit,
|
||||||
|
"Attribute 'desc' should be a string.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else if nv.path.is_ident("type") {
|
||||||
|
if let syn::Lit::Str(lit) = &nv.lit {
|
||||||
|
if let Ok(ty2) = syn::parse_str::<syn::Type>(&lit.value()) {
|
||||||
|
ty = Some(ty2);
|
||||||
|
} else {
|
||||||
|
return Err(Error::new_spanned(&lit, "Expect type"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(Error::new_spanned(
|
||||||
|
&nv.lit,
|
||||||
|
"Attribute 'type' should be a string.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else if nv.path.is_ident("default") {
|
||||||
|
if let syn::Lit::Str(lit) = &nv.lit {
|
||||||
|
match parse_value(&lit.value()) {
|
||||||
|
Ok(Value::Variable(_)) => {
|
||||||
|
return Err(Error::new_spanned(
|
||||||
|
&nv.lit,
|
||||||
|
"The default cannot be a variable",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Ok(value) => default = Some(value),
|
||||||
|
Err(err) => {
|
||||||
|
return Err(Error::new_spanned(
|
||||||
|
&nv.lit,
|
||||||
|
format!("Invalid value: {}", err),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(Error::new_spanned(
|
||||||
|
&nv.lit,
|
||||||
|
"Attribute 'default' should be a string.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if name.is_none() {
|
||||||
|
return Err(Error::new_spanned(ls, "Missing name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ty.is_none() {
|
||||||
|
return Err(Error::new_spanned(ls, "Missing type"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
name: name.unwrap(),
|
||||||
|
desc,
|
||||||
|
ty: ty.unwrap(),
|
||||||
|
default,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct InterfaceField {
|
||||||
|
pub name: String,
|
||||||
|
pub method: Option<String>,
|
||||||
|
pub desc: Option<String>,
|
||||||
|
pub ty: Type,
|
||||||
|
pub args: Vec<InterfaceFieldArgument>,
|
||||||
|
pub deprecation: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InterfaceField {
|
||||||
|
pub fn parse(ls: &MetaList) -> Result<Self> {
|
||||||
|
let mut name = None;
|
||||||
|
let mut method = None;
|
||||||
|
let mut desc = None;
|
||||||
|
let mut ty = None;
|
||||||
|
let mut args = Vec::new();
|
||||||
|
let mut deprecation = None;
|
||||||
|
|
||||||
|
for meta in &ls.nested {
|
||||||
|
match meta {
|
||||||
|
NestedMeta::Meta(Meta::NameValue(nv)) => {
|
||||||
|
if nv.path.is_ident("name") {
|
||||||
|
if let syn::Lit::Str(lit) = &nv.lit {
|
||||||
|
name = Some(lit.value());
|
||||||
|
} else {
|
||||||
|
return Err(Error::new_spanned(
|
||||||
|
&nv.lit,
|
||||||
|
"Attribute 'name' should be a string.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else if nv.path.is_ident("desc") {
|
||||||
|
if let syn::Lit::Str(lit) = &nv.lit {
|
||||||
|
desc = Some(lit.value());
|
||||||
|
} else {
|
||||||
|
return Err(Error::new_spanned(
|
||||||
|
&nv.lit,
|
||||||
|
"Attribute 'desc' should be a string.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else if nv.path.is_ident("method") {
|
||||||
|
if let syn::Lit::Str(lit) = &nv.lit {
|
||||||
|
method = Some(lit.value());
|
||||||
|
} else {
|
||||||
|
return Err(Error::new_spanned(
|
||||||
|
&nv.lit,
|
||||||
|
"Attribute 'method' should be a string.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else if nv.path.is_ident("type") {
|
||||||
|
if let syn::Lit::Str(lit) = &nv.lit {
|
||||||
|
if let Ok(ty2) = syn::parse_str::<syn::Type>(&lit.value()) {
|
||||||
|
ty = Some(ty2);
|
||||||
|
} else {
|
||||||
|
return Err(Error::new_spanned(&lit, "Expect type"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(Error::new_spanned(
|
||||||
|
&nv.lit,
|
||||||
|
"Attribute 'type' should be a string.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else if nv.path.is_ident("deprecation") {
|
||||||
|
if let syn::Lit::Str(lit) = &nv.lit {
|
||||||
|
deprecation = Some(lit.value());
|
||||||
|
} else {
|
||||||
|
return Err(Error::new_spanned(
|
||||||
|
&nv.lit,
|
||||||
|
"Attribute 'deprecation' should be a string.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NestedMeta::Meta(Meta::List(ls)) if ls.path.is_ident("arg") => {
|
||||||
|
args.push(InterfaceFieldArgument::parse(ls)?);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if name.is_none() {
|
||||||
|
return Err(Error::new_spanned(ls, "Missing name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ty.is_none() {
|
||||||
|
return Err(Error::new_spanned(ls, "Missing type"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
name: name.unwrap(),
|
||||||
|
method,
|
||||||
|
desc,
|
||||||
|
ty: ty.unwrap(),
|
||||||
|
args,
|
||||||
|
deprecation,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Interface {
|
||||||
|
pub internal: bool,
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub desc: Option<String>,
|
||||||
|
pub fields: Vec<InterfaceField>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Interface {
|
||||||
|
pub fn parse(args: AttributeArgs) -> Result<Self> {
|
||||||
|
let mut internal = false;
|
||||||
|
let mut name = None;
|
||||||
|
let mut desc = None;
|
||||||
|
let mut fields = Vec::new();
|
||||||
|
|
||||||
|
for arg in args {
|
||||||
|
match arg {
|
||||||
|
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("internal") => {
|
||||||
|
internal = true;
|
||||||
|
}
|
||||||
|
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("internal") => {
|
||||||
|
internal = true;
|
||||||
|
}
|
||||||
|
NestedMeta::Meta(Meta::NameValue(nv)) => {
|
||||||
|
if nv.path.is_ident("name") {
|
||||||
|
if let syn::Lit::Str(lit) = nv.lit {
|
||||||
|
name = Some(lit.value());
|
||||||
|
} else {
|
||||||
|
return Err(Error::new_spanned(
|
||||||
|
&nv.lit,
|
||||||
|
"Attribute 'name' should be a string.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else if nv.path.is_ident("desc") {
|
||||||
|
if let syn::Lit::Str(lit) = nv.lit {
|
||||||
|
desc = Some(lit.value());
|
||||||
|
} else {
|
||||||
|
return Err(Error::new_spanned(
|
||||||
|
&nv.lit,
|
||||||
|
"Attribute 'desc' should be a string.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NestedMeta::Meta(Meta::List(ls)) if ls.path.is_ident("field") => {
|
||||||
|
fields.push(InterfaceField::parse(&ls)?);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
internal,
|
||||||
|
name,
|
||||||
|
desc,
|
||||||
|
fields,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -105,8 +105,8 @@ pub fn generate(enum_args: &args::Enum, input: &DeriveInput) -> Result<TokenStre
|
||||||
|
|
||||||
#[#crate_name::async_trait::async_trait]
|
#[#crate_name::async_trait::async_trait]
|
||||||
impl #crate_name::GQLOutputValue for #ident {
|
impl #crate_name::GQLOutputValue for #ident {
|
||||||
async fn resolve(&self, _: &#crate_name::ContextSelectionSet<'_>) -> #crate_name::Result<serde_json::Value> {
|
async fn resolve(value: &Self, _: &#crate_name::ContextSelectionSet<'_>) -> #crate_name::Result<serde_json::Value> {
|
||||||
#crate_name::GQLEnum::resolve_enum(self)
|
#crate_name::GQLEnum::resolve_enum(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
234
async-graphql-derive/src/interface.rs
Normal file
234
async-graphql-derive/src/interface.rs
Normal file
|
@ -0,0 +1,234 @@
|
||||||
|
use crate::args;
|
||||||
|
use crate::args::{InterfaceField, InterfaceFieldArgument};
|
||||||
|
use crate::output_type::OutputType;
|
||||||
|
use crate::utils::{build_value_repr, get_crate_name};
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use proc_macro2::{Ident, Span};
|
||||||
|
use quote::quote;
|
||||||
|
use syn::{Data, DeriveInput, Error, Fields, Result, Type};
|
||||||
|
|
||||||
|
// todo: Context params
|
||||||
|
|
||||||
|
pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result<TokenStream> {
|
||||||
|
let crate_name = get_crate_name(interface_args.internal);
|
||||||
|
let ident = &input.ident;
|
||||||
|
let generics = &input.generics;
|
||||||
|
let attrs = &input.attrs;
|
||||||
|
let vis = &input.vis;
|
||||||
|
let s = match &input.data {
|
||||||
|
Data::Struct(s) => s,
|
||||||
|
_ => return Err(Error::new_spanned(input, "It should be a struct.")),
|
||||||
|
};
|
||||||
|
let fields = match &s.fields {
|
||||||
|
Fields::Unnamed(fields) => fields,
|
||||||
|
_ => return Err(Error::new_spanned(input, "All fields must be unnamed.")),
|
||||||
|
};
|
||||||
|
let mut enum_names = Vec::new();
|
||||||
|
let mut enum_items = Vec::new();
|
||||||
|
let mut type_into_impls = Vec::new();
|
||||||
|
let gql_typename = interface_args
|
||||||
|
.name
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| ident.to_string());
|
||||||
|
let desc = interface_args
|
||||||
|
.desc
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| quote! {Some(#s)})
|
||||||
|
.unwrap_or_else(|| quote! {None});
|
||||||
|
let mut registry_types = Vec::new();
|
||||||
|
let mut possible_types = Vec::new();
|
||||||
|
|
||||||
|
for field in &fields.unnamed {
|
||||||
|
if let Type::Path(p) = &field.ty {
|
||||||
|
let enum_name = &p.path.segments.last().unwrap().ident;
|
||||||
|
enum_names.push(enum_name);
|
||||||
|
enum_items.push(quote! { #enum_name(#p) });
|
||||||
|
type_into_impls.push(quote! {
|
||||||
|
impl #generics From<#p> for #ident #generics {
|
||||||
|
fn from(obj: #p) -> Self {
|
||||||
|
#ident::#enum_name(obj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
registry_types.push(quote! {
|
||||||
|
<#p as async_graphql::GQLType>::create_type_info(registry);
|
||||||
|
registry.add_implements(&<#p as GQLType>::type_name(), #gql_typename);
|
||||||
|
});
|
||||||
|
possible_types.push(quote! {
|
||||||
|
<#p as async_graphql::GQLType>::type_name().to_string()
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return Err(Error::new_spanned(field, "Invalid type"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut methods = Vec::new();
|
||||||
|
let mut schema_fields = Vec::new();
|
||||||
|
let mut resolvers = Vec::new();
|
||||||
|
|
||||||
|
for InterfaceField {
|
||||||
|
name,
|
||||||
|
method: method_name,
|
||||||
|
desc,
|
||||||
|
ty,
|
||||||
|
args,
|
||||||
|
deprecation,
|
||||||
|
} in &interface_args.fields
|
||||||
|
{
|
||||||
|
let method_name = Ident::new(
|
||||||
|
method_name.as_ref().unwrap_or_else(|| &name),
|
||||||
|
Span::call_site(),
|
||||||
|
);
|
||||||
|
let mut calls = Vec::new();
|
||||||
|
let mut use_params = Vec::new();
|
||||||
|
let mut decl_params = Vec::new();
|
||||||
|
let mut get_params = Vec::new();
|
||||||
|
let mut schema_args = Vec::new();
|
||||||
|
|
||||||
|
for InterfaceFieldArgument {
|
||||||
|
name,
|
||||||
|
desc,
|
||||||
|
ty,
|
||||||
|
default,
|
||||||
|
} in args
|
||||||
|
{
|
||||||
|
let ident = Ident::new(name, Span::call_site());
|
||||||
|
decl_params.push(quote! { #ident: #ty });
|
||||||
|
use_params.push(ident.clone());
|
||||||
|
|
||||||
|
let param_default = match &default {
|
||||||
|
Some(default) => {
|
||||||
|
let repr = build_value_repr(&crate_name, &default);
|
||||||
|
quote! {|| #repr }
|
||||||
|
}
|
||||||
|
None => quote! { || #crate_name::Value::Null },
|
||||||
|
};
|
||||||
|
get_params.push(quote! {
|
||||||
|
let #ident: #ty = ctx_field.param_value(#name, #param_default)?;
|
||||||
|
});
|
||||||
|
|
||||||
|
let desc = desc
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| quote! {Some(#s)})
|
||||||
|
.unwrap_or_else(|| quote! {None});
|
||||||
|
let schema_default = default
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| {
|
||||||
|
let s = v.to_string();
|
||||||
|
quote! {Some(#s)}
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| quote! {None});
|
||||||
|
schema_args.push(quote! {
|
||||||
|
#crate_name::registry::InputValue {
|
||||||
|
name: #name,
|
||||||
|
description: #desc,
|
||||||
|
ty: <#ty as #crate_name::GQLType>::create_type_info(registry),
|
||||||
|
default_value: #schema_default,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for enum_name in &enum_names {
|
||||||
|
calls.push(quote! {
|
||||||
|
#ident::#enum_name(obj) => obj.#method_name(#(#use_params),*).await
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
methods.push(quote! {
|
||||||
|
async fn #method_name(&self, #(#decl_params),*) -> #ty {
|
||||||
|
match self {
|
||||||
|
#(#calls,)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let desc = desc
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| quote! {Some(#s)})
|
||||||
|
.unwrap_or_else(|| quote! {None});
|
||||||
|
let deprecation = deprecation
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| quote! {Some(#s)})
|
||||||
|
.unwrap_or_else(|| quote! {None});
|
||||||
|
|
||||||
|
let ty = OutputType::parse(ty)?;
|
||||||
|
let value_ty = ty.value_type();
|
||||||
|
|
||||||
|
schema_fields.push(quote! {
|
||||||
|
#crate_name::registry::Field {
|
||||||
|
name: #name,
|
||||||
|
description: #desc,
|
||||||
|
args: vec![#(#schema_args),*],
|
||||||
|
ty: <#value_ty as #crate_name::GQLType>::create_type_info(registry),
|
||||||
|
deprecation: #deprecation,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let resolve_obj = match &ty {
|
||||||
|
OutputType::Value(_) => quote! {
|
||||||
|
self.#method_name(#(#use_params),*).await
|
||||||
|
},
|
||||||
|
OutputType::Result(_, _) => {
|
||||||
|
quote! {
|
||||||
|
self.#method_name(#(#use_params),*).await.
|
||||||
|
map_err(|err| err.with_position(field.position))?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
resolvers.push(quote! {
|
||||||
|
if field.name.as_str() == #name {
|
||||||
|
#(#get_params)*
|
||||||
|
let ctx_obj = ctx.with_item(&field.selection_set);
|
||||||
|
return #crate_name::GQLOutputValue::resolve(&#resolve_obj, &ctx_obj).await.
|
||||||
|
map_err(|err| err.with_position(field.position).into());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let expanded = quote! {
|
||||||
|
#(#attrs)*
|
||||||
|
#vis enum #ident #generics { #(#enum_items),* }
|
||||||
|
|
||||||
|
#(#type_into_impls)*
|
||||||
|
|
||||||
|
impl #generics #ident #generics {
|
||||||
|
#(#methods)*
|
||||||
|
}
|
||||||
|
|
||||||
|
impl #generics #crate_name::GQLType for #ident #generics {
|
||||||
|
fn type_name() -> Cow<'static, str> {
|
||||||
|
Cow::Borrowed(#gql_typename)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_type_info(registry: &mut #crate_name::registry::Registry) -> String {
|
||||||
|
registry.create_type::<Self, _>(|registry| {
|
||||||
|
#(#registry_types)*
|
||||||
|
|
||||||
|
async_graphql::registry::Type::Interface {
|
||||||
|
name: #gql_typename,
|
||||||
|
description: #desc,
|
||||||
|
fields: vec![#(#schema_fields),*],
|
||||||
|
possible_types: vec![#(#possible_types),*],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[#crate_name::async_trait::async_trait]
|
||||||
|
impl #generics #crate_name::GQLObject for #ident #generics {
|
||||||
|
async fn resolve_field(&self, ctx: &#crate_name::Context<'_>, field: &#crate_name::graphql_parser::query::Field) -> #crate_name::Result<#crate_name::serde_json::Value> {
|
||||||
|
use #crate_name::ErrorWithPosition;
|
||||||
|
|
||||||
|
#(#resolvers)*
|
||||||
|
|
||||||
|
anyhow::bail!(#crate_name::QueryError::FieldNotFound {
|
||||||
|
field_name: field.name.clone(),
|
||||||
|
object: #gql_typename.to_string(),
|
||||||
|
}
|
||||||
|
.with_position(field.position));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(expanded.into())
|
||||||
|
}
|
|
@ -3,7 +3,9 @@ extern crate proc_macro;
|
||||||
mod args;
|
mod args;
|
||||||
mod r#enum;
|
mod r#enum;
|
||||||
mod input_object;
|
mod input_object;
|
||||||
|
mod interface;
|
||||||
mod object;
|
mod object;
|
||||||
|
mod output_type;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
|
@ -51,3 +53,17 @@ pub fn InputObject(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
Err(err) => err.to_compile_error().into(),
|
Err(err) => err.to_compile_error().into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn Interface(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
let interface_args = match args::Interface::parse(parse_macro_input!(args as AttributeArgs)) {
|
||||||
|
Ok(interface_args) => interface_args,
|
||||||
|
Err(err) => return err.to_compile_error().into(),
|
||||||
|
};
|
||||||
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
|
match interface::generate(&interface_args, &input) {
|
||||||
|
Ok(expanded) => expanded,
|
||||||
|
Err(err) => err.to_compile_error().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,16 +1,9 @@
|
||||||
use crate::args;
|
use crate::args;
|
||||||
|
use crate::output_type::OutputType;
|
||||||
use crate::utils::{build_value_repr, get_crate_name};
|
use crate::utils::{build_value_repr, get_crate_name};
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{
|
use syn::{Error, FnArg, ImplItem, ItemImpl, Pat, Result, ReturnType, Type, TypeReference};
|
||||||
Error, FnArg, GenericArgument, ImplItem, ItemImpl, Pat, PathArguments, Result, ReturnType,
|
|
||||||
Type, TypeReference,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum OutputType<'a> {
|
|
||||||
Value(&'a Type),
|
|
||||||
Result(&'a Type, &'a Type),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<TokenStream> {
|
pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<TokenStream> {
|
||||||
let crate_name = get_crate_name(object_args.internal);
|
let crate_name = get_crate_name(object_args.internal);
|
||||||
|
@ -58,49 +51,9 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
|
||||||
.map(|s| quote! {Some(#s)})
|
.map(|s| quote! {Some(#s)})
|
||||||
.unwrap_or_else(|| quote! {None});
|
.unwrap_or_else(|| quote! {None});
|
||||||
let ty = match &method.sig.output {
|
let ty = match &method.sig.output {
|
||||||
ReturnType::Type(_, ty) => {
|
ReturnType::Type(_, ty) => OutputType::parse(ty)?,
|
||||||
if let Type::Path(p) = ty.as_ref() {
|
|
||||||
if p.path.is_ident("Result") {
|
|
||||||
if let PathArguments::AngleBracketed(args) =
|
|
||||||
&p.path.segments[0].arguments
|
|
||||||
{
|
|
||||||
if args.args.len() == 0 {
|
|
||||||
return Err(Error::new_spanned(
|
|
||||||
&method.sig.output,
|
|
||||||
"Invalid type",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let mut res = None;
|
|
||||||
for arg in &args.args {
|
|
||||||
if let GenericArgument::Type(value_ty) = arg {
|
|
||||||
res = Some(OutputType::Result(ty, value_ty));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if res.is_none() {
|
|
||||||
return Err(Error::new_spanned(
|
|
||||||
&method.sig.output,
|
|
||||||
"Invalid type",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
res.unwrap()
|
|
||||||
} else {
|
|
||||||
return Err(Error::new_spanned(
|
|
||||||
&method.sig.output,
|
|
||||||
"Invalid type",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
OutputType::Value(ty)
|
|
||||||
}
|
|
||||||
} else if let Type::Reference(_) = ty.as_ref() {
|
|
||||||
OutputType::Value(ty)
|
|
||||||
} else {
|
|
||||||
return Err(Error::new_spanned(&method.sig.output, "Invalid type"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ReturnType::Default => {
|
ReturnType::Default => {
|
||||||
return Err(Error::new_spanned(&method.sig.output, "Missing type"));
|
return Err(Error::new_spanned(&method.sig.output, "Missing type"))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -194,7 +147,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
|
||||||
None => quote! { || #crate_name::Value::Null },
|
None => quote! { || #crate_name::Value::Null },
|
||||||
};
|
};
|
||||||
get_params.push(quote! {
|
get_params.push(quote! {
|
||||||
let #ident: #ty = ctx_field.param_value(#name, #default)?;
|
let #ident: #ty = ctx.param_value(#name, #default)?;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,7 +166,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
|
||||||
});
|
});
|
||||||
|
|
||||||
let ctx_field = match arg_ctx {
|
let ctx_field = match arg_ctx {
|
||||||
true => quote! { &ctx_field, },
|
true => quote! { &ctx, },
|
||||||
false => quote! {},
|
false => quote! {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -233,10 +186,9 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
|
||||||
resolvers.push(quote! {
|
resolvers.push(quote! {
|
||||||
if field.name.as_str() == #field_name {
|
if field.name.as_str() == #field_name {
|
||||||
#(#get_params)*
|
#(#get_params)*
|
||||||
let ctx_obj = ctx_field.with_item(&field.selection_set);
|
let ctx_obj = ctx.with_item(&field.selection_set);
|
||||||
let value = #resolve_obj.resolve(&ctx_obj).await.map_err(|err| err.with_position(field.position))?;
|
return #crate_name::GQLOutputValue::resolve(&#resolve_obj, &ctx_obj).await.
|
||||||
result.insert(field.alias.clone().unwrap_or_else(|| field.name.clone()), value.into());
|
map_err(|err| err.with_position(field.position).into());
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -257,49 +209,25 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
|
||||||
registry.create_type::<Self, _>(|registry| #crate_name::registry::Type::Object {
|
registry.create_type::<Self, _>(|registry| #crate_name::registry::Type::Object {
|
||||||
name: #gql_typename,
|
name: #gql_typename,
|
||||||
description: #desc,
|
description: #desc,
|
||||||
fields: vec![#(#schema_fields),*]
|
fields: vec![#(#schema_fields),*],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[#crate_name::async_trait::async_trait]
|
#[#crate_name::async_trait::async_trait]
|
||||||
impl #generics #crate_name::GQLOutputValue for #self_ty {
|
impl#generics #crate_name::GQLObject for #self_ty {
|
||||||
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>) -> #crate_name::Result<#crate_name::serde_json::Value> {
|
async fn resolve_field(&self, ctx: &#crate_name::Context<'_>, field: &#crate_name::graphql_parser::query::Field) -> #crate_name::Result<#crate_name::serde_json::Value> {
|
||||||
use #crate_name::ErrorWithPosition;
|
use #crate_name::ErrorWithPosition;
|
||||||
|
|
||||||
if ctx.items.is_empty() {
|
#(#resolvers)*
|
||||||
#crate_name::anyhow::bail!(#crate_name::QueryError::MustHaveSubFields {
|
|
||||||
object: #gql_typename,
|
|
||||||
}.with_position(ctx.span.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut result = #crate_name::serde_json::Map::<String, #crate_name::serde_json::Value>::new();
|
anyhow::bail!(#crate_name::QueryError::FieldNotFound {
|
||||||
for field in ctx.fields(&*ctx) {
|
field_name: field.name.clone(),
|
||||||
let field = field?;
|
object: #gql_typename.to_string(),
|
||||||
let ctx_field = ctx.with_item(field);
|
|
||||||
if ctx_field.is_skip(&field.directives)? {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if field.name.as_str() == "__typename" {
|
|
||||||
let name = field.alias.clone().unwrap_or_else(|| field.name.clone());
|
|
||||||
result.insert(name, #gql_typename.into());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if field.name.as_str() == "__schema" {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
#(#resolvers)*
|
|
||||||
#crate_name::anyhow::bail!(#crate_name::QueryError::FieldNotFound {
|
|
||||||
field_name: field.name.clone(),
|
|
||||||
object: #gql_typename,
|
|
||||||
}.with_position(field.position));
|
|
||||||
}
|
}
|
||||||
|
.with_position(field.position));
|
||||||
Ok(#crate_name::serde_json::Value::Object(result))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl#generics #crate_name::GQLObject for #self_ty {}
|
|
||||||
};
|
};
|
||||||
Ok(expanded.into())
|
Ok(expanded.into())
|
||||||
}
|
}
|
||||||
|
|
47
async-graphql-derive/src/output_type.rs
Normal file
47
async-graphql-derive/src/output_type.rs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
use syn::{Error, GenericArgument, PathArguments, Result, Type};
|
||||||
|
|
||||||
|
pub enum OutputType<'a> {
|
||||||
|
Value(&'a Type),
|
||||||
|
Result(&'a Type, &'a Type),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> OutputType<'a> {
|
||||||
|
pub fn parse(input: &'a Type) -> Result<Self> {
|
||||||
|
let ty = if let Type::Path(p) = input {
|
||||||
|
if p.path.segments.last().unwrap().ident == "Result" {
|
||||||
|
if let PathArguments::AngleBracketed(args) = &p.path.segments[0].arguments {
|
||||||
|
if args.args.len() == 0 {
|
||||||
|
return Err(Error::new_spanned(input, "Invalid type"));
|
||||||
|
}
|
||||||
|
let mut res = None;
|
||||||
|
for arg in &args.args {
|
||||||
|
if let GenericArgument::Type(value_ty) = arg {
|
||||||
|
res = Some(OutputType::Result(input, value_ty));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if res.is_none() {
|
||||||
|
return Err(Error::new_spanned(input, "Invalid type"));
|
||||||
|
}
|
||||||
|
res.unwrap()
|
||||||
|
} else {
|
||||||
|
return Err(Error::new_spanned(input, "Invalid type"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
OutputType::Value(input)
|
||||||
|
}
|
||||||
|
} else if let Type::Reference(_) = input {
|
||||||
|
OutputType::Value(input)
|
||||||
|
} else {
|
||||||
|
return Err(Error::new_spanned(input, "Invalid type"));
|
||||||
|
};
|
||||||
|
Ok(ty)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn value_type(&self) -> &Type {
|
||||||
|
match self {
|
||||||
|
OutputType::Value(ty) => ty,
|
||||||
|
OutputType::Result(_, ty) => ty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -117,14 +117,6 @@ impl StarWars {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hero(&self, episode: Episode) -> usize {
|
|
||||||
if episode == Episode::EMPIRE {
|
|
||||||
self.luke
|
|
||||||
} else {
|
|
||||||
self.artoo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn human(&self, id: &str) -> Option<usize> {
|
pub fn human(&self, id: &str) -> Option<usize> {
|
||||||
self.human_data.get(id).cloned()
|
self.human_data.get(id).cloned()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use super::StarWars;
|
use super::StarWars;
|
||||||
|
use async_graphql::GQLType;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
#[async_graphql::Enum(desc = "One of the films in the Star Wars Trilogy")]
|
#[async_graphql::Enum(desc = "One of the films in the Star Wars Trilogy")]
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
|
@ -31,13 +33,16 @@ impl<'a> Human<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[field(desc = "The friends of the human, or an empty list if they have none.")]
|
#[field(desc = "The friends of the human, or an empty list if they have none.")]
|
||||||
async fn friends(&self) -> Vec<Human<'a>> {
|
async fn friends(&self) -> Vec<Character<'a>> {
|
||||||
self.starwars.chars[self.id]
|
self.starwars.chars[self.id]
|
||||||
.friends
|
.friends
|
||||||
.iter()
|
.iter()
|
||||||
.map(|id| Human {
|
.map(|id| {
|
||||||
id: *id,
|
Human {
|
||||||
starwars: self.starwars,
|
id: *id,
|
||||||
|
starwars: self.starwars,
|
||||||
|
}
|
||||||
|
.into()
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
@ -74,13 +79,16 @@ impl<'a> Droid<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[field(desc = "The friends of the droid, or an empty list if they have none.")]
|
#[field(desc = "The friends of the droid, or an empty list if they have none.")]
|
||||||
async fn friends(&self) -> Vec<Droid<'a>> {
|
async fn friends(&self) -> Vec<Character<'a>> {
|
||||||
self.starwars.chars[self.id]
|
self.starwars.chars[self.id]
|
||||||
.friends
|
.friends
|
||||||
.iter()
|
.iter()
|
||||||
.map(|id| Droid {
|
.map(|id| {
|
||||||
id: *id,
|
Droid {
|
||||||
starwars: self.starwars,
|
id: *id,
|
||||||
|
starwars: self.starwars,
|
||||||
|
}
|
||||||
|
.into()
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
@ -107,10 +115,19 @@ impl QueryRoot {
|
||||||
desc = "If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode."
|
desc = "If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode."
|
||||||
)]
|
)]
|
||||||
episode: Episode,
|
episode: Episode,
|
||||||
) -> Human<'_> {
|
) -> Character<'_> {
|
||||||
Human {
|
if episode == Episode::EMPIRE {
|
||||||
id: self.0.hero(episode),
|
Human {
|
||||||
starwars: &self.0,
|
id: self.0.luke,
|
||||||
|
starwars: &self.0,
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
Droid {
|
||||||
|
id: self.0.artoo,
|
||||||
|
starwars: &self.0,
|
||||||
|
}
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,3 +147,13 @@ impl QueryRoot {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#[async_graphql::Interface(
|
||||||
|
field(name = "id", type = "&str"),
|
||||||
|
field(name = "name", type = "&str"),
|
||||||
|
field(name = "friends", type = "Vec<Character<'a>>"),
|
||||||
|
field(name = "appearsIn", method = "appears_in", type = "&[Episode]")
|
||||||
|
)]
|
||||||
|
pub struct Character<'a>(Human<'a>, Droid<'a>);
|
||||||
|
|
65
src/base.rs
65
src/base.rs
|
@ -1,8 +1,7 @@
|
||||||
use crate::{registry, ContextSelectionSet, Result};
|
use crate::{registry, Context, ContextSelectionSet, Result};
|
||||||
use graphql_parser::query::Value;
|
use graphql_parser::query::{Field, Value};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub trait GQLType {
|
pub trait GQLType {
|
||||||
fn type_name() -> Cow<'static, str>;
|
fn type_name() -> Cow<'static, str>;
|
||||||
|
|
||||||
|
@ -13,25 +12,24 @@ pub trait GQLType {
|
||||||
fn create_type_info(registry: &mut registry::Registry) -> String;
|
fn create_type_info(registry: &mut registry::Registry) -> String;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub trait GQLInputValue: GQLType + Sized {
|
pub trait GQLInputValue: GQLType + Sized {
|
||||||
fn parse(value: &Value) -> Option<Self>;
|
fn parse(value: &Value) -> Option<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait GQLOutputValue: GQLType {
|
pub trait GQLOutputValue: GQLType {
|
||||||
async fn resolve(&self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value>;
|
async fn resolve(value: &Self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[async_trait::async_trait]
|
||||||
pub trait GQLObject: GQLOutputValue {
|
pub trait GQLObject: GQLOutputValue {
|
||||||
fn is_empty() -> bool {
|
fn is_empty() -> bool {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn resolve_field(&self, ctx: &Context<'_>, field: &Field) -> Result<serde_json::Value>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub trait GQLInputObject: GQLInputValue {}
|
pub trait GQLInputObject: GQLInputValue {}
|
||||||
|
|
||||||
pub trait GQLScalar: Sized + Send {
|
pub trait GQLScalar: Sized + Send {
|
||||||
|
@ -43,28 +41,43 @@ pub trait GQLScalar: Sized + Send {
|
||||||
fn to_json(&self) -> Result<serde_json::Value>;
|
fn to_json(&self) -> Result<serde_json::Value>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: GQLScalar> GQLType for T {
|
#[macro_export]
|
||||||
fn type_name() -> Cow<'static, str> {
|
macro_rules! impl_scalar {
|
||||||
Cow::Borrowed(T::type_name())
|
($ty:ty) => {
|
||||||
}
|
impl crate::GQLType for $ty {
|
||||||
|
fn type_name() -> std::borrow::Cow<'static, str> {
|
||||||
|
std::borrow::Cow::Borrowed(<$ty as crate::GQLScalar>::type_name())
|
||||||
|
}
|
||||||
|
|
||||||
fn create_type_info(registry: &mut registry::Registry) -> String {
|
fn create_type_info(registry: &mut crate::registry::Registry) -> String {
|
||||||
registry.create_type::<T, _>(|_| registry::Type::Scalar {
|
registry.create_type::<$ty, _>(|_| crate::registry::Type::Scalar {
|
||||||
name: T::type_name().to_string(),
|
name: <$ty as crate::GQLScalar>::type_name().to_string(),
|
||||||
description: T::description(),
|
description: <$ty>::description(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: GQLScalar> GQLInputValue for T {
|
impl crate::GQLInputValue for $ty {
|
||||||
fn parse(value: &Value) -> Option<Self> {
|
fn parse(value: &crate::Value) -> Option<Self> {
|
||||||
T::parse(value)
|
<$ty as crate::GQLScalar>::parse(value)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl crate::GQLOutputValue for $ty {
|
||||||
|
async fn resolve(
|
||||||
|
value: &Self,
|
||||||
|
_: &crate::ContextSelectionSet<'_>,
|
||||||
|
) -> crate::Result<serde_json::Value> {
|
||||||
|
value.to_json()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<T: GQLScalar + Sync> GQLOutputValue for T {
|
impl<T: GQLObject + Send + Sync> GQLOutputValue for T {
|
||||||
async fn resolve(&self, _: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
|
async fn resolve(value: &Self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
|
||||||
T::to_json(self)
|
crate::resolver::do_resolve(ctx, value).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,7 +120,17 @@ impl<'a, T> Iterator for FieldIter<'a, T> {
|
||||||
.into()));
|
.into()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Selection::InlineFragment(_) => {}
|
Selection::InlineFragment(inline_fragment) => {
|
||||||
|
let skip = match self.ctx.is_skip(&inline_fragment.directives) {
|
||||||
|
Ok(skip) => skip,
|
||||||
|
Err(err) => return Some(Err(err)),
|
||||||
|
};
|
||||||
|
if skip {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// todo: check type
|
||||||
|
self.stack.push(inline_fragment.selection_set.items.iter());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.stack.pop();
|
self.stack.pop();
|
||||||
|
@ -287,4 +297,8 @@ impl<'a> ContextBase<'a, &'a Field> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn result_name(&self) -> String {
|
||||||
|
self.item.alias.clone().unwrap_or_else(|| self.name.clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,16 +22,13 @@ pub enum QueryError {
|
||||||
},
|
},
|
||||||
|
|
||||||
#[error("Cannot query field \"{field_name}\" on type \"{object}\".")]
|
#[error("Cannot query field \"{field_name}\" on type \"{object}\".")]
|
||||||
FieldNotFound {
|
FieldNotFound { field_name: String, object: String },
|
||||||
field_name: String,
|
|
||||||
object: &'static str,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[error("Unknown operation named \"{name}\"")]
|
#[error("Unknown operation named \"{name}\"")]
|
||||||
UnknownOperationNamed { name: String },
|
UnknownOperationNamed { name: String },
|
||||||
|
|
||||||
#[error("Type \"{object}\" must have a selection of subfields.")]
|
#[error("Type \"{object}\" must have a selection of subfields.")]
|
||||||
MustHaveSubFields { object: &'static str },
|
MustHaveSubFields { object: String },
|
||||||
|
|
||||||
#[error("Schema is not configured for mutations.")]
|
#[error("Schema is not configured for mutations.")]
|
||||||
NotConfiguredMutations,
|
NotConfiguredMutations,
|
||||||
|
|
|
@ -57,6 +57,7 @@ mod base;
|
||||||
mod context;
|
mod context;
|
||||||
mod error;
|
mod error;
|
||||||
mod model;
|
mod model;
|
||||||
|
mod resolver;
|
||||||
mod scalars;
|
mod scalars;
|
||||||
mod schema;
|
mod schema;
|
||||||
mod types;
|
mod types;
|
||||||
|
@ -72,7 +73,7 @@ pub use serde_json;
|
||||||
|
|
||||||
pub mod http;
|
pub mod http;
|
||||||
|
|
||||||
pub use async_graphql_derive::{Enum, InputObject, Object};
|
pub use async_graphql_derive::{Enum, InputObject, Interface, Object};
|
||||||
pub use base::{GQLInputObject, GQLInputValue, GQLObject, GQLOutputValue, GQLScalar, GQLType};
|
pub use base::{GQLInputObject, GQLInputValue, GQLObject, GQLOutputValue, GQLScalar, GQLType};
|
||||||
pub use context::{Context, ContextBase, Variables};
|
pub use context::{Context, ContextBase, Variables};
|
||||||
pub use error::{ErrorWithPosition, PositionError, QueryError, QueryParseError};
|
pub use error::{ErrorWithPosition, PositionError, QueryError, QueryParseError};
|
||||||
|
|
|
@ -123,6 +123,23 @@ impl<'a> __Type<'a> {
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
|
} else if let TypeDetail::Simple(Type::Interface { fields, .. }) = &self.detail {
|
||||||
|
Some(
|
||||||
|
fields
|
||||||
|
.iter()
|
||||||
|
.filter(|field| {
|
||||||
|
if include_deprecated {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
field.deprecation.is_none()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(|field| __Field {
|
||||||
|
registry: self.registry,
|
||||||
|
field,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -130,8 +147,16 @@ impl<'a> __Type<'a> {
|
||||||
|
|
||||||
#[field]
|
#[field]
|
||||||
async fn interfaces(&self) -> Option<Vec<__Type<'a>>> {
|
async fn interfaces(&self) -> Option<Vec<__Type<'a>>> {
|
||||||
if let TypeDetail::Simple(Type::Object { .. }) = &self.detail {
|
if let TypeDetail::Simple(Type::Object { name, .. }) = &self.detail {
|
||||||
Some(vec![])
|
Some(
|
||||||
|
self.registry
|
||||||
|
.implements
|
||||||
|
.get(*name)
|
||||||
|
.unwrap_or(&Default::default())
|
||||||
|
.iter()
|
||||||
|
.map(|ty| __Type::new(self.registry, ty))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -139,7 +164,16 @@ impl<'a> __Type<'a> {
|
||||||
|
|
||||||
#[field(name = "possibleTypes")]
|
#[field(name = "possibleTypes")]
|
||||||
async fn possible_types(&self) -> Option<Vec<__Type<'a>>> {
|
async fn possible_types(&self) -> Option<Vec<__Type<'a>>> {
|
||||||
None
|
if let TypeDetail::Simple(Type::Interface { possible_types, .. }) = &self.detail {
|
||||||
|
Some(
|
||||||
|
possible_types
|
||||||
|
.iter()
|
||||||
|
.map(|ty| __Type::new(self.registry, ty))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[field(name = "enumValues")]
|
#[field(name = "enumValues")]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{model, GQLType};
|
use crate::{model, GQLType};
|
||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
pub struct InputValue {
|
pub struct InputValue {
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
|
@ -36,7 +36,7 @@ pub enum Type {
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
description: Option<&'static str>,
|
description: Option<&'static str>,
|
||||||
fields: Vec<Field>,
|
fields: Vec<Field>,
|
||||||
possible_types: Vec<usize>,
|
possible_types: Vec<String>,
|
||||||
},
|
},
|
||||||
Union {
|
Union {
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
|
@ -66,6 +66,7 @@ pub struct Directive {
|
||||||
pub struct Registry {
|
pub struct Registry {
|
||||||
pub types: HashMap<String, Type>,
|
pub types: HashMap<String, Type>,
|
||||||
pub directives: Vec<Directive>,
|
pub directives: Vec<Directive>,
|
||||||
|
pub implements: HashMap<String, HashSet<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Registry {
|
impl Registry {
|
||||||
|
@ -88,4 +89,17 @@ impl Registry {
|
||||||
pub fn add_directive(&mut self, directive: Directive) {
|
pub fn add_directive(&mut self, directive: Directive) {
|
||||||
self.directives.push(directive);
|
self.directives.push(directive);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_implements(&mut self, ty: &str, interface: &str) {
|
||||||
|
self.implements
|
||||||
|
.entry(ty.to_string())
|
||||||
|
.and_modify(|interfaces| {
|
||||||
|
interfaces.insert(interface.to_string());
|
||||||
|
})
|
||||||
|
.or_insert({
|
||||||
|
let mut interfaces = HashSet::new();
|
||||||
|
interfaces.insert(interface.to_string());
|
||||||
|
interfaces
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
83
src/resolver.rs
Normal file
83
src/resolver.rs
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
use crate::{ContextSelectionSet, ErrorWithPosition, GQLObject, QueryError, Result};
|
||||||
|
use graphql_parser::query::Selection;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
struct Resolver<'a, T> {
|
||||||
|
ctx: &'a ContextSelectionSet<'a>,
|
||||||
|
obj: &'a T,
|
||||||
|
result: &'a mut serde_json::Map<String, serde_json::Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: GQLObject + Send + Sync> Resolver<'a, T> {
|
||||||
|
pub fn resolve(&'a mut self) -> Pin<Box<dyn Future<Output = Result<()>> + 'a + Send>> {
|
||||||
|
Box::pin(async move {
|
||||||
|
if self.ctx.items.is_empty() {
|
||||||
|
anyhow::bail!(QueryError::MustHaveSubFields {
|
||||||
|
object: T::type_name().to_string(),
|
||||||
|
}
|
||||||
|
.with_position(self.ctx.span.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
for selection in &self.ctx.item.items {
|
||||||
|
match selection {
|
||||||
|
Selection::Field(field) => {
|
||||||
|
let ctx_field = self.ctx.with_item(field);
|
||||||
|
if ctx_field.is_skip(&field.directives)? {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if field.name.as_str() == "__typename" {
|
||||||
|
self.result
|
||||||
|
.insert(ctx_field.result_name(), T::type_name().to_string().into());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.result.insert(
|
||||||
|
ctx_field.result_name(),
|
||||||
|
self.obj.resolve_field(&ctx_field, field).await?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Selection::FragmentSpread(fragment_spread) => {
|
||||||
|
if let Some(fragment) =
|
||||||
|
self.ctx.fragments.get(&fragment_spread.fragment_name)
|
||||||
|
{
|
||||||
|
Resolver {
|
||||||
|
ctx: &self.ctx.with_item(&fragment.selection_set),
|
||||||
|
obj: self.obj,
|
||||||
|
result: self.result,
|
||||||
|
}
|
||||||
|
.resolve()
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
return Err(QueryError::UnknownFragment {
|
||||||
|
name: fragment_spread.fragment_name.clone(),
|
||||||
|
}
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Selection::InlineFragment(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn do_resolve<'a, T: GQLObject + Send + Sync>(
|
||||||
|
ctx: &'a ContextSelectionSet<'a>,
|
||||||
|
root: &'a T,
|
||||||
|
) -> Result<serde_json::Value> {
|
||||||
|
let mut result = serde_json::Map::<String, serde_json::Value>::new();
|
||||||
|
|
||||||
|
Resolver {
|
||||||
|
ctx,
|
||||||
|
obj: root,
|
||||||
|
result: &mut result,
|
||||||
|
}
|
||||||
|
.resolve()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(serde_json::Value::Object(result))
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{Result, GQLScalar, Value};
|
use crate::{impl_scalar, GQLScalar, Result, Value};
|
||||||
|
|
||||||
impl GQLScalar for bool {
|
impl GQLScalar for bool {
|
||||||
fn type_name() -> &'static str {
|
fn type_name() -> &'static str {
|
||||||
|
@ -20,3 +20,5 @@ impl GQLScalar for bool {
|
||||||
Ok((*self).into())
|
Ok((*self).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_scalar!(bool);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{Result, GQLScalar, Value};
|
use crate::{impl_scalar, GQLScalar, Result, Value};
|
||||||
use chrono::{DateTime, TimeZone, Utc};
|
use chrono::{DateTime, TimeZone, Utc};
|
||||||
|
|
||||||
impl GQLScalar for DateTime<Utc> {
|
impl GQLScalar for DateTime<Utc> {
|
||||||
|
@ -17,3 +17,5 @@ impl GQLScalar for DateTime<Utc> {
|
||||||
Ok(self.to_rfc3339().into())
|
Ok(self.to_rfc3339().into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_scalar!(DateTime<Utc>);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{Result, GQLScalar, Value};
|
use crate::{impl_scalar, GQLScalar, Result, Value};
|
||||||
|
|
||||||
macro_rules! impl_float_scalars {
|
macro_rules! impl_float_scalars {
|
||||||
($($ty:ty),*) => {
|
($($ty:ty),*) => {
|
||||||
|
@ -24,6 +24,8 @@ macro_rules! impl_float_scalars {
|
||||||
Ok((*self).into())
|
Ok((*self).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_scalar!($ty);
|
||||||
)*
|
)*
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{Result, GQLScalar, Value};
|
use crate::{impl_scalar, GQLScalar, Result, Value};
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
|
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
|
||||||
|
@ -35,3 +35,5 @@ impl GQLScalar for ID {
|
||||||
Ok(self.0.clone().into())
|
Ok(self.0.clone().into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_scalar!(ID);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{Result, GQLScalar, Value};
|
use crate::{impl_scalar, GQLScalar, Result, Value};
|
||||||
|
|
||||||
macro_rules! impl_integer_scalars {
|
macro_rules! impl_integer_scalars {
|
||||||
($($ty:ty),*) => {
|
($($ty:ty),*) => {
|
||||||
|
@ -23,6 +23,8 @@ macro_rules! impl_integer_scalars {
|
||||||
Ok((*self).into())
|
Ok((*self).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_scalar!($ty);
|
||||||
)*
|
)*
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::registry;
|
use crate::{
|
||||||
use crate::{ContextSelectionSet, GQLOutputValue, GQLScalar, GQLType, Result, Value};
|
impl_scalar, registry, ContextSelectionSet, GQLOutputValue, GQLScalar, GQLType, Result, Value,
|
||||||
|
};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
const STRING_DESC:&'static str = "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.";
|
const STRING_DESC:&'static str = "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.";
|
||||||
|
@ -25,6 +26,8 @@ impl GQLScalar for String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_scalar!(String);
|
||||||
|
|
||||||
impl<'a> GQLType for &'a str {
|
impl<'a> GQLType for &'a str {
|
||||||
fn type_name() -> Cow<'static, str> {
|
fn type_name() -> Cow<'static, str> {
|
||||||
Cow::Borrowed("String")
|
Cow::Borrowed("String")
|
||||||
|
@ -40,7 +43,7 @@ impl<'a> GQLType for &'a str {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<'a> GQLOutputValue for &'a str {
|
impl<'a> GQLOutputValue for &'a str {
|
||||||
async fn resolve(&self, _: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
|
async fn resolve(value: &Self, _: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
|
||||||
Ok(self.to_string().into())
|
Ok(value.to_string().into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{Result, GQLScalar, Value};
|
use crate::{impl_scalar, GQLScalar, Result, Value};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
impl GQLScalar for Uuid {
|
impl GQLScalar for Uuid {
|
||||||
|
@ -17,3 +17,5 @@ impl GQLScalar for Uuid {
|
||||||
Ok(self.to_string().into())
|
Ok(self.to_string().into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_scalar!(Uuid);
|
|
@ -150,7 +150,7 @@ impl<'a, Query, Mutation> QueryBuilder<'a, Query, Mutation> {
|
||||||
data: self.data,
|
data: self.data,
|
||||||
fragments: &fragments,
|
fragments: &fragments,
|
||||||
};
|
};
|
||||||
return self.query.resolve(&ctx).await;
|
return GQLOutputValue::resolve(self.query, &ctx).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Definition::Operation(OperationDefinition::Query(query)) => {
|
Definition::Operation(OperationDefinition::Query(query)) => {
|
||||||
|
@ -165,7 +165,7 @@ impl<'a, Query, Mutation> QueryBuilder<'a, Query, Mutation> {
|
||||||
data: self.data,
|
data: self.data,
|
||||||
fragments: &fragments,
|
fragments: &fragments,
|
||||||
};
|
};
|
||||||
return self.query.resolve(&ctx).await;
|
return GQLOutputValue::resolve(self.query, &ctx).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Definition::Operation(OperationDefinition::Mutation(mutation)) => {
|
Definition::Operation(OperationDefinition::Mutation(mutation)) => {
|
||||||
|
@ -180,7 +180,7 @@ impl<'a, Query, Mutation> QueryBuilder<'a, Query, Mutation> {
|
||||||
data: self.data,
|
data: self.data,
|
||||||
fragments: &fragments,
|
fragments: &fragments,
|
||||||
};
|
};
|
||||||
return self.mutation.resolve(&ctx).await;
|
return GQLOutputValue::resolve(self.mutation, &ctx).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
use crate::{
|
use crate::{registry, Context, GQLObject, GQLType, QueryError, Result};
|
||||||
registry, ContextSelectionSet, ErrorWithPosition, GQLObject, GQLOutputValue, GQLType,
|
use graphql_parser::query::Field;
|
||||||
QueryError, Result,
|
|
||||||
};
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
pub struct GQLEmptyMutation;
|
pub struct GQLEmptyMutation;
|
||||||
|
@ -21,14 +19,12 @@ impl GQLType for GQLEmptyMutation {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl GQLOutputValue for GQLEmptyMutation {
|
|
||||||
async fn resolve(&self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
|
|
||||||
anyhow::bail!(QueryError::NotConfiguredMutations.with_position(ctx.item.span.0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GQLObject for GQLEmptyMutation {
|
impl GQLObject for GQLEmptyMutation {
|
||||||
fn is_empty() -> bool {
|
fn is_empty() -> bool {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn resolve_field(&self, _ctx: &Context<'_>, _name: &Field) -> Result<serde_json::Value> {
|
||||||
|
return Err(QueryError::NotConfiguredMutations.into());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
use crate::{GQLType, Result};
|
use crate::{GQLType, Result};
|
||||||
use graphql_parser::query::Value;
|
use graphql_parser::query::Value;
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub struct GQLEnumItem<T> {
|
pub struct GQLEnumItem<T> {
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
pub value: T,
|
pub value: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait GQLEnum: GQLType + Sized + Eq + Send + Copy + Sized + 'static {
|
pub trait GQLEnum: GQLType + Sized + Eq + Send + Copy + Sized + 'static {
|
||||||
fn items() -> &'static [GQLEnumItem<Self>];
|
fn items() -> &'static [GQLEnumItem<Self>];
|
||||||
|
|
|
@ -33,10 +33,10 @@ impl<T: GQLInputValue> GQLInputValue for Vec<T> {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<T: GQLOutputValue + Send + Sync> GQLOutputValue for Vec<T> {
|
impl<T: GQLOutputValue + Send + Sync> GQLOutputValue for Vec<T> {
|
||||||
async fn resolve(&self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
|
async fn resolve(value: &Self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
|
||||||
let mut res = Vec::new();
|
let mut res = Vec::new();
|
||||||
for item in self {
|
for item in value {
|
||||||
res.push(item.resolve(&ctx).await?);
|
res.push(GQLOutputValue::resolve(item, &ctx).await?);
|
||||||
}
|
}
|
||||||
Ok(res.into())
|
Ok(res.into())
|
||||||
}
|
}
|
||||||
|
@ -54,10 +54,10 @@ impl<T: GQLType> GQLType for &[T] {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<T: GQLOutputValue + Send + Sync> GQLOutputValue for &[T] {
|
impl<T: GQLOutputValue + Send + Sync> GQLOutputValue for &[T] {
|
||||||
async fn resolve(&self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
|
async fn resolve(value: &Self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
|
||||||
let mut res = Vec::new();
|
let mut res = Vec::new();
|
||||||
for item in self.iter() {
|
for item in value.iter() {
|
||||||
res.push(item.resolve(&ctx).await?);
|
res.push(GQLOutputValue::resolve(item, &ctx).await?);
|
||||||
}
|
}
|
||||||
Ok(res.into())
|
Ok(res.into())
|
||||||
}
|
}
|
||||||
|
@ -75,10 +75,10 @@ impl<T: GQLType> GQLType for &Vec<T> {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<T: GQLOutputValue + Send + Sync> GQLOutputValue for &Vec<T> {
|
impl<T: GQLOutputValue + Send + Sync> GQLOutputValue for &Vec<T> {
|
||||||
async fn resolve(&self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
|
async fn resolve(value: &Self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
|
||||||
let mut res = Vec::new();
|
let mut res = Vec::new();
|
||||||
for item in self.iter() {
|
for item in value.iter() {
|
||||||
res.push(item.resolve(&ctx).await?);
|
res.push(GQLOutputValue::resolve(item, &ctx).await?);
|
||||||
}
|
}
|
||||||
Ok(res.into())
|
Ok(res.into())
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,9 +27,10 @@ impl<T: GQLInputValue> GQLInputValue for Option<T> {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<T: GQLOutputValue + Sync> GQLOutputValue for Option<T> {
|
impl<T: GQLOutputValue + Sync> GQLOutputValue for Option<T> {
|
||||||
async fn resolve(&self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> where {
|
async fn resolve(value: &Self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> where
|
||||||
if let Some(inner) = self {
|
{
|
||||||
inner.resolve(ctx).await
|
if let Some(inner) = value {
|
||||||
|
GQLOutputValue::resolve(inner, ctx).await
|
||||||
} else {
|
} else {
|
||||||
Ok(serde_json::Value::Null)
|
Ok(serde_json::Value::Null)
|
||||||
}
|
}
|
||||||
|
@ -53,9 +54,10 @@ impl<T: GQLType> GQLType for &Option<T> {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<T: GQLOutputValue + Sync> GQLOutputValue for &Option<T> {
|
impl<T: GQLOutputValue + Sync> GQLOutputValue for &Option<T> {
|
||||||
async fn resolve(&self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> where {
|
async fn resolve(value: &Self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> where
|
||||||
if let Some(inner) = self {
|
{
|
||||||
inner.resolve(ctx).await
|
if let Some(inner) = value {
|
||||||
|
GQLOutputValue::resolve(inner, ctx).await
|
||||||
} else {
|
} else {
|
||||||
Ok(serde_json::Value::Null)
|
Ok(serde_json::Value::Null)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
use crate::model::__Schema;
|
use crate::model::{__Schema, __Type};
|
||||||
use crate::{registry, ContextSelectionSet, GQLOutputValue, GQLType, Result};
|
use crate::{
|
||||||
use graphql_parser::query::Selection;
|
registry, Context, ErrorWithPosition, GQLObject, GQLOutputValue, GQLType, Result, Value,
|
||||||
|
};
|
||||||
|
use graphql_parser::query::Field;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
pub struct QueryRoot<T> {
|
pub struct QueryRoot<T> {
|
||||||
|
@ -20,30 +22,34 @@ impl<T: GQLType> GQLType for QueryRoot<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<T: GQLOutputValue + Send + Sync> GQLOutputValue for QueryRoot<T> {
|
impl<T: GQLObject + Send + Sync> GQLObject for QueryRoot<T> {
|
||||||
async fn resolve(&self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
|
async fn resolve_field(&self, ctx: &Context<'_>, field: &Field) -> Result<serde_json::Value> {
|
||||||
let mut res = self.inner.resolve(ctx).await?;
|
if field.name.as_str() == "__schema" {
|
||||||
|
let ctx_obj = ctx.with_item(&field.selection_set);
|
||||||
if let serde_json::Value::Object(obj) = &mut res {
|
return GQLOutputValue::resolve(
|
||||||
for item in &ctx.item.items {
|
&__Schema {
|
||||||
if let Selection::Field(field) = item {
|
registry: &ctx.registry,
|
||||||
if field.name == "__schema" {
|
query_type: &self.query_type,
|
||||||
let ctx_obj = ctx.with_item(&field.selection_set);
|
mutation_type: self.mutation_type.as_deref(),
|
||||||
obj.insert(
|
},
|
||||||
"__schema".to_string(),
|
&ctx_obj,
|
||||||
__Schema {
|
)
|
||||||
registry: &ctx.registry,
|
.await
|
||||||
query_type: &self.query_type,
|
.map_err(|err| err.with_position(field.position).into());
|
||||||
mutation_type: self.mutation_type.as_deref(),
|
} else if field.name.as_str() == "__type" {
|
||||||
}
|
let type_name: String = ctx.param_value("name", || Value::Null)?;
|
||||||
.resolve(&ctx_obj)
|
let ctx_obj = ctx.with_item(&field.selection_set);
|
||||||
.await?,
|
return GQLOutputValue::resolve(
|
||||||
);
|
&ctx.registry
|
||||||
}
|
.types
|
||||||
}
|
.get(&type_name)
|
||||||
}
|
.map(|ty| __Type::new_simple(ctx.registry, ty)),
|
||||||
|
&ctx_obj,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|err| err.with_position(field.position).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(res)
|
return self.inner.resolve_field(ctx, field).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user