use std::collections::BTreeSet;
use std::fmt;
use crate::client::{
Collection, CombinatorialDerivation, Component, ComponentReference, FeatureRef, SbolObject,
SubComponent,
};
use crate::validation::DocumentSet;
use crate::vocab::SBOL_REFERS_TO;
use crate::{Document, Object, Resource, SbolClass};
pub trait ObjectGraph {
fn get(&self, iri: &Resource) -> Option<&Object>;
fn resolve(&self, iri: &Resource) -> Option<&SbolObject>;
fn iter_typed(&self) -> Box<dyn Iterator<Item = &SbolObject> + '_>;
}
impl ObjectGraph for Document {
fn get(&self, iri: &Resource) -> Option<&Object> {
Document::get(self, iri)
}
fn resolve(&self, iri: &Resource) -> Option<&SbolObject> {
Document::resolve(self, iri)
}
fn iter_typed(&self) -> Box<dyn Iterator<Item = &SbolObject> + '_> {
Box::new(self.typed_objects().iter())
}
}
impl<'a> ObjectGraph for DocumentSet<'a> {
fn get(&self, iri: &Resource) -> Option<&Object> {
DocumentSet::get(self, iri)
}
fn resolve(&self, iri: &Resource) -> Option<&SbolObject> {
self.documents()
.iter()
.find_map(|doc| Document::resolve(doc, iri))
}
fn iter_typed(&self) -> Box<dyn Iterator<Item = &SbolObject> + '_> {
Box::new(
self.documents()
.iter()
.flat_map(|doc| doc.typed_objects().iter()),
)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ReferenceError {
Missing {
on: Resource,
field: &'static str,
},
NotFound(Resource),
WrongType {
iri: Resource,
expected: &'static str,
found: &'static str,
},
Cycle(Vec<Resource>),
}
impl fmt::Display for ReferenceError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ReferenceError::Missing { on, field } => {
write!(f, "object {on} is missing required reference `{field}`")
}
ReferenceError::NotFound(iri) => {
write!(f, "referenced object {iri} is not present in the graph")
}
ReferenceError::WrongType {
iri,
expected,
found,
} => write!(
f,
"referenced object {iri} has class {found}, expected {expected}"
),
ReferenceError::Cycle(path) => {
f.write_str("cyclic refersTo chain: ")?;
for (i, iri) in path.iter().enumerate() {
if i > 0 {
f.write_str(" -> ")?;
}
write!(f, "{iri}")?;
}
Ok(())
}
}
}
}
impl std::error::Error for ReferenceError {}
#[derive(Clone, Debug)]
pub struct FeatureTrace<'a> {
requested: Resource,
target: FeatureRef<'a>,
target_iri: Resource,
path: Vec<Resource>,
}
impl<'a> FeatureTrace<'a> {
pub fn requested(&self) -> &Resource {
&self.requested
}
pub fn target(&self) -> FeatureRef<'a> {
self.target
}
pub fn target_iri(&self) -> &Resource {
&self.target_iri
}
pub fn path(&self) -> &[Resource] {
&self.path
}
}
#[derive(Clone, Debug, Default)]
pub struct VariantSet<'a> {
from_variants: Vec<&'a Component>,
from_collections: Vec<&'a Component>,
from_derivations: Vec<&'a Component>,
}
impl<'a> VariantSet<'a> {
pub fn from_variants(&self) -> &[&'a Component] {
&self.from_variants
}
pub fn from_collections(&self) -> &[&'a Component] {
&self.from_collections
}
pub fn from_derivations(&self) -> &[&'a Component] {
&self.from_derivations
}
pub fn flatten(&self) -> impl Iterator<Item = &'a Component> + '_ {
self.from_variants
.iter()
.copied()
.chain(self.from_collections.iter().copied())
.chain(self.from_derivations.iter().copied())
}
pub fn is_empty(&self) -> bool {
self.from_variants.is_empty()
&& self.from_collections.is_empty()
&& self.from_derivations.is_empty()
}
pub fn len(&self) -> usize {
self.from_variants.len() + self.from_collections.len() + self.from_derivations.len()
}
}
impl SubComponent {
pub fn definition<'a, G>(&self, graph: &'a G) -> Result<&'a Component, ReferenceError>
where
G: ObjectGraph + ?Sized,
{
let iri = self
.instance_of
.as_ref()
.ok_or_else(|| ReferenceError::Missing {
on: self.identity.clone(),
field: "instanceOf",
})?;
let typed = graph
.resolve(iri)
.ok_or_else(|| ReferenceError::NotFound(iri.clone()))?;
match typed {
SbolObject::Component(component) => Ok(component),
other => Err(ReferenceError::WrongType {
iri: iri.clone(),
expected: "Component",
found: other.class().local_name(),
}),
}
}
}
impl ComponentReference {
pub fn target<'a, G>(&self, graph: &'a G) -> Result<FeatureRef<'a>, ReferenceError>
where
G: ObjectGraph + ?Sized,
{
Ok(self.trace(graph)?.target())
}
pub fn trace<'a, G>(&self, graph: &'a G) -> Result<FeatureTrace<'a>, ReferenceError>
where
G: ObjectGraph + ?Sized,
{
let head = self
.refers_to
.as_ref()
.ok_or_else(|| ReferenceError::Missing {
on: self.identity.clone(),
field: "refersTo",
})?
.clone();
let requested = head.clone();
let mut current = head;
let mut path = Vec::new();
let mut visited = BTreeSet::new();
loop {
if !visited.insert(current.clone()) {
path.push(current);
return Err(ReferenceError::Cycle(path));
}
let object = graph
.get(¤t)
.ok_or_else(|| ReferenceError::NotFound(current.clone()))?;
if !object.has_class(SbolClass::ComponentReference) {
let typed = graph
.resolve(¤t)
.ok_or_else(|| ReferenceError::NotFound(current.clone()))?;
let feature =
FeatureRef::from_object(typed).ok_or_else(|| ReferenceError::WrongType {
iri: current.clone(),
expected: "Feature",
found: typed.class().local_name(),
})?;
return Ok(FeatureTrace {
requested,
target: feature,
target_iri: current,
path,
});
}
path.push(current.clone());
let next = object
.first_resource(SBOL_REFERS_TO)
.ok_or_else(|| ReferenceError::Missing {
on: current.clone(),
field: "refersTo",
})?
.clone();
current = next;
}
}
}
impl CombinatorialDerivation {
pub fn variants<'a, G>(&self, graph: &'a G) -> Result<VariantSet<'a>, ReferenceError>
where
G: ObjectGraph + ?Sized,
{
let mut from_variants_iris: BTreeSet<Resource> = BTreeSet::new();
let mut from_collections_iris: BTreeSet<Resource> = BTreeSet::new();
let mut from_derivations_iris: BTreeSet<Resource> = BTreeSet::new();
let mut from_variants = Vec::new();
let mut from_collections = Vec::new();
let mut from_derivations = Vec::new();
for vf_iri in &self.variable_features {
let vf_typed = graph
.resolve(vf_iri)
.ok_or_else(|| ReferenceError::NotFound(vf_iri.clone()))?;
let variable_feature = match vf_typed {
SbolObject::VariableFeature(v) => v,
other => {
return Err(ReferenceError::WrongType {
iri: vf_iri.clone(),
expected: "VariableFeature",
found: other.class().local_name(),
});
}
};
for variant_iri in &variable_feature.variants {
if !from_variants_iris.insert(variant_iri.clone()) {
continue;
}
let typed = graph
.resolve(variant_iri)
.ok_or_else(|| ReferenceError::NotFound(variant_iri.clone()))?;
match typed {
SbolObject::Component(c) => from_variants.push(c),
other => {
return Err(ReferenceError::WrongType {
iri: variant_iri.clone(),
expected: "Component",
found: other.class().local_name(),
});
}
}
}
for collection_iri in &variable_feature.variant_collections {
let mut visited = BTreeSet::new();
collect_collection_components(
graph,
collection_iri,
&mut visited,
&mut from_collections_iris,
&mut from_collections,
)?;
}
for derivation_iri in &variable_feature.variant_derivations {
let typed = graph
.resolve(derivation_iri)
.ok_or_else(|| ReferenceError::NotFound(derivation_iri.clone()))?;
if !matches!(typed, SbolObject::CombinatorialDerivation(_)) {
return Err(ReferenceError::WrongType {
iri: derivation_iri.clone(),
expected: "CombinatorialDerivation",
found: typed.class().local_name(),
});
}
for object in graph.iter_typed() {
let SbolObject::Component(component) = object else {
continue;
};
if !component
.identified
.derived_from
.iter()
.any(|src| src == derivation_iri)
{
continue;
}
if from_derivations_iris.insert(component.identity.clone()) {
from_derivations.push(component);
}
}
}
}
Ok(VariantSet {
from_variants,
from_collections,
from_derivations,
})
}
pub fn template_component<'a, G>(&self, graph: &'a G) -> Result<&'a Component, ReferenceError>
where
G: ObjectGraph + ?Sized,
{
let iri = self
.template
.as_ref()
.ok_or_else(|| ReferenceError::Missing {
on: self.identity.clone(),
field: "template",
})?;
let typed = graph
.resolve(iri)
.ok_or_else(|| ReferenceError::NotFound(iri.clone()))?;
match typed {
SbolObject::Component(c) => Ok(c),
other => Err(ReferenceError::WrongType {
iri: iri.clone(),
expected: "Component",
found: other.class().local_name(),
}),
}
}
}
fn collect_collection_components<'a, G>(
graph: &'a G,
collection_iri: &Resource,
visited: &mut BTreeSet<Resource>,
seen: &mut BTreeSet<Resource>,
out: &mut Vec<&'a Component>,
) -> Result<(), ReferenceError>
where
G: ObjectGraph + ?Sized,
{
if !visited.insert(collection_iri.clone()) {
return Ok(());
}
let typed = graph
.resolve(collection_iri)
.ok_or_else(|| ReferenceError::NotFound(collection_iri.clone()))?;
let collection: &Collection = match typed {
SbolObject::Collection(c) => c,
other => {
return Err(ReferenceError::WrongType {
iri: collection_iri.clone(),
expected: "Collection",
found: other.class().local_name(),
});
}
};
for member_iri in &collection.members {
let Some(member) = graph.resolve(member_iri) else {
return Err(ReferenceError::NotFound(member_iri.clone()));
};
match member {
SbolObject::Component(component) => {
if seen.insert(component.identity.clone()) {
out.push(component);
}
}
SbolObject::Collection(_) => {
collect_collection_components(graph, member_iri, visited, seen, out)?;
}
_ => {
}
}
}
Ok(())
}