forked from STI/Astroport.ONE
86 lines
3.1 KiB
Python
86 lines
3.1 KiB
Python
|
from typing import Any, Dict, cast
|
||
|
|
||
|
from ...error import GraphQLError
|
||
|
from ...execution.collect_fields import collect_fields
|
||
|
from ...language import (
|
||
|
FieldNode,
|
||
|
FragmentDefinitionNode,
|
||
|
OperationDefinitionNode,
|
||
|
OperationType,
|
||
|
)
|
||
|
from . import ValidationRule
|
||
|
|
||
|
__all__ = ["SingleFieldSubscriptionsRule"]
|
||
|
|
||
|
|
||
|
class SingleFieldSubscriptionsRule(ValidationRule):
|
||
|
"""Subscriptions must only include a single non-introspection field.
|
||
|
|
||
|
A GraphQL subscription is valid only if it contains a single root field and
|
||
|
that root field is not an introspection field.
|
||
|
|
||
|
See https://spec.graphql.org/draft/#sec-Single-root-field
|
||
|
"""
|
||
|
|
||
|
def enter_operation_definition(
|
||
|
self, node: OperationDefinitionNode, *_args: Any
|
||
|
) -> None:
|
||
|
if node.operation != OperationType.SUBSCRIPTION:
|
||
|
return
|
||
|
schema = self.context.schema
|
||
|
subscription_type = schema.subscription_type
|
||
|
if subscription_type:
|
||
|
operation_name = node.name.value if node.name else None
|
||
|
variable_values: Dict[str, Any] = {}
|
||
|
document = self.context.document
|
||
|
fragments: Dict[str, FragmentDefinitionNode] = {
|
||
|
definition.name.value: definition
|
||
|
for definition in document.definitions
|
||
|
if isinstance(definition, FragmentDefinitionNode)
|
||
|
}
|
||
|
fields = collect_fields(
|
||
|
schema,
|
||
|
fragments,
|
||
|
variable_values,
|
||
|
subscription_type,
|
||
|
node.selection_set,
|
||
|
)
|
||
|
if len(fields) > 1:
|
||
|
field_selection_lists = list(fields.values())
|
||
|
extra_field_selection_lists = field_selection_lists[1:]
|
||
|
extra_field_selection = [
|
||
|
field
|
||
|
for fields in extra_field_selection_lists
|
||
|
for field in (
|
||
|
fields
|
||
|
if isinstance(fields, list)
|
||
|
else [cast(FieldNode, fields)]
|
||
|
)
|
||
|
]
|
||
|
self.report_error(
|
||
|
GraphQLError(
|
||
|
(
|
||
|
"Anonymous Subscription"
|
||
|
if operation_name is None
|
||
|
else f"Subscription '{operation_name}'"
|
||
|
)
|
||
|
+ " must select only one top level field.",
|
||
|
extra_field_selection,
|
||
|
)
|
||
|
)
|
||
|
for field_nodes in fields.values():
|
||
|
field = field_nodes[0]
|
||
|
field_name = field.name.value
|
||
|
if field_name.startswith("__"):
|
||
|
self.report_error(
|
||
|
GraphQLError(
|
||
|
(
|
||
|
"Anonymous Subscription"
|
||
|
if operation_name is None
|
||
|
else f"Subscription '{operation_name}'"
|
||
|
)
|
||
|
+ " must not select an introspection top level field.",
|
||
|
field_nodes,
|
||
|
)
|
||
|
)
|