Commit a763ef605d for bind
commit a763ef605d804ae8f88dad640739d0621094abd8
Author: Štěpán Balážik <stepan@isc.org>
Date: Tue Sep 8 19:34:19 2026 +0200
Find a leaf matcher inside a combination
A handler built on `Qname(...) & Qtype(...)` has no way to read the
names or types it was declared with back, short of repeating them.
of() finds the one matcher of a given class in a combination, typed
as that class.
Assisted-by: Claude:claude-fable-5
diff --git a/bin/tests/system/isctest/asyncserver/matchers.py b/bin/tests/system/isctest/asyncserver/matchers.py
index c9a62f8983..6e9c597a69 100644
--- a/bin/tests/system/isctest/asyncserver/matchers.py
+++ b/bin/tests/system/isctest/asyncserver/matchers.py
@@ -9,6 +9,9 @@
# See the COPYRIGHT file distributed with this work for additional
# information regarding copyright ownership.
+from collections.abc import Iterator
+from typing import TypeVar
+
import abc
import dns.name
@@ -16,6 +19,8 @@ import dns.rdatatype
from .context import QueryContext
+M = TypeVar("M", bound="Matcher")
+
class Matcher(abc.ABC):
"""
@@ -41,6 +46,22 @@ class Matcher(abc.ABC):
def __invert__(self) -> "Matcher":
return Not(self)
+ def of(self, cls: type[M]) -> M:
+ """
+ The one matcher of class `cls` this matcher is built from, for a
+ handler to read what it was declared with: `matcher.of(Qname).qnames`.
+ """
+ found = [m for m in self.leaves() if isinstance(m, cls)]
+ assert len(found) == 1, f"{self} has {len(found)} {cls.__name__} matchers"
+ return found[0]
+
+ def leaves(self) -> Iterator["Matcher"]:
+ """
+ The matchers this one is built from; a matcher built from nothing
+ yields itself.
+ """
+ yield self
+
def __str__(self) -> str:
return f"{self.__class__.__name__}()"
@@ -55,6 +76,10 @@ class _Combinator(Matcher):
def __init__(self, *matchers: Matcher) -> None:
self._matchers = matchers
+ def leaves(self) -> Iterator[Matcher]:
+ for matcher in self._matchers:
+ yield from matcher.leaves()
+
def __str__(self) -> str:
return f"({self._SEPARATOR.join(str(m) for m in self._matchers)})"
@@ -95,6 +120,9 @@ class Not(Matcher):
def __init__(self, matcher: Matcher) -> None:
self._matcher = matcher
+ def leaves(self) -> Iterator[Matcher]:
+ yield from self._matcher.leaves()
+
def match(self, qctx: QueryContext) -> bool:
return not self._matcher.match(qctx)