]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/smb: add specific exception for wrong types when dict/list expected
authorJohn Mulligan <jmulligan@redhat.com>
Tue, 24 Mar 2026 21:49:29 +0000 (17:49 -0400)
committerJohn Mulligan <jmulligan@redhat.com>
Wed, 25 Mar 2026 21:04:41 +0000 (17:04 -0400)
Add a specific exception to be raised when the resourcelib is unpacking
a type that should be a list or dict and it is not.

Fixes: https://tracker.ceph.com/issues/75723
Signed-off-by: John Mulligan <jmulligan@redhat.com>
src/pybind/mgr/smb/resourcelib.py

index d10b26941fa0e54115bcb89baa36a3ae5f6d50c9..239ad138cef940480005d0204480ec3fff09e143 100644 (file)
@@ -187,6 +187,31 @@ class MissingRequiredFieldError(KeyError, ResourceTypeError):
         return f'data object missing required field: {self.key}'
 
 
+class InvalidObjectTypeFieldError(ResourceTypeError):
+    """Exception raised when an object can not be converted from unstructured
+    data due to having an incorrect type in the unstructured data.
+    """
+
+    def __init__(self, key: str, expected: str = '', found: str = '') -> None:
+        self.key = key
+        self.expected = expected
+        self.found = found
+
+    def __str__(self) -> str:
+        msg = f'invalid type for field: {self.key}'
+        if self.expected:
+            msg += f': expected {self.expected}'
+        if self.found:
+            msg += f', found {self.found}'
+        return msg
+
+    @classmethod
+    def from_value(cls, key: str, value: Any, expected: str = '') -> Self:
+        tname = type(value).__name__
+        tname = 'string' if tname == 'str' else tname
+        return cls(key, expected=expected, found=tname)
+
+
 # ---- Internal Resource Types ----
 
 # Sentinel object for unset/missing value conditions.
@@ -390,25 +415,42 @@ class Resource:
             return _fs(value)
 
         if fld.takes(list):
-            if isinstance(value, str):
-                raise ResourceTypeError(
-                    f'{fld.name} expects a list not a string'
-                )
-            subtype = fld.list_element_type()
-            return [
-                self._object_sub_from_simplified(subtype, v) for v in value
-            ]
+            return self._extract_list_field(fld, value)
         if fld.takes(dict):
-            ktype, vtype = fld.dict_element_types()
-            # keys must be simple types right now so we just
-            # cast it directly
-            return {
-                ktype(k): self._object_sub_from_simplified(vtype, v)
-                for k, v in value.items()
-            }
+            return self._extract_dict_field(fld, value)
 
         return inner_type(value)
 
+    @_xt
+    def _extract_list_field(self, fld: Field, value: Any) -> Any:
+        # reject iterable but insufficiently list-like types
+        if isinstance(value, (str, bytes)):
+            raise InvalidObjectTypeFieldError(
+                fld.name, expected='list', found='string'
+            )
+        if isinstance(value, dict):
+            raise InvalidObjectTypeFieldError(
+                fld.name, expected='list', found='object/mapping'
+            )
+        subtype = fld.list_element_type()
+        return [self._object_sub_from_simplified(subtype, v) for v in value]
+
+    @_xt
+    def _extract_dict_field(self, fld: Field, value: Any) -> Any:
+        try:
+            _items = value.items()
+        except AttributeError:
+            raise InvalidObjectTypeFieldError.from_value(
+                fld.name, value, expected='object/mapping'
+            )
+        ktype, vtype = fld.dict_element_types()
+        # keys must be simple types right now so we just
+        # cast it directly
+        return {
+            ktype(k): self._object_sub_from_simplified(vtype, v)
+            for k, v in _items
+        }
+
     @_xt
     def _object_sub_from_simplified(
         self, subtype: Any, data: Simplified