From 98f2c21872b2d33041c11528ce823f6d80b49a03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Thu, 24 Sep 2026 17:53:34 +0800 Subject: [PATCH] =?UTF-8?q?3D=20=E5=AE=9A=E4=BB=B7=E6=95=B4=E6=AE=B5?= =?UTF-8?q?=E5=85=9C=E5=BA=95=E4=B9=9F=E8=A6=81=E8=BF=87=E7=AB=AF=E7=82=B9?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - server-rs/crates/api-server/src/editor_generation_model3d_records.rs:记录整段缺失时回退到本地配置前先跑 endpoint.validate(),缺键分支与兜底分支走同一条 fail-closed 路径,不再依赖调用方事后补 config.validate() - server-rs/crates/api-server/src/editor_generation_model3d_records.rs:补用例锁住「整段缺失 + 兜底配置不合法」必须被拒绝 --- .../src/editor_generation_model3d_records.rs | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/server-rs/crates/api-server/src/editor_generation_model3d_records.rs b/server-rs/crates/api-server/src/editor_generation_model3d_records.rs index d47a4cf9e..197981e36 100644 --- a/server-rs/crates/api-server/src/editor_generation_model3d_records.rs +++ b/server-rs/crates/api-server/src/editor_generation_model3d_records.rs @@ -126,7 +126,17 @@ fn endpoint_pricing_from_record( // 记录整段缺失(老行、或迁移前从未配过 3D)时直接用本地配置:这才是「覆盖文件只做 // 种子与兜底」的落点,缺键补齐与整段兜底走同一条路径。 let Some(record) = record else { - return Ok(fallback.cloned()); + // 兜底值同样过一遍端点校验:否则这条分支的正确性要靠调用方事后补一次 + // `config.validate()`,模块自己就不再是 fail-closed 的。 + let Some(fallback) = fallback else { + return Ok(None); + }; + fallback.validate(endpoint).map_err(|message| { + EditorGenerationPricingError::Invalid(format!( + "SpacetimeDB 模型定价配置 3D 段回退到本地配置,但本地配置不合法:{message}" + )) + })?; + return Ok(Some(fallback.clone())); }; let mut version_prices = BTreeMap::new(); @@ -337,6 +347,25 @@ mod tests { ); } + #[test] + fn missing_section_with_incomplete_local_fallback_fails_closed() { + let mut sections = + model3d_sections_from_config(Some(&config())).expect("完整配置应能写成记录"); + sections.text_to_model_pricing = None; + let mut incomplete_fallback = config(); + incomplete_fallback + .text_to_model_pricing + .version_prices + .remove(&Model3dModelVersion::P2); + + let error = model3d_config_from_sections(sections, Some(&incomplete_fallback)) + .expect_err("整段缺失但与兜底配置不合法时不能放行"); + assert!( + error.to_string().contains("本地配置不合法"), + "报错应点名兜底配置不合法,实际为:{error}" + ); + } + #[test] fn half_configured_sections_are_rejected() { let mut sections =