add schema support

This commit is contained in:
sunli 2020-03-03 19:15:18 +08:00
parent 8f448c91e9
commit d0c6db0aba
35 changed files with 621 additions and 437 deletions

View File

@ -1,3 +1,5 @@
# <div style="color: red">WARNING: Some features are not yet implemented. Please do not use in a production environment.</div>
# The GraphQL server library implemented by rust
<div align="center">
@ -49,14 +51,15 @@
- [X] Complex Types
- [X] List
- [X] Non-Null
- [X] Object
- [ ] Object
- [ ] Generic Types
- [X] Lifetime cycle
- [X] Enum
- [X] InputObject
- [ ] Field default value
- [X] Deprecated flag
- [ ] Interface
- [ ] Union
- [ ] Generic Types
- [ ] Query
- [X] Fields
- [X] Arguments
@ -67,6 +70,8 @@
- [ ] Inline fragments
- [X] Operation name
- [X] Variables
- [X] Parse value
- [ ] Check type
- [ ] Directives
- [ ] @include
- [ ] @skip

View File

@ -15,6 +15,11 @@ pub fn generate(enum_args: &args::Enum, input: &DeriveInput) -> Result<TokenStre
};
let gql_typename = enum_args.name.clone().unwrap_or_else(|| ident.to_string());
let desc = enum_args
.desc
.as_ref()
.map(|s| quote! { Some(#s) })
.unwrap_or_else(|| quote! {None});
let mut enum_items = Vec::new();
let mut items = Vec::new();
@ -51,12 +56,11 @@ pub fn generate(enum_args: &args::Enum, input: &DeriveInput) -> Result<TokenStre
items.push(quote! {
#crate_name::GQLEnumItem {
name: #gql_item_name,
desc: #item_desc,
value: #ident::#item_ident,
}
});
schema_enum_items.push(quote! {
#crate_name::schema::EnumValue {
#crate_name::registry::EnumValue {
name: #gql_item_name,
description: #item_desc,
deprecation: #item_deprecation,
@ -82,9 +86,11 @@ pub fn generate(enum_args: &args::Enum, input: &DeriveInput) -> Result<TokenStre
std::borrow::Cow::Borrowed(#gql_typename)
}
fn create_type_info(registry: &mut #crate_name::schema::Registry) -> String {
fn create_type_info(registry: &mut #crate_name::registry::Registry) -> String {
registry.create_type(&Self::type_name(), |registry| {
#crate_name::schema::Type::Enum {
#crate_name::registry::Type::Enum {
name: #gql_typename,
description: #desc,
enum_values: vec![#(#schema_enum_items),*],
}
})
@ -92,11 +98,11 @@ pub fn generate(enum_args: &args::Enum, input: &DeriveInput) -> Result<TokenStre
}
impl #crate_name::GQLInputValue for #ident {
fn parse(value: #crate_name::Value) -> #crate_name::Result<Self> {
fn parse(value: #crate_name::Value) -> Option<Self> {
#crate_name::GQLEnum::parse_enum(value)
}
fn parse_from_json(value: #crate_name::serde_json::Value) -> #crate_name::Result<Self> {
fn parse_from_json(value: #crate_name::serde_json::Value) -> Option<Self> {
#crate_name::GQLEnum::parse_json_enum(value)
}
}

View File

@ -34,6 +34,11 @@ pub fn generate(object_args: &args::InputObject, input: &DeriveInput) -> Result<
.name
.clone()
.unwrap_or_else(|| ident.to_string());
let desc = object_args
.desc
.as_ref()
.map(|s| quote! {Some(#s)})
.unwrap_or_else(|| quote! {None});
let mut get_fields = Vec::new();
let mut get_json_fields = Vec::new();
@ -66,7 +71,7 @@ pub fn generate(object_args: &args::InputObject, input: &DeriveInput) -> Result<
});
fields.push(ident);
schema_fields.push(quote! {
#crate_name::schema::InputValue {
#crate_name::registry::InputValue {
name: #name,
description: #desc,
ty: <#ty as #crate_name::GQLType>::create_type_info(registry),
@ -83,38 +88,34 @@ pub fn generate(object_args: &args::InputObject, input: &DeriveInput) -> Result<
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),*]
fn create_type_info(registry: &mut #crate_name::registry::Registry) -> String {
registry.create_type(&Self::type_name(), |registry| #crate_name::registry::Type::InputObject {
name: #gql_typename,
description: #desc,
input_fields: vec![#(#schema_fields),*]
})
}
}
impl #crate_name::GQLInputValue for #ident {
fn parse(value: #crate_name::Value) -> #crate_name::Result<Self> {
fn parse(value: #crate_name::Value) -> Option<Self> {
use #crate_name::GQLType;
if let #crate_name::Value::Object(mut obj) = value {
#(#get_fields)*
Ok(Self { #(#fields),* })
Some(Self { #(#fields),* })
} else {
Err(#crate_name::QueryError::ExpectedType {
expect: Self::type_name(),
actual: value,
}.into())
None
}
}
fn parse_from_json(value: #crate_name::serde_json::Value) -> #crate_name::Result<Self> {
fn parse_from_json(value: #crate_name::serde_json::Value) -> Option<Self> {
use #crate_name::GQLType;
if let #crate_name::serde_json::Value::Object(mut obj) = value {
#(#get_json_fields)*
Ok(Self { #(#fields),* })
Some(Self { #(#fields),* })
} else {
Err(#crate_name::QueryError::ExpectedJsonType {
expect: Self::type_name(),
actual: value,
}.into())
None
}
}
}

View File

@ -10,6 +10,7 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<Token
let crate_name = get_crate_name(object_args.internal);
let vis = &input.vis;
let ident = &input.ident;
let generics = &input.generics;
match &input.data {
Data::Struct(_) => {}
_ => return Err(Error::new_spanned(input, "It should be a struct.")),
@ -19,6 +20,11 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<Token
.name
.clone()
.unwrap_or_else(|| ident.to_string());
let desc = object_args
.desc
.as_ref()
.map(|s| quote! {Some(#s)})
.unwrap_or_else(|| quote! {None});
let trait_ident = Ident::new(&format!("{}Fields", ident.to_string()), Span::call_site());
let mut trait_fns = Vec::new();
let mut resolvers = Vec::new();
@ -68,7 +74,7 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<Token
});
use_params.push(quote! { #snake_case_name });
schema_args.push(quote! {
#crate_name::schema::InputValue {
#crate_name::registry::InputValue {
name: #name_str,
description: #desc,
ty: <#ty as #crate_name::GQLType>::create_type_info(registry),
@ -109,7 +115,7 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<Token
});
schema_fields.push(quote! {
#crate_name::schema::Field {
#crate_name::registry::Field {
name: #field_name,
description: #desc,
args: vec![#(#schema_args),*],
@ -127,20 +133,22 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<Token
#(#trait_fns)*
}
impl #crate_name::GQLType for #ident {
impl#generics #crate_name::GQLType for #ident#generics {
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 {
fn create_type_info(registry: &mut #crate_name::registry::Registry) -> String {
registry.create_type(&Self::type_name(), |registry| #crate_name::registry::Type::Object {
name: #gql_typename,
description: #desc,
fields: vec![#(#schema_fields),*]
})
}
}
#[#crate_name::async_trait::async_trait]
impl #crate_name::GQLOutputValue for #ident {
impl#generics #crate_name::GQLOutputValue for #ident#generics {
async fn resolve(&self, ctx: &#crate_name::ContextSelectionSet<'_>) -> #crate_name::Result<#crate_name::serde_json::Value> {
use #crate_name::ErrorWithPosition;
@ -160,6 +168,9 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<Token
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(),
@ -174,7 +185,7 @@ pub fn generate(object_args: &args::Object, input: &DeriveInput) -> Result<Token
}
}
impl #crate_name::GQLObject for #ident {}
impl#generics #crate_name::GQLObject for #ident#generics {}
};
Ok(expanded.into())
}

View File

@ -36,13 +36,15 @@ impl MyObjFields for MyObj {
#[async_std::main]
async fn main() {
let res = async_graphql::QueryBuilder::new(
MyObj { value: 100 },
async_graphql::GQLEmptyMutation,
"{ a b c }",
)
.execute()
.await
.unwrap();
let schema = async_graphql::Schema::<MyObj, async_graphql::GQLEmptyMutation>::new();
let res = schema
.query(
MyObj { value: 100 },
async_graphql::GQLEmptyMutation,
"{ a b c __schema { types { kind name description } } }",
)
.execute()
.await
.unwrap();
serde_json::to_writer_pretty(std::io::stdout(), &res).unwrap();
}

View File

@ -1,4 +1,4 @@
use crate::{schema, ContextSelectionSet, Result};
use crate::{registry, ContextSelectionSet, Result};
use graphql_parser::query::Value;
use std::borrow::Cow;
@ -6,13 +6,17 @@ use std::borrow::Cow;
pub trait GQLType {
fn type_name() -> Cow<'static, str>;
fn create_type_info(registry: &mut schema::Registry) -> String;
fn qualified_type_name() -> String {
format!("{}!", Self::type_name())
}
fn create_type_info(registry: &mut registry::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>;
fn parse(value: Value) -> Option<Self>;
fn parse_from_json(value: serde_json::Value) -> Option<Self>;
}
#[doc(hidden)]
@ -29,8 +33,11 @@ pub trait GQLInputObject: GQLInputValue {}
pub trait Scalar: Sized + Send {
fn type_name() -> &'static str;
fn parse(value: Value) -> Result<Self>;
fn parse_from_json(value: serde_json::Value) -> Result<Self>;
fn description() -> Option<&'static str> {
None
}
fn parse(value: Value) -> Option<Self>;
fn parse_from_json(value: serde_json::Value) -> Option<Self>;
fn to_json(&self) -> Result<serde_json::Value>;
}
@ -39,17 +46,20 @@ impl<T: Scalar> GQLType for T {
Cow::Borrowed(T::type_name())
}
fn create_type_info(registry: &mut schema::Registry) -> String {
registry.create_type(T::type_name(), |_| schema::Type::Scalar)
fn create_type_info(registry: &mut registry::Registry) -> String {
registry.create_type(&T::type_name(), |_| registry::Type::Scalar {
name: T::type_name().to_string(),
description: T::description(),
})
}
}
impl<T: Scalar> GQLInputValue for T {
fn parse(value: Value) -> Result<Self> {
fn parse(value: Value) -> Option<Self> {
T::parse(value)
}
fn parse_from_json(value: serde_json::Value) -> Result<Self> {
fn parse_from_json(value: serde_json::Value) -> Option<Self> {
T::parse_from_json(value)
}
}

View File

@ -1,3 +1,4 @@
use crate::registry::Registry;
use crate::{ErrorWithPosition, GQLInputValue, QueryError, Result};
use fnv::FnvHasher;
use graphql_parser::query::{Field, SelectionSet, Value};
@ -43,6 +44,7 @@ pub struct ContextBase<'a, T> {
pub(crate) item: T,
pub(crate) data: Option<&'a Data>,
pub(crate) variables: Option<&'a Variables>,
pub(crate) registry: &'a Registry,
}
impl<'a, T> Deref for ContextBase<'a, T> {
@ -60,6 +62,7 @@ impl<'a, T> ContextBase<'a, T> {
item,
data: self.data,
variables: self.variables,
registry: self.registry.clone(),
}
}
@ -85,8 +88,14 @@ impl<'a> ContextBase<'a, &'a Field> {
if let Some(Value::Variable(var_name)) = &value {
if let Some(vars) = &self.variables {
if let Some(var_value) = vars.get(&*var_name).cloned() {
let res = GQLInputValue::parse_from_json(var_value)
.map_err(|err| err.with_position(self.item.position))?;
let res =
GQLInputValue::parse_from_json(var_value.clone()).ok_or_else(|| {
QueryError::ExpectedJsonType {
expect: T::qualified_type_name(),
actual: var_value,
}
.with_position(self.item.position)
})?;
return Ok(res);
}
}
@ -97,8 +106,14 @@ impl<'a> ContextBase<'a, &'a Field> {
.into());
};
let res = GQLInputValue::parse(value.unwrap_or(Value::Null))
.map_err(|err| err.with_position(self.item.position))?;
let value = value.unwrap_or(Value::Null);
let res = GQLInputValue::parse(value.clone()).ok_or_else(|| {
QueryError::ExpectedType {
expect: T::qualified_type_name(),
actual: value,
}
.with_position(self.item.position)
})?;
Ok(res)
}
}

View File

@ -1,7 +1,6 @@
use crate::Error;
use graphql_parser::query::Value;
use graphql_parser::Pos;
use std::borrow::Cow;
use std::fmt::{Debug, Display, Formatter};
#[derive(Debug, Error)]
@ -14,14 +13,11 @@ pub enum QueryError {
NotSupported,
#[error("Expected type \"{expect}\", found {actual}.")]
ExpectedType {
expect: Cow<'static, str>,
actual: Value,
},
ExpectedType { expect: String, actual: Value },
#[error("Expected type \"{expect}\", found {actual}.")]
ExpectedJsonType {
expect: Cow<'static, str>,
expect: String,
actual: serde_json::Value,
},
@ -41,10 +37,7 @@ pub enum QueryError {
NotConfiguredMutations,
#[error("Invalid value for enum \"{ty}\".")]
InvalidEnumValue {
ty: Cow<'static, str>,
value: String,
},
InvalidEnumValue { ty: String, value: String },
#[error("Required field \"{field_name}\" for InputObject \"{object}\" does not exist.")]
RequiredField {

View File

@ -36,9 +36,9 @@ extern crate thiserror;
mod base;
mod context;
mod error;
mod query;
mod query_schema;
mod model;
mod scalars;
mod schema;
mod types;
#[doc(hidden)]
@ -55,8 +55,8 @@ 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 scalars::ID;
pub use schema::{QueryBuilder, Schema};
pub use types::GQLEmptyMutation;
pub type Result<T> = anyhow::Result<T>;
@ -68,6 +68,6 @@ pub use base::{GQLInputObject, GQLInputValue, GQLObject, GQLOutputValue, GQLType
#[doc(hidden)]
pub use context::ContextSelectionSet;
#[doc(hidden)]
pub mod schema;
pub mod registry;
#[doc(hidden)]
pub use types::{GQLEnum, GQLEnumItem};

View File

@ -1,4 +1,4 @@
use crate::{Context, Result};
use crate::{registry, Context, Result};
use async_graphql_derive::Object;
#[Object(
@ -9,23 +9,26 @@ use async_graphql_derive::Object;
field(name = "isDeprecated", type = "bool", owned),
field(name = "deprecationReason", type = "Option<String>", owned)
)]
pub struct __EnumValue {}
pub struct __EnumValue<'a> {
pub registry: &'a registry::Registry,
pub value: &'a registry::EnumValue,
}
#[async_trait::async_trait]
impl __EnumValueFields for __EnumValue {
impl<'a> __EnumValueFields for __EnumValue<'a> {
async fn name(&self, _: &Context<'_>) -> Result<String> {
todo!()
Ok(self.value.name.to_string())
}
async fn description(&self, _: &Context<'_>) -> Result<Option<String>> {
todo!()
Ok(self.value.description.map(|s| s.to_string()))
}
async fn is_deprecated(&self, _: &Context<'_>) -> Result<bool> {
todo!()
Ok(self.value.deprecation.is_some())
}
async fn deprecation_reason(&self, _: &Context<'_>) -> Result<Option<String>> {
todo!()
Ok(self.value.deprecation.map(|s| s.to_string()))
}
}

View File

@ -1,5 +1,5 @@
use crate::query_schema::__Type;
use crate::{Context, Result};
use crate::model::__Type;
use crate::{registry, Context, Result};
use async_graphql_derive::Object;
#[Object(
@ -11,28 +11,34 @@ use async_graphql_derive::Object;
field(name = "isDeprecated", type = "bool", owned),
field(name = "deprecationReason", type = "Option<String>", owned)
)]
pub struct __Field {}
pub struct __Field<'a> {
pub registry: &'a registry::Registry,
pub field: &'a registry::Field,
}
#[async_trait::async_trait]
#[allow()]
impl __FieldFields for __Field {
impl<'a> __FieldFields for __Field<'a> {
async fn name(&self, _: &Context<'_>) -> Result<String> {
todo!()
Ok(self.field.name.to_string())
}
async fn description(&self, _: &Context<'_>) -> Result<Option<String>> {
todo!()
Ok(self.field.description.map(|s| s.to_string()))
}
async fn ty(&self, _: &Context<'_>) -> Result<__Type> {
todo!()
async fn ty<'b>(&'b self, _: &Context<'_>) -> Result<__Type<'b>> {
Ok(__Type {
registry: self.registry,
ty: &self.registry[&self.field.ty],
})
}
async fn is_deprecated(&self, _: &Context<'_>) -> Result<bool> {
todo!()
Ok(self.field.deprecation.is_some())
}
async fn deprecation_reason(&self, _: &Context<'_>) -> Result<Option<String>> {
todo!()
Ok(self.field.deprecation.map(|s| s.to_string()))
}
}

38
src/model/input_value.rs Normal file
View File

@ -0,0 +1,38 @@
use crate::model::__Type;
use crate::{registry, Context, Result};
use async_graphql_derive::Object;
#[Object(
internal,
desc = "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.",
field(name = "name", type = "String", owned),
field(name = "description", type = "Option<String>", owned),
field(name = "type", resolver = "ty", type = "__Type", owned),
field(name = "defaultValue", type = "Option<String>", owned)
)]
pub struct __InputValue<'a> {
pub registry: &'a registry::Registry,
pub input_value: &'a registry::InputValue,
}
#[async_trait::async_trait]
impl<'a> __InputValueFields for __InputValue<'a> {
async fn name(&self, _: &Context<'_>) -> Result<String> {
Ok(self.input_value.name.to_string())
}
async fn description(&self, _: &Context<'_>) -> Result<Option<String>> {
Ok(self.input_value.description.map(|s| s.to_string()))
}
async fn ty<'b>(&'b self, _: &Context<'_>) -> Result<__Type<'b>> {
Ok(__Type {
registry: self.registry,
ty: &self.registry[&self.input_value.ty],
})
}
async fn default_value(&self, _: &Context<'_>) -> Result<Option<String>> {
Ok(self.input_value.default_value.map(|s| s.to_string()))
}
}

View File

@ -2,6 +2,7 @@ mod enum_value;
mod field;
mod input_value;
mod kind;
mod schema;
mod r#type;
pub use enum_value::__EnumValue;
@ -9,3 +10,4 @@ pub use field::__Field;
pub use input_value::__InputValue;
pub use kind::__TypeKind;
pub use r#type::__Type;
pub use schema::__Schema;

60
src/model/schema.rs Normal file
View File

@ -0,0 +1,60 @@
use crate::model::__Type;
use crate::registry;
use crate::{Context, Result};
use async_graphql_derive::Object;
#[Object(
internal,
desc = "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.",
field(
name = "types",
desc = "A list of all types supported by this server.",
type = "Vec<__Type>",
owned
),
field(
name = "queryType",
desc = "The type that query operations will be rooted at.",
type = "__Type",
owned
),
field(
name = "mutationType",
desc = "If this server supports mutation, the type that mutation operations will be rooted at.",
type = "__Type",
owned
)
)]
pub struct __Schema<'a> {
pub registry: &'a registry::Registry,
pub query_type: &'a str,
pub mutation_type: &'a str,
}
#[async_trait::async_trait]
impl<'a> __SchemaFields for __Schema<'a> {
async fn types<'b>(&'b self, _: &Context<'_>) -> Result<Vec<__Type<'b>>> {
Ok(self
.registry
.values()
.map(|ty| __Type {
registry: &self.registry,
ty,
})
.collect())
}
async fn query_type<'b>(&'b self, _: &Context<'_>) -> Result<__Type<'b>> {
Ok(__Type {
registry: &self.registry,
ty: &self.registry[self.query_type],
})
}
async fn mutation_type<'b>(&'b self, _: &Context<'_>) -> Result<__Type<'b>> {
Ok(__Type {
registry: &self.registry,
ty: &self.registry[self.mutation_type],
})
}
}

160
src/model/type.rs Normal file
View File

@ -0,0 +1,160 @@
use crate::model::{__EnumValue, __Field, __InputValue, __TypeKind};
use crate::registry::Type;
use crate::{registry, 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 = "Option<String>", owned),
field(name = "description", type = "Option<String>", owned),
field(
name = "fields",
type = "Option<Vec<__Field>>",
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,
arg(name = "includeDeprecated", type = "bool")
),
field(name = "inputFields", type = "Option<Vec<__InputValue>>", owned),
field(name = "ofType", type = "Option<__Type>", owned)
)]
pub struct __Type<'a> {
pub registry: &'a registry::Registry,
pub ty: &'a registry::Type,
}
#[async_trait::async_trait]
impl<'a> __TypeFields for __Type<'a> {
async fn kind(&self, _: &Context<'_>) -> Result<__TypeKind> {
Ok(match self.ty {
registry::Type::Scalar { .. } => __TypeKind::SCALAR,
registry::Type::Object { .. } => __TypeKind::OBJECT,
registry::Type::Interface { .. } => __TypeKind::INTERFACE,
registry::Type::Union { .. } => __TypeKind::UNION,
registry::Type::Enum { .. } => __TypeKind::ENUM,
registry::Type::InputObject { .. } => __TypeKind::INPUT_OBJECT,
registry::Type::List { .. } => __TypeKind::LIST,
registry::Type::NonNull { .. } => __TypeKind::NON_NULL,
})
}
async fn name(&self, _: &Context<'_>) -> Result<Option<String>> {
Ok(match self.ty {
registry::Type::Scalar { name, .. } => Some(name.clone()),
registry::Type::Object { name, .. } => Some(name.to_string()),
registry::Type::Interface { name, .. } => Some(name.to_string()),
registry::Type::Union { name, .. } => Some(name.to_string()),
registry::Type::Enum { name, .. } => Some(name.to_string()),
registry::Type::InputObject { name, .. } => Some(name.to_string()),
registry::Type::List { .. } => None,
registry::Type::NonNull { .. } => None,
})
}
async fn description(&self, _: &Context<'_>) -> Result<Option<String>> {
Ok(match self.ty {
registry::Type::Scalar { description, .. } => description.map(|s| s.to_string()),
registry::Type::Object { description, .. } => description.map(|s| s.to_string()),
registry::Type::Interface { description, .. } => description.map(|s| s.to_string()),
registry::Type::Union { description, .. } => description.map(|s| s.to_string()),
registry::Type::Enum { description, .. } => description.map(|s| s.to_string()),
registry::Type::InputObject { description, .. } => description.map(|s| s.to_string()),
registry::Type::List { .. } => None,
registry::Type::NonNull { .. } => None,
})
}
async fn fields<'b>(
&'b self,
_: &Context<'_>,
include_deprecated: bool,
) -> Result<Option<Vec<__Field<'b>>>> {
if let Type::Object { fields, .. } = self.ty {
Ok(Some(
fields
.iter()
.filter(|field| {
if include_deprecated {
true
} else {
field.deprecation.is_none()
}
})
.map(|field| __Field {
registry: self.registry,
field,
})
.collect(),
))
} else {
Ok(None)
}
}
async fn interfaces<'b>(&'b self, _: &Context<'_>) -> Result<Option<Vec<__Type<'b>>>> {
Ok(None)
}
async fn possible_types<'b>(&'b self, _: &Context<'_>) -> Result<Option<Vec<__Type<'b>>>> {
Ok(None)
}
async fn enum_values<'b>(
&'b self,
_: &Context<'_>,
include_deprecated: bool,
) -> Result<Option<Vec<__EnumValue<'b>>>> {
if let Type::Enum { enum_values, .. } = self.ty {
Ok(Some(
enum_values
.iter()
.filter(|field| {
if include_deprecated {
true
} else {
field.deprecation.is_none()
}
})
.map(|value| __EnumValue {
registry: self.registry,
value,
})
.collect(),
))
} else {
Ok(None)
}
}
async fn input_fields<'b>(&'b self, _: &Context<'_>) -> Result<Option<Vec<__InputValue<'b>>>> {
if let Type::InputObject { input_fields, .. } = self.ty {
Ok(Some(
input_fields
.iter()
.map(|input_value| __InputValue {
registry: self.registry,
input_value,
})
.collect(),
))
} else {
Ok(None)
}
}
async fn of_type<'b>(&'b self, _: &Context<'_>) -> Result<Option<__Type<'b>>> {
Ok(None)
}
}

View File

@ -1,32 +0,0 @@
use crate::query_schema::__Type;
use crate::{Context, Result};
use async_graphql_derive::Object;
#[Object(
internal,
desc = "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.",
field(name = "name", type = "String", owned),
field(name = "description", type = "Option<String>", owned),
field(name = "type", resolver = "ty", type = "__Type", owned),
field(name = "defaultValue", type = "String", owned)
)]
pub struct __InputValue {}
#[async_trait::async_trait]
impl __InputValueFields for __InputValue {
async fn name(&self, _: &Context<'_>) -> Result<String> {
todo!()
}
async fn description(&self, _: &Context<'_>) -> Result<Option<String>> {
todo!()
}
async fn ty(&self, _: &Context<'_>) -> Result<__Type> {
todo!()
}
async fn default_value(&self, _: &Context<'_>) -> Result<String> {
todo!()
}
}

View File

@ -1,70 +0,0 @@
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

@ -1,12 +1,20 @@
use crate::schema::Type;
use std::cell::RefCell;
use crate::registry::Type;
use std::collections::HashMap;
use std::ops::Deref;
#[derive(Default)]
pub struct Registry {
types: HashMap<String, Type>,
}
impl Deref for Registry {
type Target = HashMap<String, Type>;
fn deref(&self) -> &Self::Target {
&self.types
}
}
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) {
@ -15,8 +23,4 @@ impl Registry {
}
name.to_string()
}
pub fn get_type(&self, name: &str) -> Option<&Type> {
self.types.get(name)
}
}

View File

@ -20,21 +20,34 @@ pub struct EnumValue {
}
pub enum Type {
Scalar,
Scalar {
name: String,
description: Option<&'static str>,
},
Object {
name: &'static str,
description: Option<&'static str>,
fields: Vec<Field>,
},
Interface {
name: &'static str,
description: Option<&'static str>,
fields: Vec<Field>,
possible_types: Vec<usize>,
},
Union {
name: &'static str,
description: Option<&'static str>,
possible_types: Vec<usize>,
},
Enum {
name: &'static str,
description: Option<&'static str>,
enum_values: Vec<EnumValue>,
},
InputObject {
name: &'static str,
description: Option<&'static str>,
input_fields: Vec<InputValue>,
},
List {

View File

@ -1,33 +1,25 @@
use crate::{GQLType, QueryError, Result, Scalar, Value};
use crate::{Result, Scalar, Value};
impl Scalar for bool {
fn type_name() -> &'static str {
"Boolean!"
"Boolean"
}
fn parse(value: Value) -> Result<Self> {
fn description() -> Option<&'static str> {
Some("The `Boolean` scalar type represents `true` or `false`.")
}
fn parse(value: Value) -> Option<Self> {
match value {
Value::Boolean(n) => Ok(n),
_ => {
return Err(QueryError::ExpectedType {
expect: <Self as GQLType>::type_name(),
actual: value,
}
.into())
}
Value::Boolean(n) => Some(n),
_ => None,
}
}
fn parse_from_json(value: serde_json::Value) -> Result<Self> {
fn parse_from_json(value: serde_json::Value) -> Option<Self> {
match value {
serde_json::Value::Bool(n) => Ok(n),
_ => {
return Err(QueryError::ExpectedJsonType {
expect: <Self as GQLType>::type_name(),
actual: value,
}
.into())
}
serde_json::Value::Bool(n) => Some(n),
_ => None,
}
}

View File

@ -1,35 +1,22 @@
use crate::{QueryError, Result, Scalar, Value};
use crate::{Result, Scalar, Value};
use chrono::{DateTime, TimeZone, Utc};
use std::borrow::Cow;
impl Scalar for DateTime<Utc> {
fn type_name() -> &'static str {
"DateTime"
}
fn parse(value: Value) -> Result<Self> {
fn parse(value: Value) -> Option<Self> {
match value {
Value::String(s) => Ok(Utc.datetime_from_str(&s, "%+")?),
_ => {
return Err(QueryError::ExpectedType {
expect: Cow::Borrowed(Self::type_name()),
actual: value,
}
.into())
}
Value::String(s) => Some(Utc.datetime_from_str(&s, "%+").ok()?),
_ => None,
}
}
fn parse_from_json(value: serde_json::Value) -> Result<Self> {
fn parse_from_json(value: serde_json::Value) -> Option<Self> {
match value {
serde_json::Value::String(s) => Ok(Utc.datetime_from_str(&s, "%+")?),
_ => {
return Err(QueryError::ExpectedJsonType {
expect: Cow::Borrowed(Self::type_name()),
actual: value,
}
.into())
}
serde_json::Value::String(s) => Some(Utc.datetime_from_str(&s, "%+").ok()?),
_ => None,
}
}

View File

@ -1,38 +1,29 @@
use crate::{GQLType, QueryError, Result, Scalar, Value};
use std::borrow::Cow;
use crate::{Result, Scalar, Value};
macro_rules! impl_float_scalars {
($($ty:ty),*) => {
$(
impl Scalar for $ty {
fn type_name() -> &'static str {
"Float!"
"Float"
}
fn parse(value: Value) -> Result<Self> {
fn description() -> Option<&'static str> {
Some("The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point).")
}
fn parse(value: Value) -> Option<Self> {
match value {
Value::Int(n) => Ok(n.as_i64().unwrap() as Self),
Value::Float(n) => Ok(n as Self),
_ => {
return Err(QueryError::ExpectedType {
expect: Cow::Borrowed(<Self as Scalar>::type_name()),
actual: value,
}
.into())
}
Value::Int(n) => Some(n.as_i64().unwrap() as Self),
Value::Float(n) => Some(n as Self),
_ => None
}
}
fn parse_from_json(value: serde_json::Value) -> Result<Self> {
fn parse_from_json(value: serde_json::Value) -> Option<Self> {
match value {
serde_json::Value::Number(n) => Ok(n.as_f64().unwrap() as Self),
_ => {
return Err(QueryError::ExpectedJsonType {
expect: <Self as GQLType>::type_name(),
actual: value,
}
.into())
}
serde_json::Value::Number(n) => Some(n.as_f64().unwrap() as Self),
_ => None
}
}

View File

@ -1,5 +1,4 @@
use crate::{QueryError, Result, Scalar, Value};
use std::borrow::Cow;
use crate::{Result, Scalar, Value};
use std::ops::{Deref, DerefMut};
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
@ -24,31 +23,19 @@ impl Scalar for ID {
"ID"
}
fn parse(value: Value) -> Result<Self> {
fn parse(value: Value) -> Option<Self> {
match value {
Value::Int(n) => Ok(ID(n.as_i64().unwrap().to_string())),
Value::String(s) => Ok(ID(s)),
_ => {
return Err(QueryError::ExpectedType {
expect: Cow::Borrowed(Self::type_name()),
actual: value,
}
.into())
}
Value::Int(n) => Some(ID(n.as_i64().unwrap().to_string())),
Value::String(s) => Some(ID(s)),
_ => None,
}
}
fn parse_from_json(value: serde_json::Value) -> Result<Self> {
fn parse_from_json(value: serde_json::Value) -> Option<Self> {
match value {
serde_json::Value::Number(n) if n.is_i64() => Ok(ID(n.as_i64().unwrap().to_string())),
serde_json::Value::String(s) => Ok(ID(s)),
_ => {
return Err(QueryError::ExpectedJsonType {
expect: Cow::Borrowed(Self::type_name()),
actual: value,
}
.into())
}
serde_json::Value::Number(n) if n.is_i64() => Some(ID(n.as_i64().unwrap().to_string())),
serde_json::Value::String(s) => Some(ID(s)),
_ => None,
}
}

View File

@ -1,38 +1,29 @@
use crate::{GQLType, QueryError, Result, Scalar, Value};
use std::borrow::Cow;
use crate::{Result, Scalar, Value};
macro_rules! impl_integer_scalars {
($($ty:ty),*) => {
$(
impl Scalar for $ty {
fn type_name() -> &'static str {
"Int!"
"Int"
}
fn parse(value: Value) -> Result<Self> {
fn description() -> Option<&'static str> {
Some("The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.")
}
fn parse(value: Value) -> Option<Self> {
match value {
Value::Int(n) => Ok(n.as_i64().unwrap() as Self),
_ => {
return Err(QueryError::ExpectedType {
expect: Cow::Borrowed(<Self as Scalar>::type_name()),
actual: value,
}
.into())
}
Value::Int(n) => Some(n.as_i64().unwrap() as Self),
_ => None
}
}
fn parse_from_json(value: serde_json::Value) -> Result<Self> {
fn parse_from_json(value: serde_json::Value) -> Option<Self> {
match value {
serde_json::Value::Number(n) if n.is_i64() => Ok(n.as_i64().unwrap() as Self),
serde_json::Value::Number(n) => Ok(n.as_f64().unwrap() as Self),
_ => {
return Err(QueryError::ExpectedJsonType {
expect: <Self as GQLType>::type_name(),
actual: value,
}
.into())
}
serde_json::Value::Number(n) if n.is_i64() => Some(n.as_i64().unwrap() as Self),
serde_json::Value::Number(n) => Some(n.as_f64().unwrap() as Self),
_ => None,
}
}

View File

@ -1,35 +1,29 @@
use crate::schema;
use crate::{ContextSelectionSet, GQLOutputValue, GQLType, QueryError, Result, Scalar, Value};
use crate::registry;
use crate::{ContextSelectionSet, GQLOutputValue, GQLType, Result, Scalar, Value};
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.";
impl Scalar for String {
fn type_name() -> &'static str {
"String!"
"String"
}
fn parse(value: Value) -> Result<Self> {
fn description() -> Option<&'static str> {
Some(STRING_DESC)
}
fn parse(value: Value) -> Option<Self> {
match value {
Value::String(s) => Ok(s),
_ => {
return Err(QueryError::ExpectedType {
expect: <Self as GQLType>::type_name(),
actual: value,
}
.into())
}
Value::String(s) => Some(s),
_ => None,
}
}
fn parse_from_json(value: serde_json::Value) -> Result<Self> {
fn parse_from_json(value: serde_json::Value) -> Option<Self> {
match value {
serde_json::Value::String(s) => Ok(s),
_ => {
return Err(QueryError::ExpectedJsonType {
expect: <Self as GQLType>::type_name(),
actual: value,
}
.into())
}
serde_json::Value::String(s) => Some(s),
_ => None,
}
}
@ -40,11 +34,14 @@ impl Scalar for String {
impl<'a> GQLType for &'a str {
fn type_name() -> Cow<'static, str> {
Cow::Borrowed("String!")
Cow::Borrowed("String")
}
fn create_type_info(registry: &mut schema::Registry) -> String {
registry.create_type(&Self::type_name(), |_| schema::Type::Scalar)
fn create_type_info(registry: &mut registry::Registry) -> String {
registry.create_type(&Self::type_name(), |_| registry::Type::Scalar {
name: Self::type_name().to_string(),
description: Some(STRING_DESC),
})
}
}

View File

@ -1,5 +1,4 @@
use crate::{QueryError, Result, Scalar, Value};
use std::borrow::Cow;
use crate::{Result, Scalar, Value};
use uuid::Uuid;
impl Scalar for Uuid {
@ -7,29 +6,17 @@ impl Scalar for Uuid {
"UUID"
}
fn parse(value: Value) -> Result<Self> {
fn parse(value: Value) -> Option<Self> {
match value {
Value::String(s) => Ok(Uuid::parse_str(&s)?),
_ => {
return Err(QueryError::ExpectedType {
expect: Cow::Borrowed(Self::type_name()),
actual: value,
}
.into())
}
Value::String(s) => Some(Uuid::parse_str(&s).ok()?),
_ => None,
}
}
fn parse_from_json(value: serde_json::Value) -> Result<Self> {
fn parse_from_json(value: serde_json::Value) -> Option<Self> {
match value {
serde_json::Value::String(s) => Ok(Uuid::parse_str(&s)?),
_ => {
return Err(QueryError::ExpectedJsonType {
expect: Cow::Borrowed(Self::type_name()),
actual: value,
}
.into())
}
serde_json::Value::String(s) => Some(Uuid::parse_str(&s).ok()?),
_ => None,
}
}

View File

@ -1,12 +1,57 @@
use crate::registry::Registry;
use crate::types::QueryRoot;
use crate::{
ContextBase, Data, ErrorWithPosition, GQLObject, QueryError, QueryParseError, Result, Variables,
ContextBase, Data, ErrorWithPosition, GQLObject, GQLOutputValue, QueryError, QueryParseError,
Result, Variables,
};
use graphql_parser::parse_query;
use graphql_parser::query::{Definition, OperationDefinition};
use serde::export::PhantomData;
pub struct Schema<Query, Mutation> {
mark_query: PhantomData<Query>,
mark_mutation: PhantomData<Mutation>,
registry: Registry,
}
impl<Query: GQLObject, Mutation: GQLObject> Schema<Query, Mutation> {
pub fn new() -> Self {
let mut registry = Default::default();
Query::create_type_info(&mut registry);
Mutation::create_type_info(&mut registry);
Self {
mark_query: PhantomData,
mark_mutation: PhantomData,
registry,
}
}
pub fn query<'a>(
&'a self,
query: Query,
mutation: Mutation,
query_source: &'a str,
) -> QueryBuilder<'a, Query, Mutation> {
QueryBuilder {
query: QueryRoot {
inner: query,
query_type: Query::type_name().to_string(),
mutation_type: Mutation::type_name().to_string(),
},
mutation,
registry: &self.registry,
query_source,
operation_name: None,
variables: None,
data: None,
}
}
}
pub struct QueryBuilder<'a, Query, Mutation> {
query: Query,
query: QueryRoot<Query>,
mutation: Mutation,
registry: &'a Registry,
query_source: &'a str,
operation_name: Option<&'a str>,
variables: Option<&'a Variables>,
@ -14,17 +59,6 @@ pub struct QueryBuilder<'a, Query, Mutation> {
}
impl<'a, Query, Mutation> QueryBuilder<'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 {
QueryBuilder {
operation_name: Some(name),
@ -48,8 +82,8 @@ impl<'a, Query, Mutation> QueryBuilder<'a, Query, Mutation> {
pub async fn execute(self) -> Result<serde_json::Value>
where
Query: GQLObject,
Mutation: GQLObject,
Query: GQLObject + Send + Sync,
Mutation: GQLObject + Send + Sync,
{
let document =
parse_query(self.query_source).map_err(|err| QueryParseError(err.to_string()))?;
@ -62,6 +96,7 @@ impl<'a, Query, Mutation> QueryBuilder<'a, Query, Mutation> {
item: selection_set,
data: self.data.as_deref(),
variables: self.variables.as_deref(),
registry: &self.registry,
};
return self.query.resolve(&ctx).await;
}
@ -74,6 +109,7 @@ impl<'a, Query, Mutation> QueryBuilder<'a, Query, Mutation> {
item: &query.selection_set,
data: self.data.as_deref(),
variables: self.variables.as_deref(),
registry: self.registry.clone(),
};
return self.query.resolve(&ctx).await;
}
@ -86,6 +122,7 @@ impl<'a, Query, Mutation> QueryBuilder<'a, Query, Mutation> {
item: &mutation.selection_set,
data: self.data.as_deref(),
variables: self.variables.as_deref(),
registry: self.registry.clone(),
};
return self.mutation.resolve(&ctx).await;
}

View File

@ -1,6 +1,6 @@
use crate::{
schema, ContextSelectionSet, ErrorWithPosition, GQLObject, GQLOutputValue, GQLType, QueryError,
Result,
registry, ContextSelectionSet, ErrorWithPosition, GQLObject, GQLOutputValue, GQLType,
QueryError, Result,
};
use std::borrow::Cow;
@ -11,8 +11,10 @@ impl GQLType for GQLEmptyMutation {
Cow::Borrowed("EmptyMutation")
}
fn create_type_info(registry: &mut schema::Registry) -> String {
registry.create_type(&Self::type_name(), |_| schema::Type::Object {
fn create_type_info(registry: &mut registry::Registry) -> String {
registry.create_type(&Self::type_name(), |_| registry::Type::Object {
name: "EmptyMutation",
description: None,
fields: Vec::new(),
})
}

View File

@ -1,10 +1,9 @@
use crate::{GQLType, QueryError, Result};
use crate::{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,
}
@ -13,54 +12,34 @@ pub struct GQLEnumItem<T> {
pub trait GQLEnum: GQLType + Sized + Eq + Send + Copy + Sized + 'static {
fn items() -> &'static [GQLEnumItem<Self>];
fn parse_enum(value: Value) -> Result<Self> {
fn parse_enum(value: Value) -> Option<Self> {
match value {
Value::Enum(s) => {
let items = Self::items();
for item in items {
if item.name == s {
return Ok(item.value);
return Some(item.value);
}
}
Err(QueryError::InvalidEnumValue {
ty: Self::type_name(),
value: s,
}
.into())
}
_ => {
return Err(QueryError::ExpectedType {
expect: Self::type_name(),
actual: value,
}
.into())
}
_ => {}
}
None
}
fn parse_json_enum(value: serde_json::Value) -> Result<Self> {
fn parse_json_enum(value: serde_json::Value) -> Option<Self> {
match value {
serde_json::Value::String(s) => {
let items = Self::items();
for item in items {
if item.name == s {
return Ok(item.value);
return Some(item.value);
}
}
Err(QueryError::InvalidEnumValue {
ty: Self::type_name(),
value: s,
}
.into())
}
_ => {
return Err(QueryError::ExpectedJsonType {
expect: Self::type_name(),
actual: value,
}
.into())
}
_ => {}
}
None
}
fn resolve_enum(&self) -> Result<serde_json::Value> {

View File

@ -1,54 +1,40 @@
use crate::{
schema, ContextSelectionSet, GQLInputValue, GQLOutputValue, GQLType, QueryError, Result, Value,
};
use crate::{registry, ContextSelectionSet, GQLInputValue, GQLOutputValue, GQLType, Result, Value};
use std::borrow::Cow;
impl<T: GQLType> GQLType for Vec<T> {
fn type_name() -> Cow<'static, str> {
Cow::Owned(format!("[{}]!", T::type_name()))
Cow::Owned(format!("[{}]", T::qualified_type_name()))
}
fn create_type_info(registry: &mut schema::Registry) -> String {
fn create_type_info(registry: &mut registry::Registry) -> String {
T::create_type_info(registry)
}
}
impl<T: GQLInputValue> GQLInputValue for Vec<T> {
fn parse(value: Value) -> Result<Self> {
fn parse(value: Value) -> Option<Self> {
match value {
Value::List(values) => {
let mut result = Vec::new();
for value in values {
result.push(GQLInputValue::parse(value)?);
}
Ok(result)
}
_ => {
return Err(QueryError::ExpectedType {
expect: Self::type_name(),
actual: value,
}
.into())
Some(result)
}
_ => None,
}
}
fn parse_from_json(value: serde_json::Value) -> Result<Self> {
fn parse_from_json(value: serde_json::Value) -> Option<Self> {
match value {
serde_json::Value::Array(values) => {
let mut result = Vec::new();
for value in values {
result.push(GQLInputValue::parse_from_json(value)?);
}
Ok(result)
}
_ => {
return Err(QueryError::ExpectedJsonType {
expect: Self::type_name(),
actual: value,
}
.into())
Some(result)
}
_ => None,
}
}
}
@ -58,7 +44,7 @@ impl<T: GQLOutputValue + Send + Sync> 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?);
res.push(item.resolve(&ctx).await?);
}
Ok(res.into())
}
@ -66,10 +52,10 @@ impl<T: GQLOutputValue + Send + Sync> GQLOutputValue for Vec<T> {
impl<T: GQLType> GQLType for &[T] {
fn type_name() -> Cow<'static, str> {
Cow::Owned(format!("[{}]!", T::type_name()))
Cow::Owned(format!("[{}]", T::type_name()))
}
fn create_type_info(registry: &mut schema::Registry) -> String {
fn create_type_info(registry: &mut registry::Registry) -> String {
T::create_type_info(registry)
}
}
@ -79,7 +65,7 @@ impl<T: GQLOutputValue + Send + Sync> GQLOutputValue for &[T] {
async fn resolve(&self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
let mut res = Vec::new();
for item in self.iter() {
res.push(item.resolve(ctx).await?);
res.push(item.resolve(&ctx).await?);
}
Ok(res.into())
}

View File

@ -1,8 +1,9 @@
mod empty_mutation;
mod r#enum;
mod list;
mod non_null;
mod optional;
mod query_root;
pub use empty_mutation::GQLEmptyMutation;
pub use query_root::QueryRoot;
pub use r#enum::{GQLEnum, GQLEnumItem};

View File

@ -1,29 +1,32 @@
use crate::{schema, ContextSelectionSet, GQLInputValue, GQLOutputValue, GQLType, Result, Value};
use crate::{registry, ContextSelectionSet, GQLInputValue, GQLOutputValue, GQLType, Result, Value};
use std::borrow::Cow;
impl<T: GQLType> GQLType for Option<T> {
fn type_name() -> Cow<'static, str> {
let name = T::type_name();
Cow::Owned(format!("{}", &name[..name.len() - 1]))
T::type_name()
}
fn create_type_info(registry: &mut schema::Registry) -> String {
fn qualified_type_name() -> String {
T::type_name().to_string()
}
fn create_type_info(registry: &mut registry::Registry) -> String {
T::create_type_info(registry)
}
}
impl<T: GQLInputValue> GQLInputValue for Option<T> {
fn parse(value: Value) -> Result<Self> {
fn parse(value: Value) -> Option<Self> {
match value {
Value::Null => Ok(None),
_ => Ok(Some(GQLInputValue::parse(value)?)),
Value::Null => Some(None),
_ => Some(GQLInputValue::parse(value)?),
}
}
fn parse_from_json(value: serde_json::Value) -> Result<Self> {
fn parse_from_json(value: serde_json::Value) -> Option<Self> {
match value {
serde_json::Value::Null => Ok(None),
_ => Ok(Some(GQLInputValue::parse_from_json(value)?)),
serde_json::Value::Null => Some(None),
_ => Some(GQLInputValue::parse_from_json(value)?),
}
}
}

View File

@ -1,15 +1,20 @@
use crate::{schema, ContextSelectionSet, GQLOutputValue, GQLType, Result};
use crate::model::__Schema;
use crate::{registry, ContextSelectionSet, GQLOutputValue, GQLType, Result};
use graphql_parser::query::Selection;
use std::borrow::Cow;
struct QueryRoot<T>(T);
pub struct QueryRoot<T> {
pub inner: T,
pub query_type: String,
pub mutation_type: String,
}
impl<T: GQLType> GQLType for QueryRoot<T> {
fn type_name() -> Cow<'static, str> {
T::type_name()
}
fn create_type_info(registry: &mut schema::Registry) -> String {
fn create_type_info(registry: &mut registry::Registry) -> String {
T::create_type_info(registry)
}
}
@ -17,16 +22,28 @@ impl<T: GQLType> GQLType for QueryRoot<T> {
#[async_trait::async_trait]
impl<T: GQLOutputValue + Send + Sync> GQLOutputValue for QueryRoot<T> {
async fn resolve(&self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
let mut value = self.0.resolve(ctx).await?;
if let serde_json::Value::Object(obj) = &mut value {
let mut res = self.inner.resolve(ctx).await?;
if let serde_json::Value::Object(obj) = &mut res {
for item in &ctx.item.items {
if let Selection::Field(field) = item {
if field.name == "__schema" {
todo!()
let ctx_obj = ctx.with_item(&field.selection_set);
obj.insert(
"__schema".to_string(),
__Schema {
registry: &ctx.registry,
query_type: &self.query_type,
mutation_type: &self.mutation_type,
}
.resolve(&ctx_obj)
.await?,
);
}
}
}
}
Ok(value)
Ok(res)
}
}