init commit
This commit is contained in:
commit
5030a28361
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
/target
|
||||
Cargo.lock
|
||||
.idea
|
||||
.DS_Store
|
29
Cargo.toml
Normal file
29
Cargo.toml
Normal file
|
@ -0,0 +1,29 @@
|
|||
[package]
|
||||
name = "async-graphql"
|
||||
version = "0.1.0"
|
||||
authors = ["sunli <scott_s829@163.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
default = ["chrono", "uuid"]
|
||||
|
||||
[dependencies]
|
||||
async-graphql-derive = { path = "async-graphql-derive" }
|
||||
graphql-parser = "0.2.3"
|
||||
anyhow = "1.0.26"
|
||||
thiserror = "1.0.11"
|
||||
async-trait = "0.1.24"
|
||||
serde = "1.0.104"
|
||||
serde_derive = "1.0.104"
|
||||
serde_json = "1.0.48"
|
||||
fnv = "1.0.6"
|
||||
chrono = { version = "0.4.10", optional = true }
|
||||
uuid = { version = "0.8.1", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
async-std = { version = "1.5.0", features = ["attributes"] }
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"async-graphql-derive"
|
||||
]
|
13
async-graphql-derive/Cargo.toml
Normal file
13
async-graphql-derive/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "async-graphql-derive"
|
||||
version = "0.1.0"
|
||||
authors = ["sunli <scott_s829@163.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.6"
|
||||
syn = { version = "1.0.13", features = ["full"] }
|
||||
quote = "1.0.2"
|
317
async-graphql-derive/src/args.rs
Normal file
317
async-graphql-derive/src/args.rs
Normal file
|
@ -0,0 +1,317 @@
|
|||
use syn::{Attribute, AttributeArgs, Error, Meta, NestedMeta, Result, Type};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Object {
|
||||
pub name: Option<String>,
|
||||
pub desc: Option<String>,
|
||||
}
|
||||
|
||||
impl Object {
|
||||
pub fn parse(args: AttributeArgs) -> Result<Self> {
|
||||
let mut name = None;
|
||||
let mut desc = None;
|
||||
|
||||
for arg in args {
|
||||
match arg {
|
||||
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.",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self { name, desc })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Argument {
|
||||
pub name: String,
|
||||
pub desc: Option<String>,
|
||||
pub ty: Type,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Field {
|
||||
pub name: Option<String>,
|
||||
pub desc: Option<String>,
|
||||
pub is_attr: bool,
|
||||
pub arguments: Vec<Argument>,
|
||||
}
|
||||
|
||||
impl Field {
|
||||
pub fn parse(attrs: &[Attribute]) -> Result<Option<Self>> {
|
||||
let mut is_field = false;
|
||||
let mut name = None;
|
||||
let mut desc = None;
|
||||
let mut is_attr = false;
|
||||
let mut arguments = Vec::new();
|
||||
|
||||
for attr in attrs {
|
||||
if attr.path.is_ident("field") {
|
||||
is_field = true;
|
||||
if let Ok(Meta::List(args)) = attr.parse_meta() {
|
||||
for meta in args.nested {
|
||||
match meta {
|
||||
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("attr") => {
|
||||
is_attr = 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("arg") {
|
||||
let mut name = None;
|
||||
let mut desc = None;
|
||||
let mut ty = None;
|
||||
|
||||
for meta in &ls.nested {
|
||||
if let NestedMeta::Meta(Meta::NameValue(nv)) = meta {
|
||||
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.",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if name.is_none() {
|
||||
return Err(Error::new_spanned(ls, "missing name."));
|
||||
}
|
||||
|
||||
if ty.is_none() {
|
||||
return Err(Error::new_spanned(ls, "missing type."));
|
||||
}
|
||||
|
||||
arguments.push(Argument {
|
||||
name: name.unwrap(),
|
||||
desc,
|
||||
ty: ty.unwrap(),
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if is_field {
|
||||
Ok(Some(Self {
|
||||
name,
|
||||
desc,
|
||||
is_attr,
|
||||
arguments,
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Enum {
|
||||
pub name: Option<String>,
|
||||
pub desc: Option<String>,
|
||||
}
|
||||
|
||||
impl Enum {
|
||||
pub fn parse(args: AttributeArgs) -> Result<Self> {
|
||||
let mut name = None;
|
||||
let mut desc = None;
|
||||
|
||||
for arg in args {
|
||||
match arg {
|
||||
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.",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self { name, desc })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EnumItem {
|
||||
pub name: Option<String>,
|
||||
pub desc: Option<String>,
|
||||
}
|
||||
|
||||
impl EnumItem {
|
||||
pub fn parse(attrs: &[Attribute]) -> Result<Self> {
|
||||
let mut name = None;
|
||||
let mut desc = None;
|
||||
|
||||
for attr in attrs {
|
||||
if attr.path.is_ident("item") {
|
||||
if let Ok(Meta::List(args)) = attr.parse_meta() {
|
||||
for meta in args.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.",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self { name, desc })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InputField {
|
||||
pub name: Option<String>,
|
||||
pub desc: Option<String>,
|
||||
}
|
||||
|
||||
impl InputField {
|
||||
pub fn parse(attrs: &[Attribute]) -> Result<Self> {
|
||||
let mut name = None;
|
||||
let mut desc = None;
|
||||
|
||||
for attr in attrs {
|
||||
if attr.path.is_ident("field") {
|
||||
if let Ok(Meta::List(args)) = attr.parse_meta() {
|
||||
for meta in args.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.",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self { name, desc })
|
||||
}
|
||||
}
|
83
async-graphql-derive/src/enum.rs
Normal file
83
async-graphql-derive/src/enum.rs
Normal file
|
@ -0,0 +1,83 @@
|
|||
use crate::args;
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{Data, DeriveInput, Error, Result};
|
||||
|
||||
pub fn generate(enum_args: &args::Enum, input: &DeriveInput) -> Result<TokenStream> {
|
||||
let attrs = &input.attrs;
|
||||
let vis = &input.vis;
|
||||
let ident = &input.ident;
|
||||
let e = match &input.data {
|
||||
Data::Enum(e) => e,
|
||||
_ => return Err(Error::new_spanned(input, "It should be a enum")),
|
||||
};
|
||||
|
||||
let gql_typename = enum_args.name.clone().unwrap_or_else(|| ident.to_string());
|
||||
|
||||
let mut enum_items = Vec::new();
|
||||
let mut items = Vec::new();
|
||||
for variant in &e.variants {
|
||||
if !variant.fields.is_empty() {
|
||||
return Err(Error::new_spanned(
|
||||
&variant,
|
||||
format!(
|
||||
"Invalid enum variant {}.\nGraphQL enums may only contain unit variants.",
|
||||
variant.ident
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
let item_ident = &variant.ident;
|
||||
let mut item_args = args::EnumItem::parse(&variant.attrs)?;
|
||||
let gql_item_name = item_args
|
||||
.name
|
||||
.take()
|
||||
.unwrap_or_else(|| variant.ident.to_string());
|
||||
enum_items.push(variant);
|
||||
let desc = match item_args.desc.take() {
|
||||
Some(desc) => quote! { Some(#desc) },
|
||||
None => quote! { None },
|
||||
};
|
||||
items.push(quote! {
|
||||
async_graphql::GQLEnumItem {
|
||||
name: #gql_item_name,
|
||||
desc: #desc,
|
||||
value: #ident::#item_ident,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let expanded = quote! {
|
||||
#(#attrs)*
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
#vis enum #ident {
|
||||
#(#enum_items),*
|
||||
}
|
||||
|
||||
impl async_graphql::GQLEnum for #ident {
|
||||
fn items() -> &'static [async_graphql::GQLEnumItem<#ident>] {
|
||||
&[#(#items),*]
|
||||
}
|
||||
}
|
||||
|
||||
impl async_graphql::GQLType for #ident {
|
||||
fn type_name() -> String {
|
||||
#gql_typename.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl async_graphql::GQLInputValue for #ident {
|
||||
fn parse(value: graphql_parser::query::Value) -> Result<Self> {
|
||||
Self::parse_enum(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_graphql::async_trait::async_trait]
|
||||
impl async_graphql::GQLOutputValue for #ident {
|
||||
async fn resolve(self, _: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
|
||||
self.resolve_enum()
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(expanded.into())
|
||||
}
|
40
async-graphql-derive/src/input_object.rs
Normal file
40
async-graphql-derive/src/input_object.rs
Normal file
|
@ -0,0 +1,40 @@
|
|||
use crate::args;
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::Span;
|
||||
use quote::quote;
|
||||
use syn::{Data, DeriveInput, Error, Ident, Result};
|
||||
|
||||
pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<TokenStream> {
|
||||
let attrs = &input.attrs;
|
||||
let vis = &input.vis;
|
||||
let ident = &input.ident;
|
||||
let s = match &input.data {
|
||||
Data::Struct(s) => s,
|
||||
_ => return Err(Error::new_spanned(input, "It should be a struct.")),
|
||||
};
|
||||
|
||||
let gql_typename = object_args
|
||||
.name
|
||||
.clone()
|
||||
.unwrap_or_else(|| ident.to_string());
|
||||
|
||||
for field in &s.fields {
|
||||
let field_args = args::Field::parse(&field.attrs)?;
|
||||
let ident = field.ident.as_ref().unwrap();
|
||||
}
|
||||
|
||||
let expanded = quote! {
|
||||
#input
|
||||
|
||||
impl async_graphql::Type for #ident {
|
||||
fn type_name() -> String {
|
||||
#gql_typename.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl async_graphql::GQLInputObject for #ident {
|
||||
|
||||
}
|
||||
};
|
||||
Ok(expanded.into())
|
||||
}
|
52
async-graphql-derive/src/lib.rs
Normal file
52
async-graphql-derive/src/lib.rs
Normal file
|
@ -0,0 +1,52 @@
|
|||
extern crate proc_macro;
|
||||
|
||||
mod args;
|
||||
mod r#enum;
|
||||
mod input_object;
|
||||
mod object;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use syn::parse_macro_input;
|
||||
use syn::{AttributeArgs, DeriveInput};
|
||||
|
||||
#[proc_macro_attribute]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn Object(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let object_args = match args::Object::parse(parse_macro_input!(args as AttributeArgs)) {
|
||||
Ok(object_args) => object_args,
|
||||
Err(err) => return err.to_compile_error().into(),
|
||||
};
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
match object::generate(&object_args, &input) {
|
||||
Ok(expanded) => expanded,
|
||||
Err(err) => err.to_compile_error().into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn Enum(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let enum_args = match args::Enum::parse(parse_macro_input!(args as AttributeArgs)) {
|
||||
Ok(enum_args) => enum_args,
|
||||
Err(err) => return err.to_compile_error().into(),
|
||||
};
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
match r#enum::generate(&enum_args, &input) {
|
||||
Ok(expanded) => expanded,
|
||||
Err(err) => err.to_compile_error().into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn InputObject(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let object_args = match args::Object::parse(parse_macro_input!(args as AttributeArgs)) {
|
||||
Ok(enum_args) => enum_args,
|
||||
Err(err) => return err.to_compile_error().into(),
|
||||
};
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
match input_object::generate(&object_args, &input) {
|
||||
Ok(expanded) => expanded,
|
||||
Err(err) => err.to_compile_error().into(),
|
||||
}
|
||||
}
|
128
async-graphql-derive/src/object.rs
Normal file
128
async-graphql-derive/src/object.rs
Normal file
|
@ -0,0 +1,128 @@
|
|||
use crate::args;
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::Span;
|
||||
use quote::quote;
|
||||
use syn::{Data, DeriveInput, Error, Ident, Result};
|
||||
|
||||
pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<TokenStream> {
|
||||
let attrs = &input.attrs;
|
||||
let vis = &input.vis;
|
||||
let ident = &input.ident;
|
||||
let s = match &input.data {
|
||||
Data::Struct(s) => s,
|
||||
_ => return Err(Error::new_spanned(input, "It should be a struct.")),
|
||||
};
|
||||
|
||||
let gql_typename = object_args
|
||||
.name
|
||||
.clone()
|
||||
.unwrap_or_else(|| ident.to_string());
|
||||
let trait_ident = Ident::new(&format!("{}Fields", ident.to_string()), Span::call_site());
|
||||
let mut new_fields = Vec::new();
|
||||
let mut trait_fns = Vec::new();
|
||||
let mut resolvers = Vec::new();
|
||||
|
||||
for field in &s.fields {
|
||||
if let Some(field_args) = args::Field::parse(&field.attrs)? {
|
||||
// is field
|
||||
let vis = &field.vis;
|
||||
let ty = &field.ty;
|
||||
let ident = field.ident.as_ref().unwrap();
|
||||
let field_name = ident.to_string();
|
||||
|
||||
if field_args.is_attr {
|
||||
new_fields.push(quote! { #vis #ident: #ty });
|
||||
}
|
||||
|
||||
let mut decl_params = Vec::new();
|
||||
let mut get_params = Vec::new();
|
||||
let mut use_params = Vec::new();
|
||||
|
||||
for arg in &field_args.arguments {
|
||||
let name = Ident::new(&arg.name, Span::call_site());
|
||||
let ty = &arg.ty;
|
||||
let name_str = name.to_string();
|
||||
|
||||
decl_params.push(quote! { #name: #ty });
|
||||
get_params.push(quote! {
|
||||
let #name: #ty = ctx_field.param_value(#name_str)?;
|
||||
});
|
||||
use_params.push(quote! { #name });
|
||||
}
|
||||
|
||||
trait_fns.push(quote! {
|
||||
async fn #ident(&self, ctx: &async_graphql::ContextField<'_>, #(#decl_params),*) -> async_graphql::Result<#ty>;
|
||||
});
|
||||
|
||||
resolvers.push(quote! {
|
||||
if field.name.as_str() == #field_name {
|
||||
#(#get_params)*
|
||||
let obj = #trait_ident::#ident(&self, &ctx_field, #(#use_params),*).await.
|
||||
map_err(|err| err.with_position(field.position))?;
|
||||
let ctx_obj = ctx_field.with_item(&field.selection_set);
|
||||
let value = obj.resolve(&ctx_obj).await.
|
||||
map_err(|err| err.with_position(field.position))?;
|
||||
result.insert(#field_name.to_string(), value.into());
|
||||
continue;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
new_fields.push(quote! { #field });
|
||||
}
|
||||
}
|
||||
|
||||
let expanded = quote! {
|
||||
#(#attrs)*
|
||||
#vis struct #ident {
|
||||
#(#new_fields),*
|
||||
}
|
||||
|
||||
#[async_graphql::async_trait::async_trait]
|
||||
#vis trait #trait_ident {
|
||||
#(#trait_fns)*
|
||||
}
|
||||
|
||||
impl async_graphql::GQLType for #ident {
|
||||
fn type_name() -> String {
|
||||
#gql_typename.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_graphql::async_trait::async_trait]
|
||||
impl async_graphql::GQLOutputValue for #ident {
|
||||
async fn resolve(self, ctx: &async_graphql::ContextSelectionSet<'_>) -> async_graphql::Result<async_graphql::serde_json::Value> {
|
||||
use async_graphql::GQLErrorWithPosition;
|
||||
|
||||
if ctx.items.is_empty() {
|
||||
async_graphql::anyhow::bail!(async_graphql::GQLQueryError::MustHaveSubFields {
|
||||
object: #gql_typename,
|
||||
}.with_position(ctx.span.0));
|
||||
}
|
||||
|
||||
let mut result = async_graphql::serde_json::Map::<String, async_graphql::serde_json::Value>::new();
|
||||
for selection in &ctx.items {
|
||||
match selection {
|
||||
async_graphql::graphql_parser::query::Selection::Field(field) => {
|
||||
let ctx_field = ctx.with_item(field);
|
||||
if field.name.as_str() == "__typename" {
|
||||
result.insert("__typename".to_string(), #gql_typename.into());
|
||||
continue;
|
||||
}
|
||||
#(#resolvers)*
|
||||
async_graphql::anyhow::bail!(async_graphql::GQLQueryError::FieldNotFound {
|
||||
field_name: field.name.clone(),
|
||||
object: #gql_typename,
|
||||
}.with_position(field.position));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(async_graphql::serde_json::Value::Object(result))
|
||||
}
|
||||
}
|
||||
|
||||
impl async_graphql::GQLObject for #ident {}
|
||||
};
|
||||
Ok(expanded.into())
|
||||
}
|
68
examples/helloworld.rs
Normal file
68
examples/helloworld.rs
Normal file
|
@ -0,0 +1,68 @@
|
|||
use async_graphql::*;
|
||||
|
||||
#[async_graphql::Enum(name = "haha", desc = "hehe")]
|
||||
enum MyEnum {
|
||||
A,
|
||||
B,
|
||||
}
|
||||
|
||||
#[async_graphql::Object(name = "haha", desc = "hehe")]
|
||||
struct MyObj {
|
||||
#[field(
|
||||
attr,
|
||||
name = "a",
|
||||
arg(name = "a1", type = "i32"),
|
||||
arg(name = "a2", type = "f32")
|
||||
)]
|
||||
a: i32,
|
||||
|
||||
#[field(desc = "hehe")]
|
||||
b: i32,
|
||||
|
||||
#[field(arg(name = "input", type = "MyEnum"))]
|
||||
c: MyEnum,
|
||||
|
||||
#[field]
|
||||
child: ChildObj,
|
||||
}
|
||||
|
||||
#[async_graphql::Object]
|
||||
struct ChildObj {
|
||||
#[field(attr)]
|
||||
value: f32,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MyObjFields for MyObj {
|
||||
async fn a(&self, ctx: &ContextField<'_>, a1: i32, a2: f32) -> Result<i32> {
|
||||
Ok(a1 + a2 as i32)
|
||||
}
|
||||
|
||||
async fn b(&self, ctx: &ContextField<'_>) -> Result<i32> {
|
||||
Ok(999)
|
||||
}
|
||||
|
||||
async fn c(&self, ctx: &ContextField<'_>, input: MyEnum) -> Result<MyEnum> {
|
||||
Ok(input)
|
||||
}
|
||||
|
||||
async fn child(&self, ctx: &ContextField<'_>) -> async_graphql::Result<ChildObj> {
|
||||
Ok(ChildObj { value: 10.0 })
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl ChildObjFields for ChildObj {
|
||||
async fn value(&self, ctx: &ContextField<'_>) -> Result<f32> {
|
||||
Ok(self.value)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() {
|
||||
let res = GQLQueryBuilder::new(MyObj { a: 10 }, GQLEmptyMutation, "{ b c(input:B) }")
|
||||
.execute()
|
||||
.await
|
||||
.unwrap();
|
||||
serde_json::to_writer_pretty(std::io::stdout(), &res).unwrap();
|
||||
}
|
85
src/context.rs
Normal file
85
src/context.rs
Normal file
|
@ -0,0 +1,85 @@
|
|||
use crate::{GQLInputValue, Result};
|
||||
use fnv::FnvHasher;
|
||||
use graphql_parser::query::{Field, SelectionSet, Value};
|
||||
use std::any::{Any, TypeId};
|
||||
use std::collections::HashMap;
|
||||
use std::hash::BuildHasherDefault;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
pub struct Variables(HashMap<String, serde_json::Value>);
|
||||
|
||||
impl Deref for Variables {
|
||||
type Target = HashMap<String, serde_json::Value>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Variables {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Data(HashMap<TypeId, Box<dyn Any + Sync + Send>, BuildHasherDefault<FnvHasher>>);
|
||||
|
||||
impl Data {
|
||||
pub fn insert<D: Any + Send + Sync>(&mut self, data: D) {
|
||||
self.0.insert(TypeId::of::<D>(), Box::new(data));
|
||||
}
|
||||
|
||||
pub fn remove<D: Any + Send + Sync>(&mut self) {
|
||||
self.0.remove(&TypeId::of::<D>());
|
||||
}
|
||||
}
|
||||
|
||||
pub type ContextSelectionSet<'a> = Context<'a, &'a SelectionSet>;
|
||||
pub type ContextField<'a> = Context<'a, &'a Field>;
|
||||
|
||||
pub struct Context<'a, T> {
|
||||
pub(crate) item: T,
|
||||
pub(crate) data: Option<&'a Data>,
|
||||
pub(crate) variables: Option<&'a Variables>,
|
||||
}
|
||||
|
||||
impl<'a, T> Deref for Context<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.item
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Context<'a, T> {
|
||||
pub fn with_item<R>(&self, item: R) -> Context<'a, R> {
|
||||
Context {
|
||||
item,
|
||||
data: self.data,
|
||||
variables: self.variables,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn data<D: Any + Send + Sync>(&self) -> Option<&D> {
|
||||
self.data.and_then(|data| {
|
||||
data.0
|
||||
.get(&TypeId::of::<D>())
|
||||
.and_then(|d| d.downcast_ref::<D>())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Context<'a, &'a Field> {
|
||||
pub fn param_value<T: GQLInputValue>(&self, name: &str) -> Result<T> {
|
||||
let value = self
|
||||
.arguments
|
||||
.iter()
|
||||
.find(|(n, _)| n == name)
|
||||
.map(|(_, v)| v)
|
||||
.cloned()
|
||||
.unwrap_or(Value::Null);
|
||||
GQLInputValue::parse(value)
|
||||
}
|
||||
}
|
25
src/datetime.rs
Normal file
25
src/datetime.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
use crate::{GQLQueryError, GQLScalar, Result, Value};
|
||||
use chrono::{DateTime, TimeZone, Utc};
|
||||
|
||||
impl GQLScalar for DateTime<Utc> {
|
||||
fn type_name() -> &'static str {
|
||||
"DateTime"
|
||||
}
|
||||
|
||||
fn parse(value: Value) -> Result<Self> {
|
||||
match value {
|
||||
Value::String(s) => Ok(Utc.datetime_from_str(&s, "%+")?),
|
||||
_ => {
|
||||
return Err(GQLQueryError::ExpectedType {
|
||||
expect: Self::type_name().to_string(),
|
||||
actual: value,
|
||||
}
|
||||
.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn into_json(self) -> Result<serde_json::Value> {
|
||||
Ok(self.to_rfc3339().into())
|
||||
}
|
||||
}
|
50
src/enum.rs
Normal file
50
src/enum.rs
Normal file
|
@ -0,0 +1,50 @@
|
|||
use crate::{GQLQueryError, GQLType, Result};
|
||||
use graphql_parser::query::Value;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct GQLEnumItem<T> {
|
||||
pub name: &'static str,
|
||||
pub desc: Option<&'static str>,
|
||||
pub value: T,
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[async_trait::async_trait]
|
||||
pub trait GQLEnum: GQLType + Sized + Eq + Send + Copy + Sized + 'static {
|
||||
fn items() -> &'static [GQLEnumItem<Self>];
|
||||
|
||||
fn parse_enum(value: Value) -> Result<Self> {
|
||||
match value {
|
||||
Value::Enum(s) => {
|
||||
let items = Self::items();
|
||||
for item in items {
|
||||
if item.name == s {
|
||||
return Ok(item.value);
|
||||
}
|
||||
}
|
||||
Err(GQLQueryError::InvalidEnumValue {
|
||||
enum_type: Self::type_name(),
|
||||
value: s,
|
||||
}
|
||||
.into())
|
||||
}
|
||||
_ => {
|
||||
return Err(GQLQueryError::ExpectedType {
|
||||
expect: Self::type_name(),
|
||||
actual: value,
|
||||
}
|
||||
.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_enum(self) -> Result<serde_json::Value> {
|
||||
let items = Self::items();
|
||||
for item in items {
|
||||
if item.value == self {
|
||||
return Ok(item.name.clone().into());
|
||||
}
|
||||
}
|
||||
unreachable!()
|
||||
}
|
||||
}
|
77
src/error.rs
Normal file
77
src/error.rs
Normal file
|
@ -0,0 +1,77 @@
|
|||
use crate::Error;
|
||||
use graphql_parser::query::Value;
|
||||
use graphql_parser::Pos;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error("{0}")]
|
||||
pub struct GQLQueryParseError(pub(crate) String);
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum GQLQueryError {
|
||||
#[error("Not supported.")]
|
||||
NotSupported,
|
||||
|
||||
#[error("Expected type \"{expect}\", found {actual}.")]
|
||||
ExpectedType { expect: String, actual: Value },
|
||||
|
||||
#[error("Cannot query field \"{field_name}\" on type \"{object}\".")]
|
||||
FieldNotFound {
|
||||
field_name: String,
|
||||
object: &'static str,
|
||||
},
|
||||
|
||||
#[error("Unknown operation named \"{name}\"")]
|
||||
UnknownOperationNamed { name: String },
|
||||
|
||||
#[error("Type \"{object}\" must have a selection of subfields.")]
|
||||
MustHaveSubFields { object: &'static str },
|
||||
|
||||
#[error("Schema is not configured for mutations.")]
|
||||
NotConfiguredMutations,
|
||||
|
||||
#[error("Invalid value for enum \"{enum_type}\"")]
|
||||
InvalidEnumValue { enum_type: String, value: String },
|
||||
}
|
||||
|
||||
pub trait GQLErrorWithPosition {
|
||||
type Result;
|
||||
fn with_position(self, position: Pos) -> GQLPositionError;
|
||||
}
|
||||
|
||||
impl<T: Into<Error>> GQLErrorWithPosition for T {
|
||||
type Result = GQLPositionError;
|
||||
|
||||
fn with_position(self, position: Pos) -> GQLPositionError {
|
||||
GQLPositionError {
|
||||
position,
|
||||
inner: self.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub struct GQLPositionError {
|
||||
pub position: Pos,
|
||||
pub inner: Error,
|
||||
}
|
||||
|
||||
impl GQLPositionError {
|
||||
pub fn new(position: Pos, inner: Error) -> Self {
|
||||
Self { position, inner }
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> Error {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for GQLPositionError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}:{}: {}",
|
||||
self.position.line, self.position.column, self.inner
|
||||
)
|
||||
}
|
||||
}
|
35
src/lib.rs
Normal file
35
src/lib.rs
Normal file
|
@ -0,0 +1,35 @@
|
|||
#[macro_use]
|
||||
extern crate thiserror;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
mod context;
|
||||
mod r#enum;
|
||||
mod error;
|
||||
mod query;
|
||||
mod scalar;
|
||||
mod r#type;
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
mod datetime;
|
||||
#[cfg(feature = "uuid")]
|
||||
mod uuid;
|
||||
|
||||
pub use anyhow;
|
||||
pub use async_trait;
|
||||
pub use graphql_parser;
|
||||
pub use serde_json;
|
||||
|
||||
pub use async_graphql_derive::{Enum, InputObject, Object};
|
||||
pub use context::{Context, ContextField, ContextSelectionSet, Data, Variables};
|
||||
pub use error::{GQLErrorWithPosition, GQLPositionError, GQLQueryError, GQLQueryParseError};
|
||||
pub use graphql_parser::query::Value;
|
||||
pub use query::GQLQueryBuilder;
|
||||
pub use r#enum::{GQLEnum, GQLEnumItem};
|
||||
pub use r#type::{
|
||||
GQLEmptyMutation, GQLInputObject, GQLInputValue, GQLObject, GQLOutputValue, GQLType,
|
||||
};
|
||||
pub use scalar::GQLScalar;
|
||||
|
||||
pub type Result<T> = anyhow::Result<T>;
|
||||
pub type Error = anyhow::Error;
|
111
src/query.rs
Normal file
111
src/query.rs
Normal file
|
@ -0,0 +1,111 @@
|
|||
use crate::{
|
||||
Context, Data, GQLErrorWithPosition, GQLObject, GQLQueryError, GQLQueryParseError, Result,
|
||||
Variables,
|
||||
};
|
||||
use graphql_parser::parse_query;
|
||||
use graphql_parser::query::{Definition, OperationDefinition};
|
||||
|
||||
pub struct GQLQueryBuilder<'a, Query, Mutation> {
|
||||
query: Query,
|
||||
mutation: Mutation,
|
||||
query_source: &'a str,
|
||||
operation_name: Option<&'a str>,
|
||||
variables: Option<&'a Variables>,
|
||||
data: Option<&'a Data>,
|
||||
}
|
||||
|
||||
impl<'a, Query, Mutation> GQLQueryBuilder<'a, Query, Mutation> {
|
||||
pub fn new(query: Query, mutation: Mutation, query_source: &'a str) -> Self {
|
||||
Self {
|
||||
query,
|
||||
mutation,
|
||||
query_source,
|
||||
operation_name: None,
|
||||
variables: None,
|
||||
data: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn operator_name(self, name: &'a str) -> Self {
|
||||
GQLQueryBuilder {
|
||||
operation_name: Some(name),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn variables(self, vars: &'a Variables) -> Self {
|
||||
GQLQueryBuilder {
|
||||
variables: Some(vars),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn data(self, data: &'a Data) -> Self {
|
||||
GQLQueryBuilder {
|
||||
data: Some(data),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn execute(self) -> Result<serde_json::Value>
|
||||
where
|
||||
Query: GQLObject,
|
||||
Mutation: GQLObject,
|
||||
{
|
||||
let document =
|
||||
parse_query(self.query_source).map_err(|err| GQLQueryParseError(err.to_string()))?;
|
||||
|
||||
for definition in &document.definitions {
|
||||
match definition {
|
||||
Definition::Operation(OperationDefinition::SelectionSet(selection_set)) => {
|
||||
if self.operation_name.is_none() {
|
||||
let ctx = Context {
|
||||
item: selection_set,
|
||||
data: self.data.as_deref(),
|
||||
variables: self.variables.as_deref(),
|
||||
};
|
||||
return self.query.resolve(&ctx).await;
|
||||
}
|
||||
}
|
||||
Definition::Operation(OperationDefinition::Query(query)) => {
|
||||
if self.operation_name.is_none()
|
||||
|| self.operation_name == query.name.as_ref().map(|s| s.as_str())
|
||||
{
|
||||
let ctx = Context {
|
||||
item: &query.selection_set,
|
||||
data: self.data.as_deref(),
|
||||
variables: self.variables.as_deref(),
|
||||
};
|
||||
return self.query.resolve(&ctx).await;
|
||||
}
|
||||
}
|
||||
Definition::Operation(OperationDefinition::Mutation(mutation)) => {
|
||||
if self.operation_name.is_none()
|
||||
|| self.operation_name == mutation.name.as_ref().map(|s| s.as_str())
|
||||
{
|
||||
let ctx = Context {
|
||||
item: &mutation.selection_set,
|
||||
data: self.data.as_deref(),
|
||||
variables: self.variables.as_deref(),
|
||||
};
|
||||
return self.mutation.resolve(&ctx).await;
|
||||
}
|
||||
}
|
||||
Definition::Operation(OperationDefinition::Subscription(subscription)) => {
|
||||
anyhow::bail!(GQLQueryError::NotSupported.with_position(subscription.position));
|
||||
}
|
||||
Definition::Fragment(fragment) => {
|
||||
anyhow::bail!(GQLQueryError::NotSupported.with_position(fragment.position));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(operation_name) = self.operation_name {
|
||||
anyhow::bail!(GQLQueryError::UnknownOperationNamed {
|
||||
name: operation_name.to_string()
|
||||
});
|
||||
}
|
||||
|
||||
Ok(serde_json::Value::Null)
|
||||
}
|
||||
}
|
137
src/scalar.rs
Normal file
137
src/scalar.rs
Normal file
|
@ -0,0 +1,137 @@
|
|||
use crate::r#type::{GQLInputValue, GQLOutputValue, GQLType};
|
||||
use crate::{ContextSelectionSet, GQLQueryError, Result};
|
||||
use graphql_parser::query::Value;
|
||||
|
||||
pub trait GQLScalar: Sized + Send {
|
||||
fn type_name() -> &'static str;
|
||||
fn parse(value: Value) -> Result<Self>;
|
||||
fn into_json(self) -> Result<serde_json::Value>;
|
||||
}
|
||||
|
||||
impl<T: GQLScalar> GQLType for T {
|
||||
fn type_name() -> String {
|
||||
T::type_name().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: GQLScalar> GQLInputValue for T {
|
||||
fn parse(value: Value) -> Result<Self> {
|
||||
T::parse(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<T: GQLScalar> GQLOutputValue for T {
|
||||
async fn resolve(self, _: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
|
||||
T::into_json(self)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_integer_scalars {
|
||||
($($ty:ty),*) => {
|
||||
$(
|
||||
impl GQLScalar for $ty {
|
||||
fn type_name() -> &'static str {
|
||||
"Int!"
|
||||
}
|
||||
|
||||
fn parse(value: Value) -> Result<Self> {
|
||||
match value {
|
||||
Value::Int(n) => Ok(n.as_i64().unwrap() as Self),
|
||||
_ => {
|
||||
return Err(GQLQueryError::ExpectedType {
|
||||
expect: <Self as GQLScalar>::type_name().to_string(),
|
||||
actual: value,
|
||||
}
|
||||
.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn into_json(self) -> Result<serde_json::Value> {
|
||||
Ok(self.into())
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl_integer_scalars!(i8, i16, i32, i64, u8, u16, u32, u64);
|
||||
|
||||
macro_rules! impl_float_scalars {
|
||||
($($ty:ty),*) => {
|
||||
$(
|
||||
impl GQLScalar for $ty {
|
||||
fn type_name() -> &'static str {
|
||||
"Float!"
|
||||
}
|
||||
|
||||
fn parse(value: Value) -> Result<Self> {
|
||||
match value {
|
||||
Value::Int(n) => Ok(n.as_i64().unwrap() as Self),
|
||||
Value::Float(n) => Ok(n as Self),
|
||||
_ => {
|
||||
return Err(GQLQueryError::ExpectedType {
|
||||
expect: <Self as GQLScalar>::type_name().to_string(),
|
||||
actual: value,
|
||||
}
|
||||
.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn into_json(self) -> Result<serde_json::Value> {
|
||||
Ok(self.into())
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl_float_scalars!(f32, f64);
|
||||
|
||||
impl GQLScalar for String {
|
||||
fn type_name() -> &'static str {
|
||||
"String!"
|
||||
}
|
||||
|
||||
fn parse(value: Value) -> Result<Self> {
|
||||
match value {
|
||||
Value::String(s) => Ok(s),
|
||||
_ => {
|
||||
return Err(GQLQueryError::ExpectedType {
|
||||
expect: <Self as GQLScalar>::type_name().to_string(),
|
||||
actual: value,
|
||||
}
|
||||
.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn into_json(self) -> Result<serde_json::Value> {
|
||||
Ok(self.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl GQLScalar for bool {
|
||||
fn type_name() -> &'static str {
|
||||
"Boolean!"
|
||||
}
|
||||
|
||||
fn parse(value: Value) -> Result<Self> {
|
||||
match value {
|
||||
Value::Boolean(n) => Ok(n),
|
||||
_ => {
|
||||
return Err(GQLQueryError::ExpectedType {
|
||||
expect: <Self as GQLScalar>::type_name().to_string(),
|
||||
actual: value,
|
||||
}
|
||||
.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn into_json(self) -> Result<serde_json::Value> {
|
||||
Ok(self.into())
|
||||
}
|
||||
}
|
105
src/type.rs
Normal file
105
src/type.rs
Normal file
|
@ -0,0 +1,105 @@
|
|||
use crate::{ContextSelectionSet, GQLErrorWithPosition, GQLQueryError, Result};
|
||||
use graphql_parser::query::Value;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub trait GQLType {
|
||||
fn type_name() -> String;
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub trait GQLInputValue: GQLType + Sized {
|
||||
fn parse(value: Value) -> Result<Self>;
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[async_trait::async_trait]
|
||||
pub trait GQLOutputValue: GQLType {
|
||||
async fn resolve(self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value>;
|
||||
}
|
||||
|
||||
impl<T: GQLType> GQLType for Vec<T> {
|
||||
fn type_name() -> String {
|
||||
format!("[{}]!", T::type_name()).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: GQLInputValue> GQLInputValue for Vec<T> {
|
||||
fn parse(value: Value) -> Result<Self> {
|
||||
match value {
|
||||
Value::List(values) => {
|
||||
let mut result = Vec::new();
|
||||
for value in values {
|
||||
result.push(GQLInputValue::parse(value)?);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
_ => {
|
||||
return Err(GQLQueryError::ExpectedType {
|
||||
expect: Self::type_name(),
|
||||
actual: value,
|
||||
}
|
||||
.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<T: GQLOutputValue + Send> GQLOutputValue for Vec<T> {
|
||||
async fn resolve(self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
|
||||
let mut res = Vec::new();
|
||||
for item in self {
|
||||
res.push(item.resolve(ctx).await?);
|
||||
}
|
||||
Ok(res.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: GQLType> GQLType for Option<T> {
|
||||
fn type_name() -> String {
|
||||
format!("{}", T::type_name().trim_end_matches("!")).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: GQLInputValue> GQLInputValue for Option<T> {
|
||||
fn parse(value: Value) -> Result<Self> {
|
||||
match value {
|
||||
Value::Null => Ok(None),
|
||||
_ => Ok(Some(GQLInputValue::parse(value)?)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<T: GQLOutputValue + Send> GQLOutputValue for Option<T> {
|
||||
async fn resolve(self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
|
||||
if let Some(inner) = self {
|
||||
inner.resolve(ctx).await
|
||||
} else {
|
||||
Ok(serde_json::Value::Null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub trait GQLObject: GQLType + GQLOutputValue {}
|
||||
|
||||
pub struct GQLEmptyMutation;
|
||||
|
||||
impl GQLType for GQLEmptyMutation {
|
||||
fn type_name() -> String {
|
||||
"EmptyMutation".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl GQLOutputValue for GQLEmptyMutation {
|
||||
async fn resolve(self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
|
||||
anyhow::bail!(GQLQueryError::NotConfiguredMutations.with_position(ctx.item.span.0));
|
||||
}
|
||||
}
|
||||
|
||||
impl GQLObject for GQLEmptyMutation {}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub trait GQLInputObject: GQLType + GQLInputValue {}
|
25
src/uuid.rs
Normal file
25
src/uuid.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
use crate::{GQLQueryError, GQLScalar, Result, Value};
|
||||
use uuid::Uuid;
|
||||
|
||||
impl GQLScalar for Uuid {
|
||||
fn type_name() -> &'static str {
|
||||
"UUID"
|
||||
}
|
||||
|
||||
fn parse(value: Value) -> Result<Self> {
|
||||
match value {
|
||||
Value::String(s) => Ok(Uuid::parse_str(&s)?),
|
||||
_ => {
|
||||
return Err(GQLQueryError::ExpectedType {
|
||||
expect: Self::type_name().to_string(),
|
||||
actual: value,
|
||||
}
|
||||
.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn into_json(self) -> Result<serde_json::Value> {
|
||||
Ok(self.to_string().into())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user