{"record":{"id":"47cb43fc3ac6fe16","repo":"aio-libs/aiohttp","slug":"domain-must-be-str-47cb43","errorCode":null,"errorMessage":"Domain must be str","messagePattern":"Domain must be str","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"aiohttp/web_urldispatcher.py","lineNumber":781,"sourceCode":"    @abc.abstractmethod  # pragma: no branch\n    def canonical(self) -> str:\n        \"\"\"Return a str\"\"\"\n\n\nclass Domain(AbstractRuleMatching):\n    re_part = re.compile(r\"(?!-)[a-z\\d-]{1,63}(?<!-)\")\n\n    def __init__(self, domain: str) -> None:\n        super().__init__()\n        self._domain = self.validation(domain)\n\n    @property\n    def canonical(self) -> str:\n        return self._domain\n\n    def validation(self, domain: str) -> str:\n        if not isinstance(domain, str):\n            raise TypeError(\"Domain must be str\")\n        domain = domain.rstrip(\".\").lower()\n        if not domain:\n            raise ValueError(\"Domain cannot be empty\")\n        elif \"://\" in domain:\n            raise ValueError(\"Scheme not supported\")\n        url = URL(\"http://\" + domain)\n        assert url.raw_host is not None\n        if not all(self.re_part.fullmatch(x) for x in url.raw_host.split(\".\")):\n            raise ValueError(\"Domain not valid\")\n        if url.port == 80:\n            return url.raw_host\n        return f\"{url.raw_host}:{url.port}\"\n\n    async def match(self, request: Request) -> bool:\n        host = request.headers.get(hdrs.HOST)\n        if not host:\n            return False\n        return self.match_domain(host)","sourceCodeStart":763,"sourceCodeEnd":799,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/web_urldispatcher.py#L763-L799","documentation":"DomainValidator (used by DomainMatchRule / the Host matcher) requires its domain argument to be a str. The validation method checks isinstance(domain, str) and raises TypeError if not, because all subsequent processing (rstrip, lower, URL parsing, regex matching) assumes string operations.","triggerScenarios":"Constructing a domain-based matcher with a non-string: e.g. passing bytes (b'example.com'), an integer (port number), None, or a yarl.URL object instead of the host string. This typically happens when building a host-based routing rule (app.router.add_resource with host constraints) or the Domain middleware.","commonSituations":"Passing a URL object where the host string is expected; passing bytes from a low-level parser; configuration loaded from a binary source; off-by-one extraction that grabs the wrong field.","solutions":["Pass the domain as a str, e.g. 'example.com' or 'example.com:8080'.","If you have a URL object, extract url.raw_host (and url.port if non-default) and pass the host string.","Decode bytes to str before passing."],"exampleFix":"// before\nrule = Domain(b'example.com')   # bytes\n// after\nrule = Domain('example.com')    # str","handlingStrategy":"type-guard","validationCode":"def validate_domain(domain):\n    if not isinstance(domain, str):\n        raise TypeError(f'Domain must be str, got {type(domain).__name__}')\n    return domain","typeGuard":"def is_domain_str(domain) -> bool:\n    return isinstance(domain, str)","tryCatchPattern":null,"preventionTips":["Always pass the host as a str, not bytes or a URL object.","Extract url.raw_host from yarl.URL before constructing a Domain rule.","Decode bytes to str at the boundary where low-level data enters."],"tags":["routing","domain","host","type-check"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}