更新组件绑定验证逻辑以支持已有独立素材验证

- 调整验证逻辑以支持引用项目中已存在的素材,同时拒绝不存在的素材
- 补充相关测试,验证未知素材与跨批次素材引用行为
This commit is contained in:
2026-08-20 10:47:49 +08:00
parent 2da982e3fd
commit ca0b16cf36
@@ -107,7 +107,7 @@ fn collect_editable_nodes<'a>(node: &'a Node, output: &mut Vec<EditableNodeConte
fn validate_and_materialize(
changes: Vec<BindingChangeDraft>,
editable_ids: &HashSet<NodeId>,
batch_sprite_ids: &HashSet<SpriteAssetId>,
known_sprite_ids: &HashSet<SpriteAssetId>,
) -> Result<BindingDTO, String> {
let mut changed_ids = HashSet::new();
let mut materialized = Vec::with_capacity(changes.len());
@@ -126,9 +126,9 @@ fn validate_and_materialize(
if image
.target_graphic
.as_ref()
.is_some_and(|id| !batch_sprite_ids.contains(id))
.is_some_and(|id| !known_sprite_ids.contains(id))
{
return Err("组件绑定引用了不属于当前批次的独立素材".to_string());
return Err("组件绑定引用了不存在的独立素材".to_string());
}
}
}
@@ -258,7 +258,8 @@ pub(crate) async fn bind_components_impl(
.ok_or_else(|| "LLM 未返回 bind_ui_components 工具调用".to_string())?;
let parsed = serde_json::from_str::<BindingResponse>(&call.arguments)
.map_err(|error| format!("组件绑定工具参数无效:{error}"))?;
let result = validate_and_materialize(parsed.changes, &editable_ids, &batch_sprite_ids)?;
let known_sprite_ids = state.sprite_assets.keys().cloned().collect::<HashSet<_>>();
let result = validate_and_materialize(parsed.changes, &editable_ids, &known_sprite_ids)?;
eprintln!(
"ui_binding.completed ui_images={} sprites={} editable_nodes={} changes={}",
state.ui_design_images.len(),
@@ -280,21 +281,27 @@ mod tests {
}
#[test]
fn materialization_rejects_unapproved_node_and_foreign_sprite() {
fn materialization_rejects_unapproved_node_and_unknown_sprite() {
let editable = HashSet::from([id("editable")]);
let sprites = HashSet::from([SpriteAssetId::new("sprite").expect("valid sprite")]);
let known = HashSet::from([
SpriteAssetId::new("sprite").expect("valid sprite"),
SpriteAssetId::new("other-batch-sprite").expect("valid sprite"),
]);
let unapproved = BindingChangeDraft {
node_id: id("other"),
components: Vec::new(),
components_status: DraftStatus::NoProblem,
};
assert!(validate_and_materialize(vec![unapproved], &editable, &sprites).is_err());
assert!(validate_and_materialize(vec![unapproved], &editable, &known).is_err());
let foreign = BindingChangeDraft {
// References to sprites from another batch are allowed once they exist in the project.
let other_batch = BindingChangeDraft {
node_id: id("editable"),
components: vec![Component::Image(
crate::ui_editor::component::image::ImageComponent {
target_graphic: Some(SpriteAssetId::new("foreign").expect("valid sprite")),
target_graphic: Some(
SpriteAssetId::new("other-batch-sprite").expect("valid sprite"),
),
image_type: crate::ui_editor::component::image::ImageType::Simple {
preserve_aspect: false,
},
@@ -302,7 +309,22 @@ mod tests {
)],
components_status: DraftStatus::NoProblem,
};
assert!(validate_and_materialize(vec![foreign], &editable, &sprites).is_err());
assert!(validate_and_materialize(vec![other_batch], &editable, &known).is_ok());
// References to sprites that do not exist in the project at all are still rejected.
let unknown = BindingChangeDraft {
node_id: id("editable"),
components: vec![Component::Image(
crate::ui_editor::component::image::ImageComponent {
target_graphic: Some(SpriteAssetId::new("unknown").expect("valid sprite")),
image_type: crate::ui_editor::component::image::ImageType::Simple {
preserve_aspect: false,
},
},
)],
components_status: DraftStatus::NoProblem,
};
assert!(validate_and_materialize(vec![unknown], &editable, &known).is_err());
}
#[test]