Add Analyzer extension.
This commit is contained in:
parent
5120813937
commit
0d76987f40
107
src/extensions/analyzer.rs
Normal file
107
src/extensions/analyzer.rs
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
use crate::extensions::{Extension, ExtensionContext, ExtensionFactory};
|
||||||
|
use crate::{value, ValidationResult, Value};
|
||||||
|
|
||||||
|
/// Analyzer extension
|
||||||
|
///
|
||||||
|
/// This extension will output the `analyzer` field containing `complexity` and `depth` in the response extension of each query.
|
||||||
|
pub struct Analyzer;
|
||||||
|
|
||||||
|
impl ExtensionFactory for Analyzer {
|
||||||
|
fn create(&self) -> Box<dyn Extension> {
|
||||||
|
Box::new(AnalyzerExtension::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct AnalyzerExtension {
|
||||||
|
complexity: usize,
|
||||||
|
depth: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Extension for AnalyzerExtension {
|
||||||
|
fn name(&self) -> Option<&'static str> {
|
||||||
|
Some("analyzer")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validation_end(&mut self, _ctx: &ExtensionContext<'_>, result: &ValidationResult) {
|
||||||
|
self.complexity = result.complexity;
|
||||||
|
self.depth = result.depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn result(&mut self, _ctx: &ExtensionContext<'_>) -> Option<Value> {
|
||||||
|
Some(value! ({
|
||||||
|
"complexity": self.complexity,
|
||||||
|
"depth": self.depth,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
struct Query;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
struct MyObj;
|
||||||
|
|
||||||
|
#[Object(internal)]
|
||||||
|
impl MyObj {
|
||||||
|
async fn value(&self) -> i32 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn obj(&self) -> MyObj {
|
||||||
|
MyObj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Object(internal)]
|
||||||
|
impl Query {
|
||||||
|
async fn value(&self) -> i32 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn obj(&self) -> MyObj {
|
||||||
|
MyObj
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql(complexity = "count * child_complexity")]
|
||||||
|
async fn objs(&self, count: usize) -> Vec<MyObj> {
|
||||||
|
vec![MyObj; count as usize]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn analyzer() {
|
||||||
|
let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
|
||||||
|
.extension(extensions::Analyzer)
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
let extensions = schema
|
||||||
|
.execute(
|
||||||
|
r#"{
|
||||||
|
value obj {
|
||||||
|
value obj {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
objs(count: 10) { value }
|
||||||
|
}"#,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.into_result()
|
||||||
|
.unwrap()
|
||||||
|
.extensions
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
extensions,
|
||||||
|
value!({
|
||||||
|
"analyzer": {
|
||||||
|
"complexity": 5 + 10,
|
||||||
|
"depth": 3,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
//! Extensions for schema
|
//! Extensions for schema
|
||||||
|
|
||||||
|
mod analyzer;
|
||||||
#[cfg(feature = "apollo_persisted_queries")]
|
#[cfg(feature = "apollo_persisted_queries")]
|
||||||
pub mod apollo_persisted_queries;
|
pub mod apollo_persisted_queries;
|
||||||
#[cfg(feature = "apollo_tracing")]
|
#[cfg(feature = "apollo_tracing")]
|
||||||
|
@ -14,9 +15,10 @@ use std::collections::BTreeMap;
|
||||||
|
|
||||||
use crate::context::{QueryPathNode, ResolveId};
|
use crate::context::{QueryPathNode, ResolveId};
|
||||||
use crate::parser::types::ExecutableDocument;
|
use crate::parser::types::ExecutableDocument;
|
||||||
use crate::{Data, Request, Result, ServerError, ServerResult, Variables};
|
use crate::{Data, Request, Result, ServerError, ServerResult, ValidationResult, Variables};
|
||||||
use crate::{Error, Name, Value};
|
use crate::{Error, Name, Value};
|
||||||
|
|
||||||
|
pub use self::analyzer::Analyzer;
|
||||||
#[cfg(feature = "apollo_tracing")]
|
#[cfg(feature = "apollo_tracing")]
|
||||||
pub use self::apollo_tracing::ApolloTracing;
|
pub use self::apollo_tracing::ApolloTracing;
|
||||||
#[cfg(feature = "log")]
|
#[cfg(feature = "log")]
|
||||||
|
@ -121,7 +123,7 @@ pub trait Extension: Sync + Send + 'static {
|
||||||
fn validation_start(&mut self, ctx: &ExtensionContext<'_>) {}
|
fn validation_start(&mut self, ctx: &ExtensionContext<'_>) {}
|
||||||
|
|
||||||
/// Called at the end of the validation.
|
/// Called at the end of the validation.
|
||||||
fn validation_end(&mut self, ctx: &ExtensionContext<'_>) {}
|
fn validation_end(&mut self, ctx: &ExtensionContext<'_>, result: &ValidationResult) {}
|
||||||
|
|
||||||
/// Called at the begin of the execution.
|
/// Called at the begin of the execution.
|
||||||
fn execution_start(&mut self, ctx: &ExtensionContext<'_>) {}
|
fn execution_start(&mut self, ctx: &ExtensionContext<'_>) {}
|
||||||
|
@ -236,9 +238,11 @@ impl Extensions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validation_end(&mut self, ctx: &ExtensionContext<'_>) {
|
pub fn validation_end(&mut self, ctx: &ExtensionContext<'_>, result: &ValidationResult) {
|
||||||
if let Some(e) = &mut self.0 {
|
if let Some(e) = &mut self.0 {
|
||||||
e.get_mut().iter_mut().for_each(|e| e.validation_end(ctx));
|
e.get_mut()
|
||||||
|
.iter_mut()
|
||||||
|
.for_each(|e| e.validation_end(ctx, result));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ use tracing::{span, Level, Span};
|
||||||
|
|
||||||
use crate::extensions::{Extension, ExtensionContext, ExtensionFactory, ResolveInfo};
|
use crate::extensions::{Extension, ExtensionContext, ExtensionFactory, ResolveInfo};
|
||||||
use crate::parser::types::ExecutableDocument;
|
use crate::parser::types::ExecutableDocument;
|
||||||
use crate::{ServerError, Variables};
|
use crate::{ServerError, ValidationResult, Variables};
|
||||||
|
|
||||||
/// Tracing extension configuration for each request.
|
/// Tracing extension configuration for each request.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -136,7 +136,7 @@ impl Extension for TracingExtension {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validation_end(&mut self, _ctx: &ExtensionContext<'_>) {
|
fn validation_end(&mut self, _ctx: &ExtensionContext<'_>, _result: &ValidationResult) {
|
||||||
self.validation
|
self.validation
|
||||||
.take()
|
.take()
|
||||||
.and_then(|span| span.with_subscriber(|(id, d)| d.exit(id)));
|
.and_then(|span| span.with_subscriber(|(id, d)| d.exit(id)));
|
||||||
|
|
|
@ -220,7 +220,7 @@ pub use request::{BatchRequest, Request};
|
||||||
pub use resolver_utils::{ContainerType, EnumType, ScalarType};
|
pub use resolver_utils::{ContainerType, EnumType, ScalarType};
|
||||||
pub use response::{BatchResponse, Response};
|
pub use response::{BatchResponse, Response};
|
||||||
pub use schema::{Schema, SchemaBuilder, SchemaEnv};
|
pub use schema::{Schema, SchemaBuilder, SchemaEnv};
|
||||||
pub use validation::{ValidationMode, VisitorContext};
|
pub use validation::{ValidationMode, ValidationResult, VisitorContext};
|
||||||
|
|
||||||
pub use context::*;
|
pub use context::*;
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
|
|
|
@ -16,7 +16,7 @@ use crate::registry::{MetaDirective, MetaInputValue, Registry};
|
||||||
use crate::resolver_utils::{resolve_container, resolve_container_serial};
|
use crate::resolver_utils::{resolve_container, resolve_container_serial};
|
||||||
use crate::subscription::collect_subscription_streams;
|
use crate::subscription::collect_subscription_streams;
|
||||||
use crate::types::QueryRoot;
|
use crate::types::QueryRoot;
|
||||||
use crate::validation::{check_rules, CheckResult, ValidationMode};
|
use crate::validation::{check_rules, ValidationMode};
|
||||||
use crate::{
|
use crate::{
|
||||||
BatchRequest, BatchResponse, CacheControl, ContextBase, ObjectType, QueryEnv, Request,
|
BatchRequest, BatchResponse, CacheControl, ContextBase, ObjectType, QueryEnv, Request,
|
||||||
Response, ServerError, SubscriptionType, Type, Value, ID,
|
Response, ServerError, SubscriptionType, Type, Value, ID,
|
||||||
|
@ -372,29 +372,25 @@ where
|
||||||
|
|
||||||
// check rules
|
// check rules
|
||||||
extensions.validation_start(&ctx_extension);
|
extensions.validation_start(&ctx_extension);
|
||||||
let CheckResult {
|
let validation_result = check_rules(
|
||||||
cache_control,
|
|
||||||
complexity,
|
|
||||||
depth,
|
|
||||||
} = check_rules(
|
|
||||||
&self.env.registry,
|
&self.env.registry,
|
||||||
&document,
|
&document,
|
||||||
Some(&request.variables),
|
Some(&request.variables),
|
||||||
self.validation_mode,
|
self.validation_mode,
|
||||||
)
|
)
|
||||||
.log_error(&ctx_extension, &extensions)?;
|
.log_error(&ctx_extension, &extensions)?;
|
||||||
extensions.validation_end(&ctx_extension);
|
extensions.validation_end(&ctx_extension, &validation_result);
|
||||||
|
|
||||||
// check limit
|
// check limit
|
||||||
if let Some(limit_complexity) = self.complexity {
|
if let Some(limit_complexity) = self.complexity {
|
||||||
if complexity > limit_complexity {
|
if validation_result.complexity > limit_complexity {
|
||||||
return Err(vec![ServerError::new("Query is too complex.")])
|
return Err(vec![ServerError::new("Query is too complex.")])
|
||||||
.log_error(&ctx_extension, &extensions);
|
.log_error(&ctx_extension, &extensions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(limit_depth) = self.depth {
|
if let Some(limit_depth) = self.depth {
|
||||||
if depth > limit_depth {
|
if validation_result.depth > limit_depth {
|
||||||
return Err(vec![ServerError::new("Query is nested too deep.")])
|
return Err(vec![ServerError::new("Query is nested too deep.")])
|
||||||
.log_error(&ctx_extension, &extensions);
|
.log_error(&ctx_extension, &extensions);
|
||||||
}
|
}
|
||||||
|
@ -437,7 +433,7 @@ where
|
||||||
uploads: request.uploads,
|
uploads: request.uploads,
|
||||||
ctx_data: Arc::new(data),
|
ctx_data: Arc::new(data),
|
||||||
};
|
};
|
||||||
Ok((env, cache_control))
|
Ok((env, validation_result.cache_control))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn execute_once(&self, env: QueryEnv) -> Response {
|
async fn execute_once(&self, env: QueryEnv) -> Response {
|
||||||
|
|
|
@ -11,13 +11,19 @@ mod visitors;
|
||||||
use crate::parser::types::ExecutableDocument;
|
use crate::parser::types::ExecutableDocument;
|
||||||
use crate::registry::Registry;
|
use crate::registry::Registry;
|
||||||
use crate::{CacheControl, ServerError, Variables};
|
use crate::{CacheControl, ServerError, Variables};
|
||||||
use visitor::{visit, VisitorNil};
|
|
||||||
|
|
||||||
pub use visitor::VisitorContext;
|
pub use visitor::VisitorContext;
|
||||||
|
use visitor::{visit, VisitorNil};
|
||||||
|
|
||||||
pub struct CheckResult {
|
/// Validation results.
|
||||||
|
pub struct ValidationResult {
|
||||||
|
/// Cache control
|
||||||
pub cache_control: CacheControl,
|
pub cache_control: CacheControl,
|
||||||
|
|
||||||
|
/// Query complexity
|
||||||
pub complexity: usize,
|
pub complexity: usize,
|
||||||
|
|
||||||
|
/// Query depth
|
||||||
pub depth: usize,
|
pub depth: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +42,7 @@ pub fn check_rules(
|
||||||
doc: &ExecutableDocument,
|
doc: &ExecutableDocument,
|
||||||
variables: Option<&Variables>,
|
variables: Option<&Variables>,
|
||||||
mode: ValidationMode,
|
mode: ValidationMode,
|
||||||
) -> Result<CheckResult, Vec<ServerError>> {
|
) -> Result<ValidationResult, Vec<ServerError>> {
|
||||||
let mut ctx = VisitorContext::new(registry, doc, variables);
|
let mut ctx = VisitorContext::new(registry, doc, variables);
|
||||||
let mut cache_control = CacheControl::default();
|
let mut cache_control = CacheControl::default();
|
||||||
let mut complexity = 0;
|
let mut complexity = 0;
|
||||||
|
@ -90,7 +96,7 @@ pub fn check_rules(
|
||||||
return Err(ctx.errors.into_iter().map(Into::into).collect());
|
return Err(ctx.errors.into_iter().map(Into::into).collect());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(CheckResult {
|
Ok(ValidationResult {
|
||||||
cache_control,
|
cache_control,
|
||||||
complexity,
|
complexity,
|
||||||
depth,
|
depth,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user