diff --git a/Cargo.lock b/Cargo.lock index 8c82c83..af8e7df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -354,7 +354,7 @@ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libhaystack" -version = "2.0.1" +version = "2.0.2" dependencies = [ "chrono", "chrono-tz", diff --git a/Cargo.toml b/Cargo.toml index 6f9320d..b517c59 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libhaystack" -version = "2.0.1" +version = "2.0.2" description = "Rust implementation of the Haystack 4 data types, defs, filter, units, and encodings" authors = ["J2 Innovations", "Radu Racariu "] edition = "2021" diff --git a/src/c_api/filter.rs b/src/c_api/filter.rs index cb79b69..d2a1296 100644 --- a/src/c_api/filter.rs +++ b/src/c_api/filter.rs @@ -45,7 +45,7 @@ pub unsafe extern "C" fn haystack_filter_parse(val: *const c_char) -> Option { - new_error(&format!("Invalid C string. {}", err)); + new_error(&format!("Invalid C string. {err}")); None } } diff --git a/src/haystack/defs/namespace.rs b/src/haystack/defs/namespace.rs index ee58163..b628827 100644 --- a/src/haystack/defs/namespace.rs +++ b/src/haystack/defs/namespace.rs @@ -831,89 +831,98 @@ impl<'a> Namespace<'a> { ref_target: &Option, resolve: &F, ) -> bool { - if let Some(relationship) = self.get(rel_name) { - if !self - .inheritance(rel_name) - .iter() - .any(|def| def.def_name() == "relationship") - { - return false; - } + // Def must be registered. + let Some(relationship) = self.get(rel_name) else { + return false; + }; - let transitive = relationship.has_marker("transitive"); + // Def must be a relationship. + if !self + .inheritance(rel_name) + .iter() + .any(|def| def.def_name() == "relationship") + { + return false; + } - // https://project-haystack.dev/doc/docHaystack/Relationships#reciprocalOf - let reciprocal_of = relationship.get_symbol("reciprocalOf"); + let is_transitive = relationship.has_marker("transitive"); - let mut queried_refs = HashSet::::new(); - let mut ref_tag: Option = ref_target.as_ref().cloned(); - let mut cur_subject: Dict = subject.clone(); + // https://project-haystack.dev/doc/docHaystack/Relationships#reciprocalOf + let reciprocal_of = relationship.get_symbol("reciprocalOf"); - 'search: loop { - let id = cur_subject.get_ref("id"); - for (subject_key, subject_val) in cur_subject.iter() { - let subject_def = self.get_by_name(subject_key); - let mut rel_val = subject_def.and_then(|def| def.get(&rel_name.value)); + let mut queried_refs = HashSet::new(); + let mut ref_tag = ref_target.as_ref().cloned(); + let mut subjects = vec![subject.clone()]; - // Handle a reciprocal relationship. A reciprocal relationship can only - // be inverted when a ref is specified. - if rel_val.is_none() && ref_tag.as_ref() == id && subject_val.is_ref() { - if let Some(reciprocal_of) = reciprocal_of { - rel_val = subject_def.and_then(|def| def.get(&reciprocal_of.value)); + 'search: loop { + let Some(mut cur_subject) = subjects.pop() else { + break; + }; - if rel_val.is_some() { - if let Value::Ref(val) = subject_val { - ref_tag = Some(val.clone()) - } + let id = cur_subject.get_ref("id").cloned(); + while let Some((subject_key, subject_val)) = cur_subject.pop_first() { + let subject_def = self.get_by_name(&subject_key); + let mut rel_val = subject_def.and_then(|def| def.get(&rel_name.value)); + + // Handle a reciprocal relationship. A reciprocal relationship can only + // be inverted when a ref is specified. + if rel_val.is_none() && ref_tag.as_ref() == id.as_ref() && subject_val.is_ref() { + if let Some(reciprocal_of) = reciprocal_of { + rel_val = subject_def.and_then(|def| def.get(&reciprocal_of.value)); + + if rel_val.is_some() { + if let Value::Ref(ref val) = subject_val { + ref_tag = Some(val.clone()) } } } + } - // Test to see if the relationship exists on any of the - // reflected defs for an entry in subject. - if let Some(Value::Symbol(rel_val)) = rel_val { - // If we're testing against a relationship value then - // ensure the target is also a symbol so we can see if it fits. - let mut has_match = if let Some(rel_term) = rel_term.as_ref() { - self.fits(rel_val, rel_term) - } else { - true - }; - - // Test to see if the value matches. - if has_match && ref_tag.is_some() { - has_match = false; - - if matches!(subject_val, Value::Ref(val) if Some(val) == ref_tag.as_ref()) - { - has_match = true; - } else if transitive { - if let Value::Ref(subject_val) = subject_val { - if !queried_refs.contains(subject_val) { - queried_refs.insert(subject_val.clone()); - - // If the value doesn't match but the relationship is transitive - // then follow the refs until we find a match or not. - // https://project-haystack.dev/doc/docHaystack/Relationships#transitive - if let Some(new_subject) = resolve(subject_val) { - if !new_subject.is_empty() { - cur_subject = new_subject; - continue 'search; - } + // Test to see if the relationship exists on any of the + // reflected defs for an entry in subject. + if let Some(Value::Symbol(rel_val)) = rel_val { + // If we're testing against a relationship value then + // ensure the target is also a symbol so we can see if it fits. + let mut has_match = if let Some(rel_term) = rel_term.as_ref() { + self.fits(rel_val, rel_term) + } else { + true + }; + + // Test to see if the value matches. + if has_match && ref_tag.is_some() { + has_match = false; + + if matches!(subject_val, Value::Ref(ref val) if Some(val) == ref_tag.as_ref()) + { + has_match = true; + } else if is_transitive { + if let Value::Ref(subject_val) = subject_val { + if !queried_refs.contains(&subject_val) { + queried_refs.insert(subject_val.clone()); + + // If the value doesn't match but the relationship is transitive + // then follow the refs until we find a match or not. + // https://project-haystack.dev/doc/docHaystack/Relationships#transitive + if let Some(new_subject) = resolve(&subject_val) { + if !new_subject.is_empty() { + subjects.push(cur_subject); + subjects.push(new_subject); + continue 'search; } } } } } + } - if has_match { - return true; - } + if has_match { + return true; } } - break; } } + false } } diff --git a/src/haystack/encoding/zinc/encode.rs b/src/haystack/encoding/zinc/encode.rs index 1470b1e..a5d1813 100644 --- a/src/haystack/encoding/zinc/encode.rs +++ b/src/haystack/encoding/zinc/encode.rs @@ -146,9 +146,9 @@ impl ToZinc for Number { } else { "" }; - writer.write_fmt(format_args!("{}INF", sign))? + writer.write_fmt(format_args!("{sign}INF"))? } else if let Some(unit) = &self.unit { - writer.write_fmt(format_args!("{}{}", self.value, unit))? + writer.write_fmt(format_args!("{value}{unit}", value = self.value))? } else { writer.write_fmt(format_args!("{}", self.value))? } @@ -203,7 +203,7 @@ impl ToZinc for Str { writer.write_all(br"\$")? } else { let chunk = c.encode_utf8(&mut buf); - writer.write_fmt(format_args!("{}", chunk))? + writer.write_fmt(format_args!("{chunk}"))? } } writer.write_all(b"\"")?; diff --git a/src/haystack/filter/nodes.rs b/src/haystack/filter/nodes.rs index 3242246..c47cd3e 100644 --- a/src/haystack/filter/nodes.rs +++ b/src/haystack/filter/nodes.rs @@ -405,11 +405,11 @@ impl Display for Relation { write!(f, "{}?", self.rel.value)?; if let Some(rel_term) = &self.rel_term { - write!(f, " {}", rel_term)?; + write!(f, " {rel_term}")?; } if let Some(ref_value) = &self.ref_value { - write!(f, " {}", ref_value) + write!(f, " {ref_value}") } else { Ok(()) } diff --git a/src/haystack/filter/parser.rs b/src/haystack/filter/parser.rs index da2a803..3a464c2 100644 --- a/src/haystack/filter/parser.rs +++ b/src/haystack/filter/parser.rs @@ -37,7 +37,7 @@ impl<'a, R: Read> Parser>> { // If there is something left over then we have an invalid haystack filter. self.parse_or().and_then(|or| match &self.lexer.cur.value { None => Ok(or), - Some(value) => self.make_generic_err(&format!("Unexpected token: {:?}.", value)), + Some(value) => self.make_generic_err(&format!("Unexpected token: {value:?}")), }) } diff --git a/tests/defs/namespace.rs b/tests/defs/namespace.rs index 7215232..703776d 100644 --- a/tests/defs/namespace.rs +++ b/tests/defs/namespace.rs @@ -1159,6 +1159,12 @@ fn test_namespace_relationship() { fn test_namespace_transitive_relationship() { let mut map = HashMap::<&str, &Dict>::new(); + let site = dict! { + "id" => Value::make_ref("site"), + "site" => Value::Marker, + }; + map.insert("site", &site); + let ahu = dict! { "id" => Value::make_ref("ahu"), "ahu" => Value::Marker, @@ -1171,6 +1177,8 @@ fn test_namespace_transitive_relationship() { "discharge" => Value::Marker, "fan" => Value::Marker, "equip" => Value::Marker, + // Not typically done but used for indirectly multiple transitive relationships. + "siteRef" => Value::make_ref("site"), "equipRef" => Value::make_ref("ahu"), }; map.insert("fan", &fan); @@ -1235,6 +1243,18 @@ fn test_namespace_transitive_relationship() { &resolve, ); assert!(!has); + + // true for a point that indirectly references a site + // The fan has a siteRef that also needs to be traversed as well as the equipRef. This + // tests multiple transitive relationships on the same dict. + let has = DEFS_NS.has_relationship( + &status, + &Symbol::from("containedBy"), + &None, + &Some(Ref::from("site")), + &resolve, + ); + assert!(has); } #[test] diff --git a/unit-gen/src/parser.rs b/unit-gen/src/parser.rs index 7de18bf..31a30d2 100644 --- a/unit-gen/src/parser.rs +++ b/unit-gen/src/parser.rs @@ -109,7 +109,7 @@ pub(super) fn parse_unit<'a>() -> Parser<'a, u8, Unit> { "A" => cur.a = *scale, "mol" => cur.mol = *scale, "cd" => cur.cd = *scale, - _ => panic!("Invalid dimension {}", dim), + _ => panic!("Invalid dimension {dim}"), }; cur })