From: John Mulligan Date: Tue, 24 Mar 2026 21:49:29 +0000 (-0400) Subject: mgr/smb: add specific exception for wrong types when dict/list expected X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=48e6fc2c4e32f5c55c15a3496433e8045cab32a2;p=ceph.git mgr/smb: add specific exception for wrong types when dict/list expected 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 --- diff --git a/src/pybind/mgr/smb/resourcelib.py b/src/pybind/mgr/smb/resourcelib.py index d10b26941fa0..239ad138cef9 100644 --- a/src/pybind/mgr/smb/resourcelib.py +++ b/src/pybind/mgr/smb/resourcelib.py @@ -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