Add Analyzer extension.

This commit is contained in:
Sunli 2020-12-18 23:58:03 +08:00
parent 5120813937
commit 0d76987f40
6 changed files with 134 additions and 21 deletions

107
src/extensions/analyzer.rs Normal file
View 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,
}
})
);
}
}

View File

@ -1,5 +1,6 @@
//! Extensions for schema
mod analyzer;
#[cfg(feature = "apollo_persisted_queries")]
pub mod apollo_persisted_queries;
#[cfg(feature = "apollo_tracing")]
@ -14,9 +15,10 @@ use std::collections::BTreeMap;
use crate::context::{QueryPathNode, ResolveId};
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};
pub use self::analyzer::Analyzer;
#[cfg(feature = "apollo_tracing")]
pub use self::apollo_tracing::ApolloTracing;
#[cfg(feature = "log")]
@ -121,7 +123,7 @@ pub trait Extension: Sync + Send + 'static {
fn validation_start(&mut self, ctx: &ExtensionContext<'_>) {}
/// 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.
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 {
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));
}
}

View File

@ -4,7 +4,7 @@ use tracing::{span, Level, Span};
use crate::extensions::{Extension, ExtensionContext, ExtensionFactory, ResolveInfo};
use crate::parser::types::ExecutableDocument;
use crate::{ServerError, Variables};
use crate::{ServerError, ValidationResult, Variables};
/// Tracing extension configuration for each request.
#[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
.take()
.and_then(|span| span.with_subscriber(|(id, d)| d.exit(id)));

View File

@ -220,7 +220,7 @@ pub use request::{BatchRequest, Request};
pub use resolver_utils::{ContainerType, EnumType, ScalarType};
pub use response::{BatchResponse, Response};
pub use schema::{Schema, SchemaBuilder, SchemaEnv};
pub use validation::{ValidationMode, VisitorContext};
pub use validation::{ValidationMode, ValidationResult, VisitorContext};
pub use context::*;
#[doc(no_inline)]

View File

@ -16,7 +16,7 @@ use crate::registry::{MetaDirective, MetaInputValue, Registry};
use crate::resolver_utils::{resolve_container, resolve_container_serial};
use crate::subscription::collect_subscription_streams;
use crate::types::QueryRoot;
use crate::validation::{check_rules, CheckResult, ValidationMode};
use crate::validation::{check_rules, ValidationMode};
use crate::{
BatchRequest, BatchResponse, CacheControl, ContextBase, ObjectType, QueryEnv, Request,
Response, ServerError, SubscriptionType, Type, Value, ID,
@ -372,29 +372,25 @@ where
// check rules
extensions.validation_start(&ctx_extension);
let CheckResult {
cache_control,
complexity,
depth,
} = check_rules(
let validation_result = check_rules(
&self.env.registry,
&document,
Some(&request.variables),
self.validation_mode,
)
.log_error(&ctx_extension, &extensions)?;
extensions.validation_end(&ctx_extension);
extensions.validation_end(&ctx_extension, &validation_result);
// check limit
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.")])
.log_error(&ctx_extension, &extensions);
}
}
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.")])
.log_error(&ctx_extension, &extensions);
}
@ -437,7 +433,7 @@ where
uploads: request.uploads,
ctx_data: Arc::new(data),
};
Ok((env, cache_control))
Ok((env, validation_result.cache_control))
}
async fn execute_once(&self, env: QueryEnv) -> Response {

View File

@ -11,13 +11,19 @@ mod visitors;
use crate::parser::types::ExecutableDocument;
use crate::registry::Registry;
use crate::{CacheControl, ServerError, Variables};
use visitor::{visit, VisitorNil};
pub use visitor::VisitorContext;
use visitor::{visit, VisitorNil};
pub struct CheckResult {
/// Validation results.
pub struct ValidationResult {
/// Cache control
pub cache_control: CacheControl,
/// Query complexity
pub complexity: usize,
/// Query depth
pub depth: usize,
}
@ -36,7 +42,7 @@ pub fn check_rules(
doc: &ExecutableDocument,
variables: Option<&Variables>,
mode: ValidationMode,
) -> Result<CheckResult, Vec<ServerError>> {
) -> Result<ValidationResult, Vec<ServerError>> {
let mut ctx = VisitorContext::new(registry, doc, variables);
let mut cache_control = CacheControl::default();
let mut complexity = 0;
@ -90,7 +96,7 @@ pub fn check_rules(
return Err(ctx.errors.into_iter().map(Into::into).collect());
}
Ok(CheckResult {
Ok(ValidationResult {
cache_control,
complexity,
depth,