add some validation code
This commit is contained in:
parent
a09fd09cb1
commit
53dcd0aae5
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -13,5 +13,7 @@ jobs:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cargo build --all --verbose
|
run: cargo build --all --verbose
|
||||||
|
- name: Check format
|
||||||
|
run: cargo fmt --all -- --check
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: cargo test --all --verbose
|
run: cargo test --all --verbose
|
||||||
|
|
|
@ -60,11 +60,11 @@ pub fn generate(enum_args: &args::Enum, input: &DeriveInput) -> Result<TokenStre
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
schema_enum_items.push(quote! {
|
schema_enum_items.push(quote! {
|
||||||
#crate_name::registry::EnumValue {
|
enum_items.insert(#gql_item_name, #crate_name::registry::EnumValue {
|
||||||
name: #gql_item_name,
|
name: #gql_item_name,
|
||||||
description: #item_desc,
|
description: #item_desc,
|
||||||
deprecation: #item_deprecation,
|
deprecation: #item_deprecation,
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +91,11 @@ pub fn generate(enum_args: &args::Enum, input: &DeriveInput) -> Result<TokenStre
|
||||||
#crate_name::registry::Type::Enum {
|
#crate_name::registry::Type::Enum {
|
||||||
name: #gql_typename,
|
name: #gql_typename,
|
||||||
description: #desc,
|
description: #desc,
|
||||||
enum_values: vec![#(#schema_enum_items),*],
|
enum_values: {
|
||||||
|
let mut enum_items = std::collections::HashMap::new();
|
||||||
|
#(#schema_enum_items)*
|
||||||
|
enum_items
|
||||||
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,12 +132,12 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| quote! {None});
|
.unwrap_or_else(|| quote! {None});
|
||||||
schema_args.push(quote! {
|
schema_args.push(quote! {
|
||||||
#crate_name::registry::InputValue {
|
args.insert(#name, #crate_name::registry::InputValue {
|
||||||
name: #name,
|
name: #name,
|
||||||
description: #desc,
|
description: #desc,
|
||||||
ty: <#ty as #crate_name::GQLType>::create_type_info(registry),
|
ty: <#ty as #crate_name::GQLType>::create_type_info(registry),
|
||||||
default_value: #schema_default,
|
default_value: #schema_default,
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,13 +174,17 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result
|
||||||
let schema_ty = ty.value_type();
|
let schema_ty = ty.value_type();
|
||||||
|
|
||||||
schema_fields.push(quote! {
|
schema_fields.push(quote! {
|
||||||
#crate_name::registry::Field {
|
fields.insert(#name, #crate_name::registry::Field {
|
||||||
name: #name,
|
name: #name,
|
||||||
description: #desc,
|
description: #desc,
|
||||||
args: vec![#(#schema_args),*],
|
args: {
|
||||||
|
let mut args = std::collections::HashMap::new();
|
||||||
|
#(#schema_args)*
|
||||||
|
args
|
||||||
|
},
|
||||||
ty: <#schema_ty as #crate_name::GQLType>::create_type_info(registry),
|
ty: <#schema_ty as #crate_name::GQLType>::create_type_info(registry),
|
||||||
deprecation: #deprecation,
|
deprecation: #deprecation,
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
let resolve_obj = match &ty {
|
let resolve_obj = match &ty {
|
||||||
|
@ -227,7 +231,11 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result
|
||||||
#crate_name::registry::Type::Interface {
|
#crate_name::registry::Type::Interface {
|
||||||
name: #gql_typename,
|
name: #gql_typename,
|
||||||
description: #desc,
|
description: #desc,
|
||||||
fields: vec![#(#schema_fields),*],
|
fields: {
|
||||||
|
let mut fields = std::collections::HashMap::new();
|
||||||
|
#(#schema_fields)*
|
||||||
|
fields
|
||||||
|
},
|
||||||
possible_types: vec![#(#possible_types),*],
|
possible_types: vec![#(#possible_types),*],
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -241,7 +249,7 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result
|
||||||
|
|
||||||
#(#resolvers)*
|
#(#resolvers)*
|
||||||
|
|
||||||
anyhow::bail!(#crate_name::QueryError::FieldNotFound {
|
#crate_name::anyhow::bail!(#crate_name::QueryError::FieldNotFound {
|
||||||
field_name: field.name.clone(),
|
field_name: field.name.clone(),
|
||||||
object: #gql_typename.to_string(),
|
object: #gql_typename.to_string(),
|
||||||
}
|
}
|
||||||
|
@ -250,7 +258,7 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result
|
||||||
|
|
||||||
async fn resolve_inline_fragment(&self, name: &str, ctx: &#crate_name::ContextSelectionSet<'_>, result: &mut #crate_name::serde_json::Map<String, serde_json::Value>) -> #crate_name::Result<()> {
|
async fn resolve_inline_fragment(&self, name: &str, ctx: &#crate_name::ContextSelectionSet<'_>, result: &mut #crate_name::serde_json::Map<String, serde_json::Value>) -> #crate_name::Result<()> {
|
||||||
#(#inline_fragment_resolvers)*
|
#(#inline_fragment_resolvers)*
|
||||||
anyhow::bail!(#crate_name::QueryError::UnrecognizedInlineFragment {
|
#crate_name::anyhow::bail!(#crate_name::QueryError::UnrecognizedInlineFragment {
|
||||||
object: #gql_typename.to_string(),
|
object: #gql_typename.to_string(),
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
});
|
});
|
||||||
|
|
|
@ -129,12 +129,12 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
|
||||||
.unwrap_or_else(|| quote! {None});
|
.unwrap_or_else(|| quote! {None});
|
||||||
|
|
||||||
schema_args.push(quote! {
|
schema_args.push(quote! {
|
||||||
#crate_name::registry::InputValue {
|
args.insert(#name, #crate_name::registry::InputValue {
|
||||||
name: #name,
|
name: #name,
|
||||||
description: #desc,
|
description: #desc,
|
||||||
ty: <#ty as #crate_name::GQLType>::create_type_info(registry),
|
ty: <#ty as #crate_name::GQLType>::create_type_info(registry),
|
||||||
default_value: #schema_default,
|
default_value: #schema_default,
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
use_params.push(quote! { #ident });
|
use_params.push(quote! { #ident });
|
||||||
|
@ -153,13 +153,17 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
|
||||||
|
|
||||||
let schema_ty = ty.value_type();
|
let schema_ty = ty.value_type();
|
||||||
schema_fields.push(quote! {
|
schema_fields.push(quote! {
|
||||||
#crate_name::registry::Field {
|
fields.insert(#field_name, #crate_name::registry::Field {
|
||||||
name: #field_name,
|
name: #field_name,
|
||||||
description: #field_desc,
|
description: #field_desc,
|
||||||
args: vec![#(#schema_args),*],
|
args: {
|
||||||
|
let mut args = std::collections::HashMap::new();
|
||||||
|
#(#schema_args)*
|
||||||
|
args
|
||||||
|
},
|
||||||
ty: <#schema_ty as #crate_name::GQLType>::create_type_info(registry),
|
ty: <#schema_ty as #crate_name::GQLType>::create_type_info(registry),
|
||||||
deprecation: #field_deprecation,
|
deprecation: #field_deprecation,
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
let ctx_field = match arg_ctx {
|
let ctx_field = match arg_ctx {
|
||||||
|
@ -206,7 +210,11 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
|
||||||
registry.create_type::<Self, _>(|registry| #crate_name::registry::Type::Object {
|
registry.create_type::<Self, _>(|registry| #crate_name::registry::Type::Object {
|
||||||
name: #gql_typename,
|
name: #gql_typename,
|
||||||
description: #desc,
|
description: #desc,
|
||||||
fields: vec![#(#schema_fields),*],
|
fields: {
|
||||||
|
let mut fields = std::collections::HashMap::new();
|
||||||
|
#(#schema_fields)*
|
||||||
|
fields
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -218,7 +226,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
|
||||||
|
|
||||||
#(#resolvers)*
|
#(#resolvers)*
|
||||||
|
|
||||||
anyhow::bail!(#crate_name::QueryError::FieldNotFound {
|
#crate_name::anyhow::bail!(#crate_name::QueryError::FieldNotFound {
|
||||||
field_name: field.name.clone(),
|
field_name: field.name.clone(),
|
||||||
object: #gql_typename.to_string(),
|
object: #gql_typename.to_string(),
|
||||||
}
|
}
|
||||||
|
@ -226,7 +234,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn resolve_inline_fragment(&self, name: &str, ctx: &#crate_name::ContextSelectionSet<'_>, result: &mut #crate_name::serde_json::Map<String, serde_json::Value>) -> #crate_name::Result<()> {
|
async fn resolve_inline_fragment(&self, name: &str, ctx: &#crate_name::ContextSelectionSet<'_>, result: &mut #crate_name::serde_json::Map<String, serde_json::Value>) -> #crate_name::Result<()> {
|
||||||
anyhow::bail!(#crate_name::QueryError::UnrecognizedInlineFragment {
|
#crate_name::anyhow::bail!(#crate_name::QueryError::UnrecognizedInlineFragment {
|
||||||
object: #gql_typename.to_string(),
|
object: #gql_typename.to_string(),
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
});
|
});
|
||||||
|
|
|
@ -41,10 +41,17 @@ pub trait GQLInputObject: GQLInputValue {}
|
||||||
|
|
||||||
pub trait GQLScalar: Sized + Send {
|
pub trait GQLScalar: Sized + Send {
|
||||||
fn type_name() -> &'static str;
|
fn type_name() -> &'static str;
|
||||||
|
|
||||||
fn description() -> Option<&'static str> {
|
fn description() -> Option<&'static str> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse(value: &Value) -> Option<Self>;
|
fn parse(value: &Value) -> Option<Self>;
|
||||||
|
|
||||||
|
fn is_valid(value: &Value) -> bool {
|
||||||
|
Self::parse(value).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
fn to_json(&self) -> Result<serde_json::Value>;
|
fn to_json(&self) -> Result<serde_json::Value>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,6 +67,7 @@ macro_rules! impl_scalar {
|
||||||
registry.create_type::<$ty, _>(|_| crate::registry::Type::Scalar {
|
registry.create_type::<$ty, _>(|_| crate::registry::Type::Scalar {
|
||||||
name: <$ty as crate::GQLScalar>::type_name().to_string(),
|
name: <$ty as crate::GQLScalar>::type_name().to_string(),
|
||||||
description: <$ty>::description(),
|
description: <$ty>::description(),
|
||||||
|
is_valid: |value| <$ty as crate::GQLScalar>::is_valid(value),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,6 +81,7 @@ macro_rules! impl_scalar {
|
||||||
registry.create_type::<$ty, _>(|_| crate::registry::Type::Scalar {
|
registry.create_type::<$ty, _>(|_| crate::registry::Type::Scalar {
|
||||||
name: <$ty as crate::GQLScalar>::type_name().to_string(),
|
name: <$ty as crate::GQLScalar>::type_name().to_string(),
|
||||||
description: <$ty>::description(),
|
description: <$ty>::description(),
|
||||||
|
is_valid: |value| <$ty as crate::GQLScalar>::is_valid(value),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
20
src/error.rs
20
src/error.rs
|
@ -104,3 +104,23 @@ impl Display for PositionError {
|
||||||
write!(f, "{}", self.inner)
|
write!(f, "{}", self.inner)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RuleError {
|
||||||
|
pub locations: Vec<Pos>,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub struct RuleErrors {
|
||||||
|
pub errors: Vec<RuleError>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for RuleErrors {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
for error in &self.errors {
|
||||||
|
writeln!(f, "{}", error.message)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,8 +4,10 @@ mod playground_source;
|
||||||
pub use graphiql_source::graphiql_source;
|
pub use graphiql_source::graphiql_source;
|
||||||
pub use playground_source::playground_source;
|
pub use playground_source::playground_source;
|
||||||
|
|
||||||
|
use crate::error::{RuleError, RuleErrors};
|
||||||
use crate::{GQLObject, PositionError, Result, Schema, Variables};
|
use crate::{GQLObject, PositionError, Result, Schema, Variables};
|
||||||
use serde::ser::SerializeMap;
|
use graphql_parser::Pos;
|
||||||
|
use serde::ser::{SerializeMap, SerializeSeq};
|
||||||
use serde::{Serialize, Serializer};
|
use serde::{Serialize, Serializer};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
@ -57,7 +59,7 @@ impl Serialize for GQLResponse {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let mut map = serializer.serialize_map(None)?;
|
let mut map = serializer.serialize_map(None)?;
|
||||||
map.serialize_key("errors")?;
|
map.serialize_key("errors")?;
|
||||||
map.serialize_value(&[GQLError(err)])?;
|
map.serialize_value(&GQLError(err))?;
|
||||||
map.end()
|
map.end()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,25 +81,75 @@ impl<'a> Serialize for GQLError<'a> {
|
||||||
where
|
where
|
||||||
S: Serializer,
|
S: Serializer,
|
||||||
{
|
{
|
||||||
let mut map = serializer.serialize_map(None)?;
|
if let Some(err) = self.0.downcast_ref::<PositionError>() {
|
||||||
|
let mut seq = serializer.serialize_seq(Some(1))?;
|
||||||
match self.0.downcast_ref::<PositionError>() {
|
seq.serialize_element(&GQLPositionError(err))?;
|
||||||
Some(err) => {
|
seq.end()
|
||||||
map.serialize_key("message")?;
|
} else if let Some(err) = self.0.downcast_ref::<RuleErrors>() {
|
||||||
map.serialize_value(&err.to_string())?;
|
let mut seq = serializer.serialize_seq(Some(err.errors.len()))?;
|
||||||
|
for err in &err.errors {
|
||||||
map.serialize_key("locations")?;
|
seq.serialize_element(&GQLRuleError(err))?;
|
||||||
map.serialize_value(&[serde_json::json! ({
|
|
||||||
"line": err.position.line,
|
|
||||||
"column": err.position.column,
|
|
||||||
})])?;
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
map.serialize_key("message")?;
|
|
||||||
map.serialize_value(&self.0.to_string())?;
|
|
||||||
}
|
}
|
||||||
|
seq.end()
|
||||||
|
} else {
|
||||||
|
let mut seq = serializer.serialize_seq(None)?;
|
||||||
|
seq.serialize_element(&serde_json::json!({
|
||||||
|
"message": self.0.to_string(),
|
||||||
|
}))?;
|
||||||
|
seq.end()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GQLErrorPos<'a>(&'a Pos);
|
||||||
|
|
||||||
|
impl<'a> Serialize for GQLErrorPos<'a> {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let mut map = serializer.serialize_map(Some(2))?;
|
||||||
|
map.serialize_entry("line", &self.0.line)?;
|
||||||
|
map.serialize_entry("column", &self.0.column)?;
|
||||||
|
map.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GQLPositionError<'a>(&'a PositionError);
|
||||||
|
|
||||||
|
impl<'a> Serialize for GQLPositionError<'a> {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let mut map = serializer.serialize_map(None)?;
|
||||||
|
map.serialize_entry("message", &self.0.inner.to_string())?;
|
||||||
|
map.serialize_entry(
|
||||||
|
"locations",
|
||||||
|
std::slice::from_ref(&GQLErrorPos(&self.0.position)),
|
||||||
|
)?;
|
||||||
|
map.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GQLRuleError<'a>(&'a RuleError);
|
||||||
|
|
||||||
|
impl<'a> Serialize for GQLRuleError<'a> {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let mut map = serializer.serialize_map(None)?;
|
||||||
|
map.serialize_entry("message", &self.0.message)?;
|
||||||
|
map.serialize_entry(
|
||||||
|
"locations",
|
||||||
|
&self
|
||||||
|
.0
|
||||||
|
.locations
|
||||||
|
.iter()
|
||||||
|
.map(|pos| GQLErrorPos(pos))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)?;
|
||||||
map.end()
|
map.end()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,6 +61,7 @@ mod resolver;
|
||||||
mod scalars;
|
mod scalars;
|
||||||
mod schema;
|
mod schema;
|
||||||
mod types;
|
mod types;
|
||||||
|
mod validation;
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use anyhow;
|
pub use anyhow;
|
||||||
|
|
|
@ -97,7 +97,7 @@ impl<'a> __Directive<'a> {
|
||||||
async fn args(&self) -> Vec<__InputValue<'a>> {
|
async fn args(&self) -> Vec<__InputValue<'a>> {
|
||||||
self.directive
|
self.directive
|
||||||
.args
|
.args
|
||||||
.iter()
|
.values()
|
||||||
.map(|input_value| __InputValue {
|
.map(|input_value| __InputValue {
|
||||||
registry: self.registry,
|
registry: self.registry,
|
||||||
input_value,
|
input_value,
|
||||||
|
|
|
@ -26,7 +26,7 @@ impl<'a> __Field<'a> {
|
||||||
async fn args(&self) -> Vec<__InputValue<'a>> {
|
async fn args(&self) -> Vec<__InputValue<'a>> {
|
||||||
self.field
|
self.field
|
||||||
.args
|
.args
|
||||||
.iter()
|
.values()
|
||||||
.map(|input_value| __InputValue {
|
.map(|input_value| __InputValue {
|
||||||
registry: self.registry,
|
registry: self.registry,
|
||||||
input_value,
|
input_value,
|
||||||
|
|
|
@ -4,8 +4,6 @@ use async_graphql_derive::Object;
|
||||||
|
|
||||||
pub struct __Schema<'a> {
|
pub struct __Schema<'a> {
|
||||||
pub registry: &'a registry::Registry,
|
pub registry: &'a registry::Registry,
|
||||||
pub query_type: &'a str,
|
|
||||||
pub mutation_type: Option<&'a str>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Object(
|
#[Object(
|
||||||
|
@ -27,7 +25,10 @@ impl<'a> __Schema<'a> {
|
||||||
desc = "The type that query operations will be rooted at."
|
desc = "The type that query operations will be rooted at."
|
||||||
)]
|
)]
|
||||||
async fn query_type(&self) -> __Type<'a> {
|
async fn query_type(&self) -> __Type<'a> {
|
||||||
__Type::new_simple(self.registry, &self.registry.types[self.query_type])
|
__Type::new_simple(
|
||||||
|
self.registry,
|
||||||
|
&self.registry.types[&self.registry.query_type],
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[field(
|
#[field(
|
||||||
|
@ -35,7 +36,7 @@ impl<'a> __Schema<'a> {
|
||||||
desc = "If this server supports mutation, the type that mutation operations will be rooted at."
|
desc = "If this server supports mutation, the type that mutation operations will be rooted at."
|
||||||
)]
|
)]
|
||||||
async fn mutation_type(&self) -> Option<__Type<'a>> {
|
async fn mutation_type(&self) -> Option<__Type<'a>> {
|
||||||
if let Some(ty) = self.mutation_type {
|
if let Some(ty) = &self.registry.mutation_type {
|
||||||
Some(__Type::new_simple(self.registry, &self.registry.types[ty]))
|
Some(__Type::new_simple(self.registry, &self.registry.types[ty]))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -54,7 +55,7 @@ impl<'a> __Schema<'a> {
|
||||||
async fn directives(&self) -> Vec<__Directive<'a>> {
|
async fn directives(&self) -> Vec<__Directive<'a>> {
|
||||||
self.registry
|
self.registry
|
||||||
.directives
|
.directives
|
||||||
.iter()
|
.values()
|
||||||
.map(|directive| __Directive {
|
.map(|directive| __Directive {
|
||||||
registry: &self.registry,
|
registry: &self.registry,
|
||||||
directive,
|
directive,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::model::{__EnumValue, __Field, __InputValue, __TypeKind};
|
use crate::model::{__EnumValue, __Field, __InputValue, __TypeKind};
|
||||||
use crate::registry;
|
use crate::registry;
|
||||||
use crate::registry::Type;
|
use crate::registry::{Type, TypeInfo};
|
||||||
use async_graphql_derive::Object;
|
use async_graphql_derive::Object;
|
||||||
|
|
||||||
enum TypeDetail<'a> {
|
enum TypeDetail<'a> {
|
||||||
|
@ -23,21 +23,19 @@ impl<'a> __Type<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(registry: &'a registry::Registry, type_name: &str) -> __Type<'a> {
|
pub fn new(registry: &'a registry::Registry, type_name: &str) -> __Type<'a> {
|
||||||
if let Some(type_name) = parse_non_null(type_name) {
|
match TypeInfo::create(type_name) {
|
||||||
__Type {
|
TypeInfo::NonNull(ty) => __Type {
|
||||||
registry,
|
registry,
|
||||||
detail: TypeDetail::NonNull(type_name.to_string()),
|
detail: TypeDetail::NonNull(ty.to_string()),
|
||||||
}
|
},
|
||||||
} else if let Some(type_name) = parse_list(type_name) {
|
TypeInfo::List(ty) => __Type {
|
||||||
__Type {
|
|
||||||
registry,
|
registry,
|
||||||
detail: TypeDetail::List(type_name.to_string()),
|
detail: TypeDetail::List(ty.to_string()),
|
||||||
}
|
},
|
||||||
} else {
|
TypeInfo::Type(ty) => __Type {
|
||||||
__Type {
|
|
||||||
registry,
|
registry,
|
||||||
detail: TypeDetail::Simple(®istry.types[type_name]),
|
detail: TypeDetail::Simple(®istry.types[ty]),
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,14 +68,7 @@ impl<'a> __Type<'a> {
|
||||||
#[field]
|
#[field]
|
||||||
async fn name(&self) -> Option<String> {
|
async fn name(&self) -> Option<String> {
|
||||||
match &self.detail {
|
match &self.detail {
|
||||||
TypeDetail::Simple(ty) => match ty {
|
TypeDetail::Simple(ty) => Some(ty.name().to_string()),
|
||||||
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()),
|
|
||||||
},
|
|
||||||
TypeDetail::NonNull(_) => None,
|
TypeDetail::NonNull(_) => None,
|
||||||
TypeDetail::List(_) => None,
|
TypeDetail::List(_) => None,
|
||||||
}
|
}
|
||||||
|
@ -106,40 +97,22 @@ impl<'a> __Type<'a> {
|
||||||
&self,
|
&self,
|
||||||
#[arg(name = "includeDeprecated", default = "false")] include_deprecated: bool,
|
#[arg(name = "includeDeprecated", default = "false")] include_deprecated: bool,
|
||||||
) -> Option<Vec<__Field<'a>>> {
|
) -> Option<Vec<__Field<'a>>> {
|
||||||
if let TypeDetail::Simple(Type::Object { fields, .. }) = &self.detail {
|
if let TypeDetail::Simple(ty) = &self.detail {
|
||||||
Some(
|
ty.fields().and_then(|fields| {
|
||||||
fields
|
Some(
|
||||||
.iter()
|
fields
|
||||||
.filter(|field| {
|
.values()
|
||||||
if include_deprecated {
|
.filter(|field| {
|
||||||
true
|
(include_deprecated || field.deprecation.is_none())
|
||||||
} else {
|
&& !field.name.starts_with("__")
|
||||||
field.deprecation.is_none()
|
})
|
||||||
}
|
.map(|field| __Field {
|
||||||
})
|
registry: self.registry,
|
||||||
.map(|field| __Field {
|
field,
|
||||||
registry: self.registry,
|
})
|
||||||
field,
|
.collect(),
|
||||||
})
|
)
|
||||||
.collect(),
|
})
|
||||||
)
|
|
||||||
} else if let TypeDetail::Simple(Type::Interface { fields, .. }) = &self.detail {
|
|
||||||
Some(
|
|
||||||
fields
|
|
||||||
.iter()
|
|
||||||
.filter(|field| {
|
|
||||||
if include_deprecated {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
field.deprecation.is_none()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.map(|field| __Field {
|
|
||||||
registry: self.registry,
|
|
||||||
field,
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -191,14 +164,8 @@ impl<'a> __Type<'a> {
|
||||||
if let TypeDetail::Simple(Type::Enum { enum_values, .. }) = &self.detail {
|
if let TypeDetail::Simple(Type::Enum { enum_values, .. }) = &self.detail {
|
||||||
Some(
|
Some(
|
||||||
enum_values
|
enum_values
|
||||||
.iter()
|
.values()
|
||||||
.filter(|field| {
|
.filter(|field| include_deprecated || field.deprecation.is_none())
|
||||||
if include_deprecated {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
field.deprecation.is_none()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.map(|value| __EnumValue {
|
.map(|value| __EnumValue {
|
||||||
registry: self.registry,
|
registry: self.registry,
|
||||||
value,
|
value,
|
||||||
|
@ -238,19 +205,3 @@ impl<'a> __Type<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_non_null(type_name: &str) -> Option<&str> {
|
|
||||||
if type_name.ends_with("!") {
|
|
||||||
Some(&type_name[..type_name.len() - 1])
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_list(type_name: &str) -> Option<&str> {
|
|
||||||
if type_name.starts_with("[") {
|
|
||||||
Some(&type_name[1..type_name.len() - 1])
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
107
src/registry.rs
107
src/registry.rs
|
@ -1,6 +1,40 @@
|
||||||
use crate::{model, GQLType};
|
use crate::{model, GQLType, Value};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
|
fn parse_non_null(type_name: &str) -> Option<&str> {
|
||||||
|
if type_name.ends_with("!") {
|
||||||
|
Some(&type_name[..type_name.len() - 1])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_list(type_name: &str) -> Option<&str> {
|
||||||
|
if type_name.starts_with("[") {
|
||||||
|
Some(&type_name[1..type_name.len() - 1])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum TypeInfo<'a> {
|
||||||
|
List(&'a str),
|
||||||
|
NonNull(&'a str),
|
||||||
|
Type(&'a str),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TypeInfo<'a> {
|
||||||
|
pub fn create(type_name: &str) -> TypeInfo {
|
||||||
|
if let Some(type_name) = parse_non_null(type_name) {
|
||||||
|
TypeInfo::NonNull(type_name)
|
||||||
|
} else if let Some(type_name) = parse_list(type_name) {
|
||||||
|
TypeInfo::List(type_name)
|
||||||
|
} else {
|
||||||
|
TypeInfo::Type(type_name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct InputValue {
|
pub struct InputValue {
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
pub description: Option<&'static str>,
|
pub description: Option<&'static str>,
|
||||||
|
@ -11,7 +45,7 @@ pub struct InputValue {
|
||||||
pub struct Field {
|
pub struct Field {
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
pub description: Option<&'static str>,
|
pub description: Option<&'static str>,
|
||||||
pub args: Vec<InputValue>,
|
pub args: HashMap<&'static str, InputValue>,
|
||||||
pub ty: String,
|
pub ty: String,
|
||||||
pub deprecation: Option<&'static str>,
|
pub deprecation: Option<&'static str>,
|
||||||
}
|
}
|
||||||
|
@ -26,16 +60,17 @@ pub enum Type {
|
||||||
Scalar {
|
Scalar {
|
||||||
name: String,
|
name: String,
|
||||||
description: Option<&'static str>,
|
description: Option<&'static str>,
|
||||||
|
is_valid: fn(value: &Value) -> bool,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
description: Option<&'static str>,
|
description: Option<&'static str>,
|
||||||
fields: Vec<Field>,
|
fields: HashMap<&'static str, Field>,
|
||||||
},
|
},
|
||||||
Interface {
|
Interface {
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
description: Option<&'static str>,
|
description: Option<&'static str>,
|
||||||
fields: Vec<Field>,
|
fields: HashMap<&'static str, Field>,
|
||||||
possible_types: Vec<String>,
|
possible_types: Vec<String>,
|
||||||
},
|
},
|
||||||
Union {
|
Union {
|
||||||
|
@ -46,7 +81,7 @@ pub enum Type {
|
||||||
Enum {
|
Enum {
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
description: Option<&'static str>,
|
description: Option<&'static str>,
|
||||||
enum_values: Vec<EnumValue>,
|
enum_values: HashMap<&'static str, EnumValue>,
|
||||||
},
|
},
|
||||||
InputObject {
|
InputObject {
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
|
@ -55,18 +90,44 @@ pub enum Type {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Type {
|
||||||
|
pub fn field_by_name(&self, name: &str) -> Option<&Field> {
|
||||||
|
self.fields().and_then(|fields| fields.get(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fields(&self) -> Option<&HashMap<&'static str, Field>> {
|
||||||
|
match self {
|
||||||
|
Type::Object { fields, .. } => Some(&fields),
|
||||||
|
Type::Interface { fields, .. } => Some(&fields),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Type::Scalar { name, .. } => &name,
|
||||||
|
Type::Object { name, .. } => name,
|
||||||
|
Type::Interface { name, .. } => name,
|
||||||
|
Type::Union { name, .. } => name,
|
||||||
|
Type::Enum { name, .. } => name,
|
||||||
|
Type::InputObject { name, .. } => name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Directive {
|
pub struct Directive {
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
pub description: Option<&'static str>,
|
pub description: Option<&'static str>,
|
||||||
pub locations: Vec<model::__DirectiveLocation>,
|
pub locations: Vec<model::__DirectiveLocation>,
|
||||||
pub args: Vec<InputValue>,
|
pub args: HashMap<&'static str, InputValue>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Registry {
|
pub struct Registry {
|
||||||
pub types: HashMap<String, Type>,
|
pub types: HashMap<String, Type>,
|
||||||
pub directives: Vec<Directive>,
|
pub directives: HashMap<String, Directive>,
|
||||||
pub implements: HashMap<String, HashSet<String>>,
|
pub implements: HashMap<String, HashSet<String>>,
|
||||||
|
pub query_type: String,
|
||||||
|
pub mutation_type: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Registry {
|
impl Registry {
|
||||||
|
@ -75,19 +136,33 @@ impl Registry {
|
||||||
if !self.types.contains_key(name.as_ref()) {
|
if !self.types.contains_key(name.as_ref()) {
|
||||||
self.types.insert(
|
self.types.insert(
|
||||||
name.to_string(),
|
name.to_string(),
|
||||||
Type::Scalar {
|
Type::Object {
|
||||||
name: String::new(),
|
name: "",
|
||||||
description: None,
|
description: None,
|
||||||
|
fields: Default::default(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
let ty = f(self);
|
let mut ty = f(self);
|
||||||
|
if let Type::Object { fields, .. } = &mut ty {
|
||||||
|
fields.insert(
|
||||||
|
"__typename",
|
||||||
|
Field {
|
||||||
|
name: "",
|
||||||
|
description: None,
|
||||||
|
args: Default::default(),
|
||||||
|
ty: "String!".to_string(),
|
||||||
|
deprecation: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
self.types.insert(name.to_string(), ty);
|
self.types.insert(name.to_string(), ty);
|
||||||
}
|
}
|
||||||
T::qualified_type_name()
|
T::qualified_type_name()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_directive(&mut self, directive: Directive) {
|
pub fn add_directive(&mut self, directive: Directive) {
|
||||||
self.directives.push(directive);
|
self.directives
|
||||||
|
.insert(directive.name.to_string(), directive);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_implements(&mut self, ty: &str, interface: &str) {
|
pub fn add_implements(&mut self, ty: &str, interface: &str) {
|
||||||
|
@ -102,4 +177,12 @@ impl Registry {
|
||||||
interfaces
|
interfaces
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_basic_type(&self, type_name: &str) -> Option<&Type> {
|
||||||
|
match TypeInfo::create(type_name) {
|
||||||
|
TypeInfo::Type(type_name) => self.types.get(type_name),
|
||||||
|
TypeInfo::List(type_name) => self.get_basic_type(type_name),
|
||||||
|
TypeInfo::NonNull(type_name) => self.get_basic_type(type_name),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,13 @@ impl GQLScalar for String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_valid(value: &Value) -> bool {
|
||||||
|
match value {
|
||||||
|
Value::String(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn to_json(&self) -> Result<serde_json::Value> {
|
fn to_json(&self) -> Result<serde_json::Value> {
|
||||||
Ok(self.clone().into())
|
Ok(self.clone().into())
|
||||||
}
|
}
|
||||||
|
@ -37,6 +44,10 @@ impl<'a> GQLType for &'a str {
|
||||||
registry.create_type::<Self, _>(|_| registry::Type::Scalar {
|
registry.create_type::<Self, _>(|_| registry::Type::Scalar {
|
||||||
name: Self::type_name().to_string(),
|
name: Self::type_name().to_string(),
|
||||||
description: Some(STRING_DESC),
|
description: Some(STRING_DESC),
|
||||||
|
is_valid: |value| match value {
|
||||||
|
Value::String(_) => true,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ use crate::context::Data;
|
||||||
use crate::model::__DirectiveLocation;
|
use crate::model::__DirectiveLocation;
|
||||||
use crate::registry::{Directive, InputValue, Registry};
|
use crate::registry::{Directive, InputValue, Registry};
|
||||||
use crate::types::QueryRoot;
|
use crate::types::QueryRoot;
|
||||||
|
use crate::validation::check_rules;
|
||||||
use crate::{
|
use crate::{
|
||||||
ContextBase, GQLObject, GQLOutputValue, GQLType, QueryError, QueryParseError, Result, Variables,
|
ContextBase, GQLObject, GQLOutputValue, GQLType, QueryError, QueryParseError, Result, Variables,
|
||||||
};
|
};
|
||||||
|
@ -19,7 +20,17 @@ pub struct Schema<Query, Mutation> {
|
||||||
|
|
||||||
impl<Query: GQLObject, Mutation: GQLObject> Schema<Query, Mutation> {
|
impl<Query: GQLObject, Mutation: GQLObject> Schema<Query, Mutation> {
|
||||||
pub fn new(query: Query, mutation: Mutation) -> Self {
|
pub fn new(query: Query, mutation: Mutation) -> Self {
|
||||||
let mut registry = Registry::default();
|
let mut registry = Registry {
|
||||||
|
types: Default::default(),
|
||||||
|
directives: Default::default(),
|
||||||
|
implements: Default::default(),
|
||||||
|
query_type: Query::type_name().to_string(),
|
||||||
|
mutation_type: if Mutation::is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(Mutation::type_name().to_string())
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
registry.add_directive(Directive {
|
registry.add_directive(Directive {
|
||||||
name: "include",
|
name: "include",
|
||||||
|
@ -29,12 +40,16 @@ impl<Query: GQLObject, Mutation: GQLObject> Schema<Query, Mutation> {
|
||||||
__DirectiveLocation::FRAGMENT_SPREAD,
|
__DirectiveLocation::FRAGMENT_SPREAD,
|
||||||
__DirectiveLocation::INLINE_FRAGMENT
|
__DirectiveLocation::INLINE_FRAGMENT
|
||||||
],
|
],
|
||||||
args: vec![InputValue{
|
args: {
|
||||||
name: "if",
|
let mut args = HashMap::new();
|
||||||
description: Some("Included when true."),
|
args.insert("if", InputValue {
|
||||||
ty: "Boolean!".to_string(),
|
name: "if",
|
||||||
default_value: None
|
description: Some("Included when true."),
|
||||||
}]
|
ty: "Boolean!".to_string(),
|
||||||
|
default_value: None
|
||||||
|
});
|
||||||
|
args
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
registry.add_directive(Directive {
|
registry.add_directive(Directive {
|
||||||
|
@ -45,12 +60,16 @@ impl<Query: GQLObject, Mutation: GQLObject> Schema<Query, Mutation> {
|
||||||
__DirectiveLocation::FRAGMENT_SPREAD,
|
__DirectiveLocation::FRAGMENT_SPREAD,
|
||||||
__DirectiveLocation::INLINE_FRAGMENT
|
__DirectiveLocation::INLINE_FRAGMENT
|
||||||
],
|
],
|
||||||
args: vec![InputValue{
|
args: {
|
||||||
name: "if",
|
let mut args = HashMap::new();
|
||||||
description: Some("Skipped when true."),
|
args.insert("if", InputValue {
|
||||||
ty: "Boolean!".to_string(),
|
name: "if",
|
||||||
default_value: None
|
description: Some("Skipped when true."),
|
||||||
}]
|
ty: "Boolean!".to_string(),
|
||||||
|
default_value: None
|
||||||
|
});
|
||||||
|
args
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// register scalars
|
// register scalars
|
||||||
|
@ -59,21 +78,13 @@ impl<Query: GQLObject, Mutation: GQLObject> Schema<Query, Mutation> {
|
||||||
f32::create_type_info(&mut registry);
|
f32::create_type_info(&mut registry);
|
||||||
String::create_type_info(&mut registry);
|
String::create_type_info(&mut registry);
|
||||||
|
|
||||||
Query::create_type_info(&mut registry);
|
QueryRoot::<Query>::create_type_info(&mut registry);
|
||||||
if !Mutation::is_empty() {
|
if !Mutation::is_empty() {
|
||||||
Mutation::create_type_info(&mut registry);
|
Mutation::create_type_info(&mut registry);
|
||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
query: QueryRoot {
|
query: QueryRoot { inner: query },
|
||||||
inner: query,
|
|
||||||
query_type: Query::type_name().to_string(),
|
|
||||||
mutation_type: if !Mutation::is_empty() {
|
|
||||||
Some(Mutation::type_name().to_string())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mutation,
|
mutation,
|
||||||
registry,
|
registry,
|
||||||
data: Default::default(),
|
data: Default::default(),
|
||||||
|
@ -132,6 +143,8 @@ impl<'a, Query, Mutation> QueryBuilder<'a, Query, Mutation> {
|
||||||
parse_query(self.query_source).map_err(|err| QueryParseError(err.to_string()))?;
|
parse_query(self.query_source).map_err(|err| QueryParseError(err.to_string()))?;
|
||||||
let mut fragments = HashMap::new();
|
let mut fragments = HashMap::new();
|
||||||
|
|
||||||
|
check_rules(self.registry, &document)?;
|
||||||
|
|
||||||
for definition in &document.definitions {
|
for definition in &document.definitions {
|
||||||
if let Definition::Fragment(fragment) = definition {
|
if let Definition::Fragment(fragment) = definition {
|
||||||
fragments.insert(fragment.name.clone(), fragment);
|
fragments.insert(fragment.name.clone(), fragment);
|
||||||
|
|
|
@ -14,7 +14,7 @@ impl GQLType for GQLEmptyMutation {
|
||||||
registry.create_type::<Self, _>(|_| registry::Type::Object {
|
registry.create_type::<Self, _>(|_| registry::Type::Object {
|
||||||
name: "EmptyMutation",
|
name: "EmptyMutation",
|
||||||
description: None,
|
description: None,
|
||||||
fields: Vec::new(),
|
fields: Default::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
use crate::model::{__Schema, __Type};
|
use crate::model::{__Schema, __Type};
|
||||||
|
use crate::registry::Type;
|
||||||
use crate::{
|
use crate::{
|
||||||
registry, Context, ContextSelectionSet, ErrorWithPosition, GQLObject, GQLOutputValue, GQLType,
|
registry, Context, ContextSelectionSet, ErrorWithPosition, GQLObject, GQLOutputValue, GQLType,
|
||||||
QueryError, Result, Value,
|
QueryError, Result, Value,
|
||||||
};
|
};
|
||||||
use graphql_parser::query::Field;
|
use graphql_parser::query::Field;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub struct QueryRoot<T> {
|
pub struct QueryRoot<T> {
|
||||||
pub inner: T,
|
pub inner: T,
|
||||||
pub query_type: String,
|
|
||||||
pub mutation_type: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: GQLType> GQLType for QueryRoot<T> {
|
impl<T: GQLType> GQLType for QueryRoot<T> {
|
||||||
|
@ -18,7 +18,44 @@ impl<T: GQLType> GQLType for QueryRoot<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_type_info(registry: &mut registry::Registry) -> String {
|
fn create_type_info(registry: &mut registry::Registry) -> String {
|
||||||
T::create_type_info(registry)
|
let schema_type = __Schema::create_type_info(registry);
|
||||||
|
let root = T::create_type_info(registry);
|
||||||
|
if let Some(Type::Object { fields, .. }) = registry.types.get_mut(T::type_name().as_ref()) {
|
||||||
|
fields.insert(
|
||||||
|
"__schema",
|
||||||
|
registry::Field {
|
||||||
|
name: "__schema",
|
||||||
|
description: Some("Access the current type schema of this server."),
|
||||||
|
args: Default::default(),
|
||||||
|
ty: schema_type,
|
||||||
|
deprecation: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
fields.insert(
|
||||||
|
"__type",
|
||||||
|
registry::Field {
|
||||||
|
name: "__type",
|
||||||
|
description: Some("Request the type information of a single type."),
|
||||||
|
args: {
|
||||||
|
let mut args = HashMap::new();
|
||||||
|
args.insert(
|
||||||
|
"name",
|
||||||
|
registry::InputValue {
|
||||||
|
name: "name",
|
||||||
|
description: None,
|
||||||
|
ty: "String!".to_string(),
|
||||||
|
default_value: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
args
|
||||||
|
},
|
||||||
|
ty: "__Type".to_string(),
|
||||||
|
deprecation: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
root
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,8 +67,6 @@ impl<T: GQLObject + Send + Sync> GQLObject for QueryRoot<T> {
|
||||||
return GQLOutputValue::resolve(
|
return GQLOutputValue::resolve(
|
||||||
&__Schema {
|
&__Schema {
|
||||||
registry: &ctx.registry,
|
registry: &ctx.registry,
|
||||||
query_type: &self.query_type,
|
|
||||||
mutation_type: self.mutation_type.as_deref(),
|
|
||||||
},
|
},
|
||||||
&ctx_obj,
|
&ctx_obj,
|
||||||
)
|
)
|
||||||
|
|
36
src/validation/context.rs
Normal file
36
src/validation/context.rs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
use crate::error::RuleError;
|
||||||
|
use crate::registry::{Registry, Type};
|
||||||
|
use graphql_parser::Pos;
|
||||||
|
|
||||||
|
pub struct ValidatorContext<'a> {
|
||||||
|
pub registry: &'a Registry,
|
||||||
|
pub errors: Vec<RuleError>,
|
||||||
|
type_stack: Vec<&'a Type>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ValidatorContext<'a> {
|
||||||
|
pub fn new(registry: &'a Registry) -> Self {
|
||||||
|
Self {
|
||||||
|
registry,
|
||||||
|
errors: Default::default(),
|
||||||
|
type_stack: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn report_error<T: Into<String>>(&mut self, locations: Vec<Pos>, msg: T) {
|
||||||
|
self.errors.push(RuleError {
|
||||||
|
locations,
|
||||||
|
message: msg.into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_type<F: FnMut(&mut ValidatorContext<'a>)>(&mut self, ty: &'a Type, mut f: F) {
|
||||||
|
self.type_stack.push(ty);
|
||||||
|
f(self);
|
||||||
|
self.type_stack.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parent_type(&self) -> &'a Type {
|
||||||
|
self.type_stack.last().unwrap()
|
||||||
|
}
|
||||||
|
}
|
23
src/validation/mod.rs
Normal file
23
src/validation/mod.rs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
use crate::error::RuleErrors;
|
||||||
|
use crate::registry::Registry;
|
||||||
|
use crate::validation::context::ValidatorContext;
|
||||||
|
use crate::validation::visitor::{visit, VisitorNil};
|
||||||
|
use crate::Result;
|
||||||
|
use graphql_parser::query::Document;
|
||||||
|
|
||||||
|
mod context;
|
||||||
|
mod rules;
|
||||||
|
mod utils;
|
||||||
|
mod visitor;
|
||||||
|
|
||||||
|
pub fn check_rules(registry: &Registry, doc: &Document) -> Result<()> {
|
||||||
|
let mut ctx = ValidatorContext::new(registry);
|
||||||
|
let mut visitor = VisitorNil.with(rules::ArgumentsOfCorrectType::default());
|
||||||
|
|
||||||
|
visit(&mut visitor, &mut ctx, doc);
|
||||||
|
if !ctx.errors.is_empty() {
|
||||||
|
Err(RuleErrors { errors: ctx.errors }.into())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
55
src/validation/rules/arguments_of_correct_type.rs
Normal file
55
src/validation/rules/arguments_of_correct_type.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
use crate::registry::InputValue;
|
||||||
|
use crate::validation::context::ValidatorContext;
|
||||||
|
use crate::validation::utils::is_valid_input_value;
|
||||||
|
use crate::validation::visitor::Visitor;
|
||||||
|
use graphql_parser::query::Field;
|
||||||
|
use graphql_parser::schema::{Directive, Value};
|
||||||
|
use graphql_parser::Pos;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct ArgumentsOfCorrectType<'a> {
|
||||||
|
current_args: Option<(&'a HashMap<&'static str, InputValue>, Pos)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Visitor<'a> for ArgumentsOfCorrectType<'a> {
|
||||||
|
fn enter_directive(&mut self, ctx: &mut ValidatorContext<'a>, directive: &'a Directive) {
|
||||||
|
self.current_args = ctx
|
||||||
|
.registry
|
||||||
|
.directives
|
||||||
|
.get(&directive.name)
|
||||||
|
.map(|d| (&d.args, directive.position));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exit_directive(&mut self, _ctx: &mut ValidatorContext<'a>, _directive: &'a Directive) {
|
||||||
|
self.current_args = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_argument(&mut self, ctx: &mut ValidatorContext<'a>, name: &str, value: &'a Value) {
|
||||||
|
if let Some((arg, pos)) = self
|
||||||
|
.current_args
|
||||||
|
.and_then(|(args, pos)| args.get(name).map(|input| (input, pos)))
|
||||||
|
{
|
||||||
|
if !is_valid_input_value(ctx.registry, &arg.ty, value) {
|
||||||
|
ctx.report_error(
|
||||||
|
vec![pos],
|
||||||
|
format!(
|
||||||
|
"Invalid value for argument \"{}\", expected type \"{}\"",
|
||||||
|
arg.name, arg.ty,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_field(&mut self, ctx: &mut ValidatorContext<'a>, field: &'a Field) {
|
||||||
|
self.current_args = ctx
|
||||||
|
.parent_type()
|
||||||
|
.field_by_name(&field.name)
|
||||||
|
.map(|f| (&f.args, field.position));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exit_field(&mut self, _ctx: &mut ValidatorContext<'a>, _field: &'a Field) {
|
||||||
|
self.current_args = None;
|
||||||
|
}
|
||||||
|
}
|
3
src/validation/rules/mod.rs
Normal file
3
src/validation/rules/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
mod arguments_of_correct_type;
|
||||||
|
|
||||||
|
pub use arguments_of_correct_type::ArgumentsOfCorrectType;
|
46
src/validation/utils.rs
Normal file
46
src/validation/utils.rs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
use crate::registry::{Registry, Type, TypeInfo};
|
||||||
|
use crate::Value;
|
||||||
|
|
||||||
|
pub fn is_valid_input_value(registry: &Registry, type_name: &str, value: &Value) -> bool {
|
||||||
|
match TypeInfo::create(type_name) {
|
||||||
|
TypeInfo::NonNull(type_name) => match value {
|
||||||
|
Value::Null => false,
|
||||||
|
_ => is_valid_input_value(registry, type_name, value),
|
||||||
|
},
|
||||||
|
TypeInfo::List(type_name) => match value {
|
||||||
|
Value::List(elems) => elems
|
||||||
|
.iter()
|
||||||
|
.all(|elem| is_valid_input_value(registry, type_name, elem)),
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
TypeInfo::Type(type_name) => {
|
||||||
|
if let Some(ty) = registry.types.get(type_name) {
|
||||||
|
match ty {
|
||||||
|
Type::Scalar { is_valid, .. } => is_valid(value),
|
||||||
|
Type::Enum { enum_values, .. } => match value {
|
||||||
|
Value::Enum(name) => enum_values.contains_key(name.as_str()),
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
Type::InputObject { input_fields, .. } => match value {
|
||||||
|
Value::Object(values) => {
|
||||||
|
for field in input_fields {
|
||||||
|
if !is_valid_input_value(
|
||||||
|
registry,
|
||||||
|
&field.ty,
|
||||||
|
values.get(field.name).unwrap_or(&Value::Null),
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
445
src/validation/visitor.rs
Normal file
445
src/validation/visitor.rs
Normal file
|
@ -0,0 +1,445 @@
|
||||||
|
use crate::validation::context::ValidatorContext;
|
||||||
|
use graphql_parser::query::{
|
||||||
|
Definition, Directive, Document, Field, FragmentDefinition, FragmentSpread, InlineFragment,
|
||||||
|
Name, OperationDefinition, Selection, SelectionSet, TypeCondition, Value, VariableDefinition,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub trait Visitor<'a> {
|
||||||
|
fn enter_document(&mut self, _ctx: &mut ValidatorContext<'a>, _doc: &'a Document) {}
|
||||||
|
fn exit_document(&mut self, _ctx: &mut ValidatorContext<'a>, _doc: &'a Document) {}
|
||||||
|
|
||||||
|
fn enter_operation_definition(
|
||||||
|
&mut self,
|
||||||
|
_ctx: &mut ValidatorContext<'a>,
|
||||||
|
_operation_definition: &'a OperationDefinition,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
fn exit_operation_definition(
|
||||||
|
&mut self,
|
||||||
|
_ctx: &mut ValidatorContext<'a>,
|
||||||
|
_operation_definition: &'a OperationDefinition,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_fragment_definition(
|
||||||
|
&mut self,
|
||||||
|
_ctx: &mut ValidatorContext<'a>,
|
||||||
|
_fragment_definition: &'a FragmentDefinition,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
fn exit_fragment_definition(
|
||||||
|
&mut self,
|
||||||
|
_ctx: &mut ValidatorContext<'a>,
|
||||||
|
_fragment_definition: &'a FragmentDefinition,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_variable_definition(
|
||||||
|
&mut self,
|
||||||
|
_ctx: &mut ValidatorContext<'a>,
|
||||||
|
_variable_definition: &'a VariableDefinition,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
fn exit_variable_definition(
|
||||||
|
&mut self,
|
||||||
|
_ctx: &mut ValidatorContext<'a>,
|
||||||
|
_variable_definition: &'a VariableDefinition,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_directive(&mut self, _ctx: &mut ValidatorContext<'a>, _directive: &'a Directive) {}
|
||||||
|
fn exit_directive(&mut self, _ctx: &mut ValidatorContext<'a>, _directive: &'a Directive) {}
|
||||||
|
|
||||||
|
fn enter_argument(&mut self, _ctx: &mut ValidatorContext<'a>, _name: &str, _value: &'a Value) {}
|
||||||
|
fn exit_argument(&mut self, _ctx: &mut ValidatorContext<'a>, _name: &str, _value: &'a Value) {}
|
||||||
|
|
||||||
|
fn enter_selection(&mut self, _ctx: &mut ValidatorContext<'a>, _selection: &'a Selection) {}
|
||||||
|
fn exit_selection(&mut self, _ctx: &mut ValidatorContext<'a>, _selection: &'a Selection) {}
|
||||||
|
|
||||||
|
fn enter_field(&mut self, _ctx: &mut ValidatorContext<'a>, _field: &'a Field) {}
|
||||||
|
fn exit_field(&mut self, _ctx: &mut ValidatorContext<'a>, _field: &'a Field) {}
|
||||||
|
|
||||||
|
fn enter_fragment_spread(
|
||||||
|
&mut self,
|
||||||
|
_ctx: &mut ValidatorContext<'a>,
|
||||||
|
_fragment_spread: &'a FragmentSpread,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
fn exit_fragment_spread(
|
||||||
|
&mut self,
|
||||||
|
_ctx: &mut ValidatorContext<'a>,
|
||||||
|
_fragment_spread: &'a FragmentSpread,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_inline_fragment(
|
||||||
|
&mut self,
|
||||||
|
_ctx: &mut ValidatorContext<'a>,
|
||||||
|
_inline_fragment: &'a InlineFragment,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
fn exit_inline_fragment(
|
||||||
|
&mut self,
|
||||||
|
_ctx: &mut ValidatorContext<'a>,
|
||||||
|
_inline_fragment: &'a InlineFragment,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct VisitorNil;
|
||||||
|
|
||||||
|
impl VisitorNil {
|
||||||
|
pub fn with<V>(self, visitor: V) -> VisitorCons<V, Self> {
|
||||||
|
VisitorCons(visitor, self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct VisitorCons<A, B>(A, B);
|
||||||
|
|
||||||
|
impl<A, B> VisitorCons<A, B> {
|
||||||
|
pub fn with<V>(self, visitor: V) -> VisitorCons<V, Self> {
|
||||||
|
VisitorCons(visitor, self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Visitor<'a> for VisitorNil {}
|
||||||
|
|
||||||
|
impl<'a, A, B> Visitor<'a> for VisitorCons<A, B>
|
||||||
|
where
|
||||||
|
A: Visitor<'a> + 'a,
|
||||||
|
B: Visitor<'a> + 'a,
|
||||||
|
{
|
||||||
|
fn enter_document(&mut self, ctx: &mut ValidatorContext<'a>, doc: &'a Document) {
|
||||||
|
self.0.enter_document(ctx, doc);
|
||||||
|
self.1.enter_document(ctx, doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exit_document(&mut self, ctx: &mut ValidatorContext<'a>, doc: &'a Document) {
|
||||||
|
self.0.exit_document(ctx, doc);
|
||||||
|
self.1.exit_document(ctx, doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_operation_definition(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut ValidatorContext<'a>,
|
||||||
|
operation_definition: &'a OperationDefinition,
|
||||||
|
) {
|
||||||
|
self.0.enter_operation_definition(ctx, operation_definition);
|
||||||
|
self.1.enter_operation_definition(ctx, operation_definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exit_operation_definition(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut ValidatorContext<'a>,
|
||||||
|
operation_definition: &'a OperationDefinition,
|
||||||
|
) {
|
||||||
|
self.0.exit_operation_definition(ctx, operation_definition);
|
||||||
|
self.1.exit_operation_definition(ctx, operation_definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_fragment_definition(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut ValidatorContext<'a>,
|
||||||
|
fragment_definition: &'a FragmentDefinition,
|
||||||
|
) {
|
||||||
|
self.0.enter_fragment_definition(ctx, fragment_definition);
|
||||||
|
self.1.enter_fragment_definition(ctx, fragment_definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exit_fragment_definition(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut ValidatorContext<'a>,
|
||||||
|
fragment_definition: &'a FragmentDefinition,
|
||||||
|
) {
|
||||||
|
self.0.exit_fragment_definition(ctx, fragment_definition);
|
||||||
|
self.1.exit_fragment_definition(ctx, fragment_definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_variable_definition(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut ValidatorContext<'a>,
|
||||||
|
variable_definition: &'a VariableDefinition,
|
||||||
|
) {
|
||||||
|
self.0.enter_variable_definition(ctx, variable_definition);
|
||||||
|
self.1.enter_variable_definition(ctx, variable_definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exit_variable_definition(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut ValidatorContext<'a>,
|
||||||
|
variable_definition: &'a VariableDefinition,
|
||||||
|
) {
|
||||||
|
self.0.exit_variable_definition(ctx, variable_definition);
|
||||||
|
self.1.exit_variable_definition(ctx, variable_definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_directive(&mut self, ctx: &mut ValidatorContext<'a>, directive: &'a Directive) {
|
||||||
|
self.0.enter_directive(ctx, directive);
|
||||||
|
self.1.enter_directive(ctx, directive);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exit_directive(&mut self, ctx: &mut ValidatorContext<'a>, directive: &'a Directive) {
|
||||||
|
self.0.exit_directive(ctx, directive);
|
||||||
|
self.1.exit_directive(ctx, directive);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_argument(&mut self, ctx: &mut ValidatorContext<'a>, name: &str, value: &'a Value) {
|
||||||
|
self.0.enter_argument(ctx, name, value);
|
||||||
|
self.1.enter_argument(ctx, name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exit_argument(&mut self, ctx: &mut ValidatorContext<'a>, name: &str, value: &'a Value) {
|
||||||
|
self.0.exit_argument(ctx, name, value);
|
||||||
|
self.1.exit_argument(ctx, name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_selection(&mut self, ctx: &mut ValidatorContext<'a>, selection: &'a Selection) {
|
||||||
|
self.0.enter_selection(ctx, selection);
|
||||||
|
self.1.enter_selection(ctx, selection);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exit_selection(&mut self, ctx: &mut ValidatorContext<'a>, selection: &'a Selection) {
|
||||||
|
self.0.exit_selection(ctx, selection);
|
||||||
|
self.1.exit_selection(ctx, selection);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_field(&mut self, ctx: &mut ValidatorContext<'a>, field: &'a Field) {
|
||||||
|
self.0.enter_field(ctx, field);
|
||||||
|
self.1.enter_field(ctx, field);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exit_field(&mut self, ctx: &mut ValidatorContext<'a>, field: &'a Field) {
|
||||||
|
self.0.exit_field(ctx, field);
|
||||||
|
self.1.exit_field(ctx, field);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_fragment_spread(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut ValidatorContext<'a>,
|
||||||
|
fragment_spread: &'a FragmentSpread,
|
||||||
|
) {
|
||||||
|
self.0.enter_fragment_spread(ctx, fragment_spread);
|
||||||
|
self.1.enter_fragment_spread(ctx, fragment_spread);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exit_fragment_spread(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut ValidatorContext<'a>,
|
||||||
|
fragment_spread: &'a FragmentSpread,
|
||||||
|
) {
|
||||||
|
self.0.exit_fragment_spread(ctx, fragment_spread);
|
||||||
|
self.1.exit_fragment_spread(ctx, fragment_spread);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_inline_fragment(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut ValidatorContext<'a>,
|
||||||
|
inline_fragment: &'a InlineFragment,
|
||||||
|
) {
|
||||||
|
self.0.enter_inline_fragment(ctx, inline_fragment);
|
||||||
|
self.1.enter_inline_fragment(ctx, inline_fragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exit_inline_fragment(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut ValidatorContext<'a>,
|
||||||
|
inline_fragment: &'a InlineFragment,
|
||||||
|
) {
|
||||||
|
self.0.exit_inline_fragment(ctx, inline_fragment);
|
||||||
|
self.1.exit_inline_fragment(ctx, inline_fragment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn visit<'a, V: Visitor<'a>>(v: &mut V, ctx: &mut ValidatorContext<'a>, doc: &'a Document) {
|
||||||
|
v.enter_document(ctx, doc);
|
||||||
|
visit_definitions(v, ctx, doc);
|
||||||
|
v.exit_document(ctx, doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_definitions<'a, V: Visitor<'a>>(
|
||||||
|
v: &mut V,
|
||||||
|
ctx: &mut ValidatorContext<'a>,
|
||||||
|
doc: &'a Document,
|
||||||
|
) {
|
||||||
|
for d in &doc.definitions {
|
||||||
|
match d {
|
||||||
|
Definition::Operation(operation) => {
|
||||||
|
visit_operation_definition(v, ctx, operation);
|
||||||
|
}
|
||||||
|
Definition::Fragment(fragment) => {
|
||||||
|
let TypeCondition::On(name) = &fragment.type_condition;
|
||||||
|
if let Some(ty) = ctx.registry.types.get(name) {
|
||||||
|
ctx.with_type(ty, |ctx| visit_fragment_definition(v, ctx, fragment));
|
||||||
|
} else {
|
||||||
|
ctx.report_error(
|
||||||
|
vec![fragment.position],
|
||||||
|
format!("Unknown type \"{}\".", name),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_operation_definition<'a, V: Visitor<'a>>(
|
||||||
|
v: &mut V,
|
||||||
|
ctx: &mut ValidatorContext<'a>,
|
||||||
|
operation: &'a OperationDefinition,
|
||||||
|
) {
|
||||||
|
v.enter_operation_definition(ctx, operation);
|
||||||
|
match operation {
|
||||||
|
OperationDefinition::SelectionSet(selection_set) => {
|
||||||
|
ctx.with_type(&ctx.registry.types[&ctx.registry.query_type], |ctx| {
|
||||||
|
visit_selection_set(v, ctx, selection_set)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
OperationDefinition::Query(query) => {
|
||||||
|
ctx.with_type(&ctx.registry.types[&ctx.registry.query_type], |ctx| {
|
||||||
|
visit_variable_definitions(v, ctx, &query.variable_definitions);
|
||||||
|
visit_directives(v, ctx, &query.directives);
|
||||||
|
visit_selection_set(v, ctx, &query.selection_set);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
OperationDefinition::Mutation(mutation) => {
|
||||||
|
if let Some(mutation_type) = &ctx.registry.mutation_type {
|
||||||
|
ctx.with_type(&ctx.registry.types[mutation_type], |ctx| {
|
||||||
|
visit_variable_definitions(v, ctx, &mutation.variable_definitions);
|
||||||
|
visit_directives(v, ctx, &mutation.directives);
|
||||||
|
visit_selection_set(v, ctx, &mutation.selection_set);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ctx.report_error(
|
||||||
|
vec![mutation.position],
|
||||||
|
"Schema is not configured for mutations.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OperationDefinition::Subscription(subscription) => {
|
||||||
|
ctx.report_error(vec![subscription.position], "Not supported.");
|
||||||
|
// visit_variable_definitions(v, ctx, &subscription.variable_definitions);
|
||||||
|
// visit_directives(v, ctx, &subscription.directives);
|
||||||
|
// visit_selection_set(v, ctx, &subscription.selection_set);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.exit_operation_definition(ctx, operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_selection_set<'a, V: Visitor<'a>>(
|
||||||
|
v: &mut V,
|
||||||
|
ctx: &mut ValidatorContext<'a>,
|
||||||
|
selection_set: &'a SelectionSet,
|
||||||
|
) {
|
||||||
|
for selection in &selection_set.items {
|
||||||
|
visit_selection(v, ctx, selection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_selection<'a, V: Visitor<'a>>(
|
||||||
|
v: &mut V,
|
||||||
|
ctx: &mut ValidatorContext<'a>,
|
||||||
|
selection: &'a Selection,
|
||||||
|
) {
|
||||||
|
v.enter_selection(ctx, selection);
|
||||||
|
match selection {
|
||||||
|
Selection::Field(field) => visit_field(v, ctx, field),
|
||||||
|
Selection::FragmentSpread(fragment_spread) => {
|
||||||
|
visit_fragment_spread(v, ctx, fragment_spread)
|
||||||
|
}
|
||||||
|
Selection::InlineFragment(inline_fragment) => {
|
||||||
|
visit_inline_fragment(v, ctx, inline_fragment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.exit_selection(ctx, selection);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_field<'a, V: Visitor<'a>>(v: &mut V, ctx: &mut ValidatorContext<'a>, field: &'a Field) {
|
||||||
|
v.enter_field(ctx, field);
|
||||||
|
visit_arguments(v, ctx, &field.arguments);
|
||||||
|
visit_directives(v, ctx, &field.directives);
|
||||||
|
|
||||||
|
if let Some(schema_field) = ctx.parent_type().field_by_name(&field.name) {
|
||||||
|
ctx.with_type(
|
||||||
|
ctx.registry.get_basic_type(&schema_field.ty).unwrap(),
|
||||||
|
|ctx| {
|
||||||
|
visit_selection_set(v, ctx, &field.selection_set);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ctx.report_error(
|
||||||
|
vec![field.position],
|
||||||
|
format!(
|
||||||
|
"Cannot query field \"{}\" on type \"{}\".",
|
||||||
|
field.name,
|
||||||
|
ctx.parent_type().name()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
v.exit_field(ctx, field);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_arguments<'a, V: Visitor<'a>>(
|
||||||
|
v: &mut V,
|
||||||
|
ctx: &mut ValidatorContext<'a>,
|
||||||
|
arguments: &'a Vec<(Name, Value)>,
|
||||||
|
) {
|
||||||
|
for (name, value) in arguments {
|
||||||
|
v.enter_argument(ctx, name, value);
|
||||||
|
v.exit_argument(ctx, name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_variable_definitions<'a, V: Visitor<'a>>(
|
||||||
|
v: &mut V,
|
||||||
|
ctx: &mut ValidatorContext<'a>,
|
||||||
|
variable_definitions: &'a Vec<VariableDefinition>,
|
||||||
|
) {
|
||||||
|
for d in variable_definitions {
|
||||||
|
v.enter_variable_definition(ctx, d);
|
||||||
|
v.exit_variable_definition(ctx, d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_directives<'a, V: Visitor<'a>>(
|
||||||
|
v: &mut V,
|
||||||
|
ctx: &mut ValidatorContext<'a>,
|
||||||
|
directives: &'a Vec<Directive>,
|
||||||
|
) {
|
||||||
|
for d in directives {
|
||||||
|
v.enter_directive(ctx, d);
|
||||||
|
visit_arguments(v, ctx, &d.arguments);
|
||||||
|
v.exit_directive(ctx, d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_fragment_definition<'a, V: Visitor<'a>>(
|
||||||
|
v: &mut V,
|
||||||
|
ctx: &mut ValidatorContext<'a>,
|
||||||
|
fragment: &'a FragmentDefinition,
|
||||||
|
) {
|
||||||
|
v.enter_fragment_definition(ctx, fragment);
|
||||||
|
visit_directives(v, ctx, &fragment.directives);
|
||||||
|
visit_selection_set(v, ctx, &fragment.selection_set);
|
||||||
|
v.exit_fragment_definition(ctx, fragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_fragment_spread<'a, V: Visitor<'a>>(
|
||||||
|
v: &mut V,
|
||||||
|
ctx: &mut ValidatorContext<'a>,
|
||||||
|
fragment_spread: &'a FragmentSpread,
|
||||||
|
) {
|
||||||
|
v.enter_fragment_spread(ctx, fragment_spread);
|
||||||
|
visit_directives(v, ctx, &fragment_spread.directives);
|
||||||
|
v.exit_fragment_spread(ctx, fragment_spread);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_inline_fragment<'a, V: Visitor<'a>>(
|
||||||
|
v: &mut V,
|
||||||
|
ctx: &mut ValidatorContext<'a>,
|
||||||
|
inline_fragment: &'a InlineFragment,
|
||||||
|
) {
|
||||||
|
v.enter_inline_fragment(ctx, inline_fragment);
|
||||||
|
visit_directives(v, ctx, &inline_fragment.directives);
|
||||||
|
visit_selection_set(v, ctx, &inline_fragment.selection_set);
|
||||||
|
v.exit_inline_fragment(ctx, inline_fragment);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user