use crate::Object;
use crate::validation::validator::Validator;
use crate::vocab::*;
#[derive(Clone, Copy, PartialEq, Eq)]
enum WorkflowStage {
Design,
Build,
Test,
Learn,
}
impl WorkflowStage {
fn from_iri(iri: &str) -> Option<Self> {
match iri {
SBOL_DESIGN => Some(Self::Design),
SBOL_BUILD => Some(Self::Build),
SBOL_TEST => Some(Self::Test),
SBOL_LEARN => Some(Self::Learn),
_ => None,
}
}
fn preceding(self) -> Self {
match self {
Self::Design => Self::Learn,
Self::Build => Self::Design,
Self::Test => Self::Build,
Self::Learn => Self::Test,
}
}
fn matches_target(self, target: &Object) -> bool {
let types: Vec<&str> = target.rdf_types().iter().map(|iri| iri.as_str()).collect();
let is_implementation = types.contains(&SBOL_IMPLEMENTATION_CLASS);
let is_experimental_data = types.contains(&SBOL_EXPERIMENTAL_DATA_CLASS);
match self {
Self::Design => !is_implementation,
Self::Build => is_implementation,
Self::Test => is_experimental_data,
Self::Learn => !is_implementation,
}
}
fn label(self) -> &'static str {
match self {
Self::Design => "design",
Self::Build => "build",
Self::Test => "test",
Self::Learn => "learn",
}
}
}
impl<'a> Validator<'a> {
pub(crate) fn validate_workflow_rules(&mut self, object: &Object) {
if object
.rdf_types()
.iter()
.any(|iri| iri.as_str() == PROV_ACTIVITY)
{
self.validate_activity_workflow(object);
}
if object
.rdf_types()
.iter()
.any(|iri| iri.as_str() == PROV_USAGE)
{
self.validate_usage_workflow(object);
}
self.validate_was_generated_by_workflow(object);
}
fn validate_activity_workflow(&mut self, activity: &Object) {
let activity_stages: Vec<WorkflowStage> = activity
.iris(SBOL_TYPE)
.filter_map(|iri| WorkflowStage::from_iri(iri.as_str()))
.collect();
if activity_stages.is_empty() {
return;
}
for usage in activity.resources(PROV_QUALIFIED_USAGE) {
let Some(usage_object) = self.document.get(usage) else {
continue;
};
for role in usage_object.iris(PROV_HAD_ROLE) {
let Some(role_stage) = WorkflowStage::from_iri(role.as_str()) else {
continue;
};
let allowed = activity_stages
.iter()
.any(|stage| *stage == role_stage || stage.preceding() == role_stage);
if !allowed {
self.warning(
"sbol3-12901",
usage_object,
Some(PROV_HAD_ROLE),
format!(
"Usage role `{}` is not the same as or preceding any of \
the Activity's workflow stages",
role_stage.label()
),
);
}
}
}
for association in activity.resources(PROV_QUALIFIED_ASSOCIATION) {
let Some(association_object) = self.document.get(association) else {
continue;
};
for role in association_object.iris(PROV_HAD_ROLE) {
let Some(role_stage) = WorkflowStage::from_iri(role.as_str()) else {
continue;
};
if !activity_stages.contains(&role_stage) {
self.warning(
"sbol3-12902",
association_object,
Some(PROV_HAD_ROLE),
format!(
"Association role `{}` does not match any of the Activity's \
workflow stages",
role_stage.label()
),
);
}
}
}
}
fn validate_usage_workflow(&mut self, usage: &Object) {
for role in usage.iris(PROV_HAD_ROLE) {
let Some(stage) = WorkflowStage::from_iri(role.as_str()) else {
continue;
};
for entity in usage.resources(PROV_ENTITY) {
let Some(entity_object) = self.document.get(entity) else {
continue;
};
if !stage.matches_target(entity_object) {
self.warning(
"sbol3-13001",
usage,
Some(PROV_ENTITY),
format!(
"Usage role `{}` requires entity to be {}",
stage.label(),
stage_referred_type_label(stage)
),
);
}
}
}
}
fn validate_was_generated_by_workflow(&mut self, object: &Object) {
for activity_id in object.resources(PROV_WAS_GENERATED_BY) {
let Some(activity) = self.document.get(activity_id) else {
continue;
};
for association in activity.resources(PROV_QUALIFIED_ASSOCIATION) {
let Some(association_object) = self.document.get(association) else {
continue;
};
for role in association_object.iris(PROV_HAD_ROLE) {
let Some(stage) = WorkflowStage::from_iri(role.as_str()) else {
continue;
};
if !stage.matches_target(object) {
self.warning(
"sbol3-10205",
object,
Some(PROV_WAS_GENERATED_BY),
format!(
"wasGeneratedBy Association role `{}` requires the generated \
object to be {}",
stage.label(),
stage_referred_type_label(stage)
),
);
}
}
}
}
}
}
fn stage_referred_type_label(stage: WorkflowStage) -> &'static str {
match stage {
WorkflowStage::Design => "a TopLevel other than Implementation",
WorkflowStage::Build => "an Implementation",
WorkflowStage::Test => "an ExperimentalData",
WorkflowStage::Learn => "an Identified other than Implementation",
}
}