create type info

This commit is contained in:
sunli 2020-03-03 11:48:00 +08:00
parent f090df76f7
commit 8f448c91e9
29 changed files with 554 additions and 170 deletions

View File

@ -52,12 +52,16 @@
- [X] Object
- [X] Enum
- [X] InputObject
- [ ] Field default value
- [X] Deprecated flag
- [ ] Interface
- [ ] Union
- [ ] Generic Types
- [ ] Query
- [X] Fields
- [X] Arguments
- [ ] Default value
- [X] Deprecated flag
- [X] Alias
- [ ] Fragments
- [ ] Inline fragments
@ -69,6 +73,16 @@
- [ ] @deprecated
- [ ] Custom Directive
- [ ] Schema
- [ ] Validation rules
## License
Licensed under either of
* Apache License, Version 2.0,
(./LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
* MIT license (./LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
## References

View File

@ -20,3 +20,4 @@ proc-macro2 = "1.0.6"
syn = { version = "1.0.13", features = ["full"] }
quote = "1.0.2"
Inflector = "0.11.4"
graphql-parser = "0.2.3"

View File

@ -1,3 +1,5 @@
use crate::utils::parse_value;
use graphql_parser::query::Value;
use syn::{Attribute, AttributeArgs, Error, Meta, MetaList, NestedMeta, Result, Type};
#[derive(Debug)]
@ -62,6 +64,7 @@ pub struct Argument {
pub name: String,
pub desc: Option<String>,
pub ty: Type,
pub default: Option<Value>,
}
#[derive(Debug)]
@ -72,6 +75,7 @@ pub struct Field {
pub ty: Type,
pub is_owned: bool,
pub arguments: Vec<Argument>,
pub deprecation: Option<String>,
}
impl Field {
@ -82,6 +86,7 @@ impl Field {
let mut ty = None;
let mut is_owned = false;
let mut arguments = Vec::new();
let mut deprecation = None;
for meta in &ls.nested {
match meta {
@ -130,6 +135,15 @@ impl Field {
"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)) => {
@ -137,6 +151,7 @@ impl Field {
let mut name = None;
let mut desc = None;
let mut ty = None;
let mut default = None;
for meta in &ls.nested {
if let NestedMeta::Meta(Meta::NameValue(nv)) = meta {
@ -158,6 +173,23 @@ impl Field {
"Attribute 'desc' 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) => 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.",
));
}
} 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()) {
@ -187,6 +219,7 @@ impl Field {
name: name.unwrap(),
desc,
ty: ty.unwrap(),
default,
});
}
}
@ -209,6 +242,7 @@ impl Field {
ty: ty.unwrap(),
is_owned,
arguments,
deprecation,
})
}
}
@ -268,12 +302,14 @@ impl Enum {
pub struct EnumItem {
pub name: Option<String>,
pub desc: Option<String>,
pub deprecation: Option<String>,
}
impl EnumItem {
pub fn parse(attrs: &[Attribute]) -> Result<Self> {
let mut name = None;
let mut desc = None;
let mut deprecation = None;
for attr in attrs {
if attr.path.is_ident("item") {
@ -299,6 +335,15 @@ impl EnumItem {
"Attribute 'desc' 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.",
));
}
}
}
_ => {}
@ -308,7 +353,11 @@ impl EnumItem {
}
}
Ok(Self { name, desc })
Ok(Self {
name,
desc,
deprecation,
})
}
}
@ -317,6 +366,7 @@ pub struct InputField {
pub internal: bool,
pub name: Option<String>,
pub desc: Option<String>,
pub default: Option<Value>,
}
impl InputField {
@ -324,6 +374,7 @@ impl InputField {
let mut internal = false;
let mut name = None;
let mut desc = None;
let mut default = None;
for attr in attrs {
if attr.path.is_ident("field") {
@ -352,6 +403,23 @@ impl InputField {
"Attribute 'desc' 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) => default = Some(value),
Err(err) => {
return Err(Error::new_spanned(
&lit,
format!("Invalid value: {}", err),
));
}
}
} else {
return Err(Error::new_spanned(
&nv.lit,
"Attribute 'default' should be a string.",
));
}
}
}
_ => {}
@ -361,6 +429,58 @@ impl InputField {
}
}
Ok(Self {
internal,
name,
desc,
default,
})
}
}
#[derive(Debug)]
pub struct InputObject {
pub internal: bool,
pub name: Option<String>,
pub desc: Option<String>,
}
impl InputObject {
pub fn parse(args: AttributeArgs) -> Result<Self> {
let mut internal = false;
let mut name = None;
let mut desc = None;
for arg in args {
match arg {
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.",
));
}
}
}
_ => {}
}
}
Ok(Self {
internal,
name,

View File

@ -18,6 +18,8 @@ pub fn generate(enum_args: &args::Enum, input: &DeriveInput) -> Result<TokenStre
let mut enum_items = Vec::new();
let mut items = Vec::new();
let mut schema_enum_items = Vec::new();
for variant in &e.variants {
if !variant.fields.is_empty() {
return Err(Error::new_spanned(
@ -35,18 +37,31 @@ pub fn generate(enum_args: &args::Enum, input: &DeriveInput) -> Result<TokenStre
.name
.take()
.unwrap_or_else(|| variant.ident.to_string());
let item_deprecation = item_args
.deprecation
.as_ref()
.map(|s| quote! { Some(#s) })
.unwrap_or_else(|| quote! {None});
let item_desc = item_args
.desc
.as_ref()
.map(|s| quote! { Some(#s) })
.unwrap_or_else(|| quote! {None});
enum_items.push(&variant.ident);
let desc = match item_args.desc.take() {
Some(desc) => quote! { Some(#desc) },
None => quote! { None },
};
items.push(quote! {
#crate_name::GQLEnumItem {
name: #gql_item_name,
desc: #desc,
desc: #item_desc,
value: #ident::#item_ident,
}
});
schema_enum_items.push(quote! {
#crate_name::schema::EnumValue {
name: #gql_item_name,
description: #item_desc,
deprecation: #item_deprecation,
}
});
}
let expanded = quote! {
@ -66,6 +81,14 @@ pub fn generate(enum_args: &args::Enum, input: &DeriveInput) -> Result<TokenStre
fn type_name() -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Borrowed(#gql_typename)
}
fn create_type_info(registry: &mut #crate_name::schema::Registry) -> String {
registry.create_type(&Self::type_name(), |registry| {
#crate_name::schema::Type::Enum {
enum_values: vec![#(#schema_enum_items),*],
}
})
}
}
impl #crate_name::GQLInputValue for #ident {

View File

@ -4,14 +4,32 @@ use proc_macro::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput, Error, Result};
pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<TokenStream> {
pub fn generate(object_args: &args::InputObject, input: &DeriveInput) -> Result<TokenStream> {
let crate_name = get_crate_name(object_args.internal);
let ident = &input.ident;
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 mut struct_fields = Vec::new();
for field in &s.fields {
let vis = &field.vis;
let ty = &field.ty;
let ident = &field.ident;
struct_fields.push(quote! {
#vis #ident: #ty
});
}
let new_struct = quote! {
#(#attrs)*
#vis struct #ident {
#(#struct_fields),*
}
};
let gql_typename = object_args
.name
.clone()
@ -20,12 +38,26 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<Token
let mut get_fields = Vec::new();
let mut get_json_fields = Vec::new();
let mut fields = Vec::new();
let mut schema_fields = Vec::new();
for field in &s.fields {
let field_args = args::InputField::parse(&field.attrs)?;
let ident = field.ident.as_ref().unwrap();
let ty = &field.ty;
let name = field_args.name.unwrap_or_else(|| ident.to_string());
let desc = field_args
.desc
.as_ref()
.map(|s| quote! {Some(#s)})
.unwrap_or_else(|| quote! {None});
let default = field_args
.default
.as_ref()
.map(|v| {
let s = v.to_string();
quote! {Some(#s)}
})
.unwrap_or_else(|| quote! {None});
get_fields.push(quote! {
let #ident:#ty = #crate_name::GQLInputValue::parse(obj.remove(#name).unwrap_or(#crate_name::Value::Null))?;
});
@ -33,15 +65,29 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<Token
let #ident:#ty = #crate_name::GQLInputValue::parse_from_json(obj.remove(#name).unwrap_or(#crate_name::serde_json::Value::Null))?;
});
fields.push(ident);
schema_fields.push(quote! {
#crate_name::schema::InputValue {
name: #name,
description: #desc,
ty: <#ty as #crate_name::GQLType>::create_type_info(registry),
default_value: #default,
}
})
}
let expanded = quote! {
#input
#new_struct
impl #crate_name::GQLType for #ident {
fn type_name() -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Borrowed(#gql_typename)
}
fn create_type_info(registry: &mut #crate_name::schema::Registry) -> String {
registry.create_type(&Self::type_name(), |registry| #crate_name::schema::Type::InputObject {
input_fields: vec![#(#schema_fields),*]
})
}
}
impl #crate_name::GQLInputValue for #ident {

View File

@ -41,7 +41,7 @@ pub fn Enum(args: TokenStream, input: TokenStream) -> TokenStream {
#[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)) {
let object_args = match args::InputObject::parse(parse_macro_input!(args as AttributeArgs)) {
Ok(enum_args) => enum_args,
Err(err) => return err.to_compile_error().into(),
};

View File

@ -22,25 +22,59 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<Token
let trait_ident = Ident::new(&format!("{}Fields", ident.to_string()), Span::call_site());
let mut trait_fns = Vec::new();
let mut resolvers = Vec::new();
let mut schema_fields = Vec::new();
for field in &object_args.fields {
let ty = &field.ty;
let field_name = &field.name;
let desc = field
.desc
.as_ref()
.map(|s| quote! {Some(#s)})
.unwrap_or_else(|| quote! {None});
let deprecation = field
.deprecation
.as_ref()
.map(|s| quote! { Some(#s) })
.unwrap_or_else(|| quote! {None});
let mut decl_params = Vec::new();
let mut get_params = Vec::new();
let mut use_params = Vec::new();
let mut schema_args = Vec::new();
for arg in &field.arguments {
let name = Ident::new(&arg.name, Span::call_site());
let ty = &arg.ty;
let name_str = name.to_string();
let snake_case_name = Ident::new(&name.to_string().to_snake_case(), ident.span());
let desc = arg
.desc
.as_ref()
.map(|s| quote! { Some(#s) })
.unwrap_or_else(|| quote! {None});
let default = arg
.default
.as_ref()
.map(|v| {
let s = v.to_string();
quote! {Some(#s)}
})
.unwrap_or_else(|| quote! {None});
decl_params.push(quote! { #snake_case_name: #ty });
get_params.push(quote! {
let #snake_case_name: #ty = ctx_field.param_value(#name_str)?;
});
use_params.push(quote! { #snake_case_name });
schema_args.push(quote! {
#crate_name::schema::InputValue {
name: #name_str,
description: #desc,
ty: <#ty as #crate_name::GQLType>::create_type_info(registry),
default_value: #default,
}
});
}
let resolver = Ident::new(
@ -52,11 +86,11 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<Token
);
if field.is_owned {
trait_fns.push(quote! {
async fn #resolver(&self, ctx: &#crate_name::ContextField<'_>, #(#decl_params),*) -> #crate_name::Result<#ty>;
async fn #resolver(&self, ctx: &#crate_name::Context<'_>, #(#decl_params),*) -> #crate_name::Result<#ty>;
});
} else {
trait_fns.push(quote! {
async fn #resolver<'a>(&'a self, ctx: &#crate_name::ContextField<'_>, #(#decl_params),*) -> #crate_name::Result<&'a #ty>;
async fn #resolver<'a>(&'a self, ctx: &#crate_name::Context<'_>, #(#decl_params),*) -> #crate_name::Result<&'a #ty>;
});
}
@ -73,6 +107,16 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<Token
continue;
}
});
schema_fields.push(quote! {
#crate_name::schema::Field {
name: #field_name,
description: #desc,
args: vec![#(#schema_args),*],
ty: <#ty as #crate_name::GQLType>::create_type_info(registry),
deprecation: #deprecation,
}
});
}
let expanded = quote! {
@ -87,6 +131,12 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<Token
fn type_name() -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Borrowed(#gql_typename)
}
fn create_type_info(registry: &mut #crate_name::schema::Registry) -> String {
registry.create_type(&Self::type_name(), |registry| #crate_name::schema::Type::Object {
fields: vec![#(#schema_fields),*]
})
}
}
#[#crate_name::async_trait::async_trait]

View File

@ -11,3 +11,23 @@ pub fn get_crate_name(internal: bool) -> TokenStream {
}
}
}
pub fn parse_value(
s: &str,
) -> Result<graphql_parser::query::Value, graphql_parser::query::ParseError> {
let mut doc =
graphql_parser::query::parse_query(&format!("query ($a:Int!={}) {{ dummy }}", s))?;
let definition = doc.definitions.remove(0);
if let graphql_parser::query::Definition::Operation(
graphql_parser::query::OperationDefinition::Query(graphql_parser::query::Query {
mut variable_definitions,
..
}),
) = definition
{
let var = variable_definitions.remove(0);
Ok(var.default_value.unwrap())
} else {
unreachable!()
}
}

View File

@ -1,3 +1,15 @@
#[async_graphql::Enum]
enum MyEnum {
A,
B,
}
#[async_graphql::InputObject]
struct MyInputObj {
#[field(default = "\"hehe\"")]
a: i32,
b: i32,
}
#[async_graphql::Object(
field(name = "a", type = "i32"),
field(owned, name = "b", type = "i32"),
@ -9,21 +21,15 @@ struct MyObj {
#[async_trait::async_trait]
impl MyObjFields for MyObj {
async fn a<'a>(
&'a self,
ctx: &async_graphql::ContextField<'_>,
) -> async_graphql::Result<&'a i32> {
async fn a<'a>(&'a self, ctx: &async_graphql::Context<'_>) -> async_graphql::Result<&'a i32> {
Ok(&self.value)
}
async fn b(&self, ctx: &async_graphql::ContextField<'_>) -> async_graphql::Result<i32> {
async fn b(&self, ctx: &async_graphql::Context<'_>) -> async_graphql::Result<i32> {
Ok(999)
}
async fn c(
&self,
ctx: &async_graphql::ContextField<'_>,
) -> async_graphql::Result<Option<String>> {
async fn c(&self, ctx: &async_graphql::Context<'_>) -> async_graphql::Result<Option<String>> {
Ok(Some(format!("**{}**", self.value)))
}
}

View File

@ -1,8 +1,32 @@
use crate::r#type::{GQLInputValue, GQLOutputValue, GQLType};
use crate::{ContextSelectionSet, Result};
use crate::{schema, ContextSelectionSet, Result};
use graphql_parser::query::Value;
use std::borrow::Cow;
#[doc(hidden)]
pub trait GQLType {
fn type_name() -> Cow<'static, str>;
fn create_type_info(registry: &mut schema::Registry) -> String;
}
#[doc(hidden)]
pub trait GQLInputValue: GQLType + Sized {
fn parse(value: Value) -> Result<Self>;
fn parse_from_json(value: serde_json::Value) -> Result<Self>;
}
#[doc(hidden)]
#[async_trait::async_trait]
pub trait GQLOutputValue: GQLType {
async fn resolve(&self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value>;
}
#[doc(hidden)]
pub trait GQLObject: GQLOutputValue {}
#[doc(hidden)]
pub trait GQLInputObject: GQLInputValue {}
pub trait Scalar: Sized + Send {
fn type_name() -> &'static str;
fn parse(value: Value) -> Result<Self>;
@ -14,6 +38,10 @@ impl<T: Scalar> GQLType for T {
fn type_name() -> Cow<'static, str> {
Cow::Borrowed(T::type_name())
}
fn create_type_info(registry: &mut schema::Registry) -> String {
registry.create_type(T::type_name(), |_| schema::Type::Scalar)
}
}
impl<T: Scalar> GQLInputValue for T {

View File

@ -36,16 +36,16 @@ impl Data {
}
}
pub type ContextSelectionSet<'a> = Context<'a, &'a SelectionSet>;
pub type ContextField<'a> = Context<'a, &'a Field>;
pub type ContextSelectionSet<'a> = ContextBase<'a, &'a SelectionSet>;
pub type Context<'a> = ContextBase<'a, &'a Field>;
pub struct Context<'a, T> {
pub struct ContextBase<'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> {
impl<'a, T> Deref for ContextBase<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
@ -53,10 +53,10 @@ impl<'a, T> Deref for Context<'a, T> {
}
}
impl<'a, T> Context<'a, T> {
impl<'a, T> ContextBase<'a, T> {
#[doc(hidden)]
pub fn with_item<R>(&self, item: R) -> Context<'a, R> {
Context {
pub fn with_item<R>(&self, item: R) -> ContextBase<'a, R> {
ContextBase {
item,
data: self.data,
variables: self.variables,
@ -72,7 +72,7 @@ impl<'a, T> Context<'a, T> {
}
}
impl<'a> Context<'a, &'a Field> {
impl<'a> ContextBase<'a, &'a Field> {
#[doc(hidden)]
pub fn param_value<T: GQLInputValue>(&self, name: &str) -> Result<T> {
let value = self

View File

@ -33,13 +33,12 @@
#[macro_use]
extern crate thiserror;
mod base;
mod context;
mod error;
mod query;
mod scalar;
mod query_schema;
mod scalars;
mod schema;
mod r#type;
mod types;
#[doc(hidden)]
@ -52,11 +51,11 @@ pub use graphql_parser;
pub use serde_json;
pub use async_graphql_derive::{Enum, InputObject, Object};
pub use context::{Context, ContextField, Data, Variables};
pub use base::Scalar;
pub use context::{Context, ContextBase, Data, Variables};
pub use error::{ErrorWithPosition, PositionError, QueryError, QueryParseError};
pub use graphql_parser::query::Value;
pub use query::QueryBuilder;
pub use scalar::Scalar;
pub use scalars::ID;
pub use types::GQLEmptyMutation;
@ -65,8 +64,10 @@ pub type Error = anyhow::Error;
// internal types
#[doc(hidden)]
pub use base::{GQLInputObject, GQLInputValue, GQLObject, GQLOutputValue, GQLType};
#[doc(hidden)]
pub use context::ContextSelectionSet;
#[doc(hidden)]
pub use r#type::{GQLInputObject, GQLInputValue, GQLObject, GQLOutputValue, GQLType};
pub mod schema;
#[doc(hidden)]
pub use types::{GQLEnum, GQLEnumItem};

View File

@ -1,5 +1,5 @@
use crate::{
Context, Data, ErrorWithPosition, GQLObject, QueryError, QueryParseError, Result, Variables,
ContextBase, Data, ErrorWithPosition, GQLObject, QueryError, QueryParseError, Result, Variables,
};
use graphql_parser::parse_query;
use graphql_parser::query::{Definition, OperationDefinition};
@ -58,7 +58,7 @@ impl<'a, Query, Mutation> QueryBuilder<'a, Query, Mutation> {
match definition {
Definition::Operation(OperationDefinition::SelectionSet(selection_set)) => {
if self.operation_name.is_none() {
let ctx = Context {
let ctx = ContextBase {
item: selection_set,
data: self.data.as_deref(),
variables: self.variables.as_deref(),
@ -70,7 +70,7 @@ impl<'a, Query, Mutation> QueryBuilder<'a, Query, Mutation> {
if self.operation_name.is_none()
|| self.operation_name == query.name.as_ref().map(|s| s.as_str())
{
let ctx = Context {
let ctx = ContextBase {
item: &query.selection_set,
data: self.data.as_deref(),
variables: self.variables.as_deref(),
@ -82,7 +82,7 @@ impl<'a, Query, Mutation> QueryBuilder<'a, Query, Mutation> {
if self.operation_name.is_none()
|| self.operation_name == mutation.name.as_ref().map(|s| s.as_str())
{
let ctx = Context {
let ctx = ContextBase {
item: &mutation.selection_set,
data: self.data.as_deref(),
variables: self.variables.as_deref(),

View File

@ -1,4 +1,4 @@
use crate::{ContextField, Result};
use crate::{Context, Result};
use async_graphql_derive::Object;
#[Object(
@ -13,19 +13,19 @@ pub struct __EnumValue {}
#[async_trait::async_trait]
impl __EnumValueFields for __EnumValue {
async fn name(&self, _: &ContextField<'_>) -> Result<String> {
async fn name(&self, _: &Context<'_>) -> Result<String> {
todo!()
}
async fn description(&self, _: &ContextField<'_>) -> Result<Option<String>> {
async fn description(&self, _: &Context<'_>) -> Result<Option<String>> {
todo!()
}
async fn is_deprecated(&self, _: &ContextField<'_>) -> Result<bool> {
async fn is_deprecated(&self, _: &Context<'_>) -> Result<bool> {
todo!()
}
async fn deprecation_reason(&self, _: &ContextField<'_>) -> Result<Option<String>> {
async fn deprecation_reason(&self, _: &Context<'_>) -> Result<Option<String>> {
todo!()
}
}

View File

@ -1,5 +1,5 @@
use crate::schema::__Type;
use crate::{ContextField, Result};
use crate::query_schema::__Type;
use crate::{Context, Result};
use async_graphql_derive::Object;
#[Object(
@ -16,23 +16,23 @@ pub struct __Field {}
#[async_trait::async_trait]
#[allow()]
impl __FieldFields for __Field {
async fn name(&self, _: &ContextField<'_>) -> Result<String> {
async fn name(&self, _: &Context<'_>) -> Result<String> {
todo!()
}
async fn description(&self, _: &ContextField<'_>) -> Result<Option<String>> {
async fn description(&self, _: &Context<'_>) -> Result<Option<String>> {
todo!()
}
async fn ty(&self, _: &ContextField<'_>) -> Result<__Type> {
async fn ty(&self, _: &Context<'_>) -> Result<__Type> {
todo!()
}
async fn is_deprecated(&self, _: &ContextField<'_>) -> Result<bool> {
async fn is_deprecated(&self, _: &Context<'_>) -> Result<bool> {
todo!()
}
async fn deprecation_reason(&self, _: &ContextField<'_>) -> Result<Option<String>> {
async fn deprecation_reason(&self, _: &Context<'_>) -> Result<Option<String>> {
todo!()
}
}

View File

@ -1,5 +1,5 @@
use crate::schema::__Type;
use crate::{ContextField, Result};
use crate::query_schema::__Type;
use crate::{Context, Result};
use async_graphql_derive::Object;
#[Object(
@ -14,19 +14,19 @@ pub struct __InputValue {}
#[async_trait::async_trait]
impl __InputValueFields for __InputValue {
async fn name(&self, _: &ContextField<'_>) -> Result<String> {
async fn name(&self, _: &Context<'_>) -> Result<String> {
todo!()
}
async fn description(&self, _: &ContextField<'_>) -> Result<Option<String>> {
async fn description(&self, _: &Context<'_>) -> Result<Option<String>> {
todo!()
}
async fn ty(&self, _: &ContextField<'_>) -> Result<__Type> {
async fn ty(&self, _: &Context<'_>) -> Result<__Type> {
todo!()
}
async fn default_value(&self, _: &ContextField<'_>) -> Result<String> {
async fn default_value(&self, _: &Context<'_>) -> Result<String> {
todo!()
}
}

11
src/query_schema/mod.rs Normal file
View File

@ -0,0 +1,11 @@
mod enum_value;
mod field;
mod input_value;
mod kind;
mod r#type;
pub use enum_value::__EnumValue;
pub use field::__Field;
pub use input_value::__InputValue;
pub use kind::__TypeKind;
pub use r#type::__Type;

70
src/query_schema/type.rs Normal file
View File

@ -0,0 +1,70 @@
use crate::query_schema::{__EnumValue, __InputValue, __TypeKind};
use crate::{Context, Result};
use async_graphql_derive::Object;
#[Object(
internal,
desc = r#"
The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.
Depending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.
"#,
field(name = "kind", type = "__TypeKind", owned),
field(name = "name", type = "String", owned),
field(name = "description", type = "Option<String>", owned),
field(
name = "fields",
type = "Option<Vec<__Type>>",
owned,
arg(name = "includeDeprecated", type = "bool")
),
field(name = "interfaces", type = "Option<Vec<__Type>>", owned),
field(name = "possibleTypes", type = "Option<Vec<__Type>>", owned),
field(name = "enumValues", type = "Option<Vec<__EnumValue>>", owned),
field(name = "inputFields", type = "Option<Vec<__InputValue>>", owned),
field(name = "ofType", type = "Option<__Type>", owned)
)]
pub struct __Type {}
#[async_trait::async_trait]
impl __TypeFields for __Type {
async fn kind<'a>(&'a self, _: &Context<'_>) -> Result<__TypeKind> {
todo!()
}
async fn name<'a>(&'a self, _: &Context<'_>) -> Result<String> {
todo!()
}
async fn description<'a>(&'a self, _: &Context<'_>) -> Result<Option<String>> {
todo!()
}
async fn fields<'a>(
&'a self,
_: &Context<'_>,
include_deprecated: bool,
) -> Result<Option<Vec<__Type>>> {
todo!()
}
async fn interfaces<'a>(&'a self, _: &Context<'_>) -> Result<Option<Vec<__Type>>> {
todo!()
}
async fn possible_types<'a>(&'a self, _: &Context<'_>) -> Result<Option<Vec<__Type>>> {
todo!()
}
async fn enum_values<'a>(&'a self, _: &Context<'_>) -> Result<Option<Vec<__EnumValue>>> {
todo!()
}
async fn input_fields<'a>(&'a self, _: &Context<'_>) -> Result<Option<Vec<__InputValue>>> {
todo!()
}
async fn of_type<'a>(&'a self, _: &Context<'_>) -> Result<Option<__Type>> {
todo!()
}
}

View File

@ -44,4 +44,4 @@ macro_rules! impl_integer_scalars {
};
}
impl_integer_scalars!(i8, i16, i32, i64, u8, u16, u32, u64);
impl_integer_scalars!(i8, i16, i32, i64, u8, u16, u32, u64);

View File

@ -1,3 +1,4 @@
use crate::schema;
use crate::{ContextSelectionSet, GQLOutputValue, GQLType, QueryError, Result, Scalar, Value};
use std::borrow::Cow;
@ -41,6 +42,10 @@ impl<'a> GQLType for &'a str {
fn type_name() -> Cow<'static, str> {
Cow::Borrowed("String!")
}
fn create_type_info(registry: &mut schema::Registry) -> String {
registry.create_type(&Self::type_name(), |_| schema::Type::Scalar)
}
}
#[async_trait::async_trait]

View File

@ -1,11 +1,5 @@
mod enum_value;
mod field;
mod input_value;
mod kind;
mod registry;
mod r#type;
pub use enum_value::__EnumValue;
pub use field::__Field;
pub use input_value::__InputValue;
pub use kind::__TypeKind;
pub use r#type::__Type;
pub use r#type::{EnumValue, Field, InputValue, Type};
pub use registry::Registry;

22
src/schema/registry.rs Normal file
View File

@ -0,0 +1,22 @@
use crate::schema::Type;
use std::cell::RefCell;
use std::collections::HashMap;
#[derive(Default)]
pub struct Registry {
types: HashMap<String, Type>,
}
impl Registry {
pub fn create_type<F: FnMut(&mut Registry) -> Type>(&mut self, name: &str, mut f: F) -> String {
if !self.types.contains_key(name) {
let ty = f(self);
self.types.insert(name.to_string(), ty);
}
name.to_string()
}
pub fn get_type(&self, name: &str) -> Option<&Type> {
self.types.get(name)
}
}

View File

@ -1,70 +1,46 @@
use crate::schema::{__EnumValue, __InputValue, __TypeKind};
use crate::{ContextField, Result};
use async_graphql_derive::Object;
#[Object(
internal,
desc = r#"
The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.
Depending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.
"#,
field(name = "kind", type = "__TypeKind", owned),
field(name = "name", type = "String", owned),
field(name = "description", type = "Option<String>", owned),
field(
name = "fields",
type = "Option<Vec<__Type>>",
owned,
arg(name = "includeDeprecated", type = "bool")
),
field(name = "interfaces", type = "Option<Vec<__Type>>", owned),
field(name = "possibleTypes", type = "Option<Vec<__Type>>", owned),
field(name = "enumValues", type = "Option<Vec<__EnumValue>>", owned),
field(name = "inputFields", type = "Option<Vec<__InputValue>>", owned),
field(name = "ofType", type = "Option<__Type>", owned)
)]
pub struct __Type {}
#[async_trait::async_trait]
impl __TypeFields for __Type {
async fn kind<'a>(&'a self, _: &ContextField<'_>) -> Result<__TypeKind> {
todo!()
}
async fn name<'a>(&'a self, _: &ContextField<'_>) -> Result<String> {
todo!()
}
async fn description<'a>(&'a self, _: &ContextField<'_>) -> Result<Option<String>> {
todo!()
}
async fn fields<'a>(
&'a self,
_: &ContextField<'_>,
include_deprecated: bool,
) -> Result<Option<Vec<__Type>>> {
todo!()
}
async fn interfaces<'a>(&'a self, _: &ContextField<'_>) -> Result<Option<Vec<__Type>>> {
todo!()
}
async fn possible_types<'a>(&'a self, _: &ContextField<'_>) -> Result<Option<Vec<__Type>>> {
todo!()
}
async fn enum_values<'a>(&'a self, _: &ContextField<'_>) -> Result<Option<Vec<__EnumValue>>> {
todo!()
}
async fn input_fields<'a>(&'a self, _: &ContextField<'_>) -> Result<Option<Vec<__InputValue>>> {
todo!()
}
async fn of_type<'a>(&'a self, _: &ContextField<'_>) -> Result<Option<__Type>> {
todo!()
}
pub struct InputValue {
pub name: &'static str,
pub description: Option<&'static str>,
pub ty: String,
pub default_value: Option<&'static str>,
}
pub struct Field {
pub name: &'static str,
pub description: Option<&'static str>,
pub args: Vec<InputValue>,
pub ty: String,
pub deprecation: Option<&'static str>,
}
pub struct EnumValue {
pub name: &'static str,
pub description: Option<&'static str>,
pub deprecation: Option<&'static str>,
}
pub enum Type {
Scalar,
Object {
fields: Vec<Field>,
},
Interface {
fields: Vec<Field>,
possible_types: Vec<usize>,
},
Union {
possible_types: Vec<usize>,
},
Enum {
enum_values: Vec<EnumValue>,
},
InputObject {
input_fields: Vec<InputValue>,
},
List {
of_type: usize,
},
NonNull {
of_type: usize,
},
}

View File

@ -1,26 +0,0 @@
use crate::{ContextSelectionSet, Result};
use graphql_parser::query::Value;
use std::borrow::Cow;
#[doc(hidden)]
pub trait GQLType {
fn type_name() -> Cow<'static, str>;
}
#[doc(hidden)]
pub trait GQLInputValue: GQLType + Sized {
fn parse(value: Value) -> Result<Self>;
fn parse_from_json(value: serde_json::Value) -> Result<Self>;
}
#[doc(hidden)]
#[async_trait::async_trait]
pub trait GQLOutputValue: GQLType {
async fn resolve(&self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value>;
}
#[doc(hidden)]
pub trait GQLObject: GQLOutputValue {}
#[doc(hidden)]
pub trait GQLInputObject: GQLInputValue {}

View File

@ -1,5 +1,6 @@
use crate::{
ContextSelectionSet, ErrorWithPosition, GQLObject, GQLOutputValue, GQLType, QueryError, Result,
schema, ContextSelectionSet, ErrorWithPosition, GQLObject, GQLOutputValue, GQLType, QueryError,
Result,
};
use std::borrow::Cow;
@ -9,6 +10,12 @@ impl GQLType for GQLEmptyMutation {
fn type_name() -> Cow<'static, str> {
Cow::Borrowed("EmptyMutation")
}
fn create_type_info(registry: &mut schema::Registry) -> String {
registry.create_type(&Self::type_name(), |_| schema::Type::Object {
fields: Vec::new(),
})
}
}
#[async_trait::async_trait]

View File

@ -1,5 +1,5 @@
use crate::{
ContextSelectionSet, GQLInputValue, GQLOutputValue, GQLType, QueryError, Result, Value,
schema, ContextSelectionSet, GQLInputValue, GQLOutputValue, GQLType, QueryError, Result, Value,
};
use std::borrow::Cow;
@ -7,6 +7,10 @@ impl<T: GQLType> GQLType for Vec<T> {
fn type_name() -> Cow<'static, str> {
Cow::Owned(format!("[{}]!", T::type_name()))
}
fn create_type_info(registry: &mut schema::Registry) -> String {
T::create_type_info(registry)
}
}
impl<T: GQLInputValue> GQLInputValue for Vec<T> {
@ -64,6 +68,10 @@ impl<T: GQLType> GQLType for &[T] {
fn type_name() -> Cow<'static, str> {
Cow::Owned(format!("[{}]!", T::type_name()))
}
fn create_type_info(registry: &mut schema::Registry) -> String {
T::create_type_info(registry)
}
}
#[async_trait::async_trait]

View File

@ -1,4 +1,4 @@
use crate::{ContextSelectionSet, GQLInputValue, GQLOutputValue, GQLType, Result, Value};
use crate::{schema, ContextSelectionSet, GQLInputValue, GQLOutputValue, GQLType, Result, Value};
use std::borrow::Cow;
impl<T: GQLType> GQLType for Option<T> {
@ -6,6 +6,10 @@ impl<T: GQLType> GQLType for Option<T> {
let name = T::type_name();
Cow::Owned(format!("{}", &name[..name.len() - 1]))
}
fn create_type_info(registry: &mut schema::Registry) -> String {
T::create_type_info(registry)
}
}
impl<T: GQLInputValue> GQLInputValue for Option<T> {
@ -33,4 +37,4 @@ impl<T: GQLOutputValue + Sync> GQLOutputValue for Option<T> {
Ok(serde_json::Value::Null)
}
}
}
}

View File

@ -1,12 +1,16 @@
use crate::{ContextSelectionSet, GQLOutputValue, GQLType, Result};
use crate::{schema, ContextSelectionSet, GQLOutputValue, GQLType, Result};
use graphql_parser::query::Selection;
use std::borrow::Cow;
struct QueryRoot<T>(T);
impl<T> GQLType for QueryRoot<T> {
impl<T: GQLType> GQLType for QueryRoot<T> {
fn type_name() -> Cow<'static, str> {
Cow::Borrowed("Root")
T::type_name()
}
fn create_type_info(registry: &mut schema::Registry) -> String {
T::create_type_info(registry)
}
}