From 92085dfdd57bc4f66b3b09db0cfcbf8f5b3545ad Mon Sep 17 00:00:00 2001 From: Linghong Date: Fri, 7 Aug 2026 08:08:34 +0000 Subject: [PATCH] =?UTF-8?q?=E9=9F=B3=E6=95=88=E7=94=9F=E6=88=90=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E6=8B=92=E7=BB=9D=E6=9C=AA=E7=9F=A5=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 给 EditorSoundEffectGenerateRequest 补 deny_unknown_fields,与 OpenAPI 的 additionalProperties false 对齐。 新增 loopEnabled 等未知字段必须返回错误而非静默丢弃的契约测试。 --- .../crates/shared-contracts/src/assets.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/server-rs/crates/shared-contracts/src/assets.rs b/server-rs/crates/shared-contracts/src/assets.rs index c1dd65e71..f5345a67e 100644 --- a/server-rs/crates/shared-contracts/src/assets.rs +++ b/server-rs/crates/shared-contracts/src/assets.rs @@ -659,8 +659,11 @@ impl<'de> Deserialize<'de> for EditorSoundEffectGenerationMetadataV2 { } } +/// OpenAPI 对该请求体声明了 `additionalProperties: false`,实现必须同样拒绝未知字段: +/// 否则 `loopEnabled` 这类拼错的字段会被静默丢弃,按 `loop=false` 入队并计费, +/// 客户端拿不到 400,只能从生成结果反推自己写错了参数。 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase")] +#[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct EditorSoundEffectGenerateRequest { pub prompt: String, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -1613,6 +1616,20 @@ mod tests { assert!(!parsed.loop_enabled); } + // OpenAPI 声明 additionalProperties: false,实现必须拒绝而不是静默丢弃。 + // loopEnabled 是最容易写错的那个:丢弃后会按 loop=false 入队并照常计费。 + for unknown_field_payload in [ + json!({ "prompt": "按钮确认短促音", "loopEnabled": true }), + json!({ "prompt": "按钮确认短促音", "tempo": 120 }), + json!({ "prompt": "按钮确认短促音", "priceMudPoints": 0 }), + ] { + assert!( + serde_json::from_value::(unknown_field_payload) + .is_err(), + "sound effect requests must reject unknown fields" + ); + } + let music_payload = serde_json::to_value(EditorBackgroundMusicGenerateRequest { gpt_description_prompt: "森林冒险背景音乐".to_string(), make_instrumental: true,