add some validation code

This commit is contained in:
sunli 2020-03-08 20:35:36 +08:00
parent a09fd09cb1
commit 53dcd0aae5
24 changed files with 971 additions and 165 deletions

View File

@ -13,5 +13,7 @@ jobs:
- uses: actions/checkout@v1
- name: Build
run: cargo build --all --verbose
- name: Check format
run: cargo fmt --all -- --check
- name: Run tests
run: cargo test --all --verbose

View File

@ -60,11 +60,11 @@ pub fn generate(enum_args: &args::Enum, input: &DeriveInput) -> Result<TokenStre
}
});
schema_enum_items.push(quote! {
#crate_name::registry::EnumValue {
enum_items.insert(#gql_item_name, #crate_name::registry::EnumValue {
name: #gql_item_name,
description: #item_desc,
deprecation: #item_deprecation,
}
});
});
}
@ -91,7 +91,11 @@ pub fn generate(enum_args: &args::Enum, input: &DeriveInput) -> Result<TokenStre
#crate_name::registry::Type::Enum {
name: #gql_typename,
description: #desc,
enum_values: vec![#(#schema_enum_items),*],
enum_values: {
let mut enum_items = std::collections::HashMap::new();
#(#schema_enum_items)*
enum_items
},
}
})
}

View File

@ -132,12 +132,12 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result
})
.unwrap_or_else(|| quote! {None});
schema_args.push(quote! {
#crate_name::registry::InputValue {
args.insert(#name, #crate_name::registry::InputValue {
name: #name,
description: #desc,
ty: <#ty as #crate_name::GQLType>::create_type_info(registry),
default_value: #schema_default,
}
});
});
}
@ -174,13 +174,17 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result
let schema_ty = ty.value_type();
schema_fields.push(quote! {
#crate_name::registry::Field {
fields.insert(#name, #crate_name::registry::Field {
name: #name,
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),
deprecation: #deprecation,
}
});
});
let resolve_obj = match &ty {
@ -227,7 +231,11 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result
#crate_name::registry::Type::Interface {
name: #gql_typename,
description: #desc,
fields: vec![#(#schema_fields),*],
fields: {
let mut fields = std::collections::HashMap::new();
#(#schema_fields)*
fields
},
possible_types: vec![#(#possible_types),*],
}
})
@ -241,7 +249,7 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result
#(#resolvers)*
anyhow::bail!(#crate_name::QueryError::FieldNotFound {
#crate_name::anyhow::bail!(#crate_name::QueryError::FieldNotFound {
field_name: field.name.clone(),
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<()> {
#(#inline_fragment_resolvers)*
anyhow::bail!(#crate_name::QueryError::UnrecognizedInlineFragment {
#crate_name::anyhow::bail!(#crate_name::QueryError::UnrecognizedInlineFragment {
object: #gql_typename.to_string(),
name: name.to_string(),
});

View File

@ -129,12 +129,12 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
.unwrap_or_else(|| quote! {None});
schema_args.push(quote! {
#crate_name::registry::InputValue {
args.insert(#name, #crate_name::registry::InputValue {
name: #name,
description: #desc,
ty: <#ty as #crate_name::GQLType>::create_type_info(registry),
default_value: #schema_default,
}
});
});
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();
schema_fields.push(quote! {
#crate_name::registry::Field {
fields.insert(#field_name, #crate_name::registry::Field {
name: #field_name,
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),
deprecation: #field_deprecation,
}
});
});
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 {
name: #gql_typename,
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)*
anyhow::bail!(#crate_name::QueryError::FieldNotFound {
#crate_name::anyhow::bail!(#crate_name::QueryError::FieldNotFound {
field_name: field.name.clone(),
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<()> {
anyhow::bail!(#crate_name::QueryError::UnrecognizedInlineFragment {
#crate_name::anyhow::bail!(#crate_name::QueryError::UnrecognizedInlineFragment {
object: #gql_typename.to_string(),
name: name.to_string(),
});

View File

@ -41,10 +41,17 @@ pub trait GQLInputObject: GQLInputValue {}
pub trait GQLScalar: Sized + Send {
fn type_name() -> &'static str;
fn description() -> Option<&'static str> {
None
}
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>;
}
@ -60,6 +67,7 @@ macro_rules! impl_scalar {
registry.create_type::<$ty, _>(|_| crate::registry::Type::Scalar {
name: <$ty as crate::GQLScalar>::type_name().to_string(),
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 {
name: <$ty as crate::GQLScalar>::type_name().to_string(),
description: <$ty>::description(),
is_valid: |value| <$ty as crate::GQLScalar>::is_valid(value),
})
}
}

View File

@ -104,3 +104,23 @@ impl Display for PositionError {
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(())
}
}

View File

@ -4,8 +4,10 @@ mod playground_source;
pub use graphiql_source::graphiql_source;
pub use playground_source::playground_source;
use crate::error::{RuleError, RuleErrors};
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 std::ops::Deref;
@ -57,7 +59,7 @@ impl Serialize for GQLResponse {
Err(err) => {
let mut map = serializer.serialize_map(None)?;
map.serialize_key("errors")?;
map.serialize_value(&[GQLError(err)])?;
map.serialize_value(&GQLError(err))?;
map.end()
}
}
@ -79,25 +81,75 @@ impl<'a> Serialize for GQLError<'a> {
where
S: Serializer,
{
let mut map = serializer.serialize_map(None)?;
match self.0.downcast_ref::<PositionError>() {
Some(err) => {
map.serialize_key("message")?;
map.serialize_value(&err.to_string())?;
map.serialize_key("locations")?;
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())?;
if let Some(err) = self.0.downcast_ref::<PositionError>() {
let mut seq = serializer.serialize_seq(Some(1))?;
seq.serialize_element(&GQLPositionError(err))?;
seq.end()
} else if let Some(err) = self.0.downcast_ref::<RuleErrors>() {
let mut seq = serializer.serialize_seq(Some(err.errors.len()))?;
for err in &err.errors {
seq.serialize_element(&GQLRuleError(err))?;
}
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()
}
}

View File

@ -61,6 +61,7 @@ mod resolver;
mod scalars;
mod schema;
mod types;
mod validation;
#[doc(hidden)]
pub use anyhow;

View File

@ -97,7 +97,7 @@ impl<'a> __Directive<'a> {
async fn args(&self) -> Vec<__InputValue<'a>> {
self.directive
.args
.iter()
.values()
.map(|input_value| __InputValue {
registry: self.registry,
input_value,

View File

@ -26,7 +26,7 @@ impl<'a> __Field<'a> {
async fn args(&self) -> Vec<__InputValue<'a>> {
self.field
.args
.iter()
.values()
.map(|input_value| __InputValue {
registry: self.registry,
input_value,

View File

@ -4,8 +4,6 @@ use async_graphql_derive::Object;
pub struct __Schema<'a> {
pub registry: &'a registry::Registry,
pub query_type: &'a str,
pub mutation_type: Option<&'a str>,
}
#[Object(
@ -27,7 +25,10 @@ impl<'a> __Schema<'a> {
desc = "The type that query operations will be rooted at."
)]
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(
@ -35,7 +36,7 @@ impl<'a> __Schema<'a> {
desc = "If this server supports mutation, the type that mutation operations will be rooted at."
)]
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]))
} else {
None
@ -54,7 +55,7 @@ impl<'a> __Schema<'a> {
async fn directives(&self) -> Vec<__Directive<'a>> {
self.registry
.directives
.iter()
.values()
.map(|directive| __Directive {
registry: &self.registry,
directive,

View File

@ -1,6 +1,6 @@
use crate::model::{__EnumValue, __Field, __InputValue, __TypeKind};
use crate::registry;
use crate::registry::Type;
use crate::registry::{Type, TypeInfo};
use async_graphql_derive::Object;
enum TypeDetail<'a> {
@ -23,21 +23,19 @@ impl<'a> __Type<'a> {
}
pub fn new(registry: &'a registry::Registry, type_name: &str) -> __Type<'a> {
if let Some(type_name) = parse_non_null(type_name) {
__Type {
match TypeInfo::create(type_name) {
TypeInfo::NonNull(ty) => __Type {
registry,
detail: TypeDetail::NonNull(type_name.to_string()),
}
} else if let Some(type_name) = parse_list(type_name) {
__Type {
detail: TypeDetail::NonNull(ty.to_string()),
},
TypeInfo::List(ty) => __Type {
registry,
detail: TypeDetail::List(type_name.to_string()),
}
} else {
__Type {
detail: TypeDetail::List(ty.to_string()),
},
TypeInfo::Type(ty) => __Type {
registry,
detail: TypeDetail::Simple(&registry.types[type_name]),
}
detail: TypeDetail::Simple(&registry.types[ty]),
},
}
}
}
@ -70,14 +68,7 @@ impl<'a> __Type<'a> {
#[field]
async fn name(&self) -> Option<String> {
match &self.detail {
TypeDetail::Simple(ty) => match 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()),
},
TypeDetail::Simple(ty) => Some(ty.name().to_string()),
TypeDetail::NonNull(_) => None,
TypeDetail::List(_) => None,
}
@ -106,40 +97,22 @@ impl<'a> __Type<'a> {
&self,
#[arg(name = "includeDeprecated", default = "false")] include_deprecated: bool,
) -> Option<Vec<__Field<'a>>> {
if let TypeDetail::Simple(Type::Object { 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 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(),
)
if let TypeDetail::Simple(ty) = &self.detail {
ty.fields().and_then(|fields| {
Some(
fields
.values()
.filter(|field| {
(include_deprecated || field.deprecation.is_none())
&& !field.name.starts_with("__")
})
.map(|field| __Field {
registry: self.registry,
field,
})
.collect(),
)
})
} else {
None
}
@ -191,14 +164,8 @@ impl<'a> __Type<'a> {
if let TypeDetail::Simple(Type::Enum { enum_values, .. }) = &self.detail {
Some(
enum_values
.iter()
.filter(|field| {
if include_deprecated {
true
} else {
field.deprecation.is_none()
}
})
.values()
.filter(|field| include_deprecated || field.deprecation.is_none())
.map(|value| __EnumValue {
registry: self.registry,
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
}
}

View File

@ -1,6 +1,40 @@
use crate::{model, GQLType};
use crate::{model, GQLType, Value};
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 name: &'static str,
pub description: Option<&'static str>,
@ -11,7 +45,7 @@ pub struct InputValue {
pub struct Field {
pub name: &'static str,
pub description: Option<&'static str>,
pub args: Vec<InputValue>,
pub args: HashMap<&'static str, InputValue>,
pub ty: String,
pub deprecation: Option<&'static str>,
}
@ -26,16 +60,17 @@ pub enum Type {
Scalar {
name: String,
description: Option<&'static str>,
is_valid: fn(value: &Value) -> bool,
},
Object {
name: &'static str,
description: Option<&'static str>,
fields: Vec<Field>,
fields: HashMap<&'static str, Field>,
},
Interface {
name: &'static str,
description: Option<&'static str>,
fields: Vec<Field>,
fields: HashMap<&'static str, Field>,
possible_types: Vec<String>,
},
Union {
@ -46,7 +81,7 @@ pub enum Type {
Enum {
name: &'static str,
description: Option<&'static str>,
enum_values: Vec<EnumValue>,
enum_values: HashMap<&'static str, EnumValue>,
},
InputObject {
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 name: &'static str,
pub description: Option<&'static str>,
pub locations: Vec<model::__DirectiveLocation>,
pub args: Vec<InputValue>,
pub args: HashMap<&'static str, InputValue>,
}
#[derive(Default)]
pub struct Registry {
pub types: HashMap<String, Type>,
pub directives: Vec<Directive>,
pub directives: HashMap<String, Directive>,
pub implements: HashMap<String, HashSet<String>>,
pub query_type: String,
pub mutation_type: Option<String>,
}
impl Registry {
@ -75,19 +136,33 @@ impl Registry {
if !self.types.contains_key(name.as_ref()) {
self.types.insert(
name.to_string(),
Type::Scalar {
name: String::new(),
Type::Object {
name: "",
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);
}
T::qualified_type_name()
}
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) {
@ -102,4 +177,12 @@ impl Registry {
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),
}
}
}

View File

@ -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> {
Ok(self.clone().into())
}
@ -37,6 +44,10 @@ impl<'a> GQLType for &'a str {
registry.create_type::<Self, _>(|_| registry::Type::Scalar {
name: Self::type_name().to_string(),
description: Some(STRING_DESC),
is_valid: |value| match value {
Value::String(_) => true,
_ => false,
},
})
}
}

View File

@ -18,4 +18,4 @@ impl GQLScalar for Uuid {
}
}
impl_scalar!(Uuid);
impl_scalar!(Uuid);

View File

@ -2,6 +2,7 @@ use crate::context::Data;
use crate::model::__DirectiveLocation;
use crate::registry::{Directive, InputValue, Registry};
use crate::types::QueryRoot;
use crate::validation::check_rules;
use crate::{
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> {
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 {
name: "include",
@ -29,12 +40,16 @@ impl<Query: GQLObject, Mutation: GQLObject> Schema<Query, Mutation> {
__DirectiveLocation::FRAGMENT_SPREAD,
__DirectiveLocation::INLINE_FRAGMENT
],
args: vec![InputValue{
name: "if",
description: Some("Included when true."),
ty: "Boolean!".to_string(),
default_value: None
}]
args: {
let mut args = HashMap::new();
args.insert("if", InputValue {
name: "if",
description: Some("Included when true."),
ty: "Boolean!".to_string(),
default_value: None
});
args
}
});
registry.add_directive(Directive {
@ -45,12 +60,16 @@ impl<Query: GQLObject, Mutation: GQLObject> Schema<Query, Mutation> {
__DirectiveLocation::FRAGMENT_SPREAD,
__DirectiveLocation::INLINE_FRAGMENT
],
args: vec![InputValue{
name: "if",
description: Some("Skipped when true."),
ty: "Boolean!".to_string(),
default_value: None
}]
args: {
let mut args = HashMap::new();
args.insert("if", InputValue {
name: "if",
description: Some("Skipped when true."),
ty: "Boolean!".to_string(),
default_value: None
});
args
}
});
// register scalars
@ -59,21 +78,13 @@ impl<Query: GQLObject, Mutation: GQLObject> Schema<Query, Mutation> {
f32::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() {
Mutation::create_type_info(&mut registry);
}
Self {
query: QueryRoot {
inner: query,
query_type: Query::type_name().to_string(),
mutation_type: if !Mutation::is_empty() {
Some(Mutation::type_name().to_string())
} else {
None
},
},
query: QueryRoot { inner: query },
mutation,
registry,
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()))?;
let mut fragments = HashMap::new();
check_rules(self.registry, &document)?;
for definition in &document.definitions {
if let Definition::Fragment(fragment) = definition {
fragments.insert(fragment.name.clone(), fragment);

View File

@ -14,7 +14,7 @@ impl GQLType for GQLEmptyMutation {
registry.create_type::<Self, _>(|_| registry::Type::Object {
name: "EmptyMutation",
description: None,
fields: Vec::new(),
fields: Default::default(),
})
}
}

View File

@ -1,15 +1,15 @@
use crate::model::{__Schema, __Type};
use crate::registry::Type;
use crate::{
registry, Context, ContextSelectionSet, ErrorWithPosition, GQLObject, GQLOutputValue, GQLType,
QueryError, Result, Value,
};
use graphql_parser::query::Field;
use std::borrow::Cow;
use std::collections::HashMap;
pub struct QueryRoot<T> {
pub inner: T,
pub query_type: String,
pub mutation_type: Option<String>,
}
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 {
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(
&__Schema {
registry: &ctx.registry,
query_type: &self.query_type,
mutation_type: self.mutation_type.as_deref(),
},
&ctx_obj,
)

36
src/validation/context.rs Normal file
View 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
View 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(())
}
}

View 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;
}
}

View File

@ -0,0 +1,3 @@
mod arguments_of_correct_type;
pub use arguments_of_correct_type::ArgumentsOfCorrectType;

46
src/validation/utils.rs Normal file
View 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
View 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);
}