feat: 实现 Auth 全局状态机与 401 统一处理机制
- 新增 AuthSessionInvalidated 事件处理 token 失效场景 - ApiInterceptor 新增 authFailureCallback 单飞机制 - AuthBloc 区分 manual logout 与 auto expiry 语义 - 新增 startup recovery fallback 防止启动卡死 feat: 重构 Calendar DayWeek 视图事件布局引擎 - 新增 DayEventLayoutEngine 解耦事件计算与渲染 - 新增 DayTimelineMetrics 统一时间轴常量 - 新增 DayViewScale 支持捏合缩放 feat: 新增 Settings 页面共享 UI 组件 - 新增 BackTitlePageHeader 统一页面 header - 新增 DetailHeaderActionMenu 统一操作菜单 - 新增 DestructiveActionSheet 统一删除确认 - 新增 AppToggleSwitch 统一开关组件 feat: Chat UI Schema 支持导航操作 - 支持 navigation 类型 action 触发内部路由跳转 - 新增路径验证与参数处理 chore: 更新相关测试覆盖 auth 失效路径
This commit is contained in:
@@ -13,9 +13,16 @@ Version: 2.1
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import Any, Literal
|
||||
import re
|
||||
from typing import Any, ClassVar, Literal
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from pydantic import field_validator
|
||||
|
||||
_NAVIGATION_PATH_PATTERN = re.compile(r"^/[A-Za-z0-9/_-]*$")
|
||||
_NAVIGATION_PARAM_KEY_PATTERN = re.compile(r"^[A-Za-z][A-Za-z0-9_]{0,31}$")
|
||||
_MAX_NAVIGATION_PARAMS = 8
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Enums
|
||||
@@ -74,7 +81,7 @@ class UiHintIconSource(str, Enum):
|
||||
|
||||
|
||||
class UiHintBaseModel(BaseModel):
|
||||
model_config = ConfigDict(
|
||||
model_config: ClassVar[ConfigDict] = ConfigDict(
|
||||
extra="forbid",
|
||||
populate_by_name=True,
|
||||
)
|
||||
@@ -90,6 +97,44 @@ class UiHintActionNavigation(UiHintBaseModel):
|
||||
path: str = Field(..., description="Internal route path.")
|
||||
params: dict[str, Any] | None = Field(default=None, description="Route params.")
|
||||
|
||||
@field_validator("path")
|
||||
@classmethod
|
||||
def validate_navigation_path(cls, value: str) -> str:
|
||||
path = value.strip()
|
||||
if not path:
|
||||
raise ValueError("navigation path must not be empty")
|
||||
if len(path) > 256:
|
||||
raise ValueError("navigation path is too long")
|
||||
if path.startswith("//") or "://" in path:
|
||||
raise ValueError("navigation path must be internal")
|
||||
if "?" in path or "#" in path:
|
||||
raise ValueError("navigation path must not contain query or fragment")
|
||||
if ":" in path:
|
||||
raise ValueError("navigation path must be concrete without placeholders")
|
||||
if _NAVIGATION_PATH_PATTERN.fullmatch(path) is None:
|
||||
raise ValueError("navigation path contains unsupported characters")
|
||||
return path
|
||||
|
||||
@field_validator("params")
|
||||
@classmethod
|
||||
def validate_navigation_params(
|
||||
cls, value: dict[str, Any] | None
|
||||
) -> dict[str, Any] | None:
|
||||
if value is None:
|
||||
return None
|
||||
if len(value) > _MAX_NAVIGATION_PARAMS:
|
||||
raise ValueError("navigation params exceed limit")
|
||||
|
||||
normalized: dict[str, Any] = {}
|
||||
for key, param_value in value.items():
|
||||
if _NAVIGATION_PARAM_KEY_PATTERN.fullmatch(key) is None:
|
||||
raise ValueError("navigation param key is invalid")
|
||||
if isinstance(param_value, (str, int, float, bool)):
|
||||
normalized[key] = param_value
|
||||
continue
|
||||
raise ValueError("navigation params must be scalar")
|
||||
return normalized
|
||||
|
||||
|
||||
class UiHintActionUrl(UiHintBaseModel):
|
||||
type: Literal["url"]
|
||||
@@ -203,7 +248,7 @@ class UiHintsPayload(UiHintBaseModel):
|
||||
- 编译器负责转换为完整 UiSchemaRenderer
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(
|
||||
model_config: ClassVar[ConfigDict] = ConfigDict(
|
||||
extra="forbid",
|
||||
populate_by_name=True,
|
||||
json_schema_extra={
|
||||
|
||||
Reference in New Issue
Block a user