v0.10.4
This commit is contained in:
parent
fa526a1b20
commit
482c5b3cf3
10
README.md
10
README.md
|
@ -49,7 +49,7 @@
|
||||||
- [X] Containers
|
- [X] Containers
|
||||||
- [X] List
|
- [X] List
|
||||||
- [X] Non-Null
|
- [X] Non-Null
|
||||||
- [ ] Object
|
- [X] Object
|
||||||
- [X] Lifetime cycle
|
- [X] Lifetime cycle
|
||||||
- [X] Enum
|
- [X] Enum
|
||||||
- [X] InputObject
|
- [X] InputObject
|
||||||
|
@ -87,11 +87,11 @@
|
||||||
- [X] KnownArgumentNames
|
- [X] KnownArgumentNames
|
||||||
- [ ] KnownDirectives
|
- [ ] KnownDirectives
|
||||||
- [X] KnownFragmentNames
|
- [X] KnownFragmentNames
|
||||||
- [ ] KnownTypeNames
|
- [X] KnownTypeNames
|
||||||
- [ ] LoneAnonymousOperation
|
- [X] LoneAnonymousOperation
|
||||||
- [X] NoFragmentCycles
|
- [X] NoFragmentCycles
|
||||||
- [ ] NoUndefinedVariables
|
- [X] NoUndefinedVariables
|
||||||
- [ ] NoUnusedFragments
|
- [X] NoUnusedFragments
|
||||||
- [ ] NoUnusedVariables
|
- [ ] NoUnusedVariables
|
||||||
- [ ] OverlappingFieldsCanBeMerged
|
- [ ] OverlappingFieldsCanBeMerged
|
||||||
- [ ] PossibleFragmentSpreads
|
- [ ] PossibleFragmentSpreads
|
||||||
|
|
|
@ -117,7 +117,7 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result
|
||||||
None => quote! { || #crate_name::Value::Null },
|
None => quote! { || #crate_name::Value::Null },
|
||||||
};
|
};
|
||||||
get_params.push(quote! {
|
get_params.push(quote! {
|
||||||
let #ident: #ty = ctx_field.param_value(#name, #param_default)?;
|
let #ident: #ty = ctx.param_value(#name, #param_default)?;
|
||||||
});
|
});
|
||||||
|
|
||||||
let desc = desc
|
let desc = desc
|
||||||
|
|
113
src/base.rs
113
src/base.rs
|
@ -2,33 +2,44 @@ use crate::{registry, Context, ContextSelectionSet, Result};
|
||||||
use graphql_parser::query::{Field, Value};
|
use graphql_parser::query::{Field, Value};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
/// Represents a GraphQL type
|
||||||
pub trait GQLType {
|
pub trait GQLType {
|
||||||
|
/// Type the name.
|
||||||
fn type_name() -> Cow<'static, str>;
|
fn type_name() -> Cow<'static, str>;
|
||||||
|
|
||||||
|
/// Qualified typename.
|
||||||
fn qualified_type_name() -> String {
|
fn qualified_type_name() -> String {
|
||||||
format!("{}!", Self::type_name())
|
format!("{}!", Self::type_name())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create type information in the registry and return qualified typename.
|
||||||
fn create_type_info(registry: &mut registry::Registry) -> String;
|
fn create_type_info(registry: &mut registry::Registry) -> String;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents a GraphQL input value
|
||||||
pub trait GQLInputValue: GQLType + Sized {
|
pub trait GQLInputValue: GQLType + Sized {
|
||||||
fn parse(value: &Value) -> Option<Self>;
|
fn parse(value: &Value) -> Option<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents a GraphQL output value
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait GQLOutputValue: GQLType {
|
pub trait GQLOutputValue: GQLType {
|
||||||
async fn resolve(value: &Self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value>;
|
async fn resolve(value: &Self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents a GraphQL object
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait GQLObject: GQLOutputValue {
|
pub trait GQLObject: GQLOutputValue {
|
||||||
|
/// This function returns true of type `GQLEmptyMutation` only
|
||||||
|
#[doc(hidden)]
|
||||||
fn is_empty() -> bool {
|
fn is_empty() -> bool {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolves a field value and outputs it as a json value `serde_json::Value`.
|
||||||
async fn resolve_field(&self, ctx: &Context<'_>, field: &Field) -> Result<serde_json::Value>;
|
async fn resolve_field(&self, ctx: &Context<'_>, field: &Field) -> Result<serde_json::Value>;
|
||||||
|
|
||||||
|
/// Resolve an inline fragment with the `name`.
|
||||||
async fn resolve_inline_fragment(
|
async fn resolve_inline_fragment(
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
|
@ -37,26 +48,66 @@ pub trait GQLObject: GQLOutputValue {
|
||||||
) -> Result<()>;
|
) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents a GraphQL input object
|
||||||
pub trait GQLInputObject: GQLInputValue {}
|
pub trait GQLInputObject: GQLInputValue {}
|
||||||
|
|
||||||
|
/// Represents a GraphQL scalar
|
||||||
|
///
|
||||||
|
/// You can implement the trait to create a custom scalar.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use async_graphql::*;
|
||||||
|
///
|
||||||
|
/// struct MyInt(i32);
|
||||||
|
///
|
||||||
|
/// impl GQLScalar for MyInt {
|
||||||
|
/// fn type_name() -> &'static str {
|
||||||
|
/// "MyInt"
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn parse(value: &Value) -> Option<Self> {
|
||||||
|
/// if let Value::Int(n) = value {
|
||||||
|
/// Some(MyInt(n.as_i64().unwrap() as i32))
|
||||||
|
/// } else {
|
||||||
|
/// None
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn to_json(&self) -> Result<serde_json::Value> {
|
||||||
|
/// Ok(self.0.into())
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl_scalar!(MyInt); // // Don't forget this one
|
||||||
|
/// ```
|
||||||
pub trait GQLScalar: Sized + Send {
|
pub trait GQLScalar: Sized + Send {
|
||||||
|
/// The type name of a scalar.
|
||||||
fn type_name() -> &'static str;
|
fn type_name() -> &'static str;
|
||||||
|
|
||||||
|
/// The description of a scalar.
|
||||||
fn description() -> Option<&'static str> {
|
fn description() -> Option<&'static str> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a scalar value, return `Some(Self)` if successful, otherwise return `None`.
|
||||||
fn parse(value: &Value) -> Option<Self>;
|
fn parse(value: &Value) -> Option<Self>;
|
||||||
|
|
||||||
|
/// Checks for a valid scalar value.
|
||||||
|
///
|
||||||
|
/// The default implementation is to try to parse it, and in some cases you can implement this on your own to improve performance.
|
||||||
fn is_valid(value: &Value) -> bool {
|
fn is_valid(value: &Value) -> bool {
|
||||||
Self::parse(value).is_some()
|
Self::parse(value).is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert the scalar value to json value.
|
||||||
fn to_json(&self) -> Result<serde_json::Value>;
|
fn to_json(&self) -> Result<serde_json::Value>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! impl_scalar {
|
#[doc(hidden)]
|
||||||
|
macro_rules! impl_scalar_internal {
|
||||||
($ty:ty) => {
|
($ty:ty) => {
|
||||||
impl crate::GQLType for $ty {
|
impl crate::GQLType for $ty {
|
||||||
fn type_name() -> std::borrow::Cow<'static, str> {
|
fn type_name() -> std::borrow::Cow<'static, str> {
|
||||||
|
@ -114,6 +165,66 @@ macro_rules! impl_scalar {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! impl_scalar {
|
||||||
|
($ty:ty) => {
|
||||||
|
impl async_graphql::GQLType for $ty {
|
||||||
|
fn type_name() -> std::borrow::Cow<'static, str> {
|
||||||
|
std::borrow::Cow::Borrowed(<$ty as async_graphql::GQLScalar>::type_name())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_type_info(registry: &mut async_graphql::registry::Registry) -> String {
|
||||||
|
registry.create_type::<$ty, _>(|_| async_graphql::registry::Type::Scalar {
|
||||||
|
name: <$ty as async_graphql::GQLScalar>::type_name().to_string(),
|
||||||
|
description: <$ty>::description(),
|
||||||
|
is_valid: |value| <$ty as async_graphql::GQLScalar>::is_valid(value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl async_graphql::GQLType for &$ty {
|
||||||
|
fn type_name() -> std::borrow::Cow<'static, str> {
|
||||||
|
std::borrow::Cow::Borrowed(<$ty as async_graphql::GQLScalar>::type_name())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_type_info(registry: &mut async_graphql::registry::Registry) -> String {
|
||||||
|
registry.create_type::<$ty, _>(|_| async_graphql::registry::Type::Scalar {
|
||||||
|
name: <$ty as async_graphql::GQLScalar>::type_name().to_string(),
|
||||||
|
description: <$ty>::description(),
|
||||||
|
is_valid: |value| <$ty as async_graphql::GQLScalar>::is_valid(value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl async_graphql::GQLInputValue for $ty {
|
||||||
|
fn parse(value: &async_graphql::Value) -> Option<Self> {
|
||||||
|
<$ty as async_graphql::GQLScalar>::parse(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_graphql::async_trait::async_trait]
|
||||||
|
impl async_graphql::GQLOutputValue for $ty {
|
||||||
|
async fn resolve(
|
||||||
|
value: &Self,
|
||||||
|
_: &async_graphql::ContextSelectionSet<'_>,
|
||||||
|
) -> async_graphql::Result<serde_json::Value> {
|
||||||
|
value.to_json()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_graphql::async_trait::async_trait]
|
||||||
|
impl async_graphql::GQLOutputValue for &$ty {
|
||||||
|
async fn resolve(
|
||||||
|
value: &Self,
|
||||||
|
_: &async_graphql::ContextSelectionSet<'_>,
|
||||||
|
) -> async_graphql::Result<serde_json::Value> {
|
||||||
|
value.to_json()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a GraphQL output value
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<T: GQLObject + Send + Sync> GQLOutputValue for T {
|
impl<T: GQLObject + Send + Sync> GQLOutputValue for T {
|
||||||
async fn resolve(value: &Self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
|
async fn resolve(value: &Self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
|
||||||
|
|
|
@ -9,6 +9,7 @@ use std::collections::{BTreeMap, HashMap};
|
||||||
use std::hash::BuildHasherDefault;
|
use std::hash::BuildHasherDefault;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
/// Variables of query
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Variables(BTreeMap<String, Value>);
|
pub struct Variables(BTreeMap<String, Value>);
|
||||||
|
|
||||||
|
@ -67,6 +68,8 @@ impl Data {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type ContextSelectionSet<'a> = ContextBase<'a, &'a SelectionSet>;
|
pub type ContextSelectionSet<'a> = ContextBase<'a, &'a SelectionSet>;
|
||||||
|
|
||||||
|
/// Context object for resolve field.
|
||||||
pub type Context<'a> = ContextBase<'a, &'a Field>;
|
pub type Context<'a> = ContextBase<'a, &'a Field>;
|
||||||
|
|
||||||
pub struct ContextBase<'a, T> {
|
pub struct ContextBase<'a, T> {
|
||||||
|
@ -99,6 +102,7 @@ impl<'a, T> ContextBase<'a, T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the global data defined in the `Schema`.
|
||||||
pub fn data<D: Any + Send + Sync>(&self) -> &D {
|
pub fn data<D: Any + Send + Sync>(&self) -> &D {
|
||||||
self.data
|
self.data
|
||||||
.0
|
.0
|
||||||
|
|
331
src/lib.rs
331
src/lib.rs
|
@ -74,15 +74,14 @@ pub use serde_json;
|
||||||
|
|
||||||
pub mod http;
|
pub mod http;
|
||||||
|
|
||||||
pub use async_graphql_derive::{Enum, InputObject, Interface, Object, Union};
|
pub use async_graphql_derive::Union;
|
||||||
pub use base::{GQLInputObject, GQLInputValue, GQLObject, GQLOutputValue, GQLScalar, GQLType};
|
pub use base::GQLScalar;
|
||||||
pub use context::{Context, Variables};
|
pub use context::{Context, Variables};
|
||||||
pub use error::{ErrorWithPosition, PositionError, QueryError, QueryParseError};
|
pub use error::{ErrorWithPosition, PositionError, QueryError, QueryParseError};
|
||||||
pub use graphql_parser::query::Value;
|
pub use graphql_parser::query::Value;
|
||||||
pub use scalars::ID;
|
pub use scalars::ID;
|
||||||
pub use schema::{QueryBuilder, Schema};
|
pub use schema::{QueryBuilder, Schema};
|
||||||
pub use types::GQLEmptyMutation;
|
pub use types::GQLEmptyMutation;
|
||||||
pub use types::{GQLEnum, GQLEnumItem};
|
|
||||||
|
|
||||||
pub type Result<T> = anyhow::Result<T>;
|
pub type Result<T> = anyhow::Result<T>;
|
||||||
pub type Error = anyhow::Error;
|
pub type Error = anyhow::Error;
|
||||||
|
@ -93,6 +92,332 @@ pub use context::ContextSelectionSet;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub mod registry;
|
pub mod registry;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
pub use base::{GQLInputObject, GQLInputValue, GQLObject, GQLOutputValue, GQLType};
|
||||||
|
#[doc(hidden)]
|
||||||
pub use context::ContextBase;
|
pub use context::ContextBase;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use resolver::do_resolve;
|
pub use resolver::do_resolve;
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub use types::{GQLEnum, GQLEnumItem};
|
||||||
|
|
||||||
|
/// Define a GraphQL object
|
||||||
|
///
|
||||||
|
/// # Macro parameters
|
||||||
|
///
|
||||||
|
/// | Attribute | description | Type | Optional |
|
||||||
|
/// |-------------|---------------------------|----------|----------|
|
||||||
|
/// | name | Object name | string | Y |
|
||||||
|
/// | desc | Object description | string | Y |
|
||||||
|
///
|
||||||
|
/// # Field parameters
|
||||||
|
///
|
||||||
|
/// | Attribute | description | Type | Optional |
|
||||||
|
/// |-------------|---------------------------|----------|----------|
|
||||||
|
/// | name | Field name | string | Y |
|
||||||
|
/// | desc | Field description | string | Y |
|
||||||
|
/// | deprecation | Field deprecation reason | string | Y |
|
||||||
|
///
|
||||||
|
/// # Field argument parameters
|
||||||
|
///
|
||||||
|
/// | Attribute | description | Type | Optional |
|
||||||
|
/// |-------------|---------------------------|----------|----------|
|
||||||
|
/// | name | Argument name | string | Y |
|
||||||
|
/// | desc | Argument description | string | Y |
|
||||||
|
/// | default | Argument default value | string | Y |
|
||||||
|
///
|
||||||
|
/// # The field returns the value type
|
||||||
|
///
|
||||||
|
/// - A scalar value, such as `i32`, `bool`
|
||||||
|
/// - Borrowing of scalar values, such as `&i32`, `&bool`
|
||||||
|
/// - Vec<T>, such as `Vec<i32>`
|
||||||
|
/// - Slice<T>, such as `&[i32]`
|
||||||
|
/// - Option<T>, such as `Option<i32>`
|
||||||
|
/// - GQLObject and `&GQLObject`
|
||||||
|
/// - GQLEnum
|
||||||
|
/// - Result<T, E>, such as `Result<i32, E>`
|
||||||
|
///
|
||||||
|
/// # Context
|
||||||
|
///
|
||||||
|
/// You can define a context as an argument to a method, and the context should be the first argument to the method.
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// #[Object]
|
||||||
|
/// impl MyObject {
|
||||||
|
/// async fn value(&self, ctx: &Context<'_>) -> { ... }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use async_graphql::*;
|
||||||
|
///
|
||||||
|
/// struct MyObject {
|
||||||
|
/// value: i32,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[Object]
|
||||||
|
/// impl MyObject {
|
||||||
|
/// #[field(desc = "value")]
|
||||||
|
/// async fn value(&self) -> i32 {
|
||||||
|
/// self.value
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[field(name = "valueRef", desc = "reference value")]
|
||||||
|
/// async fn value_ref(&self) -> &i32 {
|
||||||
|
/// &self.value
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[field(name = "valueWithError", desc = "value with error")]
|
||||||
|
/// async fn value_with_error(&self) -> Result<i32> {
|
||||||
|
/// Ok(self.value)
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[field(name = "valueWithArg")]
|
||||||
|
/// async fn value_with_arg(&self, #[arg(default = "1")] a: i32) -> i32 {
|
||||||
|
/// a
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[async_std::main]
|
||||||
|
/// async fn main() {
|
||||||
|
/// let schema = Schema::new(MyObject{ value: 10 }, GQLEmptyMutation);
|
||||||
|
/// let res = schema.query(r#"{
|
||||||
|
/// value
|
||||||
|
/// valueRef
|
||||||
|
/// valueWithError
|
||||||
|
/// valueWithArg1: valueWithArg
|
||||||
|
/// valueWithArg2: valueWithArg(a: 99)
|
||||||
|
/// }"#).execute().await.unwrap();
|
||||||
|
/// assert_eq!(res, serde_json::json!({
|
||||||
|
/// "value": 10,
|
||||||
|
/// "valueRef": 10,
|
||||||
|
/// "valueWithError": 10,
|
||||||
|
/// "valueWithArg1": 1,
|
||||||
|
/// "valueWithArg2": 99
|
||||||
|
/// }));
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub use async_graphql_derive::Object;
|
||||||
|
|
||||||
|
/// Define a GraphQL enum
|
||||||
|
///
|
||||||
|
/// # Macro parameters
|
||||||
|
///
|
||||||
|
/// | Attribute | description | Type | Optional |
|
||||||
|
/// |-------------|---------------------------|----------|----------|
|
||||||
|
/// | name | Enum name | string | Y |
|
||||||
|
/// | desc | Enum description | string | Y |
|
||||||
|
///
|
||||||
|
/// # Item parameters
|
||||||
|
///
|
||||||
|
/// | Attribute | description | Type | Optional |
|
||||||
|
/// |-------------|---------------------------|----------|----------|
|
||||||
|
/// | name | Item name | string | Y |
|
||||||
|
/// | desc | Item description | string | Y |
|
||||||
|
/// | deprecation | Item deprecation reason | string | Y |
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use async_graphql::*;
|
||||||
|
///
|
||||||
|
/// #[Enum]
|
||||||
|
/// enum MyEnum {
|
||||||
|
/// A,
|
||||||
|
/// #[item(name = "b")] B,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// struct MyObject {
|
||||||
|
/// value1: MyEnum,
|
||||||
|
/// value2: MyEnum,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[Object]
|
||||||
|
/// impl MyObject {
|
||||||
|
/// #[field(desc = "value")]
|
||||||
|
/// async fn value1(&self) -> MyEnum {
|
||||||
|
/// self.value1
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[field(desc = "value")]
|
||||||
|
/// async fn value2(&self) -> MyEnum {
|
||||||
|
/// self.value2
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[async_std::main]
|
||||||
|
/// async fn main() {
|
||||||
|
/// let schema = Schema::new(MyObject{ value1: MyEnum::A, value2: MyEnum::B }, GQLEmptyMutation);
|
||||||
|
/// let res = schema.query("{ value1 value2 }").execute().await.unwrap();
|
||||||
|
/// assert_eq!(res, serde_json::json!({ "value1": "A", "value2": "b" }));
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub use async_graphql_derive::Enum;
|
||||||
|
|
||||||
|
/// Define a GraphQL input object
|
||||||
|
///
|
||||||
|
/// # Macro parameters
|
||||||
|
///
|
||||||
|
/// | Attribute | description | Type | Optional |
|
||||||
|
/// |-------------|---------------------------|----------|----------|
|
||||||
|
/// | name | Object name | string | Y |
|
||||||
|
/// | desc | Object description | string | Y |
|
||||||
|
///
|
||||||
|
/// # Field parameters
|
||||||
|
///
|
||||||
|
/// | Attribute | description | Type | Optional |
|
||||||
|
/// |-------------|---------------------------|----------|----------|
|
||||||
|
/// | name | Field name | string | Y |
|
||||||
|
/// | desc | Field description | string | Y |
|
||||||
|
/// | default | Field default value | string | Y |
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use async_graphql::*;
|
||||||
|
///
|
||||||
|
/// #[InputObject]
|
||||||
|
/// struct MyInputObject {
|
||||||
|
/// a: i32,
|
||||||
|
/// #[field(default = "10")]
|
||||||
|
/// b: i32,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// struct MyObject;
|
||||||
|
///
|
||||||
|
/// #[Object]
|
||||||
|
/// impl MyObject {
|
||||||
|
/// #[field(desc = "value")]
|
||||||
|
/// async fn value(&self, input: MyInputObject) -> i32 {
|
||||||
|
/// input.a * input.b
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[async_std::main]
|
||||||
|
/// async fn main() {
|
||||||
|
/// let schema = Schema::new(MyObject, GQLEmptyMutation);
|
||||||
|
/// let res = schema.query(r#"
|
||||||
|
/// {
|
||||||
|
/// value1: value(input:{a:9, b:3})
|
||||||
|
/// value2: value(input:{a:9})
|
||||||
|
/// }"#).execute().await.unwrap();
|
||||||
|
/// assert_eq!(res, serde_json::json!({ "value1": 27, "value2": 90 }));
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub use async_graphql_derive::InputObject;
|
||||||
|
|
||||||
|
/// Define a GraphQL interface
|
||||||
|
///
|
||||||
|
/// # Macro parameters
|
||||||
|
///
|
||||||
|
/// | Attribute | description | Type | Optional |
|
||||||
|
/// |-------------|---------------------------|----------|----------|
|
||||||
|
/// | name | Object name | string | Y |
|
||||||
|
/// | desc | Object description | string | Y |
|
||||||
|
///
|
||||||
|
/// # Field parameters
|
||||||
|
///
|
||||||
|
/// | Attribute | description | Type | Optional |
|
||||||
|
/// |-------------|---------------------------|----------|----------|
|
||||||
|
/// | name | Field name | string | N |
|
||||||
|
/// | type | Field type | string | N |
|
||||||
|
/// | desc | Field description | string | Y |
|
||||||
|
/// | method | Field method name | string | Y |
|
||||||
|
/// | context | Method with the context | string | Y |
|
||||||
|
/// | deprecation | Field deprecation reason | string | Y |
|
||||||
|
/// | args | Field arguments | [Arg] | Y |
|
||||||
|
///
|
||||||
|
/// # Field argument parameters
|
||||||
|
///
|
||||||
|
/// | Attribute | description | Type | Optional |
|
||||||
|
/// |-------------|---------------------------|----------|----------|
|
||||||
|
/// | name | Argument name | string | N |
|
||||||
|
/// | type | Argument type | string | N |
|
||||||
|
/// | desc | Argument description | string | Y |
|
||||||
|
/// | default | Argument default value | string | Y |
|
||||||
|
///
|
||||||
|
/// # Define an interface
|
||||||
|
///
|
||||||
|
/// Define TypeA, TypeB, TypeC... Implement the MyInterface
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// #[Interface]
|
||||||
|
/// struct MyInterface(TypeA, TypeB, TypeC, ...);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Fields
|
||||||
|
///
|
||||||
|
/// The type, name, and parameters of the interface field must exactly match the type that implements the interface,
|
||||||
|
/// The internal implementation is a forward of the function call.
|
||||||
|
/// You can specify the field function name that implements the interface type through the 'method' property,
|
||||||
|
/// or you can specify that the field function has a context parameter through the 'context' attribute.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use async_graphql::*;
|
||||||
|
///
|
||||||
|
/// struct TypeA {
|
||||||
|
/// value: i32,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[Object]
|
||||||
|
/// impl TypeA {
|
||||||
|
/// /// Returns data borrowed from the context
|
||||||
|
/// #[field]
|
||||||
|
/// async fn value_a<'a>(&self, ctx: &'a Context<'_>) -> &'a str {
|
||||||
|
/// ctx.data::<String>().as_str()
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// /// Returns data borrowed self
|
||||||
|
/// #[field]
|
||||||
|
/// async fn value_b(&self) -> &i32 {
|
||||||
|
/// &self.value
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// /// With parameters
|
||||||
|
/// #[field]
|
||||||
|
/// async fn value_c(&self, a: i32, b: i32) -> i32 {
|
||||||
|
/// a + b
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[Interface(
|
||||||
|
/// field(name = "value_a", type = "&'ctx str", context),
|
||||||
|
/// field(name = "value_b", type = "&i32"),
|
||||||
|
/// field(name = "value_c", type = "i32",
|
||||||
|
/// arg(name = "a", type = "i32"),
|
||||||
|
/// arg(name = "b", type = "i32")),
|
||||||
|
/// )]
|
||||||
|
/// struct MyInterface(TypeA);
|
||||||
|
///
|
||||||
|
/// struct QueryRoot;
|
||||||
|
///
|
||||||
|
/// #[Object]
|
||||||
|
/// impl QueryRoot {
|
||||||
|
/// #[field]
|
||||||
|
/// async fn type_a(&self) -> MyInterface {
|
||||||
|
/// TypeA { value: 10 }.into()
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[async_std::main]
|
||||||
|
/// async fn main() {
|
||||||
|
/// let schema = Schema::new(QueryRoot, GQLEmptyMutation).data("hello".to_string());
|
||||||
|
/// let res = schema.query(r#"
|
||||||
|
/// {
|
||||||
|
/// type_a {
|
||||||
|
/// value_a
|
||||||
|
/// value_b
|
||||||
|
/// value_c(a: 3, b: 2)
|
||||||
|
/// }
|
||||||
|
/// }"#).execute().await.unwrap();
|
||||||
|
/// assert_eq!(res, serde_json::json!({
|
||||||
|
/// "type_a": {
|
||||||
|
/// "value_a": "hello",
|
||||||
|
/// "value_b": 10,
|
||||||
|
/// "value_c": 5
|
||||||
|
/// }
|
||||||
|
/// }));
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub use async_graphql_derive::Interface;
|
||||||
|
|
|
@ -33,6 +33,14 @@ impl<'a> TypeName<'a> {
|
||||||
TypeName::Name(type_name)
|
TypeName::Name(type_name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_basic_typename(type_name: &str) -> &str {
|
||||||
|
match TypeName::create(type_name) {
|
||||||
|
TypeName::List(type_name) => Self::get_basic_typename(type_name),
|
||||||
|
TypeName::NonNull(type_name) => Self::get_basic_typename(type_name),
|
||||||
|
TypeName::Name(type_name) => type_name,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct InputValue {
|
pub struct InputValue {
|
||||||
|
@ -196,10 +204,6 @@ impl Registry {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_basic_type(&self, type_name: &str) -> Option<&Type> {
|
pub fn get_basic_type(&self, type_name: &str) -> Option<&Type> {
|
||||||
match TypeName::create(type_name) {
|
self.types.get(TypeName::get_basic_typename(type_name))
|
||||||
TypeName::Name(type_name) => self.types.get(type_name),
|
|
||||||
TypeName::List(type_name) => self.get_basic_type(type_name),
|
|
||||||
TypeName::NonNull(type_name) => self.get_basic_type(type_name),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{impl_scalar, GQLScalar, Result, Value};
|
use crate::{impl_scalar_internal, GQLScalar, Result, Value};
|
||||||
|
|
||||||
impl GQLScalar for bool {
|
impl GQLScalar for bool {
|
||||||
fn type_name() -> &'static str {
|
fn type_name() -> &'static str {
|
||||||
|
@ -21,4 +21,4 @@ impl GQLScalar for bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_scalar!(bool);
|
impl_scalar_internal!(bool);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{impl_scalar, GQLScalar, Result, Value};
|
use crate::{impl_scalar_internal, GQLScalar, Result, Value};
|
||||||
use chrono::{DateTime, TimeZone, Utc};
|
use chrono::{DateTime, TimeZone, Utc};
|
||||||
|
|
||||||
impl GQLScalar for DateTime<Utc> {
|
impl GQLScalar for DateTime<Utc> {
|
||||||
|
@ -18,4 +18,4 @@ impl GQLScalar for DateTime<Utc> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_scalar!(DateTime<Utc>);
|
impl_scalar_internal!(DateTime<Utc>);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{impl_scalar, GQLScalar, Result, Value};
|
use crate::{impl_scalar_internal, GQLScalar, Result, Value};
|
||||||
|
|
||||||
macro_rules! impl_float_scalars {
|
macro_rules! impl_float_scalars {
|
||||||
($($ty:ty),*) => {
|
($($ty:ty),*) => {
|
||||||
|
@ -25,7 +25,7 @@ macro_rules! impl_float_scalars {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_scalar!($ty);
|
impl_scalar_internal!($ty);
|
||||||
)*
|
)*
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
use crate::{impl_scalar, GQLScalar, Result, Value};
|
use crate::{impl_scalar_internal, GQLScalar, Result, Value};
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
/// ID scalar
|
||||||
|
///
|
||||||
|
/// The input is a string or integer, and the output is a string.
|
||||||
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
|
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
|
||||||
pub struct ID(String);
|
pub struct ID(String);
|
||||||
|
|
||||||
|
@ -36,4 +39,4 @@ impl GQLScalar for ID {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_scalar!(ID);
|
impl_scalar_internal!(ID);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{impl_scalar, GQLScalar, Result, Value};
|
use crate::{impl_scalar_internal, GQLScalar, Result, Value};
|
||||||
|
|
||||||
macro_rules! impl_integer_scalars {
|
macro_rules! impl_integer_scalars {
|
||||||
($($ty:ty),*) => {
|
($($ty:ty),*) => {
|
||||||
|
@ -24,7 +24,7 @@ macro_rules! impl_integer_scalars {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_scalar!($ty);
|
impl_scalar_internal!($ty);
|
||||||
)*
|
)*
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
impl_scalar, registry, ContextSelectionSet, GQLOutputValue, GQLScalar, GQLType, Result, Value,
|
impl_scalar_internal, registry, ContextSelectionSet, GQLOutputValue, GQLScalar, GQLType,
|
||||||
|
Result, Value,
|
||||||
};
|
};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
@ -33,7 +34,7 @@ impl GQLScalar for String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_scalar!(String);
|
impl_scalar_internal!(String);
|
||||||
|
|
||||||
impl<'a> GQLType for &'a str {
|
impl<'a> GQLType for &'a str {
|
||||||
fn type_name() -> Cow<'static, str> {
|
fn type_name() -> Cow<'static, str> {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{impl_scalar, GQLScalar, Result, Value};
|
use crate::{impl_scalar_internal, GQLScalar, Result, Value};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
impl GQLScalar for Uuid {
|
impl GQLScalar for Uuid {
|
||||||
|
@ -18,4 +18,4 @@ impl GQLScalar for Uuid {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_scalar!(Uuid);
|
impl_scalar_internal!(Uuid);
|
||||||
|
|
|
@ -11,6 +11,7 @@ use graphql_parser::query::{Definition, OperationDefinition};
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
/// GraphQL schema
|
||||||
pub struct Schema<Query, Mutation> {
|
pub struct Schema<Query, Mutation> {
|
||||||
query: QueryRoot<Query>,
|
query: QueryRoot<Query>,
|
||||||
mutation: Mutation,
|
mutation: Mutation,
|
||||||
|
@ -19,6 +20,10 @@ pub struct Schema<Query, Mutation> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Query: GQLObject, Mutation: GQLObject> Schema<Query, Mutation> {
|
impl<Query: GQLObject, Mutation: GQLObject> Schema<Query, Mutation> {
|
||||||
|
/// Create a schema.
|
||||||
|
///
|
||||||
|
/// The root object for the query and Mutation needs to be specified.
|
||||||
|
/// If there is no mutation, you can use `GQLEmptyMutation`.
|
||||||
pub fn new(query: Query, mutation: Mutation) -> Self {
|
pub fn new(query: Query, mutation: Mutation) -> Self {
|
||||||
let mut registry = Registry {
|
let mut registry = Registry {
|
||||||
types: Default::default(),
|
types: Default::default(),
|
||||||
|
@ -91,11 +96,13 @@ impl<Query: GQLObject, Mutation: GQLObject> Schema<Query, Mutation> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a global data that can be accessed in the `Context`.
|
||||||
pub fn data<D: Any + Send + Sync>(mut self, data: D) -> Self {
|
pub fn data<D: Any + Send + Sync>(mut self, data: D) -> Self {
|
||||||
self.data.insert(data);
|
self.data.insert(data);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Start a query and return `QueryBuilder`.
|
||||||
pub fn query<'a>(&'a self, query_source: &'a str) -> QueryBuilder<'a, Query, Mutation> {
|
pub fn query<'a>(&'a self, query_source: &'a str) -> QueryBuilder<'a, Query, Mutation> {
|
||||||
QueryBuilder {
|
QueryBuilder {
|
||||||
query: &self.query,
|
query: &self.query,
|
||||||
|
@ -109,6 +116,7 @@ impl<Query: GQLObject, Mutation: GQLObject> Schema<Query, Mutation> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Query builder
|
||||||
pub struct QueryBuilder<'a, Query, Mutation> {
|
pub struct QueryBuilder<'a, Query, Mutation> {
|
||||||
query: &'a QueryRoot<Query>,
|
query: &'a QueryRoot<Query>,
|
||||||
mutation: &'a Mutation,
|
mutation: &'a Mutation,
|
||||||
|
@ -120,6 +128,7 @@ pub struct QueryBuilder<'a, Query, Mutation> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Query, Mutation> QueryBuilder<'a, Query, Mutation> {
|
impl<'a, Query, Mutation> QueryBuilder<'a, Query, Mutation> {
|
||||||
|
/// Specify the operation name.
|
||||||
pub fn operator_name(self, name: &'a str) -> Self {
|
pub fn operator_name(self, name: &'a str) -> Self {
|
||||||
QueryBuilder {
|
QueryBuilder {
|
||||||
operation_name: Some(name),
|
operation_name: Some(name),
|
||||||
|
@ -127,6 +136,7 @@ impl<'a, Query, Mutation> QueryBuilder<'a, Query, Mutation> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Specify the variables.
|
||||||
pub fn variables(self, vars: &'a Variables) -> Self {
|
pub fn variables(self, vars: &'a Variables) -> Self {
|
||||||
QueryBuilder {
|
QueryBuilder {
|
||||||
variables: Some(vars),
|
variables: Some(vars),
|
||||||
|
@ -134,6 +144,7 @@ impl<'a, Query, Mutation> QueryBuilder<'a, Query, Mutation> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Execute the query.
|
||||||
pub async fn execute(self) -> Result<serde_json::Value>
|
pub async fn execute(self) -> Result<serde_json::Value>
|
||||||
where
|
where
|
||||||
Query: GQLObject + Send + Sync,
|
Query: GQLObject + Send + Sync,
|
||||||
|
|
|
@ -3,6 +3,24 @@ use graphql_parser::query::Field;
|
||||||
use serde_json::{Map, Value};
|
use serde_json::{Map, Value};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
/// Empty mutation
|
||||||
|
///
|
||||||
|
/// Only the parameters used to construct the Schema, representing an unconfigured mutation.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use async_graphql::*;
|
||||||
|
///
|
||||||
|
/// struct QueryRoot;
|
||||||
|
///
|
||||||
|
/// #[Object]
|
||||||
|
/// impl QueryRoot {}
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let schema = Schema::new(QueryRoot, GQLEmptyMutation);
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
pub struct GQLEmptyMutation;
|
pub struct GQLEmptyMutation;
|
||||||
|
|
||||||
impl GQLType for GQLEmptyMutation {
|
impl GQLType for GQLEmptyMutation {
|
||||||
|
|
|
@ -19,7 +19,11 @@ pub fn check_rules(registry: &Registry, doc: &Document) -> Result<()> {
|
||||||
.with(rules::FragmentsOnCompositeTypes)
|
.with(rules::FragmentsOnCompositeTypes)
|
||||||
.with(rules::KnownArgumentNames::default())
|
.with(rules::KnownArgumentNames::default())
|
||||||
.with(rules::NoFragmentCycles::default())
|
.with(rules::NoFragmentCycles::default())
|
||||||
.with(rules::KnownFragmentNames);
|
.with(rules::KnownFragmentNames)
|
||||||
|
.with(rules::KnownTypeNames)
|
||||||
|
.with(rules::LoneAnonymousOperation::default())
|
||||||
|
.with(rules::NoUndefinedVariables::default())
|
||||||
|
.with(rules::NoUnusedFragments::default());
|
||||||
|
|
||||||
visit(&mut visitor, &mut ctx, doc);
|
visit(&mut visitor, &mut ctx, doc);
|
||||||
if !ctx.errors.is_empty() {
|
if !ctx.errors.is_empty() {
|
||||||
|
|
49
src/validation/rules/known_type_names.rs
Normal file
49
src/validation/rules/known_type_names.rs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
use crate::registry::TypeName;
|
||||||
|
use crate::validation::context::ValidatorContext;
|
||||||
|
use crate::validation::visitor::Visitor;
|
||||||
|
use graphql_parser::query::{
|
||||||
|
FragmentDefinition, InlineFragment, TypeCondition, VariableDefinition,
|
||||||
|
};
|
||||||
|
use graphql_parser::Pos;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct KnownTypeNames;
|
||||||
|
|
||||||
|
impl<'a> Visitor<'a> for KnownTypeNames {
|
||||||
|
fn enter_fragment_definition(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut ValidatorContext<'a>,
|
||||||
|
fragment_definition: &'a FragmentDefinition,
|
||||||
|
) {
|
||||||
|
let TypeCondition::On(name) = &fragment_definition.type_condition;
|
||||||
|
validate_type(ctx, &name, fragment_definition.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_variable_definition(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut ValidatorContext<'a>,
|
||||||
|
variable_definition: &'a VariableDefinition,
|
||||||
|
) {
|
||||||
|
validate_type(
|
||||||
|
ctx,
|
||||||
|
TypeName::get_basic_typename(&variable_definition.name),
|
||||||
|
variable_definition.position,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_inline_fragment(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut ValidatorContext<'a>,
|
||||||
|
inline_fragment: &'a InlineFragment,
|
||||||
|
) {
|
||||||
|
if let Some(TypeCondition::On(name)) = &inline_fragment.type_condition {
|
||||||
|
validate_type(ctx, &name, inline_fragment.position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_type(ctx: &mut ValidatorContext<'_>, type_name: &str, pos: Pos) {
|
||||||
|
if ctx.registry.types.get(type_name).is_none() {
|
||||||
|
ctx.report_error(vec![pos], format!(r#"Unknown type "{}""#, type_name));
|
||||||
|
}
|
||||||
|
}
|
39
src/validation/rules/lone_anonymous_operation.rs
Normal file
39
src/validation/rules/lone_anonymous_operation.rs
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
use crate::validation::context::ValidatorContext;
|
||||||
|
use crate::validation::visitor::Visitor;
|
||||||
|
use graphql_parser::query::{Definition, Document, OperationDefinition};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct LoneAnonymousOperation {
|
||||||
|
operation_count: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Visitor<'a> for LoneAnonymousOperation {
|
||||||
|
fn enter_document(&mut self, _ctx: &mut ValidatorContext<'a>, doc: &'a Document) {
|
||||||
|
self.operation_count = Some(
|
||||||
|
doc.definitions
|
||||||
|
.iter()
|
||||||
|
.filter(|d| match d {
|
||||||
|
Definition::Operation(_) => true,
|
||||||
|
Definition::Fragment(_) => false,
|
||||||
|
})
|
||||||
|
.count(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_operation_definition(
|
||||||
|
&mut self,
|
||||||
|
ctx: &mut ValidatorContext<'a>,
|
||||||
|
operation_definition: &'a OperationDefinition,
|
||||||
|
) {
|
||||||
|
if let Some(operation_count) = self.operation_count {
|
||||||
|
if let OperationDefinition::SelectionSet(s) = operation_definition {
|
||||||
|
if operation_count > 1 {
|
||||||
|
ctx.report_error(
|
||||||
|
vec![s.span.0, s.span.1],
|
||||||
|
"This anonymous operation must be the only defined operation",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,11 @@ mod fields_on_correct_type;
|
||||||
mod fragments_on_composite_types;
|
mod fragments_on_composite_types;
|
||||||
mod known_argument_names;
|
mod known_argument_names;
|
||||||
mod known_fragment_names;
|
mod known_fragment_names;
|
||||||
|
mod known_type_names;
|
||||||
|
mod lone_anonymous_operation;
|
||||||
mod no_fragment_cycles;
|
mod no_fragment_cycles;
|
||||||
|
mod no_undefined_variables;
|
||||||
|
mod no_unused_fragments;
|
||||||
|
|
||||||
pub use arguments_of_correct_type::ArgumentsOfCorrectType;
|
pub use arguments_of_correct_type::ArgumentsOfCorrectType;
|
||||||
pub use default_values_of_correct_type::DefaultValuesOfCorrectType;
|
pub use default_values_of_correct_type::DefaultValuesOfCorrectType;
|
||||||
|
@ -12,4 +16,8 @@ pub use fields_on_correct_type::FieldsOnCorrectType;
|
||||||
pub use fragments_on_composite_types::FragmentsOnCompositeTypes;
|
pub use fragments_on_composite_types::FragmentsOnCompositeTypes;
|
||||||
pub use known_argument_names::KnownArgumentNames;
|
pub use known_argument_names::KnownArgumentNames;
|
||||||
pub use known_fragment_names::KnownFragmentNames;
|
pub use known_fragment_names::KnownFragmentNames;
|
||||||
|
pub use known_type_names::KnownTypeNames;
|
||||||
|
pub use lone_anonymous_operation::LoneAnonymousOperation;
|
||||||
pub use no_fragment_cycles::NoFragmentCycles;
|
pub use no_fragment_cycles::NoFragmentCycles;
|
||||||
|
pub use no_undefined_variables::NoUndefinedVariables;
|
||||||
|
pub use no_unused_fragments::NoUnusedFragments;
|
||||||
|
|
57
src/validation/rules/no_undefined_variables.rs
Normal file
57
src/validation/rules/no_undefined_variables.rs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
use crate::validation::context::ValidatorContext;
|
||||||
|
use crate::validation::visitor::Visitor;
|
||||||
|
use graphql_parser::query::{Field, OperationDefinition, VariableDefinition};
|
||||||
|
use graphql_parser::schema::{Directive, Value};
|
||||||
|
use graphql_parser::Pos;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct NoUndefinedVariables<'a> {
|
||||||
|
vars: HashSet<&'a str>,
|
||||||
|
pos_stack: Vec<Pos>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Visitor<'a> for NoUndefinedVariables<'a> {
|
||||||
|
fn enter_operation_definition(
|
||||||
|
&mut self,
|
||||||
|
_ctx: &mut ValidatorContext<'a>,
|
||||||
|
_operation_definition: &'a OperationDefinition,
|
||||||
|
) {
|
||||||
|
self.vars.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_variable_definition(
|
||||||
|
&mut self,
|
||||||
|
_ctx: &mut ValidatorContext<'a>,
|
||||||
|
variable_definition: &'a VariableDefinition,
|
||||||
|
) {
|
||||||
|
self.vars.insert(&variable_definition.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_directive(&mut self, _ctx: &mut ValidatorContext<'a>, directive: &'a Directive) {
|
||||||
|
self.pos_stack.push(directive.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exit_directive(&mut self, _ctx: &mut ValidatorContext<'a>, _directive: &'a Directive) {
|
||||||
|
self.pos_stack.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_argument(&mut self, ctx: &mut ValidatorContext<'a>, _name: &str, value: &'a Value) {
|
||||||
|
if let Value::Variable(var_name) = value {
|
||||||
|
if !self.vars.contains(var_name.as_str()) {
|
||||||
|
ctx.report_error(
|
||||||
|
vec![self.pos_stack.last().cloned().unwrap()],
|
||||||
|
format!("Variable \"${}\" is not defined", var_name),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_field(&mut self, _ctx: &mut ValidatorContext<'a>, field: &'a Field) {
|
||||||
|
self.pos_stack.push(field.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exit_field(&mut self, _ctx: &mut ValidatorContext<'a>, _field: &'a Field) {
|
||||||
|
self.pos_stack.pop();
|
||||||
|
}
|
||||||
|
}
|
32
src/validation/rules/no_unused_fragments.rs
Normal file
32
src/validation/rules/no_unused_fragments.rs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
use crate::validation::context::ValidatorContext;
|
||||||
|
use crate::validation::visitor::Visitor;
|
||||||
|
use graphql_parser::query::{Definition, Document, FragmentSpread};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct NoUnusedFragments<'a> {
|
||||||
|
spreads: HashSet<&'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Visitor<'a> for NoUnusedFragments<'a> {
|
||||||
|
fn exit_document(&mut self, ctx: &mut ValidatorContext<'a>, doc: &'a Document) {
|
||||||
|
for d in &doc.definitions {
|
||||||
|
if let Definition::Fragment(fragment) = d {
|
||||||
|
if !self.spreads.contains(fragment.name.as_str()) {
|
||||||
|
ctx.report_error(
|
||||||
|
vec![fragment.position],
|
||||||
|
format!(r#"Fragment "{}" is never used"#, fragment.name),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_fragment_spread(
|
||||||
|
&mut self,
|
||||||
|
_ctx: &mut ValidatorContext<'a>,
|
||||||
|
fragment_spread: &'a FragmentSpread,
|
||||||
|
) {
|
||||||
|
self.spreads.insert(&fragment_spread.fragment_name);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,10 @@ use crate::registry::{Registry, Type, TypeName};
|
||||||
use crate::Value;
|
use crate::Value;
|
||||||
|
|
||||||
pub fn is_valid_input_value(registry: &Registry, type_name: &str, value: &Value) -> bool {
|
pub fn is_valid_input_value(registry: &Registry, type_name: &str, value: &Value) -> bool {
|
||||||
|
if let Value::Variable(_) = value {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
match TypeName::create(type_name) {
|
match TypeName::create(type_name) {
|
||||||
TypeName::NonNull(type_name) => match value {
|
TypeName::NonNull(type_name) => match value {
|
||||||
Value::Null => false,
|
Value::Null => false,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user