diff --git a/src/materializer.rs b/src/materializer.rs index 4434caf..1828bb6 100644 --- a/src/materializer.rs +++ b/src/materializer.rs @@ -1,18 +1,19 @@ use crate::parsing::traits::ParsableNode; use crate::tree::node::{DynamicNode, MaterializedNode}; -use crate::tree::node_repository::NodeRepository; -use crate::tree::traits::LocatableNode; +use crate::tree::traits::{LocatableNode, NodeRepository}; use log::error; -use phenopackets::schema::v2::Phenopacket; use phenopackets::schema::v2::core::{ Diagnosis, Disease, OntologyClass, PhenotypicFeature, Resource, VitalStatus, }; +use phenopackets::schema::v2::{Cohort, Phenopacket}; pub(crate) struct NodeMaterializer; impl NodeMaterializer { - pub fn materialize_nodes(&mut self, dyn_node: &DynamicNode, repo: &mut NodeRepository) { - if let Some(oc) = OntologyClass::parse(dyn_node) { + pub fn materialize_nodes(&mut self, dyn_node: &DynamicNode, repo: &mut impl NodeRepository) { + if let Some(cohort) = Cohort::parse(dyn_node) { + Self::push_to_repo(cohort, dyn_node, repo); + } else if let Some(oc) = OntologyClass::parse(dyn_node) { Self::push_to_repo(oc, dyn_node, repo); } else if let Some(pf) = PhenotypicFeature::parse(dyn_node) { Self::push_to_repo(pf, dyn_node, repo); @@ -31,12 +32,13 @@ impl NodeMaterializer { }; } - fn push_to_repo( - materialized: T, + fn push_to_repo( + materialized: NodeType, dyn_node: &DynamicNode, - board: &mut NodeRepository, + board: &mut impl NodeRepository, ) { let node = MaterializedNode::from_dynamic(materialized, dyn_node); - board.insert(node); + // TODO: Error throwing + board.insert(node).expect("Unable to insert node"); } } diff --git a/src/parsing/parseable_nodes.rs b/src/parsing/parseable_nodes.rs index dd998e6..feb7713 100644 --- a/src/parsing/parseable_nodes.rs +++ b/src/parsing/parseable_nodes.rs @@ -1,12 +1,26 @@ use crate::parsing::traits::ParsableNode; use crate::tree::node::DynamicNode; use crate::tree::traits::LocatableNode; -use phenopackets::schema::v2::Phenopacket; use phenopackets::schema::v2::core::{ Diagnosis, Disease, OntologyClass, PhenotypicFeature, Resource, VitalStatus, }; +use phenopackets::schema::v2::{Cohort, Phenopacket}; use serde_json::Value; +impl ParsableNode for Cohort { + fn parse(node: &DynamicNode) -> Option { + if let Value::Object(map) = &node.inner + && map.contains_key("id") + && map.contains_key("members") + && let Ok(cohort) = serde_json::from_value::(node.inner.clone()) + { + Some(cohort) + } else { + None + } + } +} + impl ParsableNode for OntologyClass { fn parse(node: &DynamicNode) -> Option { if let Value::Object(map) = &node.inner @@ -41,7 +55,6 @@ impl ParsableNode for Phenopacket { if let Value::Object(map) = &node.inner && map.contains_key("id") && map.contains_key("metaData") - && node.pointer().is_root() && let Ok(pp) = serde_json::from_value::(node.inner.clone()) { Some(pp) @@ -57,6 +70,9 @@ impl ParsableNode for Resource { && map.contains_key("id") && map.contains_key("name") && map.contains_key("url") + && map.contains_key("namespacePrefix") + && map.contains_key("version") + && map.contains_key("iriPrefix") && let Ok(resource) = serde_json::from_value::(node.inner.clone()) { Some(resource) diff --git a/src/phenolint.rs b/src/phenolint.rs index 1a374d3..25867ee 100644 --- a/src/phenolint.rs +++ b/src/phenolint.rs @@ -3,7 +3,6 @@ use crate::diagnostics::enums::PhenopacketData; use crate::diagnostics::{LintFinding, LintReport}; use crate::enums::InputTypes; use crate::error::{InitError, LintResult, LinterError, ParsingError, validation_error_to_string}; -use crate::materializer::NodeMaterializer; use crate::parsing::phenopacket_parser::PhenopacketParser; use crate::patches::patch_engine::PatchEngine; use crate::patches::patch_registry::PatchRegistry; @@ -12,15 +11,15 @@ use crate::report::report_registry::ReportRegistry; use crate::rules::rule_registry::{RuleRegistry, check_duplicate_rule_ids}; use crate::schema_validation::validator::PhenopacketSchemaValidator; use crate::traits::Lint; -use crate::tree::abstract_pheno_tree::AbstractTreeTraversal; use crate::tree::node::DynamicNode; -use crate::tree::node_repository::NodeRepository; use crate::tree::pointer::Pointer; use log::{error, warn}; use phenopackets::schema::v2::Phenopacket; use prost::Message; use serde_json::Value; +use crate::tree::flat_node_repository::FlatNodeRepositoryBuilder; +use crate::tree::traits::NodeRepositoryBuilder; use std::fs; use std::path::PathBuf; @@ -28,7 +27,6 @@ pub struct Phenolint { rule_registry: RuleRegistry, patch_registry: PatchRegistry, report_registry: ReportRegistry, - node_materializer: NodeMaterializer, patch_engine: PatchEngine, validator: PhenopacketSchemaValidator, } @@ -45,7 +43,6 @@ impl Phenolint { rule_registry, report_registry, patch_registry, - node_materializer: NodeMaterializer, patch_engine: PatchEngine, validator: PhenopacketSchemaValidator::default(), } @@ -70,13 +67,7 @@ impl Lint for Phenolint { let root_node = DynamicNode::new(&values, &spans, Pointer::at_root()); - let apt = AbstractTreeTraversal::new(values, spans); - let mut node_repo: NodeRepository = NodeRepository::new(); - - for node in apt.traverse() { - self.node_materializer - .materialize_nodes(&node, &mut node_repo) - } + let node_repo = FlatNodeRepositoryBuilder::build(values, spans); let mut findings = vec![]; for rule in self.rule_registry.rules() { diff --git a/src/rules/curies/curie_format_rule.rs b/src/rules/curies/curie_format_rule.rs index a5e3a3c..41bd08d 100644 --- a/src/rules/curies/curie_format_rule.rs +++ b/src/rules/curies/curie_format_rule.rs @@ -10,7 +10,8 @@ use crate::report::traits::{CompileReport, RegisterableReport, ReportFromContext use crate::rules::rule_registration::RuleRegistration; use crate::rules::traits::RuleMetaData; use crate::rules::traits::{LintRule, RuleCheck, RuleFromContext}; -use crate::tree::node_repository::List; +use crate::tree::querying::presentation::Flattened; +use crate::tree::querying::queries::convenience::All; use crate::tree::traits::{LocatableNode, Node}; use phenolint_macros::{register_report, register_rule}; use phenopackets::schema::v2::core::OntologyClass; @@ -38,9 +39,9 @@ impl RuleFromContext for CurieFormatRule { } impl RuleCheck for CurieFormatRule { - type Data<'a> = List<'a, OntologyClass>; + type Query = All; - fn check(&self, data: Self::Data<'_>) -> Vec { + fn check(&self, data: Flattened) -> Vec { let mut violations = vec![]; for node in data.0.iter() { diff --git a/src/rules/interpretation/disease_consistency_rule.rs b/src/rules/interpretation/disease_consistency_rule.rs index cd2eafe..5a6a763 100644 --- a/src/rules/interpretation/disease_consistency_rule.rs +++ b/src/rules/interpretation/disease_consistency_rule.rs @@ -14,8 +14,9 @@ use crate::report::traits::{CompileReport, RegisterableReport, ReportFromContext use crate::rules::rule_registration::RuleRegistration; use crate::rules::traits::RuleMetaData; use crate::rules::traits::{LintRule, RuleCheck, RuleFromContext}; -use crate::tree::node_repository::List; use crate::tree::pointer::Pointer; +use crate::tree::querying::presentation::Grouped; +use crate::tree::querying::queries::convenience::GroupedIndividuals; use crate::tree::traits::{LocatableNode, Node}; use phenolint_macros::{register_patch, register_report, register_rule}; use phenopackets::schema::v2::core::{Diagnosis, Disease, OntologyClass}; @@ -38,34 +39,37 @@ impl RuleFromContext for DiseaseConsistencyRule { } impl RuleCheck for DiseaseConsistencyRule { - type Data<'a> = (List<'a, Diagnosis>, List<'a, Disease>); + type Query = (GroupedIndividuals, GroupedIndividuals); - fn check(&self, data: Self::Data<'_>) -> Vec { + fn check(&self, data: (Grouped, Grouped)) -> Vec { let mut violations = vec![]; - let disease_terms: Vec<(&str, &str)> = data - .1 - .iter() - .filter_map(|disease| { - disease - .inner - .term - .as_ref() - .map(|oc| (oc.id.as_str(), oc.label.as_str())) - }) - .collect(); - - for diagnosis in data.0.iter() { - if let Some(oc) = &diagnosis.inner.disease - && !disease_terms.contains(&(oc.id.as_str(), oc.label.as_str())) - { - violations.push(LintViolation::new( - ViolationSeverity::Warning, - LintRule::rule_id(self), - NonEmptyVec::with_single_entry( - diagnosis.pointer().clone().down("disease").clone(), - ), - )) + let (diagnosis_groups, diseases_group) = data; + + for (diagnosis, diseases) in diagnosis_groups.0.iter().zip(diseases_group.0) { + let disease_terms: Vec<(&str, &str)> = diseases + .iter() + .filter_map(|disease| { + disease + .inner + .term + .as_ref() + .map(|oc| (oc.id.as_str(), oc.label.as_str())) + }) + .collect(); + + for diagnosis in diagnosis.iter() { + if let Some(oc) = &diagnosis.inner.disease + && !disease_terms.contains(&(oc.id.as_str(), oc.label.as_str())) + { + violations.push(LintViolation::new( + ViolationSeverity::Warning, + LintRule::rule_id(self), + NonEmptyVec::with_single_entry( + diagnosis.pointer().clone().down("disease").clone(), + ), + )) + } } } diff --git a/src/rules/resources.rs b/src/rules/resources.rs index 0ae21ea..0b6b147 100644 --- a/src/rules/resources.rs +++ b/src/rules/resources.rs @@ -8,8 +8,9 @@ use crate::report::traits::RuleReport; use crate::report::traits::{CompileReport, RegisterableReport, ReportFromContext}; use crate::rules::rule_registration::RuleRegistration; use crate::rules::traits::{LintRule, RuleCheck, RuleFromContext, RuleMetaData}; -use crate::tree::node_repository::List; use crate::tree::pointer::Pointer; +use crate::tree::querying::presentation::Grouped; +use crate::tree::querying::queries::convenience::GroupedIndividuals; use crate::tree::traits::{LocatableNode, Node}; use phenolint_macros::{register_report, register_rule}; use phenopackets::schema::v2::core::{OntologyClass, Resource}; @@ -35,28 +36,34 @@ impl RuleFromContext for CuriesHaveResourcesRule { } impl RuleCheck for CuriesHaveResourcesRule { - type Data<'a> = (List<'a, OntologyClass>, List<'a, Resource>); - - fn check(&self, data: Self::Data<'_>) -> Vec { - let known_prefixes: HashSet<_> = data - .1 - .iter() - .map(|r| r.inner.namespace_prefix.as_str()) - .collect(); + type Query = ( + GroupedIndividuals, + GroupedIndividuals, + ); + fn check(&self, data: (Grouped, Grouped)) -> Vec { + let (ontology_classes, resources) = data; let mut violations = vec![]; - for node in data.0.iter() { - if let Some(prefix) = find_prefix(node.inner.id.as_str()) - && !known_prefixes.contains(prefix) - { - violations.push(LintViolation::new( - ViolationSeverity::Error, - LintRule::rule_id(self), - node.pointer().clone().into(), // <- warns about the ontology class itself - )); + for (o, r) in ontology_classes.0.iter().zip(resources.0) { + let known_prefixes: HashSet<_> = r + .iter() + .map(|r| r.inner.namespace_prefix.as_str()) + .collect(); + + for node in o.iter() { + if let Some(prefix) = find_prefix(node.inner.id.as_str()) + && !known_prefixes.contains(prefix) + { + violations.push(LintViolation::new( + ViolationSeverity::Error, + LintRule::rule_id(self), + node.pointer().clone().into(), // <- warns about the ontology class itself + )); + } } } + violations } } @@ -66,8 +73,8 @@ mod test_curies_have_resources { use crate::rules::resources::CuriesHaveResourcesRule; use crate::rules::traits::{RuleCheck, RuleMetaData}; use crate::tree::node::MaterializedNode; - use crate::tree::node_repository::List; use crate::tree::pointer::Pointer; + use crate::tree::querying::presentation::Grouped; use phenopackets::schema::v2::core::OntologyClass; #[test] @@ -82,8 +89,8 @@ mod test_curies_have_resources { Default::default(), Pointer::from("/phenotypicFeatures/0/type"), )]; - let resources = []; - let data = (List(&ocs), List(&resources)); + let resources = vec![vec![]]; + let data = (Grouped(vec![ocs.to_vec()]), Grouped(resources)); let violations = rule.check(data); diff --git a/src/rules/rule_registry.rs b/src/rules/rule_registry.rs index 9bd3534..aa1296f 100644 --- a/src/rules/rule_registry.rs +++ b/src/rules/rule_registry.rs @@ -80,7 +80,8 @@ mod tests { use crate::rules::traits::RuleCheck; use crate::rules::traits::RuleFromContext; use crate::rules::traits::RuleMetaData; - use crate::tree::node_repository::List; + use crate::tree::querying::presentation::Flattened; + use crate::tree::querying::queries::convenience::All; use phenolint_macros::register_rule; use phenopackets::schema::v2::core::OntologyClass; use rstest::rstest; @@ -100,9 +101,9 @@ mod tests { } } impl RuleCheck for TestRule { - type Data<'a> = List<'a, OntologyClass>; + type Query = All; - fn check(&self, _: Self::Data<'_>) -> Vec { + fn check(&self, _: Flattened) -> Vec { todo!() } } diff --git a/src/rules/traits.rs b/src/rules/traits.rs index 97173cb..7a8349e 100644 --- a/src/rules/traits.rs +++ b/src/rules/traits.rs @@ -1,15 +1,17 @@ use crate::LinterContext; use crate::diagnostics::LintViolation; use crate::error::FromContextError; -use crate::tree::node_repository::NodeRepository; +use crate::tree::flat_node_repository::FlatNodeRepository; +use crate::tree::querying::traits::QueryStrategy; -pub trait LintRule: RuleFromContext + Send + Sync { +pub trait LintRule: Send + Sync { fn rule_id(&self) -> &str; - fn check_erased(&self, board: &NodeRepository) -> Vec; + // Needs to be concrete type, because NodeRepository trait is not dyn compatible :( + fn check_erased(&self, board: &FlatNodeRepository) -> Vec; } -pub trait RuleMetaData: Send + Sync { +pub trait RuleMetaData { fn rule_id(&self) -> &str; } @@ -20,28 +22,22 @@ pub trait RuleFromContext { } pub trait RuleCheck: Send + Sync + 'static { - type Data<'a>: LintData<'a> + ?Sized; - fn check(&self, data: Self::Data<'_>) -> Vec; + type Query: QueryStrategy; + fn check(&self, data: ::Output) -> Vec; } impl LintRule for T where T: RuleCheck + RuleFromContext + RuleMetaData, - for<'a> ::Data<'a>: Sized, + for<'a> ::Query: Sized, { fn rule_id(&self) -> &str { self.rule_id() } - fn check_erased(&self, board: &NodeRepository) -> Vec { - let data = ::Data::fetch(board); + fn check_erased(&self, board: &FlatNodeRepository) -> Vec { + let data = ::Query::query(board); self.check(data) } } - -pub trait LintData<'a> { - fn fetch(board: &'a NodeRepository) -> Self - where - Self: Sized; -} diff --git a/src/schema_validation/validator.rs b/src/schema_validation/validator.rs index 13dfa40..d0c8016 100644 --- a/src/schema_validation/validator.rs +++ b/src/schema_validation/validator.rs @@ -131,10 +131,9 @@ impl Default for PhenopacketSchemaValidator { #[cfg(test)] mod tests { use super::*; - use crate::test_utils::json_phenopacket_dir; + use crate::test_utils::test_phenopacket_as_value; use rstest::{fixture, rstest}; use serde_json::json; - use std::fs; #[fixture] #[once] @@ -144,9 +143,7 @@ mod tests { #[fixture] fn base_phenopacket() -> Value { - let phenostr = - fs::read_to_string(json_phenopacket_dir()).expect("Could not read test file"); - serde_json::from_str(&phenostr).expect("Invalid JSON in test file") + test_phenopacket_as_value() } #[rstest] @@ -231,16 +228,14 @@ mod tests { } #[rstest] - fn test_validator_thread_safety() { + fn test_validator_thread_safety(base_phenopacket: Value) { let validator = std::sync::Arc::new(PhenopacketSchemaValidator::default()); - let phenostr = fs::read_to_string(json_phenopacket_dir()).unwrap(); - let pp: Value = serde_json::from_str(&phenostr).unwrap(); let mut handles = vec![]; for _ in 0..5 { let v_clone = validator.clone(); - let pp_clone = pp.clone(); + let pp_clone = base_phenopacket.clone(); handles.push(std::thread::spawn(move || { let res = v_clone.validate_phenopacket(&pp_clone); assert!(res.is_ok()); diff --git a/src/test_utils.rs b/src/test_utils.rs index b707610..0353815 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -2,6 +2,9 @@ use crate::diagnostics::LintFinding; use once_cell::sync::Lazy; use ontolius::io::OntologyLoaderBuilder; use ontolius::ontology::csr::FullCsrOntology; +use phenopackets::schema::v2::Phenopacket; +use serde_json::Value; +use std::fs; use std::path::PathBuf; use std::sync::Arc; @@ -15,6 +18,16 @@ pub(crate) fn json_phenopacket_dir() -> PathBuf { assets_dir().join("phenopacket.json") } +pub(crate) fn test_phenopacket_as_value() -> Value { + let phenostr = fs::read_to_string(json_phenopacket_dir()).expect("Could not read test file"); + serde_json::from_str(&phenostr).expect("Invalid JSON in test file") +} +#[allow(dead_code)] +pub(crate) fn test_phenopacket() -> Phenopacket { + let pp_dir = json_phenopacket_dir(); + serde_json::from_reader(std::fs::File::open(pp_dir).unwrap()).unwrap() +} + pub(crate) static HPO: Lazy> = Lazy::new(|| init_ontolius(assets_dir().join("hp.toy.json"))); diff --git a/src/tree/error.rs b/src/tree/error.rs new file mode 100644 index 0000000..2644f96 --- /dev/null +++ b/src/tree/error.rs @@ -0,0 +1,7 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum NodeRepositoryError { + #[error("Cant Reinstantiate Node at '{0}' with type '{1}'.")] + CantReinstantiateNode(String, String), +} diff --git a/src/tree/flat_node_repository.rs b/src/tree/flat_node_repository.rs new file mode 100644 index 0000000..de58239 --- /dev/null +++ b/src/tree/flat_node_repository.rs @@ -0,0 +1,404 @@ +#![allow(dead_code)] +use crate::materializer::NodeMaterializer; +use crate::tree::abstract_pheno_tree::AbstractTreeTraversal; +use crate::tree::error::NodeRepositoryError; +use crate::tree::node::MaterializedNode; +use crate::tree::pointer::Pointer; +use crate::tree::scopes::{ScopeLayer, ScopeMappings}; +use crate::tree::traits::{LocatableNode, NodeRepository, NodeRepositoryBuilder}; +use serde_json::Value; +use std::any::{Any, TypeId}; +use std::collections::{BTreeMap, HashMap}; +use std::ops::Range; + +#[derive(Debug)] +struct NodeEntry { + type_id: TypeId, + scope: ScopeLayer, + is_scope_boundary: bool, + inner: Box, +} + +pub struct FlatNodeRepository { + node_store: BTreeMap, + span_store: BTreeMap>, + scope_mappings: ScopeMappings, +} + +impl FlatNodeRepository { + pub(crate) fn new() -> Self { + Self { + node_store: BTreeMap::new(), + span_store: BTreeMap::new(), + scope_mappings: ScopeMappings::new(), + } + } + + fn get_subtree_spans(&self, root_path: &str) -> HashMap> { + self.span_store + .range::(root_path.to_string()..) + .take_while(|(k, _)| k.starts_with(root_path)) + .map(|(p, r)| (Pointer::from(p.as_str()), r.clone())) + .collect() + } + + fn cast_entry( + &self, + path: &str, + entry: &NodeEntry, + ) -> Result, NodeRepositoryError> + where + NodeType: Clone + 'static, + { + let content_ref = entry.inner.downcast_ref::().ok_or_else(|| { + NodeRepositoryError::CantReinstantiateNode( + path.to_string(), + std::any::type_name::().to_string(), + ) + })?; + + let content = content_ref.clone(); + let spans = self.get_subtree_spans(path); + + Ok(MaterializedNode::new(content, spans, Pointer::from(path))) + } +} + +impl NodeRepository for FlatNodeRepository { + fn insert( + &mut self, + node: MaterializedNode, + ) -> Result<(), NodeRepositoryError> { + let type_id = TypeId::of::(); + + let scope = self.scope_mappings.derive_scope(node.pointer(), &type_id); + let is_scope_boundary = self.scope_mappings.is_scope_boundary(&type_id); + + for (ptr, span) in node.spans() { + self.span_store + .entry(ptr.position().to_string()) + .or_insert_with(|| span.clone()); + } + + let ptr = node.pointer().clone(); + + let entry = NodeEntry { + type_id, + scope, + is_scope_boundary, + inner: Box::new(node.inner), + }; + + self.node_store.insert(ptr.to_string(), entry); + + Ok(()) + } + + fn get_all(&self) -> Result>, NodeRepositoryError> + where + NodeType: Clone + 'static, + { + let target_type = TypeId::of::(); + + let nodes = self + .node_store + .iter() + .filter(|(_, entry)| entry.type_id == target_type) + .map(|(path, entry)| self.cast_entry::(path.as_str(), entry)) + .collect::>, NodeRepositoryError>>()?; + + Ok(nodes) + } + + fn get_nodes_in_scope( + &self, + scope: ScopeLayer, + ) -> Result>, NodeRepositoryError> + where + NodeType: Clone + 'static, + { + let target_type = TypeId::of::(); + + let nodes = self + .node_store + .iter() + .filter(|(_, entry)| entry.type_id == target_type && entry.scope == scope) + .map(|(path, entry)| self.cast_entry::(path, entry)) + .collect::>, NodeRepositoryError>>()?; + + Ok(nodes) + } + + fn get_nodes_for_scope_per_top_level_element( + &self, + scope: ScopeLayer, + ) -> Result>>, NodeRepositoryError> + where + NodeType: Clone + 'static, + { + let target_type = TypeId::of::(); + let top_levels: Vec<&String> = self + .node_store + .iter() + .filter(|(_, entry)| entry.is_scope_boundary && entry.scope == scope) + .map(|(path, _)| path) + .collect(); + + let mut output = Vec::new(); + + for tl_path in top_levels { + let children = self + .node_store + .range::(tl_path.to_string()..) + .take_while(|(k, _)| k.starts_with(tl_path)) + .filter(|(_, entry)| entry.type_id == target_type && entry.scope == scope) + .map(|(path, entry)| self.cast_entry::(path, entry)) + .collect::>, NodeRepositoryError>>()?; + + output.push(children); + } + + Ok(output) + } +} + +pub(crate) struct FlatNodeRepositoryBuilder; + +impl NodeRepositoryBuilder for FlatNodeRepositoryBuilder { + fn build(tree: Value, spans: HashMap>) -> FlatNodeRepository { + let mut repo = FlatNodeRepository::new(); + let mut materialized = NodeMaterializer; + for node in AbstractTreeTraversal::new(tree, spans).traverse() { + materialized.materialize_nodes(&node, &mut repo); + } + repo + } +} + +#[cfg(test)] +mod test_builder { + use super::*; + use phenopackets::schema::v2::Phenopacket; + use phenopackets::schema::v2::core::{MetaData, Resource}; + + #[test] + fn test_builder_single_phenopacket() { + let test_pp = Phenopacket { + id: "some_id".to_string(), + meta_data: Some(MetaData { + created: Some(Default::default()), + created_by: "Daniel The Man".to_string(), + submitted_by: "Peter Hobbitson".to_string(), + resources: vec![Resource { + id: "HP".to_string(), + name: "HPO".to_string(), + url: "www.hpo.com".to_string(), + version: "2.0".to_string(), + namespace_prefix: "hp".to_string(), + iri_prefix: "prefix".to_string(), + }], + phenopacket_schema_version: "2".to_string(), + ..Default::default() + }), + ..Default::default() + }; + + let values = serde_json::to_value(&test_pp).unwrap(); + let repo = FlatNodeRepositoryBuilder::build(values, HashMap::new()); + + assert_eq!(repo.node_store.len(), 2); + + let boundries: Vec<_> = repo + .node_store + .values() + .filter(|node| node.is_scope_boundary) + .collect(); + + assert_eq!(boundries.len(), 1); + + let pp_node = boundries.first().unwrap(); + assert_eq!(pp_node.type_id, TypeId::of::()); + assert_eq!(pp_node.scope, ScopeLayer::Individual); + } +} + +#[cfg(test)] +mod tests_repository { + use super::*; + use crate::tree::pointer::Pointer; + use crate::tree::traits::NodeRepositoryBuilder; + use phenopackets::schema::v2::core::{MetaData, OntologyClass, Resource}; + use phenopackets::schema::v2::{Cohort, Phenopacket}; + use std::collections::HashMap; + use std::fs; + use std::path::PathBuf; + + fn generate_test_cohort() -> Cohort { + let assets_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("assets"); + + let json_phenopacket_path = assets_dir.join("phenopacket.json"); + let phenostr = fs::read_to_string(json_phenopacket_path).unwrap(); + + let pp: Phenopacket = serde_json::from_str(&phenostr).unwrap(); + + Cohort { + id: "Some".to_string(), + description: "".to_string(), + members: vec![pp.clone(), pp.clone()], + files: vec![], + meta_data: Some(MetaData { + created: None, + created_by: "Patrick".to_string(), + submitted_by: "Patrick".to_string(), + resources: vec![Resource { + id: "1".to_string(), + name: "HP".to_string(), + url: "www.example.com".to_string(), + version: "2020-10-10".to_string(), + namespace_prefix: "hp".to_string(), + iri_prefix: "hp".to_string(), + }], + updates: vec![], + phenopacket_schema_version: "2".to_string(), + external_references: vec![], + }), + } + } + + struct NodeRepoUnitTester; + + impl NodeRepoUnitTester { + pub fn test(cohort: Cohort) + where + R: NodeRepository, + B: NodeRepositoryBuilder, + { + let node_repo: R = Self::build_test_repo::(&cohort); + Self::test_get_nodes_for_scope_per_top_level_element(&node_repo, &cohort); + Self::test_get_all_nodes(&node_repo, &cohort); + Self::test_get_nodes_in_scope(&node_repo, &cohort); + } + + fn build_test_repo(cohort: &Cohort) -> R + where + R: NodeRepository, + B: NodeRepositoryBuilder, + { + let value = serde_json::to_value(cohort).unwrap(); + B::build(value, HashMap::new()) + } + + fn test_get_nodes_in_scope(repo: &R, cohort: &Cohort) + where + R: NodeRepository, + { + let cohort_level_resources = repo + .get_nodes_in_scope::(ScopeLayer::Aggregated) + .unwrap(); + + for node_resource in cohort_level_resources.iter() { + assert!( + cohort + .meta_data + .clone() + .unwrap() + .resources + .contains(&node_resource.inner) + ); + } + assert_eq!( + cohort.meta_data.clone().unwrap().resources.len(), + cohort_level_resources.len() + ); + + let pp_level_resources = repo + .get_nodes_in_scope::(ScopeLayer::Individual) + .unwrap(); + + let all_pp_resources: Vec = cohort + .members + .clone() + .iter() + .flat_map(|pp| pp.meta_data.clone().unwrap().resources) + .collect(); + + for node_resource in pp_level_resources.iter() { + assert!(all_pp_resources.contains(&node_resource.inner)); + } + assert_eq!(pp_level_resources.len(), all_pp_resources.len()); + } + + fn test_get_nodes_for_scope_per_top_level_element(repo: &R, cohort: &Cohort) + where + R: NodeRepository, + { + let retrieved = repo + .get_nodes_for_scope_per_top_level_element::(ScopeLayer::Individual) + .unwrap(); + + for (member, nodes) in cohort.members.iter().zip(&retrieved) { + let resources = &member.meta_data.as_ref().unwrap().resources; + for (resource, node) in resources.iter().zip(nodes) { + assert_eq!(&node.inner, resource); + } + } + } + + fn test_get_all_nodes(repo: &R, cohort: &Cohort) + where + R: NodeRepository, + { + let retrieved = repo.get_all::().unwrap(); + + let mut n_resources = cohort.meta_data.clone().unwrap().resources.len(); + for pp in cohort.members.clone() { + n_resources += pp.meta_data.unwrap().resources.len() + } + + assert_eq!( + retrieved.len(), + n_resources, + "Got {} resources from repo, but expected {}", + retrieved.len(), + n_resources + ); + } + } + + #[test] + fn test_node_repository() { + let cohort = generate_test_cohort(); + NodeRepoUnitTester::test::(cohort); + } + + #[test] + fn test_insert() { + let mut repo = FlatNodeRepository::new(); + + let node_pointer = Pointer::from("phenotypicFeatures/0/type"); + let mut spans = BTreeMap::new(); + spans.insert(node_pointer.to_string().clone(), 0usize..50usize); + + let node = MaterializedNode::new( + OntologyClass { + id: "HP:0000001".to_string(), + label: "All".to_string(), + }, + spans + .iter() + .map(|(key, val)| (Pointer::from(key.as_str()), val.clone())) + .collect(), + node_pointer.clone(), + ); + repo.insert(node).unwrap(); + + let node_entry = repo.node_store.get(&node_pointer.to_string()).unwrap(); + + assert_eq!(repo.span_store, spans); + assert_eq!(node_entry.type_id, TypeId::of::()); + assert_eq!(node_entry.scope, ScopeLayer::Individual); + assert!(!node_entry.is_scope_boundary); + } +} diff --git a/src/tree/mod.rs b/src/tree/mod.rs index a26bcb6..e801c0d 100644 --- a/src/tree/mod.rs +++ b/src/tree/mod.rs @@ -1,6 +1,9 @@ pub(crate) mod abstract_pheno_tree; +mod error; +pub(crate) mod flat_node_repository; pub mod node; -pub mod node_repository; pub mod pointer; +pub mod querying; +mod scopes; pub mod traits; mod utils; diff --git a/src/tree/node.rs b/src/tree/node.rs index cbf1431..1f4e1ec 100644 --- a/src/tree/node.rs +++ b/src/tree/node.rs @@ -38,15 +38,16 @@ impl LocatableNode for DynamicNode { } } -pub struct MaterializedNode { - pub inner: T, +#[derive(Clone, Debug)] +pub struct MaterializedNode { + pub inner: NodeType, spans: HashMap>, pointer: Pointer, } -impl MaterializedNode { +impl MaterializedNode { pub fn new( - materialized_node: T, + materialized_node: NodeType, spans: HashMap>, pointer: Pointer, ) -> Self { @@ -57,16 +58,21 @@ impl MaterializedNode { } } - pub(crate) fn from_dynamic(materialized: T, dyn_node: &DynamicNode) -> Self { + pub(crate) fn from_dynamic(materialized: NodeType, dyn_node: &DynamicNode) -> Self { Self::new( materialized, dyn_node.spans.clone(), dyn_node.pointer().clone(), ) } + + #[allow(dead_code)] + pub(crate) fn spans(&self) -> &HashMap> { + &self.spans + } } -impl RetrievableNode for MaterializedNode { +impl RetrievableNode for MaterializedNode { fn value_at(&self, ptr: &Pointer) -> Option> { let node_opt = serde_json::to_value(&self.inner).ok()?; let value = node_opt.pointer(ptr.position())?.clone(); @@ -74,7 +80,7 @@ impl RetrievableNode for MaterializedNode { } } -impl LocatableNode for MaterializedNode { +impl LocatableNode for MaterializedNode { fn span_at(&self, ptr: &Pointer) -> Option<&Range> { self.spans.get(ptr) } diff --git a/src/tree/node_repository.rs b/src/tree/node_repository.rs deleted file mode 100644 index d1eeac2..0000000 --- a/src/tree/node_repository.rs +++ /dev/null @@ -1,98 +0,0 @@ -use crate::rules::traits::LintData; -use crate::tree::node::MaterializedNode; -use crate::tree::pointer::Pointer; -use crate::tree::traits::LocatableNode; - -use std::any::{Any, TypeId}; -use std::collections::HashMap; -use std::ops::Deref; - -#[derive(Default)] -pub struct NodeRepository { - board: HashMap>, -} - -impl NodeRepository { - pub fn new() -> NodeRepository { - NodeRepository { - board: HashMap::new(), - } - } - - fn get_raw(&self) -> &[MaterializedNode] { - self.board - .get(&TypeId::of::()) - .and_then(|b| b.downcast_ref::>>()) - .map(|v| v.as_slice()) - .unwrap_or(&[]) - } - - pub fn insert(&mut self, node: MaterializedNode) { - self.board - .entry(TypeId::of::()) - .or_insert_with(|| Box::new(Vec::>::new())) - .downcast_mut::>>() - .unwrap() - .push(node); - } - - pub fn node_by_pointer(&self, ptr: &Pointer) -> Option<&MaterializedNode> { - for nodes in self.board.values() { - let casted_node = nodes - .downcast_ref::>>() - .expect("Should be downcastable"); - - for node in casted_node.iter() { - if node.pointer() == ptr { - return Some(node); - } - } - } - None - } -} - -pub struct Single<'a, T: 'static>(pub Option<&'a MaterializedNode>); - -impl<'a, T> LintData<'a> for Single<'a, T> { - fn fetch(board: &'a NodeRepository) -> Self { - Single(board.get_raw::().first()) - } -} - -pub struct List<'a, T: 'static>(pub &'a [MaterializedNode]); - -impl<'a, T> Deref for List<'a, T> { - type Target = &'a [MaterializedNode]; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl<'a, T> LintData<'a> for List<'a, T> { - fn fetch(board: &'a NodeRepository) -> Self { - List(board.get_raw()) - } -} - -impl<'a, A, B> LintData<'a> for (A, B) -where - A: LintData<'a>, - B: LintData<'a>, -{ - fn fetch(board: &'a NodeRepository) -> Self { - (A::fetch(board), B::fetch(board)) - } -} - -impl<'a, A, B, C> LintData<'a> for (A, B, C) -where - A: LintData<'a>, - B: LintData<'a>, - C: LintData<'a>, -{ - fn fetch(board: &'a NodeRepository) -> Self { - (A::fetch(board), B::fetch(board), C::fetch(board)) - } -} diff --git a/src/tree/querying/mod.rs b/src/tree/querying/mod.rs new file mode 100644 index 0000000..6bf3045 --- /dev/null +++ b/src/tree/querying/mod.rs @@ -0,0 +1,3 @@ +pub mod presentation; +pub mod queries; +pub mod traits; diff --git a/src/tree/querying/presentation.rs b/src/tree/querying/presentation.rs new file mode 100644 index 0000000..53f3528 --- /dev/null +++ b/src/tree/querying/presentation.rs @@ -0,0 +1,71 @@ +use crate::tree::node::MaterializedNode; +use crate::tree::querying::traits::QueryPresentation; +use std::ops::Deref; + +pub struct Flattened(pub Vec>); + +impl Deref for Flattened { + type Target = Vec>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl QueryPresentation>> for Flattened { + fn present(query_res: Vec>) -> Self { + Flattened(query_res) + } +} + +impl QueryPresentation>>> for Flattened { + fn present(query_res: Vec>>) -> Self { + Flattened(query_res.into_iter().flatten().collect()) + } +} + +pub struct First(pub Option>); + +impl Deref for First { + type Target = Option>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl QueryPresentation>> for First { + fn present(query_res: Vec>) -> Self { + First(query_res.first().cloned()) + } +} + +impl QueryPresentation>>> for First { + fn present(query_res: Vec>>) -> Self { + for i in query_res { + if let Some(j) = i.into_iter().next() { + return First(Some(j)); + } + } + + First(None) + } +} + +pub struct Grouped(pub Vec>>); + +impl Deref for Grouped { + type Target = Vec>>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl QueryPresentation>>> + for Grouped +{ + fn present(query_res: Vec>>) -> Self { + Grouped(query_res) + } +} diff --git a/src/tree/querying/queries.rs b/src/tree/querying/queries.rs new file mode 100644 index 0000000..b7e91b0 --- /dev/null +++ b/src/tree/querying/queries.rs @@ -0,0 +1,128 @@ +use crate::tree::node::MaterializedNode; + +use crate::tree::traits::NodeRepository; + +use crate::tree::querying::traits::{QueryPresentation, QueryStrategy, ScopeDefinition}; +use std::marker::PhantomData; + +#[derive(Debug)] +pub struct QueryAllNodes(PhantomData<(Presentation, NodeType)>); + +impl>>> + QueryStrategy for QueryAllNodes +{ + type Output = Presentation; + fn query(node_repo: &impl NodeRepository) -> Self::Output { + Presentation::present(node_repo.get_all::().unwrap_or_default()) + } +} +#[derive(Debug)] +pub struct QueryNodesInScope( + PhantomData<(Scope, Presentation, NodeType)>, +); + +impl< + Scope: ScopeDefinition, + NodeType: Clone + 'static, + Presentation: QueryPresentation>>, +> QueryStrategy for QueryNodesInScope +{ + type Output = Presentation; + fn query(node_repo: &impl NodeRepository) -> Self::Output { + let query_result = node_repo + .get_nodes_in_scope::(Scope::layer()) + .unwrap_or_default(); + + Presentation::present(query_result) + } +} + +#[derive(Debug)] +pub struct QueryGroupedNodes( + PhantomData<(Scope, Presentation, NodeType)>, +); + +impl< + Scope: ScopeDefinition, + NodeType: Clone + 'static, + Presentation: QueryPresentation>>>, +> QueryStrategy for QueryGroupedNodes +{ + type Output = Presentation; + fn query(node_repo: &impl NodeRepository) -> Self::Output { + let query_result = node_repo + .get_nodes_for_scope_per_top_level_element::(Scope::layer()) + .unwrap_or_default(); + + Presentation::present(query_result) + } +} + +pub mod convenience { + use crate::tree::querying::presentation::{First, Flattened, Grouped}; + use crate::tree::querying::queries::{QueryAllNodes, QueryGroupedNodes, QueryNodesInScope}; + use phenopackets::schema::v2::Phenopacket; + + // More to be added. + pub type All = QueryAllNodes>; + pub type GroupedIndividuals = + QueryGroupedNodes>; + pub type SingleInScope = QueryNodesInScope>; +} + +mod temp_test { + #![allow(dead_code)] + #![allow(unused)] + // Testing and see how it would work from here: + + use crate::tree::querying::presentation::{First, Flattened}; + use crate::tree::querying::queries::convenience::{All, GroupedIndividuals, SingleInScope}; + use crate::tree::querying::queries::{QueryAllNodes, QueryNodesInScope}; + use crate::tree::querying::traits::QueryStrategy; + use phenopackets::schema::v2::Phenopacket; + use phenopackets::schema::v2::core::{OntologyClass, PhenotypicFeature}; + + trait TheRuleTrait { + type Query: QueryStrategy; + + fn check_erased(&'_ self, board: ::Output) -> bool; + } + + struct __RuleImplementation1; + + impl TheRuleTrait for __RuleImplementation1 { + type Query = ( + QueryNodesInScope>, + QueryAllNodes>, + ); + + fn check_erased(&'_ self, board: ::Output) -> bool { + todo!() + } + } + + struct __RuleImplementation2; + + impl TheRuleTrait for __RuleImplementation2 { + type Query = ( + All, + SingleInScope, + GroupedIndividuals, + ); + + fn check_erased(&self, board: ::Output) -> bool { + let a = board; + todo!() + } + } + + struct __RuleImplementation3; + + impl TheRuleTrait for __RuleImplementation3 { + type Query = GroupedIndividuals; + + fn check_erased(&self, board: ::Output) -> bool { + todo!() + } + } +} diff --git a/src/tree/querying/traits.rs b/src/tree/querying/traits.rs new file mode 100644 index 0000000..3bfbc14 --- /dev/null +++ b/src/tree/querying/traits.rs @@ -0,0 +1,55 @@ +use crate::tree::scopes::ScopeLayer; +use crate::tree::traits::NodeRepository; + +pub trait QueryStrategy { + type Output; + #[allow(unused)] + fn query(node_repo: &impl NodeRepository) -> Self::Output; +} + +macro_rules! impl_query_strategy_tuple { + () => { + impl QueryStrategy for () { + type Output = (); + fn query(_node_repo: &impl NodeRepository) -> Self::Output { + + } + } + }; + + ($head:ident $(, $tail:ident)*) => { + impl_query_strategy_tuple!($($tail),*); + + impl<$head, $($tail),*> QueryStrategy for ($head, $($tail,)*) + where + $head: QueryStrategy, + $($tail: QueryStrategy),* + { + type Output = ($head::Output, $($tail::Output,)*); + + fn query(node_repo: &impl NodeRepository) -> Self::Output { + ( + $head::query(node_repo), + $($tail::query(node_repo),)* + ) + } + } + }; +} + +impl_query_strategy_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12); + +#[allow(unused)] +pub(crate) trait QueryPresentation { + fn present(query_res: Input) -> Self + where + Self: Sized; +} + +pub trait ScopeDefinition { + fn layer() -> ScopeLayer; + + fn partitioning_fields() -> &'static [&'static str] { + &[] + } +} diff --git a/src/tree/scopes.rs b/src/tree/scopes.rs new file mode 100644 index 0000000..430513a --- /dev/null +++ b/src/tree/scopes.rs @@ -0,0 +1,195 @@ +use crate::tree::pointer::Pointer; +use crate::tree::querying::traits::ScopeDefinition; +use phenopackets::schema::v2::{Cohort, Family, Phenopacket}; +use std::any::TypeId; +use std::borrow::Cow; +use std::cell::Cell; +use std::collections::HashMap; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum ScopeLayer { + Individual = 0, + // TODO: Find better name than Aggregated + Aggregated = 1, +} + +impl ScopeDefinition for Phenopacket { + fn layer() -> ScopeLayer { + ScopeLayer::Individual + } + + fn partitioning_fields() -> &'static [&'static str] { + &["members", "relatives", "proband"] + } +} + +impl ScopeDefinition for Family { + fn layer() -> ScopeLayer { + ScopeLayer::Aggregated + } +} + +impl ScopeDefinition for Cohort { + fn layer() -> ScopeLayer { + ScopeLayer::Aggregated + } +} + +pub(crate) struct ScopeMappings { + scope_by_type_id: HashMap, + boundaries: HashMap<&'static str, TypeId>, + max_seen_scope: Cell, +} + +impl ScopeMappings { + pub(crate) fn new() -> Self { + let mut scope_map = Self { + max_seen_scope: Cell::from(ScopeLayer::Individual), + scope_by_type_id: HashMap::new(), + boundaries: HashMap::new(), + }; + + scope_map.register::(); + scope_map.register::(); + scope_map.register::(); + + scope_map + } + + fn register(&mut self) { + let type_id = TypeId::of::(); + self.scope_by_type_id.insert(type_id, NodeType::layer()); + + for b_field in NodeType::partitioning_fields() { + self.boundaries.insert(b_field, type_id); + } + } + + pub fn get_scope(&self, type_id: &TypeId) -> Option { + self.scope_by_type_id.get(type_id).copied() + } + + pub fn is_scope_boundary(&self, type_id: &TypeId) -> bool { + self.scope_by_type_id.contains_key(type_id) + } + + pub fn derive_scope(&self, path: &Pointer, type_id: &TypeId) -> ScopeLayer { + if let Some(scope) = self.get_scope(type_id) { + let current_max = self.max_seen_scope.get(); + self.max_seen_scope.set(current_max.max(scope)); + } + + let segments: Vec> = path.iter_segments().collect(); + + for segment in segments.iter().rev() { + if let Some(boundary_type_id) = self.boundaries.get(segment.as_ref()) { + return self + .scope_by_type_id + .get(boundary_type_id) + .copied() + .expect("Configuration error: Boundary points to unknown scope"); + } + } + + self.max_seen_scope.get() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use phenopackets::schema::v2::core::Resource; + + #[test] + fn test_derive_scope_single_pp() { + let scope_map = ScopeMappings::new(); + + let type_id = TypeId::of::(); + + assert_eq!( + scope_map.derive_scope(&Pointer::at_root(), &type_id), + ScopeLayer::Individual + ); + } + + #[test] + fn test_derive_scope_cohort() { + let scope_map = ScopeMappings::new(); + + let type_id = TypeId::of::(); + + assert_eq!( + scope_map.derive_scope(&Pointer::at_root(), &type_id), + ScopeLayer::Aggregated + ); + } + + #[test] + fn test_derive_scope_family() { + let scope_map = ScopeMappings::new(); + + let type_id = TypeId::of::(); + + assert_eq!( + scope_map.derive_scope(&Pointer::at_root(), &type_id), + ScopeLayer::Aggregated + ); + } + + #[test] + fn test_derive_scope_cohort_with_pp() { + let scope_map = ScopeMappings::new(); + + let type_id = TypeId::of::(); + + assert_eq!( + scope_map.derive_scope(&Pointer::at_root(), &type_id), + ScopeLayer::Aggregated + ); + + let type_id = TypeId::of::(); + + assert_eq!( + scope_map.derive_scope( + &Pointer::from( + format!("/{}", Phenopacket::partitioning_fields().first().unwrap()).as_str() + ), + &type_id + ), + ScopeLayer::Individual + ); + } + + #[test] + fn test_derive_scope_cohort_with_random() { + let scope_map = ScopeMappings::new(); + + let type_id = TypeId::of::(); + + assert_eq!( + scope_map.derive_scope(&Pointer::at_root(), &type_id), + ScopeLayer::Aggregated + ); + + let type_id = TypeId::of::(); + + assert_eq!( + scope_map.derive_scope( + &Pointer::from( + format!( + "/{}/metaData/resources", + Phenopacket::partitioning_fields().first().unwrap() + ) + .as_str() + ), + &type_id + ), + ScopeLayer::Individual + ); + + assert_eq!( + scope_map.derive_scope(&Pointer::from("/resources"), &type_id), + ScopeLayer::Aggregated + ); + } +} diff --git a/src/tree/traits.rs b/src/tree/traits.rs index 5701813..63fed1f 100644 --- a/src/tree/traits.rs +++ b/src/tree/traits.rs @@ -1,6 +1,11 @@ +#![allow(dead_code)] +use crate::tree::error::NodeRepositoryError; +use crate::tree::node::MaterializedNode; use crate::tree::pointer::Pointer; +use crate::tree::scopes::ScopeLayer; use serde_json::Value; use std::borrow::Cow; +use std::collections::HashMap; use std::ops::Range; pub trait Node: LocatableNode + RetrievableNode {} @@ -15,3 +20,34 @@ pub trait LocatableNode { pub trait RetrievableNode { fn value_at(&self, ptr: &Pointer) -> Option>; } + +pub trait NodeRepository { + fn insert( + &mut self, + node: MaterializedNode, + ) -> Result<(), NodeRepositoryError>; + + // Gets all nodes of a type + // Example: Check if all CURIE id's are formatted correctly + fn get_all(&self) -> Result>, NodeRepositoryError>; + + // Gets all nodes of a type in a scope + // Example: Get all nodes of the phenopacket scope + all resources of the cohort. Check if pp resources are in cohort + fn get_nodes_in_scope( + &self, + scope: ScopeLayer, + ) -> Result>, NodeRepositoryError>; + + // All nodes of a type for cases per case + // Example: Check if all curie id's are represented in the resources in a phenopacket + fn get_nodes_for_scope_per_top_level_element( + &self, + scope: ScopeLayer, + ) -> Result>>, NodeRepositoryError>; +} + +pub(crate) trait NodeRepositoryBuilder { + fn build(tree: Value, spans: HashMap>) -> T + where + Self: Sized; +} diff --git a/tests/test_custom_rule.rs b/tests/test_custom_rule.rs index 75acef8..9906842 100644 --- a/tests/test_custom_rule.rs +++ b/tests/test_custom_rule.rs @@ -18,8 +18,9 @@ use phenolint::report::specs::{LabelSpecs, ReportSpecs}; use phenolint::report::traits::{CompileReport, RegisterableReport, ReportFromContext, RuleReport}; use phenolint::rules::traits::LintRule; use phenolint::rules::traits::{RuleCheck, RuleFromContext}; -use phenolint::tree::node_repository::List; use phenolint::tree::pointer::Pointer; +use phenolint::tree::querying::presentation::Flattened; +use phenolint::tree::querying::queries::convenience::All; use phenolint::tree::traits::Node; use phenolint_macros::{register_patch, register_report, register_rule}; use phenopackets::schema::v2::Phenopacket; @@ -43,9 +44,9 @@ impl RuleFromContext for CustomRule { } impl RuleCheck for CustomRule { - type Data<'a> = List<'a, OntologyClass>; + type Query = All; - fn check(&self, _: Self::Data<'_>) -> Vec { + fn check(&self, _: Flattened) -> Vec { vec![LintViolation::new( ViolationSeverity::Info, LintRule::rule_id(self),