use crate::identity::{NestedObject, RefEdge, key_of_reflect, refs_in_source};
use crate::value::{FieldValue, Reflect};
#[derive(Clone, Debug)]
pub struct OwnedRefs<'a> {
pub owner: Option<NestedObject<'a>>,
pub refs: Vec<RefEdge<'a>>,
}
pub fn collect_refs_owned(root: &dyn Reflect) -> Vec<RefEdge<'_>> {
collect_refs_by_owner(root)
.into_iter()
.next()
.map(|group| group.refs)
.unwrap_or_default()
}
pub fn collect_refs_by_owner(root: &dyn Reflect) -> Vec<OwnedRefs<'_>> {
let mut groups = vec![OwnedRefs {
owner: None,
refs: Vec::new(),
}];
walk(root, String::new(), String::new(), 0, &mut groups);
groups
}
fn join(prefix: &str, name: &str) -> String {
if prefix.is_empty() {
name.to_string()
} else {
format!("{prefix}.{name}")
}
}
fn walk<'a>(
object: &'a dyn Reflect,
full: String,
relative: String,
owner: usize,
groups: &mut Vec<OwnedRefs<'a>>,
) {
for field in object.type_info().fields {
let Some(value) = object.field(field.name) else {
continue;
};
visit(
value,
join(&full, field.xml_name),
join(&relative, field.xml_name),
field.ty.type_info(),
owner,
groups,
);
}
}
fn visit<'a>(
value: FieldValue<'a>,
full: String,
relative: String,
expected: Option<&'static crate::reflect::TypeInfo>,
owner: usize,
groups: &mut Vec<OwnedRefs<'a>>,
) {
match value {
FieldValue::Object(child) => {
let info = child.type_info();
if info.is_object_ref {
if let Some(target) = key_of_reflect(child) {
groups[owner].refs.push(RefEdge {
path: relative,
target,
expected,
});
}
} else if let Some(any) = child.as_any().downcast_ref::<crate::any_xml::AnyXml>() {
refs_in_source(&any.content, &relative, &mut groups[owner].refs);
} else if info.is_top_level
&& let Some(key) = key_of_reflect(child)
&& !key.uuid.trim().is_empty()
{
groups.push(OwnedRefs {
owner: Some(NestedObject {
path: full.clone(),
key,
}),
refs: Vec::new(),
});
let nested = groups.len() - 1;
walk(child, full, String::new(), nested, groups);
} else {
walk(child, full, relative, owner, groups);
}
}
FieldValue::List(items) => {
for (i, item) in items.into_iter().enumerate() {
visit(
item,
format!("{full}[{i}]"),
format!("{relative}[{i}]"),
expected,
owner,
groups,
);
}
}
_ => {}
}
}
#[cfg(all(test, feature = "derive"))]
mod tests {
use super::*;
use crate::identity::collect_refs;
use crate::prelude::*;
#[derive(Debug, Default, Clone, PartialEq, EnergymlObject)]
#[energyml(
package = "eml",
version = "2.3",
xml_name = "DataObjectReference",
object_ref
)]
struct Dor {
#[energyml(elem, xml_name = "Uuid")]
uuid: String,
#[energyml(elem, xml_name = "QualifiedType")]
qualified_type: String,
#[energyml(elem, xml_name = "Title")]
title: String,
#[energyml(elem, xml_name = "ObjectVersion")]
object_version: Option<String>,
}
#[derive(Debug, Default, Clone, PartialEq, EnergymlObject)]
#[energyml(
package = "witsml",
version = "2.1",
xml_name = "Channel",
top_level,
nestable,
content_type = "application/x-witsml+xml;version=2.1;type=Channel",
qualified_type = "witsml21.Channel"
)]
struct Chan {
#[energyml(attr, xml_name = "uuid")]
uuid: String,
#[energyml(elem, xml_name = "DerivedFrom")]
derived_from: Vec<Dor>,
#[energyml(elem, xml_name = "Wellbore")]
wellbore: Option<Dor>,
}
#[derive(Debug, Default, Clone, PartialEq, EnergymlObject)]
#[energyml(
package = "witsml",
version = "2.1",
xml_name = "ChannelSet",
top_level,
nestable,
content_type = "application/x-witsml+xml;version=2.1;type=ChannelSet",
qualified_type = "witsml21.ChannelSet"
)]
struct ChanSet {
#[energyml(attr, xml_name = "uuid")]
uuid: String,
#[energyml(elem, xml_name = "Channel")]
channel: Vec<Chan>,
#[energyml(elem, xml_name = "Wellbore")]
wellbore: Option<Dor>,
}
#[derive(Debug, Default, Clone, PartialEq, EnergymlObject)]
#[energyml(
package = "witsml",
version = "2.1",
xml_name = "Log",
top_level,
content_type = "application/x-witsml+xml;version=2.1;type=Log",
qualified_type = "witsml21.Log"
)]
struct LogObj {
#[energyml(attr, xml_name = "uuid")]
uuid: String,
#[energyml(elem, xml_name = "ChannelSet")]
channel_set: Vec<ChanSet>,
#[energyml(elem, xml_name = "Wellbore")]
wellbore: Option<Dor>,
}
fn dor(name: &str) -> Dor {
Dor {
uuid: name.to_string(),
qualified_type: "witsml21.Wellbore".to_string(),
title: String::new(),
object_version: None,
}
}
fn sample() -> LogObj {
LogObj {
uuid: "log-uuid".into(),
wellbore: Some(dor("log")),
channel_set: vec![
ChanSet {
uuid: "s0-uuid".into(),
wellbore: Some(dor("s0w")),
channel: vec![Chan {
uuid: "c0-uuid".into(),
wellbore: Some(dor("c0w")),
derived_from: vec![dor("d0"), dor("d1")],
}],
},
ChanSet {
uuid: "s1-uuid".into(),
wellbore: None,
channel: vec![Chan {
uuid: "c1-uuid".into(),
wellbore: None,
derived_from: vec![],
}],
},
],
}
}
fn targets(edges: &[RefEdge<'_>]) -> Vec<String> {
edges.iter().map(|edge| edge.target.uuid.to_string()).collect()
}
#[test]
fn the_root_owns_only_the_references_it_writes() {
let log = sample();
let owned = collect_refs_owned(&log);
assert_eq!(targets(&owned), ["log"]);
assert_eq!(owned[0].path, "Wellbore");
assert_eq!(
collect_refs(&log).len(),
5,
"the whole-tree walk gives all of them to the root; that is the defect"
);
}
#[test]
fn each_nested_object_owns_its_references_relative_to_itself() {
let log = sample();
let groups = collect_refs_by_owner(&log);
assert_eq!(groups.len(), 5, "the root, two sets, two channels");
assert!(groups[0].owner.is_none());
let owner_paths: Vec<String> = groups
.iter()
.skip(1)
.map(|g| g.owner.as_ref().expect("a nested owner").path.clone())
.collect();
assert_eq!(
owner_paths,
[
"ChannelSet[0]",
"ChannelSet[0].Channel[0]",
"ChannelSet[1]",
"ChannelSet[1].Channel[0]"
]
);
let owner_uuids: Vec<&str> = groups
.iter()
.skip(1)
.map(|g| g.owner.as_ref().expect("a nested owner").key.uuid)
.collect();
assert_eq!(owner_uuids, ["s0-uuid", "c0-uuid", "s1-uuid", "c1-uuid"]);
assert_eq!(
groups[1].owner.as_ref().expect("owner").key.qualified_type(),
"witsml21.ChannelSet"
);
let channel = &groups[2];
assert_eq!(targets(&channel.refs), ["d0", "d1", "c0w"]);
let paths: Vec<&str> = channel.refs.iter().map(|r| r.path.as_str()).collect();
assert_eq!(paths, ["DerivedFrom[0]", "DerivedFrom[1]", "Wellbore"]);
assert_eq!(targets(&groups[1].refs), ["s0w"]);
assert!(
groups[3].refs.is_empty(),
"a set that writes nothing owns nothing"
);
assert!(groups[4].refs.is_empty());
}
#[test]
fn the_groups_together_are_the_whole_tree_walk() {
let log = sample();
let mut whole: Vec<(String, String)> = collect_refs(&log)
.iter()
.map(|e| (e.path.clone(), e.target.uuid.to_string()))
.collect();
let mut grouped: Vec<(String, String)> = collect_refs_by_owner(&log)
.iter()
.flat_map(|group| {
let prefix = group.owner.as_ref().map(|o| o.path.clone()).unwrap_or_default();
group.refs.iter().map(move |edge| {
let path = if prefix.is_empty() {
edge.path.clone()
} else {
format!("{prefix}.{}", edge.path)
};
(path, edge.target.uuid.to_string())
})
})
.collect();
whole.sort();
grouped.sort();
assert_eq!(grouped, whole);
}
#[test]
fn a_nested_element_without_a_uuid_is_not_an_owner() {
let mut log = sample();
log.channel_set[0].uuid = String::new();
let groups = collect_refs_by_owner(&log);
assert_eq!(
groups.len(),
4,
"the root, one channel under the anonymous set, the second set and its channel"
);
assert_eq!(targets(&groups[0].refs), ["s0w", "log"]);
assert_eq!(groups[0].refs[0].path, "ChannelSet[0].Wellbore");
let channel = groups[1].owner.as_ref().expect("the channel is an owner");
assert_eq!(channel.key.uuid, "c0-uuid");
assert_eq!(channel.path, "ChannelSet[0].Channel[0]");
}
#[test]
fn an_object_with_nothing_nested_is_unchanged() {
let log = LogObj {
uuid: "alone".into(),
wellbore: Some(dor("w")),
channel_set: vec![],
};
let owned = collect_refs_owned(&log);
let whole = collect_refs(&log);
assert_eq!(targets(&owned), targets(&whole));
assert_eq!(owned[0].path, whole[0].path);
assert_eq!(collect_refs_by_owner(&log).len(), 1);
}
}