Source code for py_gql.utilities.max_depth

# -*- coding: utf-8 -*-

from typing import Any, Dict, List, Optional

from ..exc import ValidationError
from ..lang.ast import Document, Field, OperationDefinition
from ..schema import Schema
from .collect_fields import selected_fields


[docs]class MaxDepthValidationRule: """Validate that a given document doesn't exceed a given query depth. Query depth is calculated as nesting levels into object types, traversing fragments. For example, given the following document: .. code-block:: graphql { hero { name friends { ... friendsData } } } fragment friendsData on Character { friends { name friends { name } } } the depth of the query would be 4. Args: max_depth: Depth limit (inclusive). operation_name: If set this will only consider the operation matching the provided name, if not this will collect errors for all operation definitions. """ def __init__(self, max_depth: int, *, operation_name: Optional[str] = None): self.max_depth = max_depth self.operation_name = operation_name def __call__( self, schema: Schema, doc: Document, variables: Optional[Dict[str, Any]] = None, ) -> List[ValidationError]: fragments = doc.fragments variables = variables or {} depth = None # type: Optional[int] errors = [] # type: List[ValidationError] for op in doc.definitions: if not isinstance(op, OperationDefinition): continue if self.operation_name and not ( op.name and op.name.value == self.operation_name ): continue paths = ( p for f in op.selection_set.selections if isinstance(f, Field) for p in selected_fields( f, fragments=fragments, variables=variables, maxdepth=None, ) ) depth = max(x.count("/") + 1 for x in paths) if depth > self.max_depth: errors.append( ValidationError( 'Operation "%s" depth (%s) exceeds maximum depth (%s)' % ( op.name.value if op.name else "<ANONYMOUS>", depth, self.max_depth, ), nodes=[op], ), ) return errors