]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
manager: improved configuration error reporting
authorVasek Sraier <git@vakabus.cz>
Sun, 20 Feb 2022 22:34:56 +0000 (23:34 +0100)
committerAleš Mrázek <ales.mrazek@nic.cz>
Fri, 8 Apr 2022 14:17:54 +0000 (16:17 +0200)
manager/knot_resolver_manager/datamodel/logging_config.py
manager/knot_resolver_manager/datamodel/types/types.py
manager/knot_resolver_manager/exceptions.py
manager/knot_resolver_manager/utils/modelling.py

index 3e4ce9fd597a6b023958152ef76e88dc6f675ce1..cc405be1fd3821b0e64fdcf31da95795ade74815 100644 (file)
@@ -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)
index ddfe87338317f4943e733ec3beae2b3790545647..0a884b5a40046080fa560a0233af2c2fbafa8baa 100644 (file)
@@ -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 '<ip-address|interface-name>@<port>', got '{source_value}'.", object_path
+                    f"expected '<ip-address|interface-name>@<port>', 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 '<ip-address|interface-name>[@<port>]', got '{parts}'.", object_path)
+                raise SchemaException(f"expected '<ip-address|interface-name>[@<port>]', 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 '<ip-address>@<port>', got '{source_value}'.", object_path)
+                raise SchemaException(f"expected '<ip-address>@<port>', 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 '<ip-address>[@<port>]', got '{parts}'.", object_path)
+                raise SchemaException(f"expected '<ip-address>[@<port>]', 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:
index a57d70c52398b8ed9c2d37fb2e4b30cbfef72339..92c7c28a83d1e356b3863fad75af09ff3055c000 100644 (file)
@@ -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):
index 81acd37ef55148c49e6e53698f3fda4a927b2d12..8d7b1d9401e2a9fecca4e57ea3d314667ea7b064 100644 (file)
@@ -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):