Merge branch 'async-graphql-v4' of github.com:async-graphql/async-graphql into async-graphql-v4
This commit is contained in:
commit
ae52749198
|
@ -37,7 +37,7 @@ futures-util = { version = "0.3.0", default-features = false, features = ["io",
|
|||
indexmap = "1.6.2"
|
||||
once_cell = "1.7.2"
|
||||
pin-project-lite = "0.2.6"
|
||||
regex = "1.4.5"
|
||||
regex = "1.5.5"
|
||||
serde = { version = "1.0.125", features = ["derive"] }
|
||||
serde_json = "1.0.64"
|
||||
thiserror = "1.0.24"
|
||||
|
@ -53,7 +53,7 @@ fast_chemail = "0.9.6"
|
|||
# Feature optional dependencies
|
||||
bson = { version = "2.0.0", optional = true, features = ["chrono-0_4"] }
|
||||
chrono = { version = "0.4.19", optional = true, default-features = false, features = ["clock", "std"] }
|
||||
chrono-tz = { version = "0.5.3", optional = true }
|
||||
chrono-tz = { version = "0.6.1", optional = true }
|
||||
hashbrown = { version = "0.12.0", optional = true }
|
||||
iso8601-duration = { version = "0.1.0", optional = true }
|
||||
log = { version = "0.4.14", optional = true }
|
||||
|
|
|
@ -20,6 +20,6 @@ bytes = "1.0.1"
|
|||
http-body = "0.4.2"
|
||||
serde_json = "1.0.66"
|
||||
serde_urlencoded = "0.7.0"
|
||||
tokio-util = { version = "0.6.7", features = ["io", "compat"] }
|
||||
tokio-util = { version = "0.7.1", features = ["io", "compat"] }
|
||||
futures-util = "0.3.0"
|
||||
tower-service = "0.3"
|
||||
|
|
|
@ -13,11 +13,11 @@ use http::HeaderValue;
|
|||
use serde::ser::{SerializeSeq, Serializer};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::extensions::Extensions;
|
||||
use crate::parser::types::{
|
||||
Directive, Field, FragmentDefinition, OperationDefinition, Selection, SelectionSet,
|
||||
};
|
||||
use crate::schema::SchemaEnv;
|
||||
use crate::{extensions::Extensions, schema::IntrospectionMode};
|
||||
use crate::{
|
||||
Error, InputType, Lookahead, Name, OneofObjectType, PathSegment, Pos, Positioned, Result,
|
||||
ServerError, ServerResult, UploadValue, Value,
|
||||
|
@ -244,7 +244,7 @@ pub struct QueryEnvInner {
|
|||
pub session_data: Arc<Data>,
|
||||
pub ctx_data: Arc<Data>,
|
||||
pub http_headers: Mutex<HeaderMap>,
|
||||
pub disable_introspection: bool,
|
||||
pub introspection_mode: IntrospectionMode,
|
||||
pub errors: Mutex<Vec<ServerError>>,
|
||||
}
|
||||
|
||||
|
|
|
@ -9,13 +9,14 @@ use indexmap::map::IndexMap;
|
|||
use indexmap::set::IndexSet;
|
||||
|
||||
pub use crate::model::__DirectiveLocation;
|
||||
use crate::parser::types::{
|
||||
BaseType as ParsedBaseType, Field, Type as ParsedType, VariableDefinition,
|
||||
};
|
||||
use crate::{
|
||||
model, Any, Context, InputType, OutputType, Positioned, ServerResult, SubscriptionType, Value,
|
||||
VisitorContext,
|
||||
};
|
||||
use crate::{
|
||||
parser::types::{BaseType as ParsedBaseType, Field, Type as ParsedType, VariableDefinition},
|
||||
schema::IntrospectionMode,
|
||||
};
|
||||
|
||||
pub use cache_control::CacheControl;
|
||||
|
||||
|
@ -401,7 +402,7 @@ pub struct Registry {
|
|||
pub query_type: String,
|
||||
pub mutation_type: Option<String>,
|
||||
pub subscription_type: Option<String>,
|
||||
pub disable_introspection: bool,
|
||||
pub introspection_mode: IntrospectionMode,
|
||||
pub enable_federation: bool,
|
||||
pub federation_subscription: bool,
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ use serde::{Deserialize, Deserializer, Serialize};
|
|||
|
||||
use crate::parser::parse_query;
|
||||
use crate::parser::types::ExecutableDocument;
|
||||
use crate::schema::IntrospectionMode;
|
||||
use crate::{Data, ParseRequestError, ServerError, UploadValue, Value, Variables};
|
||||
|
||||
/// GraphQL request.
|
||||
|
@ -15,6 +16,7 @@ use crate::{Data, ParseRequestError, ServerError, UploadValue, Value, Variables}
|
|||
#[non_exhaustive]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[non_exhaustive]
|
||||
pub struct Request {
|
||||
/// The query source of the request.
|
||||
#[serde(default)]
|
||||
|
@ -43,11 +45,17 @@ pub struct Request {
|
|||
pub extensions: HashMap<String, Value>,
|
||||
|
||||
/// Disable introspection queries for this request.
|
||||
/// This option has priority over `introspection_mode` when set to true.
|
||||
/// `introspection_mode` has priority when `disable_introspection` set to `false`.
|
||||
#[serde(skip)]
|
||||
pub disable_introspection: bool,
|
||||
|
||||
#[serde(skip)]
|
||||
pub(crate) parsed_query: Option<ExecutableDocument>,
|
||||
|
||||
/// Sets the introspection mode for this request (defaults to [IntrospectionMode::Enabled]).
|
||||
#[serde(skip)]
|
||||
pub introspection_mode: IntrospectionMode,
|
||||
}
|
||||
|
||||
impl Request {
|
||||
|
@ -62,6 +70,7 @@ impl Request {
|
|||
extensions: Default::default(),
|
||||
disable_introspection: false,
|
||||
parsed_query: None,
|
||||
introspection_mode: IntrospectionMode::Enabled,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,6 +100,15 @@ impl Request {
|
|||
#[must_use]
|
||||
pub fn disable_introspection(mut self) -> Self {
|
||||
self.disable_introspection = true;
|
||||
self.introspection_mode = IntrospectionMode::Disabled;
|
||||
self
|
||||
}
|
||||
|
||||
/// Only allow introspection queries for this request.
|
||||
#[must_use]
|
||||
pub fn only_introspection(mut self) -> Self {
|
||||
self.disable_introspection = false;
|
||||
self.introspection_mode = IntrospectionMode::IntrospectionOnly;
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -233,6 +251,17 @@ impl BatchRequest {
|
|||
pub fn disable_introspection(mut self) -> Self {
|
||||
for request in self.iter_mut() {
|
||||
request.disable_introspection = true;
|
||||
request.introspection_mode = IntrospectionMode::Disabled;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Only allow introspection queries for each request.
|
||||
#[must_use]
|
||||
pub fn introspection_only(mut self) -> Self {
|
||||
for request in self.iter_mut() {
|
||||
request.disable_introspection = false;
|
||||
request.introspection_mode = IntrospectionMode::IntrospectionOnly;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ use std::sync::Arc;
|
|||
use futures_util::stream::{self, Stream, StreamExt};
|
||||
use indexmap::map::IndexMap;
|
||||
|
||||
use crate::context::{Data, QueryEnvInner};
|
||||
use crate::custom_directive::CustomDirectiveFactory;
|
||||
use crate::extensions::{ExtensionFactory, Extensions};
|
||||
use crate::model::__DirectiveLocation;
|
||||
|
@ -17,11 +16,29 @@ use crate::resolver_utils::{resolve_container, resolve_container_serial};
|
|||
use crate::subscription::collect_subscription_streams;
|
||||
use crate::types::QueryRoot;
|
||||
use crate::validation::{check_rules, ValidationMode};
|
||||
use crate::{
|
||||
context::{Data, QueryEnvInner},
|
||||
EmptyMutation, EmptySubscription,
|
||||
};
|
||||
use crate::{
|
||||
BatchRequest, BatchResponse, CacheControl, ContextBase, InputType, ObjectType, OutputType,
|
||||
QueryEnv, Request, Response, ServerError, SubscriptionType, Variables, ID,
|
||||
};
|
||||
|
||||
/// Introspection mode
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum IntrospectionMode {
|
||||
IntrospectionOnly,
|
||||
Enabled,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
impl Default for IntrospectionMode {
|
||||
fn default() -> Self {
|
||||
IntrospectionMode::Enabled
|
||||
}
|
||||
}
|
||||
|
||||
/// Schema builder
|
||||
pub struct SchemaBuilder<Query, Mutation, Subscription> {
|
||||
validation_mode: ValidationMode,
|
||||
|
@ -58,7 +75,14 @@ impl<Query, Mutation, Subscription> SchemaBuilder<Query, Mutation, Subscription>
|
|||
/// Disable introspection queries.
|
||||
#[must_use]
|
||||
pub fn disable_introspection(mut self) -> Self {
|
||||
self.registry.disable_introspection = true;
|
||||
self.registry.introspection_mode = IntrospectionMode::Disabled;
|
||||
self
|
||||
}
|
||||
|
||||
/// Only process introspection queries, everything else is processed as an error.
|
||||
#[must_use]
|
||||
pub fn introspection_only(mut self) -> Self {
|
||||
self.registry.introspection_mode = IntrospectionMode::IntrospectionOnly;
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -303,7 +327,7 @@ where
|
|||
} else {
|
||||
Some(Subscription::type_name().to_string())
|
||||
},
|
||||
disable_introspection: false,
|
||||
introspection_mode: IntrospectionMode::Enabled,
|
||||
enable_federation: false,
|
||||
federation_subscription: false,
|
||||
};
|
||||
|
@ -515,7 +539,11 @@ where
|
|||
session_data,
|
||||
ctx_data: query_data,
|
||||
http_headers: Default::default(),
|
||||
disable_introspection: request.disable_introspection,
|
||||
introspection_mode: if request.disable_introspection {
|
||||
IntrospectionMode::Disabled
|
||||
} else {
|
||||
request.introspection_mode
|
||||
},
|
||||
errors: Default::default(),
|
||||
};
|
||||
Ok((QueryEnv::new(env), validation_result.cache_control))
|
||||
|
@ -532,7 +560,15 @@ where
|
|||
|
||||
let res = match &env.operation.node.ty {
|
||||
OperationType::Query => resolve_container(&ctx, &self.query).await,
|
||||
OperationType::Mutation => resolve_container_serial(&ctx, &self.mutation).await,
|
||||
OperationType::Mutation => {
|
||||
if self.env.registry.introspection_mode == IntrospectionMode::IntrospectionOnly
|
||||
|| env.introspection_mode == IntrospectionMode::IntrospectionOnly
|
||||
{
|
||||
resolve_container_serial(&ctx, &EmptyMutation).await
|
||||
} else {
|
||||
resolve_container_serial(&ctx, &self.mutation).await
|
||||
}
|
||||
}
|
||||
OperationType::Subscription => Err(ServerError::new(
|
||||
"Subscriptions are not supported on this transport.",
|
||||
None,
|
||||
|
@ -627,7 +663,15 @@ where
|
|||
);
|
||||
|
||||
let mut streams = Vec::new();
|
||||
if let Err(err) = collect_subscription_streams(&ctx, &schema.subscription, &mut streams) {
|
||||
let collect_result = if schema.env.registry.introspection_mode
|
||||
== IntrospectionMode::IntrospectionOnly
|
||||
|| env.introspection_mode == IntrospectionMode::IntrospectionOnly
|
||||
{
|
||||
collect_subscription_streams(&ctx, &EmptySubscription, &mut streams)
|
||||
} else {
|
||||
collect_subscription_streams(&ctx, &schema.subscription, &mut streams)
|
||||
};
|
||||
if let Err(err) = collect_result {
|
||||
yield Response::from_errors(vec![err]);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@ use bson::{oid::ObjectId, Bson, Document};
|
|||
|
||||
#[cfg(feature = "chrono")]
|
||||
use bson::DateTime as UtcDateTime;
|
||||
#[cfg(feature = "bson-uuid")]
|
||||
use bson::Uuid;
|
||||
#[cfg(feature = "chrono")]
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
|
@ -21,6 +23,21 @@ impl ScalarType for ObjectId {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "bson-uuid")]
|
||||
#[Scalar(internal, name = "UUID")]
|
||||
impl ScalarType for Uuid {
|
||||
fn parse(value: Value) -> InputValueResult<Self> {
|
||||
match value {
|
||||
Value::String(s) => Ok(Uuid::parse_str(&s)?),
|
||||
_ => Err(InputValueError::expected_type(value)),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_value(&self) -> Value {
|
||||
Value::String(self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
#[Scalar(internal, name = "DateTime")]
|
||||
impl ScalarType for UtcDateTime {
|
||||
|
|
|
@ -2,9 +2,12 @@ use std::borrow::Cow;
|
|||
|
||||
use indexmap::map::IndexMap;
|
||||
|
||||
use crate::model::{__Schema, __Type};
|
||||
use crate::parser::types::Field;
|
||||
use crate::resolver_utils::{resolve_container, ContainerType};
|
||||
use crate::{
|
||||
model::{__Schema, __Type},
|
||||
schema::IntrospectionMode,
|
||||
};
|
||||
use crate::{
|
||||
registry, Any, Context, ContextSelectionSet, ObjectType, OutputType, Positioned, ServerError,
|
||||
ServerResult, SimpleObject, Value,
|
||||
|
@ -24,7 +27,13 @@ pub(crate) struct QueryRoot<T> {
|
|||
#[async_trait::async_trait]
|
||||
impl<T: ObjectType> ContainerType for QueryRoot<T> {
|
||||
async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult<Option<Value>> {
|
||||
if !ctx.schema_env.registry.disable_introspection && !ctx.query_env.disable_introspection {
|
||||
if matches!(
|
||||
ctx.schema_env.registry.introspection_mode,
|
||||
IntrospectionMode::Enabled | IntrospectionMode::IntrospectionOnly
|
||||
) && matches!(
|
||||
ctx.query_env.introspection_mode,
|
||||
IntrospectionMode::Enabled | IntrospectionMode::IntrospectionOnly,
|
||||
) {
|
||||
if ctx.item.node.name.node == "__schema" {
|
||||
let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
|
||||
let visible_types = ctx.schema_env.registry.find_visible_types(ctx);
|
||||
|
@ -54,6 +63,12 @@ impl<T: ObjectType> ContainerType for QueryRoot<T> {
|
|||
}
|
||||
}
|
||||
|
||||
if ctx.schema_env.registry.introspection_mode == IntrospectionMode::IntrospectionOnly
|
||||
|| ctx.query_env.introspection_mode == IntrospectionMode::IntrospectionOnly
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if ctx.schema_env.registry.enable_federation || ctx.schema_env.registry.has_entities() {
|
||||
if ctx.item.node.name.node == "_entities" {
|
||||
let (_, representations) = ctx.param_value::<Vec<Any>>("representations", None)?;
|
||||
|
@ -93,7 +108,10 @@ impl<T: ObjectType> OutputType for QueryRoot<T> {
|
|||
fn create_type_info(registry: &mut registry::Registry) -> String {
|
||||
let root = T::create_type_info(registry);
|
||||
|
||||
if !registry.disable_introspection {
|
||||
if matches!(
|
||||
registry.introspection_mode,
|
||||
IntrospectionMode::Enabled | IntrospectionMode::IntrospectionOnly
|
||||
) {
|
||||
let schema_type = __Schema::create_type_info(registry);
|
||||
if let Some(registry::MetaType::Object { fields, .. }) =
|
||||
registry.types.get_mut(T::type_name().as_ref())
|
||||
|
|
|
@ -1272,3 +1272,109 @@ pub async fn test_disable_introspection() {
|
|||
value!({ "__type": null })
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
pub async fn test_introspection_only() {
|
||||
let schema = Schema::build(Query, Mutation, EmptySubscription)
|
||||
.introspection_only()
|
||||
.finish();
|
||||
|
||||
// Test whether introspection works.
|
||||
let query = r#"
|
||||
{
|
||||
__type(name: "Mutation") {
|
||||
name
|
||||
kind
|
||||
description
|
||||
fields {
|
||||
description
|
||||
name
|
||||
type { kind name }
|
||||
args { name }
|
||||
}
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let res_json = value!({
|
||||
"__type": {
|
||||
"name": "Mutation",
|
||||
"kind": "OBJECT",
|
||||
"description": "Global mutation",
|
||||
"fields": [
|
||||
{
|
||||
"description": "simple_mutation description\nline2\nline3",
|
||||
"name": "simpleMutation",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null
|
||||
},
|
||||
"args": [
|
||||
{
|
||||
"name": "input"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
let res = schema.execute(query).await.into_result().unwrap().data;
|
||||
assert_eq!(res, res_json);
|
||||
|
||||
// Test whether introspection works.
|
||||
let query = r#"
|
||||
{
|
||||
__type(name: "Query") {
|
||||
name
|
||||
kind
|
||||
description
|
||||
fields {
|
||||
description
|
||||
name
|
||||
type { kind name }
|
||||
args { name }
|
||||
}
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let res_json = value!({
|
||||
"__type": {
|
||||
"name": "Query",
|
||||
"kind": "OBJECT",
|
||||
"description": "Global query",
|
||||
"fields": [
|
||||
{
|
||||
"description": "Get a simple object",
|
||||
"name": "simpleObject",
|
||||
"type": { "kind": "NON_NULL", "name": null },
|
||||
"args": []
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
let res = schema.execute(query).await.into_result().unwrap().data;
|
||||
assert_eq!(res, res_json);
|
||||
|
||||
// Queries shouldn't work in introspection only mode.
|
||||
let query = r#"
|
||||
{
|
||||
simpleObject {
|
||||
a
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let res_json = value!({ "simpleObject": null });
|
||||
let res = schema.execute(query).await.into_result().unwrap().data;
|
||||
assert_eq!(res, res_json);
|
||||
|
||||
// Mutations shouldn't work in introspection only mode.
|
||||
let query = r#"
|
||||
mutation {
|
||||
simpleMutation(input: { a: "" }) {
|
||||
a
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let res_json = value!({ "simpleMutation": null });
|
||||
let res = schema.execute(query).await.into_result().unwrap().data;
|
||||
assert_eq!(res, res_json);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue