Ejemplo n.º 1
0
def validate_query(
    schema: GraphQLSchema,
    document_ast: DocumentNode,
    rules: Optional[Collection[Type[ASTValidationRule]]] = None,
    max_errors: Optional[int] = None,
    type_info: Optional[TypeInfo] = None,
    enable_introspection: bool = True,
) -> List[GraphQLError]:
    if not enable_introspection:
        rules = (tuple(rules) +
                 (IntrospectionDisabledRule, ) if rules is not None else
                 (IntrospectionDisabledRule, ))
    if rules:
        # run validation against rules from spec and custom rules
        supplemented_rules = specified_rules + tuple(rules)
        return validate(
            schema,
            document_ast,
            rules=supplemented_rules,
            max_errors=max_errors,
            type_info=type_info,
        )
    # run validation using spec rules only
    return validate(schema,
                    document_ast,
                    rules=specified_rules,
                    type_info=type_info)
    def test_get_schema_for_macro_definition_validation(self) -> None:
        macro_definition_schema = get_schema_for_macro_definition(
            self.macro_registry.schema_without_macros)

        for macro, _ in VALID_MACROS_TEXT:
            macro_edge_definition_ast = safe_parse_graphql(macro)
            validate(macro_definition_schema, macro_edge_definition_ast)
Ejemplo n.º 3
0
    def pass_through_exceptions_from_rules():
        class CustomRule(ValidationRule):
            def enter_field(self, *_args):
                raise RuntimeError("Error from custom rule!")

        with raises(RuntimeError, match="^Error from custom rule!$"):
            validate(test_schema, doc, [CustomRule], max_errors=1)
Ejemplo n.º 4
0
def _contain_sensitive_field(document: "GraphQLDocument",
                             sensitive_fields: SensitiveFieldsMap):
    validator = cast(Type[ValidationRule],
                     ContainSensitiveField(sensitive_fields=sensitive_fields))
    try:
        validate(document.schema, document.document_ast, [validator])
    except SensitiveFieldError:
        return True
    return False
Ejemplo n.º 5
0
def graphql_to_ir(schema, graphql_string, type_equivalence_hints=None):
    """Convert the given GraphQL string into compiler IR, using the given schema object.

    Args:
        schema: GraphQL schema object, created using the GraphQL library
        graphql_string: string containing the GraphQL to compile to compiler IR
        type_equivalence_hints: optional dict of GraphQL interface or type -> GraphQL union.
                                Used as a workaround for GraphQL's lack of support for
                                inheritance across "types" (i.e. non-interfaces), as well as a
                                workaround for Gremlin's total lack of inheritance-awareness.
                                The key-value pairs in the dict specify that the "key" type
                                is equivalent to the "value" type, i.e. that the GraphQL type or
                                interface in the key is the most-derived common supertype
                                of every GraphQL type in the "value" GraphQL union.
                                Recursive expansion of type equivalence hints is not performed,
                                and only type-level correctness of this argument is enforced.
                                See README.md for more details on everything this parameter does.
                                *****
                                Be very careful with this option, as bad input here will
                                lead to incorrect output queries being generated.
                                *****

    Returns:
        IrAndMetadata named tuple, containing fields:
        - ir_blocks: a list of IR basic block objects
        - input_metadata: a dict of expected input parameters (string) -> inferred GraphQL type
        - output_metadata: a dict of output name (string) -> OutputMetadata object
        - location_types: a dict of location objects -> GraphQL type objects at that location

    Raises flavors of GraphQLError in the following cases:
        - if the query is invalid GraphQL (GraphQLParsingError);
        - if the query doesn't match the schema (GraphQLValidationError);
        - if the query has more than one definition block (GraphQLValidationError);
        - if the query has more than one selection in the root object (GraphQLCompilationError);
        - if the query does not obey directive usage rules (GraphQLCompilationError);
        - if the query provides invalid / disallowed / wrong number of arguments
          for a directive (GraphQLCompilationError).

    In the case of implementation bugs, could also raise ValueError, TypeError, or AssertionError.
    """
    graphql_string = _preprocess_graphql_string(graphql_string)
    try:
        ast = parse(graphql_string)
    except GraphQLSyntaxError as e:
        raise GraphQLParsingError(e)

    validation_errors = validate(schema, ast)

    if validation_errors:
        raise GraphQLValidationError(
            u'String does not validate: {}'.format(validation_errors))

    if len(ast.definitions) != 1:
        raise AssertionError(
            u'Unsupported graphql string with multiple definitions, should have '
            u'been caught in validation: \n{}\n{}'.format(graphql_string, ast))
    base_ast = ast.definitions[0]

    return _compile_root_ast_to_ir(
        schema, base_ast, type_equivalence_hints=type_equivalence_hints)
Ejemplo n.º 6
0
def graphql(schema,
            request_string='',
            root_value=None,
            context_value=None,
            variable_values=None,
            operation_name=None,
            executor=None,
            return_promise=False,
            middleware=None):
    try:
        source = Source(request_string, 'GraphQL request')
        ast = parse(source)
        validation_errors = validate(schema, ast)
        if validation_errors:
            return ExecutionResult(
                errors=validation_errors,
                invalid=True,
            )
        return execute(
            schema,
            ast,
            root_value,
            context_value,
            operation_name=operation_name,
            variable_values=variable_values or {},
            executor=executor,
            return_promise=return_promise,
            middleware=middleware,
        )
    except Exception as e:
        return ExecutionResult(
            errors=[e],
            invalid=True,
        )
Ejemplo n.º 7
0
async def execute(
    schema: GraphQLSchema,
    query: str,
    context_value: typing.Any = None,
    variable_values: typing.Dict[str, typing.Any] = None,
    operation_name: str = None,
):
    schema_validation_errors = validate_schema(schema)
    if schema_validation_errors:
        return ExecutionResult(data=None, errors=schema_validation_errors)

    try:
        document = parse(query)
    except GraphQLError as error:
        return ExecutionResult(data=None, errors=[error])
    except Exception as error:
        error = GraphQLError(str(error), original_error=error)
        return ExecutionResult(data=None, errors=[error])

    validation_errors = validate(schema, document)

    if validation_errors:
        return ExecutionResult(data=None, errors=validation_errors)

    return graphql_excute(
        schema,
        parse(query),
        middleware=[DirectivesMiddleware()],
        variable_values=variable_values,
        operation_name=operation_name,
        context_value=context_value,
    )
Ejemplo n.º 8
0
def check_query_is_valid_to_split(schema: GraphQLSchema,
                                  query_ast: DocumentNode) -> None:
    """Check the query is valid for splitting.

    In particular, ensure that the query validates against the schema, does not contain
    unsupported directives, and that in each selection, all property fields occur before all
    vertex fields.

    Args:
        schema: schema the query is written against
        query_ast: query to split

    Raises:
        GraphQLValidationError if the query doesn't validate against the schema, contains
        unsupported directives, or some property field occurs after a vertex field in some
        selection
    """
    # Check builtin errors
    built_in_validation_errors = validate(schema, query_ast)
    if len(built_in_validation_errors) > 0:
        raise GraphQLValidationError(
            "AST does not validate: {}".format(built_in_validation_errors))
    # Check no bad directives and fields are in order
    visitor = CheckQueryIsValidToSplitVisitor()
    visit(query_ast, visitor)
Ejemplo n.º 9
0
def validate_query(
    schema: GraphQLSchema,
    document_ast: DocumentNode,
    rules: Optional[Sequence[RuleType]] = None,
    type_info: Optional[TypeInfo] = None,
) -> List[GraphQLError]:
    if rules:
        # run validation against rules from spec and custom rules
        return validate(
            schema,
            document_ast,
            rules=specified_rules + list(rules),
            type_info=type_info,
        )
    # run validation using spec rules only
    return validate(schema, document_ast, rules=specified_rules, type_info=type_info)
Ejemplo n.º 10
0
 def rejects_invalid_type_info():
     with raises(TypeError) as exc_info:
         # noinspection PyTypeChecker
         assert validate(
             test_schema, parse("query { name }"), type_info={}  # type: ignore
         )
     assert str(exc_info.value) == "Not a TypeInfo object: {}."
Ejemplo n.º 11
0
    def validates_using_a_custom_type_info():
        # This TypeInfo will never return a valid field.
        type_info = TypeInfo(test_schema, lambda *args: None)

        ast = parse("""
            query {
              catOrDog {
                ... on Cat {
                  furColor
                }
                ... on Dog {
                  isHousetrained
                }
              }
            }
            """)

        errors = validate(test_schema, ast, specified_rules, type_info)

        assert [error.message for error in errors] == [
            "Cannot query field 'catOrDog' on type 'QueryRoot'."
            " Did you mean 'catOrDog'?",
            "Cannot query field 'furColor' on type 'Cat'."
            " Did you mean 'furColor'?",
            "Cannot query field 'isHousetrained' on type 'Dog'."
            " Did you mean 'isHousetrained'?"]
Ejemplo n.º 12
0
    def validates_using_a_custom_rule():
        schema = build_schema(
            """
            directive @custom(arg: String) on FIELD

            type Query {
              foo: String
            }
            """
        )

        doc = parse(
            """
            query {
              name @custom
            }
            """
        )

        class CustomRule(ValidationRule):
            def enter_directive(self, node, *_args):
                directive_def = self.context.get_directive()
                error = GraphQLError("Reporting directive: " + str(directive_def), node)
                self.context.report_error(error)

        errors = validate(schema, doc, [CustomRule])
        assert errors == [
            {"message": "Reporting directive: @custom", "locations": [(3, 20)]}
        ]
Ejemplo n.º 13
0
def test_field_cost_defined_in_map_is_multiplied_by_nested_value_from_variables(
        schema):
    query = """
        query testQuery($value: NestedInput!) {
            nested(value: $value)
        }
    """
    ast = parse(query)
    rule = cost_validator(maximum_cost=3,
                          variables={"value": {
                              "num": 5
                          }},
                          cost_map=cost_map)
    result = validate(schema, ast, [rule])
    assert result == [
        GraphQLError(
            "The query exceeds the maximum cost of 3. Actual cost is 5",
            extensions={
                "cost": {
                    "requestedQueryCost": 5,
                    "maximumAvailable": 3
                }
            },
        )
    ]
Ejemplo n.º 14
0
def test_complex_field_cost_multiplication_by_values_from_variables_handles_nulls(
    schema, ):
    query = """
        query testQuery($valueA: Int, $valueB: Int) {
            complex(valueA: $valueA, valueB: $valueB)
        }
    """
    ast = parse(query)
    rule = cost_validator(maximum_cost=3,
                          variables={
                              "valueA": 5,
                              "valueB": None
                          },
                          cost_map=cost_map)
    result = validate(schema, ast, [rule])
    assert result == [
        GraphQLError(
            "The query exceeds the maximum cost of 3. Actual cost is 5",
            extensions={
                "cost": {
                    "requestedQueryCost": 5,
                    "maximumAvailable": 3
                }
            },
        )
    ]
Ejemplo n.º 15
0
    def deprecated_validates_using_a_custom_type_info():
        # This TypeInfo will never return a valid field.
        type_info = TypeInfo(test_schema, None, lambda *args: None)

        doc = parse(
            """
            query {
              human {
                pets {
                  ... on Cat {
                    meowsVolume
                  }
                  ... on Dog {
                    barkVolume
                  }
                }
              }
            }
            """
        )

        errors = validate(test_schema, doc, None, None, type_info)

        assert [error.message for error in errors] == [
            "Cannot query field 'human' on type 'QueryRoot'. Did you mean 'human'?",
            "Cannot query field 'meowsVolume' on type 'Cat'."
            " Did you mean 'meowsVolume'?",
            "Cannot query field 'barkVolume' on type 'Dog'."
            " Did you mean 'barkVolume'?",
        ]
Ejemplo n.º 16
0
def _validate_ast_with_builtin_graphql_validation(schema, ast):
    """Validate the ast against the schema with macro directives using GraphQL validate function."""
    schema_with_macro_edge_directives = get_schema_for_macro_edge_definitions(schema)

    validation_errors = validate(schema_with_macro_edge_directives, ast)
    if validation_errors:
        raise GraphQLInvalidMacroError(
            u'Macro edge failed validation: {}'.format(validation_errors))
Ejemplo n.º 17
0
 def validate(self, document):
     if not self.schema:
         raise Exception(
             "Cannot validate locally the document, you need to pass a schema."
         )
     validation_errors = validate(self.schema, document)
     if validation_errors:
         raise validation_errors[0]
Ejemplo n.º 18
0
async def subscribe(
    schema: GraphQLSchema,
    data: Any,
    *,
    context_value: Optional[Any] = None,
    root_value: Optional[RootValue] = None,
    debug: bool = False,
    logger: Optional[str] = None,
    validation_rules: Optional[ValidationRules] = None,
    error_formatter: ErrorFormatter = format_error,
    **kwargs,
) -> SubscriptionResult:
    try:
        validate_data(data)
        query, variables, operation_name = (
            data["query"],
            data.get("variables"),
            data.get("operationName"),
        )

        document = parse_query(query)

        if callable(validation_rules):
            validation_rules = validation_rules(context_value, document, data)

        validation_errors = validate(schema, document, validation_rules)
        if validation_errors:
            for error_ in validation_errors:  # mypy issue #5080
                log_error(error_, logger)
            return (
                False,
                [error_formatter(error, debug) for error in validation_errors],
            )

        if callable(root_value):
            root_value = root_value(context_value, document)
            if isawaitable(root_value):
                root_value = await root_value

        result = await _subscribe(
            schema,
            document,
            root_value=root_value,
            context_value=context_value,
            variable_values=variables,
            operation_name=operation_name,
            **kwargs,
        )
    except GraphQLError as error:
        log_error(error, logger)
        return False, [error_formatter(error, debug)]
    else:
        if isinstance(result, ExecutionResult):
            errors = cast(List[GraphQLError], result.errors)
            for error_ in errors:  # mypy issue #5080
                log_error(error_, logger)
            return False, [error_formatter(error, debug) for error in errors]
        return True, cast(AsyncGenerator, result)
Ejemplo n.º 19
0
    def build_query(self, query):
        validation_errors = validate(self.schema, parse(query))
        if validation_errors:
            raise GraphQLError(validation_errors)

        def execute(*, context=None, **variables):
            return self.execute_query(query, context, variables=variables)

        return execute
Ejemplo n.º 20
0
def test_query_validation_fails_if_cost_map_contains_non_object_type(schema):
    ast = parse("{ constant }")
    rule = cost_validator(maximum_cost=1, cost_map={"Other": {"name": 1}})
    result = validate(schema, ast, [rule])
    assert result == [
        GraphQLError(
            "The query cost could not be calculated because cost map specifies a type "
            "Other that is defined by the schema, but is not an object type.")
    ]
Ejemplo n.º 21
0
 def rejects_invalid_rules():
     with raises(TypeError) as exc_info:
         # noinspection PyTypeChecker
         assert validate(
             test_schema, parse("query { name }"), rules=[None]  # type: ignore
         )
     assert (
         str(exc_info.value) == "Rules must be specified as a collection"
         " of ASTValidationRule subclasses."
     )
Ejemplo n.º 22
0
def validate_document(
    schema: GraphQLSchema,
    document: DocumentNode,
    validation_rules: Tuple[Type[ASTValidationRule], ...],
) -> List[GraphQLError]:
    return validate(
        schema,
        document,
        validation_rules,
    )
Ejemplo n.º 23
0
def test_query_validation_fails_if_cost_map_contains_undefined_type_field(
        schema):
    ast = parse("{ constant }")
    rule = cost_validator(maximum_cost=1, cost_map={"Query": {"undefined": 1}})
    result = validate(schema, ast, [rule])
    assert result == [
        GraphQLError(
            "The query cost could not be calculated because cost map contains a field "
            "undefined not defined by the Query type.")
    ]
Ejemplo n.º 24
0
 def rejects_invalid_max_errors():
     with raises(TypeError) as exc_info:
         # noinspection PyTypeChecker
         assert validate(
             test_schema, parse("query { name }"), max_errors=2.5  # type: ignore
         )
     assert (
         str(exc_info.value)
         == "The maximum number of errors must be passed as an int."
     )
Ejemplo n.º 25
0
def rename_query(
        ast: DocumentNode,
        renamed_schema_descriptor: RenamedSchemaDescriptor) -> DocumentNode:
    """Translate names of types using reverse_name_map of the input RenamedSchemaDescriptor.

    The direction in which types and fields are renamed is opposite of the process that
    produced the renamed schema descriptor. If a type X was renamed to Y in the schema, then
    any occurrences of type Y in the input query ast will be renamed to X.

    All type names (including ones in type coercions), as well as root vertex fields (fields
    of the query type) will be renamed. No other field names will be renamed.

    Args:
        ast: represents a query
        renamed_schema_descriptor: namedtuple including the attribute reverse_name_map, which maps
                                   the new, renamed names of types to their original names. This
                                   function will revert these renamed types in the query ast back to
                                   their original names

    Returns:
        New AST representing the renamed query

    Raises:
        - GraphQLValidationError if the AST does not have the expected form; in particular,
          if the AST fails GraphQL's builtin validation against the provided schema, if it
          contains Fragments, or if it contains an InlineFragment at the root level
    """
    built_in_validation_errors = validate(renamed_schema_descriptor.schema,
                                          ast)
    if len(built_in_validation_errors) > 0:
        raise GraphQLValidationError(
            "AST does not validate: {}".format(built_in_validation_errors))

    if len(ast.definitions
           ) > 1:  # includes either multiple queries, or fragment definitions
        raise GraphQLValidationError(
            "Only one query may be included, and fragments are not allowed.")

    query_definition = ast.definitions[0]
    if not (isinstance(query_definition, OperationDefinitionNode)
            and query_definition.operation == OperationType.QUERY):
        raise AssertionError(
            f"AST argument for rename_query is not a query. Instead, query definition was of type "
            f"{type(query_definition)}.")

    for selection in query_definition.selection_set.selections:
        if not isinstance(selection, FieldNode):  # possibly an InlineFragment
            raise GraphQLValidationError(
                'Each root selection must be of type "Field", not "{}" as in '
                'selection "{}"'.format(type(selection).__name__, selection))

    visitor = RenameQueryVisitor(renamed_schema_descriptor.reverse_name_map)
    renamed_ast = visit(ast, visitor)

    return renamed_ast
Ejemplo n.º 26
0
    def detects_unknown_fields():
        doc = parse("""
            {
              unknown
            }
            """)

        errors = validate(test_schema, doc)
        assert errors == [{
            "message":
            "Cannot query field 'unknown' on type 'QueryRoot'."
        }]
Ejemplo n.º 27
0
def test_query_validation_fails_if_cost_map_contains_undefined_type(schema):
    ast = parse("{ constant }")
    rule = cost_validator(maximum_cost=1,
                          cost_map={"Undefined": {
                              "constant": 1
                          }})
    result = validate(schema, ast, [rule])
    assert result == [
        GraphQLError(
            "The query cost could not be calculated because cost map specifies a type "
            "Undefined that is not defined by the schema.")
    ]
Ejemplo n.º 28
0
    def detects_bad_scalar_parse():
        doc = """
            query {
              invalidArg(arg: "bad value")
            }
            """

        errors = validate(test_schema, parse(doc))
        assert errors == [{
            'message': 'Expected type Invalid, found "bad value";'
                       ' Invalid scalar is always invalid: bad value',
            'locations': [(3, 31)]}]
Ejemplo n.º 29
0
 def validate(self, document):
     if not self.schema:
         raise Exception(
             "Cannot validate locally the document, you need to pass a schema."
         )
     validation_errors = validate(self.schema, document)
     if validation_errors:
         raise validation_errors[0]
     usages = find_deprecated_usages(self.schema, document)
     for usage in usages:
         message = (f"Query of deprecated grapqhl field in {usage}"
                    "Consider upgrading to newer API version.")
         warnings.warn(message, GraphqlDeprecationWarning)
Ejemplo n.º 30
0
def expect_invalid(schema, rules, query, expected_errors, sort_list=True):
    errors = validate(schema, parse(query), rules)
    assert errors, 'Should not validate'
    for error in expected_errors:
        error['locations'] = [
            {'line': loc.line, 'column': loc.column}
            for loc in error['locations']
        ]
    if sort_list:
        assert sort_lists(list(map(format_error, errors))) == sort_lists(expected_errors)

    else:
        assert list(map(format_error, errors)) == expected_errors
Ejemplo n.º 31
0
def expect_invalid(schema, rules, query, expected_errors, sort_list=True):
    errors = validate(schema, parse(query), rules)
    assert errors, 'Should not validate'
    for error in expected_errors:
        error['locations'] = [{
            'line': loc.line,
            'column': loc.column
        } for loc in error['locations']]
    if sort_list:
        assert sort_lists(list(map(format_error,
                                   errors))) == sort_lists(expected_errors)

    else:
        assert list(map(format_error, errors)) == expected_errors
Ejemplo n.º 32
0
def validation_errors(query):
    source = Source(query, 'StarWars.graphql')
    ast = parse(source)
    return validate(StarWarsSchema, ast)
Ejemplo n.º 33
0
 def validate(self, document):
     if not self.schema:
         raise Exception("Cannot validate locally the document, you need to pass a schema.")
     validation_errors = validate(self.schema, document)
     if validation_errors:
         raise validation_errors[0]
Ejemplo n.º 34
0
def expect_valid(schema, rules, query):
    errors = validate(schema, parse(query), rules)
    assert errors == [], 'Should validate'
Ejemplo n.º 35
0
def _validate_schema_and_ast(schema, ast):
    """Validate the supplied graphql schema and ast.

    This method wraps around graphql-core's validation to enforce a stricter requirement of the
    schema -- all directives supported by the compiler must be declared by the schema, regardless of
    whether each directive is used in the query or not.

    Args:
        schema: GraphQL schema object, created using the GraphQL library
        ast: abstract syntax tree representation of a graphql query

    Returns:
        list containing schema and/or query validation errors
    """
    core_graphql_errors = validate(schema, ast)

    # The following directives appear in the core-graphql library, but are not supported by the
    # graphql compiler.
    unsupported_default_directives = frozenset([
        frozenset([
            'include',
            frozenset(['FIELD', 'FRAGMENT_SPREAD', 'INLINE_FRAGMENT']),
            frozenset(['if'])
        ]),
        frozenset([
            'skip',
            frozenset(['FIELD', 'FRAGMENT_SPREAD', 'INLINE_FRAGMENT']),
            frozenset(['if'])
        ]),
        frozenset([
            'deprecated',
            frozenset(['ENUM_VALUE', 'FIELD_DEFINITION']),
            frozenset(['reason'])
        ])
    ])

    # Directives expected by the graphql compiler.
    expected_directives = {
        frozenset([
            directive.name,
            frozenset(directive.locations),
            frozenset(six.viewkeys(directive.args))
        ])
        for directive in DIRECTIVES
    }

    # Directives provided in the parsed graphql schema.
    actual_directives = {
        frozenset([
            directive.name,
            frozenset(directive.locations),
            frozenset(six.viewkeys(directive.args))
        ])
        for directive in schema.get_directives()
    }

    # Directives missing from the actual directives provided.
    missing_directives = expected_directives - actual_directives
    if missing_directives:
        missing_message = (u'The following directives were missing from the '
                           u'provided schema: {}'.format(missing_directives))
        core_graphql_errors.append(missing_message)

    # Directives that are not specified by the core graphql library. Note that Graphql-core
    # automatically injects default directives into the schema, regardless of whether
    # the schema supports said directives. Hence, while the directives contained in
    # unsupported_default_directives are incompatible with the graphql-compiler, we allow them to
    # be present in the parsed schema string.
    extra_directives = actual_directives - expected_directives - unsupported_default_directives
    if extra_directives:
        extra_message = (u'The following directives were supplied in the given schema, but are not '
                         u'not supported by the GraphQL compiler: {}'.format(extra_directives))
        core_graphql_errors.append(extra_message)

    return core_graphql_errors