Add generic endpoint list parsing

This commit is contained in:
Lyncoln
2026-09-19 16:04:03 -04:00
parent e32260fc50
commit b0af5349af
2 changed files with 124 additions and 0 deletions
+75
View File
@@ -1,9 +1,84 @@
import os import os
from dataclasses import dataclass from dataclasses import dataclass
from urllib.parse import urlsplit
from .adapters import Endpoint
from .normalizer import normalize_hostname, normalize_ipv4 from .normalizer import normalize_hostname, normalize_ipv4
def parse_endpoint_list(
plugin: str,
value: str,
default_port: int,
) -> tuple[Endpoint, ...]:
if not 1 <= default_port <= 65535:
raise RuntimeError(
"Endpoint default port must be between 1 and 65535"
)
raw_addresses = value.split(",")
if any(not item.strip() for item in raw_addresses):
raise RuntimeError(
f"{plugin.upper()} endpoint list contains an empty address"
)
endpoints: list[Endpoint] = []
identities: set[str] = set()
for raw_address in raw_addresses:
address = raw_address.strip().rstrip("/")
if "://" in address:
raise RuntimeError(
f"{plugin.upper()} endpoint {raw_address!r} must not "
"include a URL scheme"
)
if any(character.isspace() for character in address):
raise RuntimeError(
f"{plugin.upper()} endpoint {raw_address!r} "
"contains whitespace"
)
parsed = urlsplit(f"//{address}")
if (
not parsed.hostname
or parsed.username
or parsed.password
or parsed.path
or parsed.query
or parsed.fragment
):
raise RuntimeError(
f"Invalid {plugin.upper()} endpoint address: "
f"{raw_address!r}"
)
try:
port = parsed.port or default_port
except ValueError as exc:
raise RuntimeError(
f"Invalid {plugin.upper()} endpoint port: "
f"{raw_address!r}"
) from exc
host = parsed.hostname.lower()
normalized_address = (
f"[{host}]:{port}" if ":" in host else f"{host}:{port}"
)
endpoint = Endpoint(plugin, normalized_address)
if endpoint.identity in identities:
raise RuntimeError(
f"Duplicate {plugin.upper()} endpoint: {endpoint.address}"
)
identities.add(endpoint.identity)
endpoints.append(endpoint)
return tuple(endpoints)
def env_bool(name: str, default: bool) -> bool: def env_bool(name: str, default: bool) -> bool:
value = os.getenv(name) value = os.getenv(name)
if value is None: if value is None:
+49
View File
@@ -0,0 +1,49 @@
import unittest
from dns_sync.config import parse_endpoint_list
class EndpointParsingTests(unittest.TestCase):
def test_comma_separated_addresses_use_default_port(self):
endpoints = parse_endpoint_list(
"npm",
"192.168.1.3, 192.168.1.4:8081,192.168.1.5",
81,
)
self.assertEqual(
tuple(endpoint.identity for endpoint in endpoints),
(
"npm://192.168.1.3:81",
"npm://192.168.1.4:8081",
"npm://192.168.1.5:81",
),
)
def test_duplicate_is_rejected_after_normalization(self):
with self.assertRaisesRegex(RuntimeError, "Duplicate NPM"):
parse_endpoint_list(
"npm",
"192.168.1.3,192.168.1.3:81",
81,
)
def test_empty_entry_is_rejected(self):
with self.assertRaisesRegex(RuntimeError, "empty address"):
parse_endpoint_list(
"npm",
"192.168.1.3,,192.168.1.5",
81,
)
def test_url_scheme_is_rejected(self):
with self.assertRaisesRegex(RuntimeError, "must not include"):
parse_endpoint_list(
"npm",
"http://192.168.1.3:81",
81,
)
if __name__ == "__main__":
unittest.main()