From 11f7efa0904cad6f3d1ca77a8dffb1652d314c7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Tue, 18 Aug 2026 16:20:13 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E5=85=85=20Filled=20=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E7=B1=BB=E5=9E=8B=E5=8F=8D=E5=BA=8F=E5=88=97=E5=8C=96?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增字段级 amount 反序列化校验,拒绝非有限和越界值 保持现有 JSON 形状与 schema 版本,不引入迁移 增加非法填充量回归测试 --- .../src/ui_editor/component/image.rs | 48 ++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/component/image.rs b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/component/image.rs index a037cb357..e22370f9d 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/component/image.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/component/image.rs @@ -6,7 +6,7 @@ use crate::ui_editor::resource::sprite::SpriteAsset; use crate::ui_editor::utils::SpriteAssetId; use nalgebra::Vector2; use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; +use serde::{de::Deserializer, Deserialize, Serialize}; use ts_rs::TS; use typed_floats::tf32::StrictlyPositiveFinite; @@ -80,6 +80,15 @@ fn validate_fill_amount(amount: f32) -> Result<(), ComponentValueError> { Ok(()) } +fn deserialize_fill_amount<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let amount = f32::deserialize(deserializer)?; + validate_fill_amount(amount).map_err(serde::de::Error::custom)?; + Ok(amount) +} + #[derive(Clone, Copy, Debug, Deserialize, JsonSchema, PartialEq, Serialize, TS)] #[ts(export, export_to = concat!(env!("CARGO_MANIFEST_DIR"), "/../src/features/ui-editor/types/"))] pub enum ImageType { @@ -101,6 +110,7 @@ pub enum ImageType { Filled { preserve_aspect: bool, method: FillMethod, + #[serde(deserialize_with = "deserialize_fill_amount")] amount: f32, }, } @@ -225,6 +235,42 @@ impl ImageComponent { } } +#[cfg(test)] +mod tests { + use super::{FillMethod, HorizontalFillOrigin, ImageType}; + + #[test] + fn filled_amount_is_validated_when_deserialized() { + let valid = serde_json::json!({ + "Filled": { + "preserve_aspect": false, + "method": { "Horizontal": "Left" }, + "amount": 0.5 + } + }); + let image_type: ImageType = serde_json::from_value(valid).expect("valid fill amount"); + assert!(matches!( + image_type, + ImageType::Filled { + method: FillMethod::Horizontal(HorizontalFillOrigin::Left), + amount: 0.5, + .. + } + )); + + for amount in ["-0.1", "1.1", "null"] { + let value = serde_json::json!({ + "Filled": { + "preserve_aspect": false, + "method": { "Horizontal": "Left" }, + "amount": serde_json::from_str::(amount).unwrap() + } + }); + assert!(serde_json::from_value::(value).is_err()); + } + } +} + impl Default for ImageComponent { fn default() -> Self { Self::new()