Repository: mirumee/ariadne

Issue:
make_federated_schema is unable to parse repeatable directives
Hello, I'm extending our application to be integrated in GraphQL federation and I encountered a weird bug.

We are heavily utilizing directives in our schema, to extend resolving logic of our fields and we used to consume schema with ariadne function `make_executable_schema`. During integration of our service to GraphQL federation we had to switch and load our schema with `make_federated_schema` instead of `make_executable_schema`. A lot of our directives are repeatable, but it seems that  `make_federated_schema` is failing to load those. If I remove repeatable attribute from all directives or I use `make_executable_schema` application loads correctly accepting directive definitions.

I am showing this on simple GraphQL app exposed by uvicorn server:

Schema:
```GraphQL
directive @some_directive repeatable on FIELD_DEFINITION | OBJECT

type Query {
  query: String @some_directive
}
```

main.py:

```python
from ariadne.asgi import GraphQL
from ariadne import load_schema_from_path
from ariadne.contrib.federation import make_federated_schema
from directives import (
    SomeDirective,  # definition is irrelevant and not stated in this issue
)

type_defs = load_schema_from_path("schema_.graphql")

schema = make_federated_schema(
    type_defs,
    directives={
        "some_directive": SomeDirective,
    },
)

app = GraphQL(schema)
```

Actual error:

```
uvicorn main:app --reload --port 5000
INFO:     Will watch for changes in these directories: ['/home/name/PycharmProjects/supergraph/supergraph-integration-poc/pyxis']
INFO:     Uvicorn running on http://127.0.0.1:5000 (Press CTRL+C to quit)
INFO:     Started reloader process [51173] using StatReload
Process SpawnProcess-1:
Traceback (most recent call last):
  File "/usr/lib64/python3.12/multiprocessing/process.py", line 314, in _bootstrap
    self.run()
  File "/usr/lib64/python3.12/multiprocessing/process.py", line 108, in run
    self._target(*self._args, **self._kwargs)
  File "/home/name/PycharmProjects/supergraph/venv/lib64/python3.12/site-packages/uvicorn/_subprocess.py", line 78, in subprocess_started
    target(sockets=sockets)
  File "/home/name/PycharmProjects/supergraph/venv/lib64/python3.12/site-packages/uvicorn/server.py", line 62, in run
    return asyncio.run(self.serve(sockets=sockets))
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/asyncio/runners.py", line 194, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/asyncio/base_events.py", line 687, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "/home/name/PycharmProjects/supergraph/venv/lib64/python3.12/site-packages/uvicorn/server.py", line 69, in serve
    config.load()
  File "/home/name/PycharmProjects/supergraph/venv/lib64/python3.12/site-packages/uvicorn/config.py", line 433, in load
    self.loaded_app = import_from_string(self.app)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/name/PycharmProjects/supergraph/venv/lib64/python3.12/site-packages/uvicorn/importer.py", line 19, in import_from_string
    module = importlib.import_module(module_str)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.12/importlib/__init__.py", line 90, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1331, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 935, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 995, in exec_module
  File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
  File "/home/name/PycharmProjects/supergraph/supergraph-integration-poc/pyxis/main.py", line 10, in <module>
    schema = make_federated_schema(
             ^^^^^^^^^^^^^^^^^^^^^^
  File "/home/name/PycharmProjects/supergraph/venv/lib64/python3.12/site-packages/ariadne/contrib/federation/schema.py", line 68, in make_federated_schema
    type_token = "extend type" if has_query_type(sdl) else "type"
                                  ^^^^^^^^^^^^^^^^^^^
  File "/home/name/PycharmProjects/supergraph/venv/lib64/python3.12/site-packages/ariadne/contrib/federation/schema.py", line 46, in has_query_type
    ast_document = parse(type_defs)
                   ^^^^^^^^^^^^^^^^
  File "/home/name/PycharmProjects/supergraph/venv/lib64/python3.12/site-packages/graphql/language/parser.py", line 113, in parse
    return parser.parse_document()
           ^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/name/PycharmProjects/supergraph/venv/lib64/python3.12/site-packages/graphql/language/parser.py", line 241, in parse_document
    definitions=self.many(TokenKind.SOF, self.parse_definition, TokenKind.EOF),
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/name/PycharmProjects/supergraph/venv/lib64/python3.12/site-packages/graphql/language/parser.py", line 1149, in many
    nodes = [parse_fn()]
             ^^^^^^^^^^
  File "/home/name/PycharmProjects/supergraph/venv/lib64/python3.12/site-packages/graphql/language/parser.py", line 288, in parse_definition
    return getattr(self, f"parse_{method_name}")()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/name/PycharmProjects/supergraph/venv/lib64/python3.12/site-packages/graphql/language/parser.py", line 990, in parse_directive_definition
    self.expect_token(TokenKind.AT)
  File "/home/name/PycharmProjects/supergraph/venv/lib64/python3.12/site-packages/graphql/language/parser.py", line 1045, in expect_token
    raise GraphQLSyntaxError(
graphql.error.syntax_error.GraphQLSyntaxError: Syntax Error: Expected '@', found Name 'repeatable'.

GraphQL request:1:11
1 | directive repeatable on FIELD_DEFINITION | OBJECT
  |           ^
2 |

```

Can somebody please take a look on that if I am doing something wrong or this should be fixed in library?

Hints from issue comments:
@reallistic: @ezopezo I just ran into this as well and made a PR to fix it. Can you give it a try and see if it solves for your need?

Solution patch:
diff --git a/ariadne/contrib/federation/utils.py b/ariadne/contrib/federation/utils.py
index bb6bc1d..a93acc4 100644
--- a/ariadne/contrib/federation/utils.py
+++ b/ariadne/contrib/federation/utils.py
@@ -1,9 +1,14 @@
 # pylint: disable=cell-var-from-loop
 
-import re
 from inspect import isawaitable
-from typing import Any, List
+from typing import Any, List, Tuple, cast
 
+from graphql import (
+    DirectiveDefinitionNode,
+    Node,
+    parse,
+    print_ast,
+)
 from graphql.language import DirectiveNode
 from graphql.type import (
     GraphQLNamedType,
@@ -14,36 +19,6 @@ from graphql.type import (
 )
 
 
-_i_token_delimiter = r"(?:^|[\s]+|$)"
-_i_token_name = "[_A-Za-z][_0-9A-Za-z]*"
-_i_token_arguments = r"\([^)]*\)"
-_i_token_location = "[_A-Za-z][_0-9A-Za-z]*"
-_i_token_description_block_string = r"(?:\"{3}(?:[^\"]{1,}|[\s])\"{3})"
-_i_token_description_single_line = r"(?:\"(?:[^\"\n\r])*?\")"
-
-_r_directive_definition = re.compile(
-    "("
-    f"(?:{_i_token_delimiter}(?:"
-    f"{_i_token_description_block_string}|{_i_token_description_single_line}"
-    "))??"
-    f"{_i_token_delimiter}directive"
-    f"(?:{_i_token_delimiter})?@({_i_token_name})"
-    f"(?:(?:{_i_token_delimiter})?{_i_token_arguments})?"
-    f"{_i_token_delimiter}on"
-    f"{_i_token_delimiter}(?:[|]{_i_token_delimiter})?{_i_token_location}"
-    f"(?:{_i_token_delimiter}[|]{_i_token_delimiter}{_i_token_location})*"
-    ")"
-    f"(?={_i_token_delimiter})",
-)
-
-_r_directive = re.compile(
-    "("
-    f"(?:{_i_token_delimiter})?@({_i_token_name})"
-    f"(?:(?:{_i_token_delimiter})?{_i_token_arguments})?"
-    ")"
-    f"(?={_i_token_delimiter})",
-)
-
 _allowed_directives = [
     "skip",  # Default directive as per specs.
     "include",  # Default directive as per specs.
@@ -66,14 +41,39 @@ _allowed_directives = [
 ]
 
 
+def _purge_directive_nodes(nodes: Tuple[Node, ...]) -> Tuple[Node, ...]:
+    return tuple(
+        node
+        for node in nodes
+        if not isinstance(node, (DirectiveNode, DirectiveDefinitionNode))
+        or node.name.value in _allowed_directives
+    )
+
+
+def _purge_type_directives(definition: Node):
+    # Recursively check every field defined on the Node definition
+    # and remove any directives found.
+    for key in definition.keys:
+        value = getattr(definition, key, None)
+        if isinstance(value, tuple):
+            # Remove directive nodes from the tuple
+            # e.g. doc -> definitions [DirectiveDefinitionNode]
+            next_value = _purge_directive_nodes(cast(Tuple[Node, ...], value))
+            for item in next_value:
+                if isinstance(item, Node):
+                    # Look for directive nodes on sub-nodes
+                    # e.g. doc -> definitions [ObjectTypeDefinitionNode] -> fields -> directives
+                    _purge_type_directives(item)
+            setattr(definition, key, next_value)
+        elif isinstance(value, Node):
+            _purge_type_directives(value)
+
+
 def purge_schema_directives(joined_type_defs: str) -> str:
     """Remove custom schema directives from federation."""
-    joined_type_defs = _r_directive_definition.sub("", joined_type_defs)
-    joined_type_defs = _r_directive.sub(
-        lambda m: m.group(1) if m.group(2) in _allowed_directives else "",
-        joined_type_defs,
-    )
-    return joined_type_defs
+    ast_document = parse(joined_type_defs)
+    _purge_type_directives(ast_document)
+    return print_ast(ast_document)
 
 
 def resolve_entities(_: Any, info: GraphQLResolveInfo, **kwargs) -> Any:
diff --git a/benchmark/results/Linux-CPython-3.11-64bit/0023_c44d706444e8d23e13fb72292a01b8603deb1b58_20231024_134007.json b/benchmark/results/Linux-CPython-3.11-64bit/0052_e4a810143c049591b4a57c1e97da4147f070187b_20241119_163758.json
similarity index 56%
rename from benchmark/results/Linux-CPython-3.11-64bit/0023_c44d706444e8d23e13fb72292a01b8603deb1b58_20231024_134007.json
rename to benchmark/results/Linux-CPython-3.11-64bit/0052_e4a810143c049591b4a57c1e97da4147f070187b_20241119_163758.json
index 96ed7dd..eff35b3 100644
--- a/benchmark/results/Linux-CPython-3.11-64bit/0023_c44d706444e8d23e13fb72292a01b8603deb1b58_20231024_134007.json
+++ b/benchmark/results/Linux-CPython-3.11-64bit/0052_e4a810143c049591b4a57c1e97da4147f070187b_20241119_163758.json
@@ -1,20 +1,20 @@
 {
     "machine_info": {
-        "node": "fv-az477-186",
+        "node": "fv-az665-524",
         "processor": "x86_64",
         "machine": "x86_64",
         "python_compiler": "GCC 11.4.0",
         "python_implementation": "CPython",
-        "python_implementation_version": "3.11.6",
-        "python_version": "3.11.6",
+        "python_implementation_version": "3.11.10",
+        "python_version": "3.11.10",
         "python_build": [
             "main",
-            "Oct  3 2023 04:42:57"
+            "Sep  9 2024 03:20:25"
         ],
-        "release": "6.2.0-1014-azure",
+        "release": "6.5.0-1025-azure",
         "system": "Linux",
         "cpu": {
-            "python_version": "3.11.6.final.0 (64 bit)",
+            "python_version": "3.11.10.final.0 (64 bit)",
             "cpuinfo_version": [
                 9,
                 0,
@@ -23,53 +23,59 @@
             "cpuinfo_version_string": "9.0.0",
             "arch": "X86_64",
             "bits": 64,
-            "count": 2,
+            "count": 4,
             "arch_string_raw": "x86_64",
-            "vendor_id_raw": "GenuineIntel",
-            "brand_raw": "Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz",
-            "hz_advertised_friendly": "2.8000 GHz",
-            "hz_actual_friendly": "2.7934 GHz",
+            "vendor_id_raw": "AuthenticAMD",
+            "brand_raw": "AMD EPYC 7763 64-Core Processor",
+            "hz_advertised_friendly": "3.2436 GHz",
+            "hz_actual_friendly": "3.2436 GHz",
             "hz_advertised": [
-                2800000000,
+                3243559000,
                 0
             ],
             "hz_actual": [
-                2793437000,
+                3243559000,
                 0
             ],
-            "stepping": 6,
-            "model": 106,
-            "family": 6,
+            "stepping": 1,
+            "model": 1,
+            "family": 25,
             "flags": [
+                "3dnowext",
                 "3dnowprefetch",
                 "abm",
                 "adx",
                 "aes",
+                "aperfmperf",
                 "apic",
+                "arat",
                 "avx",
                 "avx2",
-                "avx512bw",
-                "avx512cd",
-                "avx512dq",
-                "avx512f",
-                "avx512vl",
                 "bmi1",
                 "bmi2",
                 "clflush",
                 "clflushopt",
+                "clwb",
+                "clzero",
                 "cmov",
+                "cmp_legacy",
                 "constant_tsc",
                 "cpuid",
+                "cr8_legacy",
                 "cx16",
                 "cx8",
                 "de",
+                "decodeassists",
                 "erms",
+                "extd_apicid",
                 "f16c",
+                "flushbyasid",
                 "fma",
                 "fpu",
                 "fsgsbase",
+                "fsrm",
                 "fxsr",
-                "hle",
+                "fxsr_opt",
                 "ht",
                 "hypervisor",
                 "invpcid",
@@ -78,64 +84,84 @@
                 "lm",
                 "mca",
                 "mce",
-                "md_clear",
+                "misalignsse",
                 "mmx",
+                "mmxext",
                 "movbe",
                 "msr",
                 "mtrr",
+                "nonstop_tsc",
                 "nopl",
+                "npt",
+                "nrip_save",
                 "nx",
+                "osvw",
                 "osxsave",
                 "pae",
                 "pat",
+                "pausefilter",
                 "pcid",
                 "pclmulqdq",
                 "pdpe1gb",
+                "pfthreshold",
                 "pge",
                 "pni",
                 "popcnt",
                 "pse",
                 "pse36",
-                "pti",
+                "rdpid",
+                "rdpru",
                 "rdrand",
                 "rdrnd",
                 "rdseed",
                 "rdtscp",
                 "rep_good",
-                "rtm",
                 "sep",
+                "sha",
+                "sha_ni",
                 "smap",
                 "smep",
-                "ss",
                 "sse",
                 "sse2",
                 "sse4_1",
                 "sse4_2",
+                "sse4a",
                 "ssse3",
+                "svm",
                 "syscall",
+                "topoext",
                 "tsc",
+                "tsc_reliable",
+                "tsc_scale",
+                "umip",
+                "v_vmsave_vmload",
+                "vaes",
+                "vmcb_clean",
                 "vme",
+                "vmmcall",
+                "vpclmulqdq",
+                "xgetbv1",
                 "xsave",
                 "xsavec",
+                "xsaveerptr",
                 "xsaveopt",
-                "xsaves",
-                "xtopology"
+                "xsaves"
             ],
-            "l3_cache_size": 50331648,
-            "l2_cache_size": "2.5 MiB",
-            "l1_data_cache_size": 98304,
+            "l3_cache_size": 524288,
+            "l2_cache_size": 1048576,
+            "l1_data_cache_size": 65536,
             "l1_instruction_cache_size": 65536,
-            "l2_cache_line_size": 256,
+            "l2_cache_line_size": 512,
             "l2_cache_associativity": 6
         }
     },
     "commit_info": {
-        "id": "c44d706444e8d23e13fb72292a01b8603deb1b58",
-        "time": "2023-10-24T13:39:36+00:00",
-        "author_time": "2023-10-24T13:39:36+00:00",
+        "id": "e4a810143c049591b4a57c1e97da4147f070187b",
+        "time": "2024-11-19T16:35:50Z",
+        "author_time": "2024-11-19T16:35:50Z",
         "dirty": false,
         "project": "ariadne",
-        "branch": "master"
+        "branch": "main"
     },
     "benchmarks": [
         {
@@ -154,22 +180,22 @@
                 "warmup": false
             },
             "stats": {
-                "min": 1.0247406389999867,
-                "max": 1.0733194379999986,
-                "mean": 1.041529148399991,
-                "stddev": 0.01877968676529115,
+                "min": 0.8565311299999792,
+                "max": 0.9826667750000126,
+                "mean": 0.9173938015999965,
+                "stddev": 0.05760671431161522,
                 "rounds": 5,
-                "median": 1.0354694059999758,
-                "iqr": 0.01897995149999332,
-                "q1": 1.030561130999999,
-                "q3": 1.0495410824999922,
+                "median": 0.9351804160000086,
+                "iqr": 0.10460864850001172,
+                "q1": 0.8573169214999865,
+                "q3": 0.9619255699999982,
                 "iqr_outliers": 0,
-                "stddev_outliers": 1,
-                "outliers": "1;0",
-                "ld15iqr": 1.0247406389999867,
-                "hd15iqr": 1.0733194379999986,
-                "ops": 0.9601267535682622,
-                "total": 5.207645741999954,
+                "stddev_outliers": 3,
+                "outliers": "3;0",
+                "ld15iqr": 0.8565311299999792,
+                "hd15iqr": 0.9826667750000126,
+                "ops": 1.0900444261296869,
+                "total": 4.586969007999983,
                 "iterations": 1
             }
         },
@@ -189,22 +215,22 @@
                 "warmup": false
             },
             "stats": {
-                "min": 1.0341329629999905,
-                "max": 1.0874000549999892,
-                "mean": 1.071857707800001,
-                "stddev": 0.02195030691004965,
+                "min": 0.8903108210000141,
+                "max": 0.968692271000009,
+                "mean": 0.9276119776000087,
+                "stddev": 0.02965538317641203,
                 "rounds": 5,
-                "median": 1.082494195999999,
-                "iqr": 0.022965520750005908,
-                "q1": 1.0619318637500044,
-                "q3": 1.0848973845000103,
+                "median": 0.9316811909999956,
+                "iqr": 0.040580448749992115,
+                "q1": 0.9048510140000161,
+                "q3": 0.9454314627500082,
                 "iqr_outliers": 0,
-                "stddev_outliers": 1,
-                "outliers": "1;0",
-                "ld15iqr": 1.0341329629999905,
-                "hd15iqr": 1.0874000549999892,
-                "ops": 0.9329596575393485,
-                "total": 5.359288539000005,
+                "stddev_outliers": 2,
+                "outliers": "2;0",
+                "ld15iqr": 0.8903108210000141,
+                "hd15iqr": 0.968692271000009,
+                "ops": 1.0780369638900948,
+                "total": 4.638059888000043,
                 "iterations": 1
             }
         },
@@ -224,22 +250,22 @@
                 "warmup": false
             },
             "stats": {
-                "min": 1.2644616430000042,
-                "max": 1.2989596930000005,
-                "mean": 1.2849725428000056,
-                "stddev": 0.013041603250701368,
+                "min": 1.0642021499999998,
+                "max": 1.1711296569999945,
+                "mean": 1.1194031344000053,
+                "stddev": 0.04730729422197723,
                 "rounds": 5,
-                "median": 1.2865678550000155,
-                "iqr": 0.016072319249992972,
-                "q1": 1.277969078500007,
-                "q3": 1.29404139775,
+                "median": 1.1388521679999997,
+                "iqr": 0.08208248799999751,
+                "q1": 1.0719371182500126,
+                "q3": 1.15401960625001,
                 "iqr_outliers": 0,
                 "stddev_outliers": 2,
                 "outliers": "2;0",
-                "ld15iqr": 1.2644616430000042,
-                "hd15iqr": 1.2989596930000005,
-                "ops": 0.778226745468787,
-                "total": 6.424862714000028,
+                "ld15iqr": 1.0642021499999998,
+                "hd15iqr": 1.1711296569999945,
+                "ops": 0.8933332141650606,
+                "total": 5.597015672000026,
                 "iterations": 1
             }
         },
@@ -259,26 +285,26 @@
                 "warmup": false
             },
             "stats": {
-                "min": 1.1686460880000027,
-                "max": 1.1859206020000101,
-                "mean": 1.1769496561999973,
-                "stddev": 0.006485035949478855,
+                "min": 0.9629680569999834,
+                "max": 1.0084722210000052,
+                "mean": 0.9904317488000004,
+                "stddev": 0.018318557074290753,
                 "rounds": 5,
-                "median": 1.1767772359999924,
-                "iqr": 0.0088983512500036,
-                "q1": 1.1723982937499926,
-                "q3": 1.1812966449999962,
+                "median": 0.9894162610000024,
+                "iqr": 0.026638148750024015,
+                "q1": 0.9798492872499907,
+                "q3": 1.0064874360000147,
                 "iqr_outliers": 0,
-                "stddev_outliers": 2,
-                "outliers": "2;0",
-                "ld15iqr": 1.1686460880000027,
-                "hd15iqr": 1.1859206020000101,
-                "ops": 0.849654014283574,
-                "total": 5.884748280999986,
+                "stddev_outliers": 1,
+                "outliers": "1;0",
+                "ld15iqr": 0.9629680569999834,
+                "hd15iqr": 1.0084722210000052,
+                "ops": 1.009660687080753,
+                "total": 4.952158744000002,
                 "iterations": 1
             }
         }
     ],
-    "datetime": "2023-10-24T13:40:42.633375",
-    "version": "4.0.0"
+    "datetime": "2024-11-19T16:38:28.816597+00:00",
+    "version": "5.1.0"
 }
\ No newline at end of file
diff --git a/benchmark/results/Linux-CPython-3.11-64bit/0053_a76315a7610009ce6150d56ca126a27917a757f4_20241119_163852.json b/benchmark/results/Linux-CPython-3.11-64bit/0053_a76315a7610009ce6150d56ca126a27917a757f4_20241119_163852.json
new file mode 100644
index 0000000..b4e6638
--- /dev/null
+++ b/benchmark/results/Linux-CPython-3.11-64bit/0053_a76315a7610009ce6150d56ca126a27917a757f4_20241119_163852.json
@@ -0,0 +1,310 @@
+{
+    "machine_info": {
+        "node": "fv-az575-736",
+        "processor": "x86_64",
+        "machine": "x86_64",
+        "python_compiler": "GCC 11.4.0",
+        "python_implementation": "CPython",
+        "python_implementation_version": "3.11.10",
+        "python_version": "3.11.10",
+        "python_build": [
+            "main",
+            "Sep  9 2024 03:20:25"
+        ],
+        "release": "6.5.0-1025-azure",
+        "system": "Linux",
+        "cpu": {
+            "python_version": "3.11.10.final.0 (64 bit)",
+            "cpuinfo_version": [
+                9,
+                0,
+                0
+            ],
+            "cpuinfo_version_string": "9.0.0",
+            "arch": "X86_64",
+            "bits": 64,
+            "count": 4,
+            "arch_string_raw": "x86_64",
+            "vendor_id_raw": "AuthenticAMD",
+            "brand_raw": "AMD EPYC 7763 64-Core Processor",
+            "hz_advertised_friendly": "2.6911 GHz",
+            "hz_actual_friendly": "2.6911 GHz",
+            "hz_advertised": [
+                2691149000,
+                0
+            ],
+            "hz_actual": [
+                2691149000,
+                0
+            ],
+            "stepping": 1,
+            "model": 1,
+            "family": 25,
+            "flags": [
+                "3dnowext",
+                "3dnowprefetch",
+                "abm",
+                "adx",
+                "aes",
+                "aperfmperf",
+                "apic",
+                "arat",
+                "avx",
+                "avx2",
+                "bmi1",
+                "bmi2",
+                "clflush",
+                "clflushopt",
+                "clwb",
+                "clzero",
+                "cmov",
+                "cmp_legacy",
+                "constant_tsc",
+                "cpuid",
+                "cr8_legacy",
+                "cx16",
+                "cx8",
+                "de",
+                "decodeassists",
+                "erms",
+                "extd_apicid",
+                "f16c",
+                "flushbyasid",
+                "fma",
+                "fpu",
+                "fsgsbase",
+                "fsrm",
+                "fxsr",
+                "fxsr_opt",
+                "ht",
+                "hypervisor",
+                "invpcid",
+                "invpcid_single",
+                "lahf_lm",
+                "lm",
+                "mca",
+                "mce",
+                "misalignsse",
+                "mmx",
+                "mmxext",
+                "movbe",
+                "msr",
+                "mtrr",
+                "nonstop_tsc",
+                "nopl",
+                "npt",
+                "nrip_save",
+                "nx",
+                "osvw",
+                "osxsave",
+                "pae",
+                "pat",
+                "pausefilter",
+                "pcid",
+                "pclmulqdq",
+                "pdpe1gb",
+                "pfthreshold",
+                "pge",
+                "pni",
+                "popcnt",
+                "pse",
+                "pse36",
+                "rdpid",
+                "rdpru",
+                "rdrand",
+                "rdrnd",
+                "rdseed",
+                "rdtscp",
+                "rep_good",
+                "sep",
+                "sha",
+                "sha_ni",
+                "smap",
+                "smep",
+                "sse",
+                "sse2",
+                "sse4_1",
+                "sse4_2",
+                "sse4a",
+                "ssse3",
+                "svm",
+                "syscall",
+                "topoext",
+                "tsc",
+                "tsc_reliable",
+                "tsc_scale",
+                "umip",
+                "v_vmsave_vmload",
+                "vaes",
+                "vmcb_clean",
+                "vme",
+                "vmmcall",
+                "vpclmulqdq",
+                "xgetbv1",
+                "xsave",
+                "xsavec",
+                "xsaveerptr",
+                "xsaveopt",
+                "xsaves"
+            ],
+            "l3_cache_size": 524288,
+            "l2_cache_size": 1048576,
+            "l1_data_cache_size": 65536,
+            "l1_instruction_cache_size": 65536,
+            "l2_cache_line_size": 512,
+            "l2_cache_associativity": 6
+        }
+    },
+    "commit_info": {
+        "id": "a76315a7610009ce6150d56ca126a27917a757f4",
+        "time": "2024-11-19T16:38:28Z",
+        "author_time": "2024-11-19T16:38:28Z",
+        "dirty": false,
+        "project": "ariadne",
+        "branch": "main"
+    },
+    "benchmarks": [
+        {
+            "group": null,
+            "name": "test_query_without_extensions",
+            "fullname": "benchmark/test_extensions.py::test_query_without_extensions",
+            "params": null,
+            "param": null,
+            "extra_info": {},
+            "options": {
+                "disable_gc": false,
+                "timer": "perf_counter",
+                "min_rounds": 5,
+                "max_time": 1.0,
+                "min_time": 5e-06,
+                "warmup": false
+            },
+            "stats": {
+                "min": 0.8244986739999831,
+                "max": 1.0209091499999943,
+                "mean": 0.8926206813999954,
+                "stddev": 0.07521454683435419,
+                "rounds": 5,
+                "median": 0.8784799190000001,
+                "iqr": 0.06657068150002488,
+                "q1": 0.8495965112499846,
+                "q3": 0.9161671927500095,
+                "iqr_outliers": 1,
+                "stddev_outliers": 1,
+                "outliers": "1;1",
+                "ld15iqr": 0.8244986739999831,
+                "hd15iqr": 1.0209091499999943,
+                "ops": 1.1202966958278289,
+                "total": 4.463103406999977,
+                "iterations": 1
+            }
+        },
+        {
+            "group": null,
+            "name": "test_query_with_noop_extension",
+            "fullname": "benchmark/test_extensions.py::test_query_with_noop_extension",
+            "params": null,
+            "param": null,
+            "extra_info": {},
+            "options": {
+                "disable_gc": false,
+                "timer": "perf_counter",
+                "min_rounds": 5,
+                "max_time": 1.0,
+                "min_time": 5e-06,
+                "warmup": false
+            },
+            "stats": {
+                "min": 0.8618312419999938,
+                "max": 0.9316322840000169,
+                "mean": 0.8996316502000014,
+                "stddev": 0.03286818404657893,
+                "rounds": 5,
+                "median": 0.9087827500000003,
+                "iqr": 0.06218699475000733,
+                "q1": 0.866556433999996,
+                "q3": 0.9287434287500034,
+                "iqr_outliers": 0,
+                "stddev_outliers": 1,
+                "outliers": "1;0",
+                "ld15iqr": 0.8618312419999938,
+                "hd15iqr": 0.9316322840000169,
+                "ops": 1.1115660501469522,
+                "total": 4.498158251000007,
+                "iterations": 1
+            }
+        },
+        {
+            "group": null,
+            "name": "test_query_with_open_telemetry_extension",
+            "fullname": "benchmark/test_extensions.py::test_query_with_open_telemetry_extension",
+            "params": null,
+            "param": null,
+            "extra_info": {},
+            "options": {
+                "disable_gc": false,
+                "timer": "perf_counter",
+                "min_rounds": 5,
+                "max_time": 1.0,
+                "min_time": 5e-06,
+                "warmup": false
+            },
+            "stats": {
+                "min": 1.021531667000005,
+                "max": 1.1193661270000064,
+                "mean": 1.0682239863999996,
+                "stddev": 0.043194687255017156,
+                "rounds": 5,
+                "median": 1.08271800899999,
+                "iqr": 0.0750813537499937,
+                "q1": 1.0241355957500033,
+                "q3": 1.099216949499997,
+                "iqr_outliers": 0,
+                "stddev_outliers": 3,
+                "outliers": "3;0",
+                "ld15iqr": 1.021531667000005,
+                "hd15iqr": 1.1193661270000064,
+                "ops": 0.9361332573799247,
+                "total": 5.341119931999998,
+                "iterations": 1
+            }
+        },
+        {
+            "group": null,
+            "name": "test_query_with_open_tracing_extension",
+            "fullname": "benchmark/test_extensions.py::test_query_with_open_tracing_extension",
+            "params": null,
+            "param": null,
+            "extra_info": {},
+            "options": {
+                "disable_gc": false,
+                "timer": "perf_counter",
+                "min_rounds": 5,
+                "max_time": 1.0,
+                "min_time": 5e-06,
+                "warmup": false
+            },
+            "stats": {
+                "min": 0.9516042349999907,
+                "max": 1.0536222220000013,
+                "mean": 0.9899438103999956,
+                "stddev": 0.04549619491812447,
+                "rounds": 5,
+                "median": 0.9677712219999819,
+                "iqr": 0.07590668000000989,
+                "q1": 0.9539704819999955,
+                "q3": 1.0298771620000053,
+                "iqr_outliers": 0,
+                "stddev_outliers": 1,
+                "outliers": "1;0",
+                "ld15iqr": 0.9516042349999907,
+                "hd15iqr": 1.0536222220000013,
+                "ops": 1.010158343831597,
+                "total": 4.949719051999978,
+                "iterations": 1
+            }
+        }
+    ],
+    "datetime": "2024-11-19T16:39:21.895829+00:00",
+    "version": "5.1.0"
+}
\ No newline at end of file
diff --git a/benchmark/results/Linux-CPython-3.11-64bit/0022_42074f0e3615fba4f8cec46d1ffae2e91a713c9c_20231024_132701.json b/benchmark/results/Linux-CPython-3.12-64bit/0026_28d6d4bd63e4d366a685ffd3f60ad5d7689d2dba_20241119_163950.json
similarity index 56%
rename from benchmark/results/Linux-CPython-3.11-64bit/0022_42074f0e3615fba4f8cec46d1ffae2e91a713c9c_20231024_132701.json
rename to benchmark/results/Linux-CPython-3.12-64bit/0026_28d6d4bd63e4d366a685ffd3f60ad5d7689d2dba_20241119_163950.json
index 9cbe4ed..bb8a643 100644
--- a/benchmark/results/Linux-CPython-3.11-64bit/0022_42074f0e3615fba4f8cec46d1ffae2e91a713c9c_20231024_132701.json
+++ b/benchmark/results/Linux-CPython-3.12-64bit/0026_28d6d4bd63e4d366a685ffd3f60ad5d7689d2dba_20241119_163950.json
@@ -1,20 +1,20 @@
 {
     "machine_info": {
-        "node": "fv-az395-589",
+        "node": "fv-az693-321",
         "processor": "x86_64",
         "machine": "x86_64",
         "python_compiler": "GCC 11.4.0",
         "python_implementation": "CPython",
-        "python_implementation_version": "3.11.6",
-        "python_version": "3.11.6",
+        "python_implementation_version": "3.12.7",
+        "python_version": "3.12.7",
         "python_build": [
             "main",
-            "Oct  3 2023 04:42:57"
+            "Oct  1 2024 15:17:55"
         ],
-        "release": "6.2.0-1014-azure",
+        "release": "6.5.0-1025-azure",
         "system": "Linux",
         "cpu": {
-            "python_version": "3.11.6.final.0 (64 bit)",
+            "python_version": "3.12.7.final.0 (64 bit)",
             "cpuinfo_version": [
                 9,
                 0,
@@ -23,53 +23,59 @@
             "cpuinfo_version_string": "9.0.0",
             "arch": "X86_64",
             "bits": 64,
-            "count": 2,
+            "count": 4,
             "arch_string_raw": "x86_64",
-            "vendor_id_raw": "GenuineIntel",
-            "brand_raw": "Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz",
-            "hz_advertised_friendly": "2.8000 GHz",
-            "hz_actual_friendly": "2.7934 GHz",
+            "vendor_id_raw": "AuthenticAMD",
+            "brand_raw": "AMD EPYC 7763 64-Core Processor",
+            "hz_advertised_friendly": "3.2289 GHz",
+            "hz_actual_friendly": "3.2289 GHz",
             "hz_advertised": [
-                2800000000,
+                3228908000,
                 0
             ],
             "hz_actual": [
-                2793437000,
+                3228908000,
                 0
             ],
-            "stepping": 6,
-            "model": 106,
-            "family": 6,
+            "stepping": 1,
+            "model": 1,
+            "family": 25,
             "flags": [
+                "3dnowext",
                 "3dnowprefetch",
                 "abm",
                 "adx",
                 "aes",
+                "aperfmperf",
                 "apic",
+                "arat",
                 "avx",
                 "avx2",
-                "avx512bw",
-                "avx512cd",
-                "avx512dq",
-                "avx512f",
-                "avx512vl",
                 "bmi1",
                 "bmi2",
                 "clflush",
                 "clflushopt",
+                "clwb",
+                "clzero",
                 "cmov",
+                "cmp_legacy",
                 "constant_tsc",
                 "cpuid",
+                "cr8_legacy",
                 "cx16",
                 "cx8",
                 "de",
+                "decodeassists",
                 "erms",
+                "extd_apicid",
                 "f16c",
+                "flushbyasid",
                 "fma",
                 "fpu",
                 "fsgsbase",
+                "fsrm",
                 "fxsr",
-                "hle",
+                "fxsr_opt",
                 "ht",
                 "hypervisor",
                 "invpcid",
@@ -78,64 +84,84 @@
                 "lm",
                 "mca",
                 "mce",
-                "md_clear",
+                "misalignsse",
                 "mmx",
+                "mmxext",
                 "movbe",
                 "msr",
                 "mtrr",
+                "nonstop_tsc",
                 "nopl",
+                "npt",
+                "nrip_save",
                 "nx",
+                "osvw",
                 "osxsave",
                 "pae",
                 "pat",
+                "pausefilter",
                 "pcid",
                 "pclmulqdq",
                 "pdpe1gb",
+                "pfthreshold",
                 "pge",
                 "pni",
                 "popcnt",
                 "pse",
                 "pse36",
-                "pti",
+                "rdpid",
+                "rdpru",
                 "rdrand",
                 "rdrnd",
                 "rdseed",
                 "rdtscp",
                 "rep_good",
-                "rtm",
                 "sep",
+                "sha",
+                "sha_ni",
                 "smap",
                 "smep",
-                "ss",
                 "sse",
                 "sse2",
                 "sse4_1",
                 "sse4_2",
+                "sse4a",
                 "ssse3",
+                "svm",
                 "syscall",
+                "topoext",
                 "tsc",
+                "tsc_reliable",
+                "tsc_scale",
+                "umip",
+                "v_vmsave_vmload",
+                "vaes",
+                "vmcb_clean",
                 "vme",
+                "vmmcall",
+                "vpclmulqdq",
+                "xgetbv1",
                 "xsave",
                 "xsavec",
+                "xsaveerptr",
                 "xsaveopt",
-                "xsaves",
-                "xtopology"
+                "xsaves"
             ],
-            "l3_cache_size": 50331648,
-            "l2_cache_size": "2.5 MiB",
-            "l1_data_cache_size": 98304,
+            "l3_cache_size": 524288,
+            "l2_cache_size": 1048576,
+            "l1_data_cache_size": 65536,
             "l1_instruction_cache_size": 65536,
-            "l2_cache_line_size": 256,
+            "l2_cache_line_size": 512,
             "l2_cache_associativity": 6
         }
     },
     "commit_info": {
-        "id": "42074f0e3615fba4f8cec46d1ffae2e91a713c9c",
-        "time": "2023-10-24T13:26:26+00:00",
-        "author_time": "2023-10-24T13:26:26+00:00",
+        "id": "28d6d4bd63e4d366a685ffd3f60ad5d7689d2dba",
+        "time": "2024-11-19T16:39:22Z",
+        "author_time": "2024-11-19T16:39:22Z",
         "dirty": false,
         "project": "ariadne",
-        "branch": "master"
+        "branch": "main"
     },
     "benchmarks": [
         {
@@ -154,22 +180,22 @@
                 "warmup": false
             },
             "stats": {
-                "min": 1.0454940950000093,
-                "max": 1.1036979910000184,
-                "mean": 1.071212337600008,
-                "stddev": 0.022626564087262716,
+                "min": 0.9730524600000194,
+                "max": 1.0778625909999846,
+                "mean": 1.0209843120000073,
+                "stddev": 0.038960955167050164,
                 "rounds": 5,
-                "median": 1.0638262260000033,
-                "iqr": 0.03218327750000327,
-                "q1": 1.056198638000005,
-                "q3": 1.0883819155000083,
+                "median": 1.0091272889999914,
+                "iqr": 0.047266674250010965,
+                "q1": 0.9995607517500105,
+                "q3": 1.0468274260000214,
                 "iqr_outliers": 0,
                 "stddev_outliers": 2,
                 "outliers": "2;0",
-                "ld15iqr": 1.0454940950000093,
-                "hd15iqr": 1.1036979910000184,
-                "ops": 0.9335217350468954,
-                "total": 5.35606168800004,
+                "ld15iqr": 0.9730524600000194,
+                "hd15iqr": 1.0778625909999846,
+                "ops": 0.9794469790050925,
+                "total": 5.104921560000037,
                 "iterations": 1
             }
         },
@@ -189,22 +215,22 @@
                 "warmup": false
             },
             "stats": {
-                "min": 1.0531387300000006,
-                "max": 1.1221798300000216,
-                "mean": 1.1005271586000105,
-                "stddev": 0.028236539272916417,
+                "min": 1.041115029000025,
+                "max": 1.1344830200000047,
+                "mean": 1.0807006030000026,
+                "stddev": 0.04019174197338685,
                 "rounds": 5,
-                "median": 1.1026557010000033,
-                "iqr": 0.031939071000017805,
-                "q1": 1.0901933590000041,
-                "q3": 1.122132430000022,
+                "median": 1.077855618000001,
+                "iqr": 0.06993157474994405,
+                "q1": 1.0432524742500249,
+                "q3": 1.113184048999969,
                 "iqr_outliers": 0,
                 "stddev_outliers": 1,
                 "outliers": "1;0",
-                "ld15iqr": 1.0531387300000006,
-                "hd15iqr": 1.1221798300000216,
-                "ops": 0.9086554495139475,
-                "total": 5.502635793000053,
+                "ld15iqr": 1.041115029000025,
+                "hd15iqr": 1.1344830200000047,
+                "ops": 0.9253256611720403,
+                "total": 5.4035030150000125,
                 "iterations": 1
             }
         },
@@ -224,22 +250,22 @@
                 "warmup": false
             },
             "stats": {
-                "min": 1.2843037929999923,
-                "max": 1.3108633270000212,
-                "mean": 1.3009426740000039,
-                "stddev": 0.010993825996397394,
+                "min": 1.2446947700000237,
+                "max": 1.277398101000017,
+                "mean": 1.2600915836000126,
+                "stddev": 0.013593212481409793,
                 "rounds": 5,
-                "median": 1.3043286179999996,
-                "iqr": 0.01676729699999413,
-                "q1": 1.2929688535000068,
-                "q3": 1.309736150500001,
+                "median": 1.2646587550000277,
+                "iqr": 0.021621150249998777,
+                "q1": 1.2470908932500038,
+                "q3": 1.2687120435000026,
                 "iqr_outliers": 0,
-                "stddev_outliers": 1,
-                "outliers": "1;0",
-                "ld15iqr": 1.2843037929999923,
-                "hd15iqr": 1.3108633270000212,
-                "ops": 0.7686733781476347,
-                "total": 6.504713370000019,
+                "stddev_outliers": 2,
+                "outliers": "2;0",
+                "ld15iqr": 1.2446947700000237,
+                "hd15iqr": 1.277398101000017,
+                "ops": 0.7935931110205933,
+                "total": 6.300457918000063,
                 "iterations": 1
             }
         },
@@ -259,26 +285,26 @@
                 "warmup": false
             },
             "stats": {
-                "min": 1.1754369140000165,
-                "max": 1.207919406000002,
-                "mean": 1.1888751612000021,
-                "stddev": 0.012841606999628762,
+                "min": 1.083641924999995,
+                "max": 1.1167823360000284,
+                "mean": 1.10227213820001,
+                "stddev": 0.012241865025813944,
                 "rounds": 5,
-                "median": 1.1863044269999818,
-                "iqr": 0.0189965357499986,
-                "q1": 1.178939419250007,
-                "q3": 1.1979359550000055,
+                "median": 1.104337982000004,
+                "iqr": 0.014599802750026925,
+                "q1": 1.0952275492499979,
+                "q3": 1.1098273520000248,
                 "iqr_outliers": 0,
                 "stddev_outliers": 2,
                 "outliers": "2;0",
-                "ld15iqr": 1.1754369140000165,
-                "hd15iqr": 1.207919406000002,
-                "ops": 0.8411312075782967,
-                "total": 5.944375806000011,
+                "ld15iqr": 1.083641924999995,
+                "hd15iqr": 1.1167823360000284,
+                "ops": 0.9072169796770709,
+                "total": 5.51136069100005,
                 "iterations": 1
             }
         }
     ],
-    "datetime": "2023-10-24T13:27:36.925230",
-    "version": "4.0.0"
+    "datetime": "2024-11-19T16:40:24.960069+00:00",
+    "version": "5.1.0"
 }
\ No newline at end of file


Test patch:
diff --git a/tests/federation/test_utils.py b/tests/federation/test_utils.py
index d6ef51e..9fccdab 100644
--- a/tests/federation/test_utils.py
+++ b/tests/federation/test_utils.py
@@ -63,6 +63,8 @@ def test_purge_directives_remove_custom_directives():
 
         directive @another on FIELD
 
+        directive @plural repeatable on FIELD
+
         type Query {
             field1: String @custom
             field2: String @other
@@ -107,6 +109,10 @@ def test_purge_directives_remove_custom_directives_with_single_line_description(
         "Any Description"
         directive @custom on FIELD
         
+        type Entity {
+            field: String @custom
+        }
+
         type Query {
             rootField: String @custom
         }
@@ -114,6 +120,10 @@ def test_purge_directives_remove_custom_directives_with_single_line_description(
 
     assert sic(purge_schema_directives(type_defs)) == sic(
         """
+            type Entity {
+                field: String
+            }
+
             type Query {
                 rootField: String
             }
@@ -127,6 +137,58 @@ def test_purge_directives_without_leading_whitespace():
     assert sic(purge_schema_directives(type_defs)) == ""
 
 
+def test_purge_directives_remove_custom_directives_from_interfaces():
+    type_defs = """
+        directive @custom on INTERFACE
+
+        interface EntityInterface @custom {
+            field: String
+        }
+
+        type Entity implements EntityInterface {
+            field: String
+        }
+
+        type Query {
+            rootField: Entity
+        }
+    """
+
+    assert sic(purge_schema_directives(type_defs)) == sic(
+        """
+        interface EntityInterface {
+            field: String
+        }
+
+        type Entity implements EntityInterface {
+            field: String
+        }
+
+        type Query {
+            rootField: Entity
+        }
+        """
+    )
+
+
+def test_purge_directives_remove_custom_directive_with_arguments():
+    type_defs = """
+        directive @custom(arg: String) on FIELD
+
+        type Query {
+            rootField: String @custom(arg: "value")
+        }
+    """
+
+    assert sic(purge_schema_directives(type_defs)) == sic(
+        """
+            type Query {
+                rootField: String
+            }
+        """
+    )
+
+
 def test_get_entity_types_with_key_directive():
     type_defs = """
         type Query {