use crate::{
defs::namespace::{DefDict, Namespace},
val::{Dict, HaystackDict, Symbol},
};
#[derive(Default)]
pub struct ContainmentRefOptions {
pub deprecated: bool,
}
pub fn get_containment_refs(
namespace: &Namespace,
options: Option<&ContainmentRefOptions>,
) -> Vec<String> {
let default_opts = ContainmentRefOptions::default();
let options = options.unwrap_or(&default_opts);
namespace
.all_subtypes_of(&Symbol::make("ref"))
.iter()
.filter(|def| def.has("containedBy"))
.filter(|def| options.deprecated || !def.has("deprecated"))
.map(|def| def.def_name().to_string())
.collect()
}
pub fn get_contained_by_refs_for_super_type(
namespace: &Namespace,
super_type: &str,
options: Option<&ContainmentRefOptions>,
) -> Vec<String> {
let default_opts = ContainmentRefOptions::default();
let options = options.unwrap_or(&default_opts);
let super_sym = Symbol::make(super_type);
namespace
.all_subtypes_of(&Symbol::make("ref"))
.iter()
.filter(|def| def.has("containedBy"))
.filter(|def| options.deprecated || !def.has("deprecated"))
.filter_map(|def| {
let contained_by_sym = def.get_symbol("containedBy")?;
if namespace.fits(&Symbol::make(contained_by_sym.value.as_str()), &super_sym) {
Some(def.def_name().to_string())
} else {
None
}
})
.collect()
}
pub fn add_containment_refs(
dict: &mut Dict,
parent: &Dict,
namespace: &Namespace,
options: Option<&ContainmentRefOptions>,
) -> String {
let default_opts = ContainmentRefOptions::default();
let options = options.unwrap_or(&default_opts);
let site_ref = if parent.has("site") {
parent.get("id").cloned()
} else {
parent.get("siteRef").cloned()
};
if let Some(v) = site_ref {
dict.insert("siteRef".into(), v);
}
let equip_ref = if parent.has("equip") {
parent.get("id").cloned()
} else {
parent.get("equipRef").cloned()
};
if let Some(v) = equip_ref {
dict.insert("equipRef".into(), v);
}
let space_ref = if parent.has("space") || parent.has("floor") {
parent.get("id").cloned()
} else {
parent.get("spaceRef").cloned()
};
if let Some(v) = space_ref {
dict.insert("spaceRef".into(), v);
}
if options.deprecated {
let floor_ref = if parent.has("floor") {
parent.get("id").cloned()
} else {
parent.get("floorRef").cloned()
};
if let Some(v) = floor_ref {
dict.insert("floorRef".into(), v);
}
}
let entity_type_name = namespace
.def_of_dict(parent)
.get_symbol("def")
.map(|s| s.value.clone())
.unwrap_or_default();
if entity_type_name.is_empty() {
return String::new();
}
let ref_name = find_containment_ref_for_type(namespace, &entity_type_name)
.map(|def| def.def_name().to_string())
.unwrap_or_default();
if !ref_name.is_empty()
&& let Some(parent_id) = parent.get("id").cloned()
&& !dict.contains_key(&ref_name)
{
dict.insert(ref_name.clone(), parent_id);
}
ref_name
}
fn find_containment_ref_for_type(namespace: &Namespace, entity_type_name: &str) -> Option<Dict> {
namespace
.all_subtypes_of(&Symbol::make("ref"))
.into_iter()
.filter(|def| def.has("containedBy"))
.find(|def| {
def.get_symbol("containedBy")
.map(|cb| namespace.fits(&Symbol::make(entity_type_name), cb))
.unwrap_or(false)
})
.cloned()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::defs::namespace::Namespace;
use crate::dict;
use crate::val::{Dict, Value};
fn test_namespace() -> Namespace {
use crate::encoding::zinc::decode::from_str as zinc_decode;
let zinc = include_str!("../../../tests/defs/defs.zinc");
let value: Value = zinc_decode(zinc).unwrap_or_else(|e| panic!("valid defs zinc: {e:?}"));
let grid =
crate::val::Grid::try_from(&value).unwrap_or_else(|e| panic!("valid defs grid: {e:?}"));
Namespace::make(grid)
}
#[test]
fn get_containment_refs_non_empty() {
let ns = test_namespace();
let refs = get_containment_refs(&ns, None);
assert!(
!refs.is_empty(),
"expected containment refs to be non-empty"
);
}
#[test]
fn get_contained_by_refs_for_equip() {
let ns = test_namespace();
let refs = get_contained_by_refs_for_super_type(&ns, "equip", None);
assert!(
refs.iter().all(|r| r.ends_with("Ref")),
"expected all results to be ref names, got: {refs:?}"
);
}
#[test]
fn get_containment_refs_excludes_deprecated_by_default() {
let ns = test_namespace();
let without = get_containment_refs(&ns, None);
let with_deprecated =
get_containment_refs(&ns, Some(&ContainmentRefOptions { deprecated: true }));
assert!(
with_deprecated.len() >= without.len(),
"enabling deprecated should not reduce results"
);
for name in &without {
let sym = Symbol::make(name.as_str());
if let Some(def) = ns.get(&sym) {
assert!(
!def.has("deprecated"),
"default results must not contain deprecated def '{name}'"
);
}
}
}
#[test]
fn get_contained_by_refs_excludes_deprecated_by_default() {
let ns = test_namespace();
let without = get_contained_by_refs_for_super_type(&ns, "equip", None);
let with_deprecated = get_contained_by_refs_for_super_type(
&ns,
"equip",
Some(&ContainmentRefOptions { deprecated: true }),
);
assert!(
with_deprecated.len() >= without.len(),
"enabling deprecated should not reduce results"
);
for name in &without {
let sym = Symbol::make(name.as_str());
if let Some(def) = ns.get(&sym) {
assert!(
!def.has("deprecated"),
"default results must not contain deprecated def '{name}'"
);
}
}
}
#[test]
fn add_containment_refs_copies_site_ref() {
let ns = test_namespace();
let parent = dict! {
"id" => Value::make_ref("parent-id"),
"siteRef" => Value::make_ref("site-id")
};
let mut child = Dict::new();
add_containment_refs(&mut child, &parent, &ns, None);
assert_eq!(
child.get("siteRef").and_then(|v| if let Value::Ref(r) = v {
Some(r.value.as_str())
} else {
None
}),
Some("site-id")
);
}
#[test]
fn add_containment_refs_site_entity_uses_id() {
let ns = test_namespace();
let parent = dict! {
"id" => Value::make_ref("the-site"),
"site" => Value::make_marker()
};
let mut child = Dict::new();
add_containment_refs(&mut child, &parent, &ns, None);
assert_eq!(
child.get("siteRef").and_then(|v| if let Value::Ref(r) = v {
Some(r.value.as_str())
} else {
None
}),
Some("the-site")
);
}
#[test]
fn add_containment_refs_deprecated_floor_ref() {
let ns = test_namespace();
let parent = dict! {
"id" => Value::make_ref("floor-id"),
"floor" => Value::make_marker()
};
let mut child_default = Dict::new();
add_containment_refs(&mut child_default, &parent, &ns, None);
assert!(
child_default.get("floorRef").is_none(),
"floorRef should not be set without deprecated option"
);
let opts = ContainmentRefOptions { deprecated: true };
let mut child_deprecated = Dict::new();
add_containment_refs(&mut child_deprecated, &parent, &ns, Some(&opts));
assert_eq!(
child_deprecated
.get("floorRef")
.and_then(|v| if let Value::Ref(r) = v {
Some(r.value.as_str())
} else {
None
}),
Some("floor-id")
);
}
}