修复资源依赖本地身份解析

资源依赖图同时索引远端资源身份和 local-asset 本地资源身份

本地 canonical 引用与远端候选去重后再建立依赖连线

补充本地身份解析与候选不歧义回归测试

更新资源自由画板技术方案的依赖身份合同
This commit is contained in:
2026-09-08 13:29:08 +08:00
parent 57a5b642fc
commit bf03a18676
@@ -400,6 +400,12 @@ pub(crate) fn build_project_resource_graph(
.push(resource_ids[0].clone()); .push(resource_ids[0].clone());
} }
let resources_by_manifest_asset_id = resource_ids_by_manifest_asset
.iter()
.filter(|(_, resource_ids)| resource_ids.len() == 1)
.map(|(asset_id, resource_ids)| (asset_id.clone(), resource_ids[0].clone()))
.collect::<BTreeMap<_, _>>();
let mut unresolved_reference_resource_ids = BTreeSet::new(); let mut unresolved_reference_resource_ids = BTreeSet::new();
let mut reference_edge_by_id = BTreeMap::<String, ProjectResourceReferenceEdge>::new(); let mut reference_edge_by_id = BTreeMap::<String, ProjectResourceReferenceEdge>::new();
for (asset_id, target_resource_ids) in &resource_ids_by_manifest_asset { for (asset_id, target_resource_ids) in &resource_ids_by_manifest_asset {
@@ -418,16 +424,32 @@ pub(crate) fn build_project_resource_graph(
.filter(|value| !value.is_empty()) .filter(|value| !value.is_empty())
.collect::<BTreeSet<_>>() .collect::<BTreeSet<_>>()
{ {
let source_candidates = resources_by_external_id let mut source_candidates = resources_by_external_id
.get(external_reference_id) .get(external_reference_id)
.map(Vec::as_slice) .map(Vec::as_slice)
.unwrap_or(&[]); .unwrap_or(&[])
.iter()
.collect::<BTreeSet<_>>();
if let Some(referenced_asset_id) = external_reference_id
.strip_prefix("local-asset:")
.map(str::trim)
.filter(|value| !value.is_empty())
{
if let Some(source_resource_id) =
resources_by_manifest_asset_id.get(referenced_asset_id)
{
source_candidates.insert(source_resource_id);
}
}
if source_candidates.len() != 1 { if source_candidates.len() != 1 {
unresolved_reference_resource_ids.insert(external_reference_id.to_string()); unresolved_reference_resource_ids.insert(external_reference_id.to_string());
continue; continue;
} }
let source_resource_id = &source_candidates[0]; let source_resource_id = source_candidates
if !resource_by_id.contains_key(source_resource_id) .iter()
.next()
.expect("a non-empty candidate set must have one resource");
if !resource_by_id.contains_key(source_resource_id.as_str())
|| !resource_by_id.contains_key(target_resource_id) || !resource_by_id.contains_key(target_resource_id)
{ {
continue; continue;
@@ -438,7 +460,7 @@ pub(crate) fn build_project_resource_graph(
ProjectResourceReferenceEdge { ProjectResourceReferenceEdge {
id, id,
kind: "asset-reference".to_string(), kind: "asset-reference".to_string(),
source_resource_id: source_resource_id.clone(), source_resource_id: source_resource_id.to_string(),
target_resource_id: target_resource_id.clone(), target_resource_id: target_resource_id.clone(),
cyclic: false, cyclic: false,
}, },
@@ -835,6 +857,75 @@ mod tests {
); );
} }
#[test]
fn graph_resolves_local_asset_reference_identities() {
let manifest = manifest(
Vec::new(),
vec![
asset("source-1", None, &[], None),
asset(
"derivative-1",
Some("local-asset:derivative-1"),
&["local-asset:source-1"],
None,
),
],
);
let graph = build_project_resource_graph(
&manifest,
vec![
resource("asset:source-1", Some("source-1"), None),
resource("asset:derivative-1", Some("derivative-1"), None),
],
&[],
false,
);
assert_eq!(graph.reference_edges.len(), 1);
assert_eq!(
graph.reference_edges[0].source_resource_id,
"asset:source-1"
);
assert_eq!(
graph.reference_edges[0].target_resource_id,
"asset:derivative-1"
);
assert!(graph.unresolved_reference_resource_ids.is_empty());
assert!(graph
.connection_index
.iter()
.any(|index| index.resource_id == "asset:derivative-1"
&& index.upstream_reference_resource_ids == vec!["asset:source-1"]));
}
#[test]
fn graph_resolves_local_asset_identity_without_ambiguous_remote_duplicate() {
let manifest = manifest(
Vec::new(),
vec![
asset("source-1", Some("external-source"), &[], None),
asset(
"derivative-1",
Some("local-asset:derivative-1"),
&["local-asset:source-1"],
None,
),
],
);
let graph = build_project_resource_graph(
&manifest,
vec![
resource("asset:source-1", Some("source-1"), None),
resource("asset:derivative-1", Some("derivative-1"), None),
],
&[],
false,
);
assert_eq!(graph.reference_edges.len(), 1);
assert!(graph.unresolved_reference_resource_ids.is_empty());
}
#[test] #[test]
fn graph_aggregates_flows_filters_missing_resources_and_detects_cycles_iteratively() { fn graph_aggregates_flows_filters_missing_resources_and_detects_cycles_iteratively() {
let manifest = manifest( let manifest = manifest(