""" Case conversion utilities for API payloads. """ import re from typing import Any, Dict, List _FIRST_CAP_RE = re.compile(r"(.)([A-Z][a-z]+)") _ALL_CAP_RE = re.compile(r"([a-z0-9])([A-Z])") _SPECIAL_ALIASES = { "avatar_url": "avatar", "avatarUrl": "avatar", } def to_snake(name: str) -> str: """Convert camelCase/PascalCase to snake_case.""" if not isinstance(name, str) or not name: return name if name.lower() == name: return name s1 = _FIRST_CAP_RE.sub(r"\1_\2", name) s2 = _ALL_CAP_RE.sub(r"\1_\2", s1) return s2.lower() def to_camel(name: str) -> str: """Convert snake_case to camelCase.""" if not isinstance(name, str) or not name: return name if "_" not in name: return name parts = name.split("_") if not parts: return name first = parts[0] rest = "".join(part.capitalize() if part else "_" for part in parts[1:]) return first + rest def convert_keys_to_snake(data: Any) -> Any: """Recursively convert dict keys to snake_case.""" if isinstance(data, list): return [convert_keys_to_snake(item) for item in data] if isinstance(data, dict): converted: Dict[Any, Any] = {} for key, value in data.items(): new_key = to_snake(key) if isinstance(key, str) else key converted[new_key] = convert_keys_to_snake(value) return converted return data def add_camelcase_aliases(data: Any) -> Any: """Recursively add camelCase aliases for snake_case keys.""" if isinstance(data, list): return [add_camelcase_aliases(item) for item in data] if isinstance(data, dict): result: Dict[Any, Any] = {} for key, value in data.items(): result[key] = add_camelcase_aliases(value) # Add camelCase aliases without overriding existing keys for key, value in list(result.items()): if isinstance(key, str): camel_key = to_camel(key) if camel_key != key and camel_key not in result: result[camel_key] = value alias_key = _SPECIAL_ALIASES.get(key) if alias_key and alias_key not in result: result[alias_key] = value return result return data