From: Vasek Sraier Date: Sun, 20 Feb 2022 22:34:56 +0000 (+0100) Subject: manager: improved configuration error reporting X-Git-Tag: v6.0.0a1~40^2~7 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=732d44964bff487fc6c05e2cf6b36fe65b471e01;p=thirdparty%2Fknot-resolver.git manager: improved configuration error reporting --- diff --git a/manager/knot_resolver_manager/datamodel/logging_config.py b/manager/knot_resolver_manager/datamodel/logging_config.py index 3e4ce9fd5..cc405be1f 100644 --- a/manager/knot_resolver_manager/datamodel/logging_config.py +++ b/manager/knot_resolver_manager/datamodel/logging_config.py @@ -1,4 +1,4 @@ -from typing import List, Optional, Union +from typing import List, Optional, Set, Union from typing_extensions import Literal, TypeAlias @@ -106,3 +106,13 @@ class LoggingSchema(SchemaNode): dnssec_bogus: bool = False dnstap: Union[Literal[False], DnstapSchema] = False debugging: DebuggingSchema = DebuggingSchema() + + def _validate(self): + if self.groups is None: + return + + checked: Set[str] = set() + for i, g in enumerate(self.groups): + if g in checked: + raise ValueError(f"duplicate logging group '{g}' on index {i}") + checked.add(g) diff --git a/manager/knot_resolver_manager/datamodel/types/types.py b/manager/knot_resolver_manager/datamodel/types/types.py index ddfe87338..0a884b5a4 100644 --- a/manager/knot_resolver_manager/datamodel/types/types.py +++ b/manager/knot_resolver_manager/datamodel/types/types.py @@ -35,7 +35,7 @@ class PortNumber(IntRangeBase): try: return cls(int(port), object_path) except ValueError as e: - raise SchemaException(f"Invalid port number {port}", object_path) from e + raise SchemaException(f"invalid port number {port}", object_path) from e class SizeUnit(UnitBase): @@ -85,12 +85,12 @@ class InterfacePort(StrBase): self.if_name = InterfaceName(parts[0]) except SchemaException as e2: raise SchemaException( - f"Expected IP address or interface name, got '{parts[0]}'.", object_path + f"expected IP address or interface name, got '{parts[0]}'.", object_path ) from e1 and e2 self.port = PortNumber.from_str(parts[1], object_path) else: raise SchemaException( - f"Expected '@', got '{source_value}'.", object_path + f"expected '@', got '{source_value}'.", object_path ) self._value = source_value else: @@ -118,12 +118,12 @@ class InterfaceOptionalPort(StrBase): self.if_name = InterfaceName(parts[0]) except SchemaException as e2: raise SchemaException( - f"Expected IP address or interface name, got '{parts[0]}'.", object_path + f"expected IP address or interface name, got '{parts[0]}'.", object_path ) from e1 and e2 if len(parts) == 2: self.port = PortNumber.from_str(parts[1], object_path) else: - raise SchemaException(f"Expected '[@]', got '{parts}'.", object_path) + raise SchemaException(f"expected '[@]', got '{parts}'.", object_path) self._value = source_value else: raise SchemaException( @@ -146,9 +146,9 @@ class IPAddressPort(StrBase): try: self.addr = ipaddress.ip_address(parts[0]) except ValueError as e: - raise SchemaException(f"Failed to parse IP address '{parts[0]}'.", object_path) from e + raise SchemaException(f"failed to parse IP address '{parts[0]}'.", object_path) from e else: - raise SchemaException(f"Expected '@', got '{source_value}'.", object_path) + raise SchemaException(f"expected '@', got '{source_value}'.", object_path) self._value = source_value else: raise SchemaException( @@ -170,11 +170,11 @@ class IPAddressOptionalPort(StrBase): try: self.addr = ipaddress.ip_address(parts[0]) except ValueError as e: - raise SchemaException(f"Failed to parse IP address '{parts[0]}'.", object_path) from e + raise SchemaException(f"failed to parse IP address '{parts[0]}'.", object_path) from e if len(parts) == 2: self.port = PortNumber.from_str(parts[1], object_path) else: - raise SchemaException(f"Expected '[@]', got '{parts}'.", object_path) + raise SchemaException(f"expected '[@]', got '{parts}'.", object_path) self._value = source_value else: raise SchemaException( @@ -193,7 +193,7 @@ class IPv4Address(CustomValueType): try: self._value: ipaddress.IPv4Address = ipaddress.IPv4Address(source_value) except ValueError as e: - raise SchemaException("Failed to parse IPv4 address.", object_path) from e + raise SchemaException("failed to parse IPv4 address.", object_path) from e else: raise SchemaException( "Unexpected value for a IPv4 address." @@ -235,7 +235,7 @@ class IPv6Address(CustomValueType): try: self._value: ipaddress.IPv6Address = ipaddress.IPv6Address(source_value) except ValueError as e: - raise SchemaException("Failed to parse IPv6 address.", object_path) from e + raise SchemaException("failed to parse IPv6 address.", object_path) from e else: raise SchemaException( "Unexpected value for a IPv6 address." @@ -280,7 +280,7 @@ class IPNetwork(CustomValueType): try: self._value: Union[ipaddress.IPv4Network, ipaddress.IPv6Network] = ipaddress.ip_network(source_value) except ValueError as e: - raise SchemaException("Failed to parse IP network.", object_path) from e + raise SchemaException("failed to parse IP network.", object_path) from e else: raise SchemaException( "Unexpected value for a network subnet." @@ -316,7 +316,7 @@ class IPv6Network96(CustomValueType): try: self._value: ipaddress.IPv6Network = ipaddress.IPv6Network(source_value) except ValueError as e: - raise SchemaException("Failed to parse IPv6 /96 network.", object_path) from e + raise SchemaException("failed to parse IPv6 /96 network.", object_path) from e if self._value.prefixlen == 128: raise SchemaException( @@ -328,7 +328,7 @@ class IPv6Network96(CustomValueType): if self._value.prefixlen != 96: raise SchemaException( - "Expected IPv6 network address with /96 prefix length." + "expected IPv6 network address with /96 prefix length." f" Got prefix lenght of {self._value.prefixlen}", object_path, ) @@ -373,7 +373,7 @@ class UncheckedPath(CustomValueType): self._value: Path = Path(source_value) else: raise SchemaException( - f"Expected file path in a string, got '{source_value}' with type '{type(source_value)}'.", object_path + f"expected file path in a string, got '{source_value}' with type '{type(source_value)}'.", object_path ) def __str__(self) -> str: diff --git a/manager/knot_resolver_manager/exceptions.py b/manager/knot_resolver_manager/exceptions.py index a57d70c52..92c7c28a8 100644 --- a/manager/knot_resolver_manager/exceptions.py +++ b/manager/knot_resolver_manager/exceptions.py @@ -17,7 +17,7 @@ class TreeException(KresManagerException): return self._tree_path def __str__(self) -> str: - return super().__str__() + f" @ {self.where()}" + return f"configuration field {self.where()}: " + super().__str__() class SchemaException(TreeException): diff --git a/manager/knot_resolver_manager/utils/modelling.py b/manager/knot_resolver_manager/utils/modelling.py index 81acd37ef..8d7b1d940 100644 --- a/manager/knot_resolver_manager/utils/modelling.py +++ b/manager/knot_resolver_manager/utils/modelling.py @@ -204,21 +204,32 @@ def _validated_object_type( if obj is None: return None else: - raise SchemaException(f"Expected None, found '{obj}'.", object_path) + raise SchemaException(f"expected None, found '{obj}'.", object_path) - # Union[*variants] (handles Optional[T] due to the way the typing system works) + # Optional[T] (could be technically handled by Union[*variants], but this way we have better error reporting) + elif is_optional(cls): + inner: Type[Any] = get_optional_inner_type(cls) + if obj is None: + return None + else: + return _validated_object_type(inner, obj, object_path=object_path) + + # Union[*variants] elif is_union(cls): variants = get_generic_type_arguments(cls) + errs: List[SchemaException] = [] for v in variants: try: return _validated_object_type(v, obj, object_path=object_path) - except SchemaException: - pass - raise SchemaException(f"Union {cls} could not be parsed - parsing of all variants failed.", object_path) + except SchemaException as e: + errs.append(e) + + err_string = "\n\t- ".join([str(e) for e in errs]) + raise SchemaException(f"failed to parse union type, all variants failed:\n\t- {err_string}", object_path) # after this, there is no place for a None object elif obj is None: - raise SchemaException(f"Unexpected value 'None' for type {cls}", object_path) + raise SchemaException(f"unexpected value 'None' for type {cls}", object_path) # int elif cls == int: @@ -226,7 +237,7 @@ def _validated_object_type( # except for CustomValueType class instances if is_obj_type(obj, int) or isinstance(obj, CustomValueType): return int(obj) - raise SchemaException(f"Expected int, found {type(obj)}", object_path) + raise SchemaException(f"expected int, found {type(obj)}", object_path) # str elif cls == str: @@ -242,7 +253,7 @@ def _validated_object_type( ) else: raise SchemaException( - f"Expected str (or number that would be cast to string), but found type {type(obj)}", object_path + f"expected str (or number that would be cast to string), but found type {type(obj)}", object_path ) # bool @@ -250,7 +261,7 @@ def _validated_object_type( if is_obj_type(obj, bool): return obj else: - raise SchemaException(f"Expected bool, found {type(obj)}", object_path) + raise SchemaException(f"expected bool, found {type(obj)}", object_path) # float elif cls == float: @@ -265,7 +276,7 @@ def _validated_object_type( if obj in expected: return obj else: - raise SchemaException(f"Literal {cls} is not matched with the value {obj}", object_path) + raise SchemaException(f"'{obj}' does not match any of the expected values {expected}", object_path) # Dict[K,V] elif is_dict(cls): @@ -287,12 +298,12 @@ def _validated_object_type( if isinstance(obj, cls): return obj else: - raise SchemaException(f"Unexpected value '{obj}' for enum '{cls}'", object_path) + raise SchemaException(f"unexpected value '{obj}' for enum '{cls}'", object_path) # List[T] elif is_list(cls): inner_type = get_generic_type_argument(cls) - return [_validated_object_type(inner_type, val, object_path=f"{object_path}[]") for val in obj] + return [_validated_object_type(inner_type, val, object_path=f"{object_path}[{i}]") for i, val in enumerate(obj)] # Tuple[A,B,C,D,...] elif is_tuple(cls): @@ -318,7 +329,7 @@ def _validated_object_type( # because we can construct a DataParser from it if isinstance(obj, (dict, SchemaNode)): return cls(obj, object_path=object_path) # type: ignore - raise SchemaException(f"Expected 'dict' or 'SchemaNode' object, found '{type(obj)}'", object_path) + raise SchemaException(f"expected 'dict' or 'SchemaNode' object, found '{type(obj)}'", object_path) # if the object matches, just pass it through elif inspect.isclass(cls) and isinstance(obj, cls):