diff --git a/clanModules/zerotier/shared.nix b/clanModules/zerotier/shared.nix index 7f5b13d22..daaf779b4 100644 --- a/clanModules/zerotier/shared.nix +++ b/clanModules/zerotier/shared.nix @@ -25,23 +25,27 @@ let ) [ ] moons; in { - options.clan.zerotier = { - excludeHosts = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = [ config.clan.core.machineName ]; - description = "Hosts that should be excluded"; + options.clan.zerotier = + let + inherit (lib.types) listOf str; + in + { + excludeHosts = lib.mkOption { + type = listOf str; + default = [ config.clan.core.machineName ]; + description = "Hosts that should be excluded"; + }; + networkIps = lib.mkOption { + type = listOf str; + default = [ ]; + description = "Extra zerotier network Ips that should be accepted"; + }; + networkIds = lib.mkOption { + type = listOf str; + default = [ ]; + description = "Extra zerotier network Ids that should be accepted"; + }; }; - networkIps = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = [ ]; - description = "Extra zerotier network Ips that should be accepted"; - }; - networkIds = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = [ ]; - description = "Extra zerotier network Ids that should be accepted"; - }; - }; config = { assertions = [ diff --git a/pkgs/clan-app/clan_app/views/webview.py b/pkgs/clan-app/clan_app/views/webview.py index 04ef178ce..654a16f1d 100644 --- a/pkgs/clan-app/clan_app/views/webview.py +++ b/pkgs/clan-app/clan_app/views/webview.py @@ -125,7 +125,11 @@ class WebExecutor(GObject.Object): result = dataclass_to_dict(data.result) # Important: # 2. ensure_ascii = False. non-ASCII characters are correctly handled, instead of being escaped. - serialized = json.dumps(result, indent=4, ensure_ascii=False) + try: + serialized = json.dumps(result, indent=4, ensure_ascii=False) + except TypeError: + log.exception(f"Error serializing result for {data.method_name}") + raise log.debug(f"Result for {data.method_name}: {serialized}") # Use idle_add to queue the response call to js on the main GTK thread diff --git a/pkgs/clan-cli/clan_cli/api/serde.py b/pkgs/clan-cli/clan_cli/api/serde.py index 04c6ae2d5..394f077e2 100644 --- a/pkgs/clan-cli/clan_cli/api/serde.py +++ b/pkgs/clan-cli/clan_cli/api/serde.py @@ -54,6 +54,35 @@ def sanitize_string(s: str) -> str: return s +def is_enum(obj: Any) -> bool: + """ + Safely checks if the object or one of its attributes is an Enum. + """ + # Check if the object itself is an Enum + if isinstance(obj, Enum): + return True + + # Check if the object has an 'enum' attribute and if it's an Enum + enum_attr = getattr(obj, "enum", None) + return isinstance(enum_attr, Enum) + + +def get_enum_value(obj: Any) -> Any: + """ + Safely checks if the object or one of its attributes is an Enum. + """ + # Check if the object itself is an Enum + value = getattr(obj, "value", None) + if value is None and obj.enum: + value = getattr(obj.enum, "value", None) + + if value is None: + error_msg = f"Cannot determine enum value for {obj}" + raise ValueError(error_msg) + + return dataclass_to_dict(value) + + def dataclass_to_dict(obj: Any, *, use_alias: bool = True) -> Any: def _to_dict(obj: Any) -> Any: """ @@ -62,6 +91,8 @@ def dataclass_to_dict(obj: Any, *, use_alias: bool = True) -> Any: It does NOT convert member functions. """ + if is_enum(obj): + return get_enum_value(obj) if is_dataclass(obj): return { # Use either the original name or name diff --git a/pkgs/clan-cli/tests/test_serializers.py b/pkgs/clan-cli/tests/test_serializers.py index 927c71ac7..b265720ca 100644 --- a/pkgs/clan-cli/tests/test_serializers.py +++ b/pkgs/clan-cli/tests/test_serializers.py @@ -122,3 +122,18 @@ def test_filters_null_fields() -> None: assert instance.home == "home" assert instance.work is None assert dataclass_to_dict(instance) == {"home": "home"} + + +def test_custom_enum() -> None: + from enum import Enum + + class CustomEnum(Enum): + FOO = "foo" + BAR = "bar" + + @dataclass + class Foo: + field: CustomEnum + + instance = Foo(field=CustomEnum.FOO) + assert dataclass_to_dict(instance) == {"field": "foo"}