Repository: strawberry-graphql/strawberry

Issue:
Validate schema early
Currently graphql-core doesn't validate the schema until execution time (to allow for partial schema generation). Since strawberry doesn't support partial generation at the moment it would make sense to validate the schema as soon as it's instantiated so that any issues are raised early.

~~As a bonus it would be nice to be able to specify which validators to run.~~ Actually the schema validation is separate to the validate "rules" that I was thinking about here so this is not the right place to tackle that problem.


Discussion:

Resolving patch:
diff --git a/strawberry/schema/execute.py b/strawberry/schema/execute.py
--- a/strawberry/schema/execute.py
+++ b/strawberry/schema/execute.py
@@ -9,7 +9,6 @@
     execute as original_execute,
     parse,
 )
-from graphql.type import validate_schema
 from graphql.validation import validate
 
 from strawberry.extensions import Extension
@@ -42,14 +41,8 @@ async def execute(
     additional_middlewares = additional_middlewares or []
 
     with extensions_runner.request():
-        schema_validation_errors = validate_schema(schema)
-
-        if schema_validation_errors:
-            return ExecutionResult(
-                data=None,
-                errors=schema_validation_errors,
-                extensions=extensions_runner.get_extensions_results(),
-            )
+        # Note: In graphql-core the schema would be validated here but in
+        # Strawberry we are validating it at initialisation time instead
 
         try:
             with extensions_runner.parsing():
@@ -122,14 +115,8 @@ def execute_sync(
     additional_middlewares = additional_middlewares or []
 
     with extensions_runner.request():
-        schema_validation_errors = validate_schema(schema)
-
-        if schema_validation_errors:
-            return ExecutionResult(
-                data=None,
-                errors=schema_validation_errors,
-                extensions=extensions_runner.get_extensions_results(),
-            )
+        # Note: In graphql-core the schema would be validated here but in
+        # Strawberry we are validating it at initialisation time instead
 
         try:
             with extensions_runner.parsing():
diff --git a/strawberry/schema/schema.py b/strawberry/schema/schema.py
--- a/strawberry/schema/schema.py
+++ b/strawberry/schema/schema.py
@@ -1,6 +1,6 @@
 from typing import Any, Dict, List, Optional, Sequence, Type, Union
 
-from graphql import GraphQLSchema, get_introspection_query, parse
+from graphql import GraphQLSchema, get_introspection_query, parse, validate_schema
 from graphql.subscription import subscribe
 from graphql.type.directives import specified_directives
 
@@ -51,6 +51,13 @@ def __init__(
             types=[get_object_type(type, self.type_map) for type in types],
         )
 
+        # Validate schema early because we want developers to know about
+        # possible issues as soon as possible
+        errors = validate_schema(self._schema)
+        if errors:
+            formatted_errors = "\n\n".join(f"❌ {error.message}" for error in errors)
+            raise ValueError(f"Invalid Schema. Errors:\n\n{formatted_errors}")
+
         self.query = self.type_map[query_type.name]
 
     def get_type_by_name(


Test patch:
diff --git a/tests/schema/test_excution_errors.py b/tests/schema/test_excution_errors.py
--- a/tests/schema/test_excution_errors.py
+++ b/tests/schema/test_excution_errors.py
@@ -9,25 +9,6 @@ def get_results(self):
         return {"example": "example"}
 
 
-def test_runs_schema_validation():
-    @strawberry.type
-    class Query:
-        ...
-
-    schema = strawberry.Schema(query=Query)
-
-    query = """
-        query {
-            example
-        }
-    """
-
-    result = schema.execute_sync(query)
-
-    assert len(result.errors) == 1
-    assert result.errors[0].message == "Type Query must define one or more fields."
-
-
 def test_runs_parsing():
     @strawberry.type
     class Query:
@@ -69,26 +50,6 @@ async def name(self) -> str:
     assert e.value.args[0] == "GraphQL execution failed to complete synchronously."
 
 
-@pytest.mark.asyncio
-async def test_runs_schema_validation_async():
-    @strawberry.type
-    class Query:
-        ...
-
-    schema = strawberry.Schema(query=Query)
-
-    query = """
-        query {
-            example
-        }
-    """
-
-    result = await schema.execute(query)
-
-    assert len(result.errors) == 1
-    assert result.errors[0].message == "Type Query must define one or more fields."
-
-
 @pytest.mark.asyncio
 async def test_runs_parsing_async():
     @strawberry.type
diff --git a/tests/schema/test_schema_generation.py b/tests/schema/test_schema_generation.py
--- a/tests/schema/test_schema_generation.py
+++ b/tests/schema/test_schema_generation.py
@@ -50,11 +50,10 @@ class Query:
     } == introspection["__schema"].keys()
 
 
-def test_schema_introspect_fails_on_an_invalid_schema():
+def test_schema_fails_on_an_invalid_schema():
     @strawberry.type
     class Query:
-        ...  # Schema must have at least one field
+        ...  # Type must have at least one field
 
-    schema = strawberry.Schema(query=Query)
-    with pytest.raises(ValueError, match="Invalid Schema. Errors .*"):
-        schema.introspect()
+    with pytest.raises(ValueError, match="Invalid Schema. Errors.*"):
+        strawberry.Schema(query=Query)