use std::collections::{HashMap, HashSet};
use crate::iri_util::last_iri_segment as last_segment;
use crate::sbol2_vocab as v2;
use crate::vocab as v3;
use crate::{Document, Iri, Resource, Term, Triple};
use sbol_rdf::Graph;
mod analyze;
mod dispatch;
mod emit;
mod helpers;
mod predicate;
mod preflight;
mod values;
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct DowngradeOptions {
pub default_version: Option<String>,
pub split_dual_role_components: bool,
}
impl Default for DowngradeOptions {
fn default() -> Self {
Self {
default_version: None,
split_dual_role_components: true,
}
}
}
impl DowngradeOptions {
pub fn new() -> Self {
Self::default()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum DowngradeWarning {
DualRoleComponent {
component: String,
component_definition: String,
module_definition: String,
},
UnresolvableConstraintToMapsTo { constraint: String, reason: String },
OrphanComponentReference { component_reference: String },
UnsupportedSbol3Type { subject: String, sbol3_type: String },
SynthesizedVersion { subject: String, version: String },
IdentityCollision {
canonical: String,
sources: Vec<String>,
},
}
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct DowngradeReport {
warnings: Vec<DowngradeWarning>,
counts: DowngradeCounts,
}
impl DowngradeReport {
pub fn warnings(&self) -> &[DowngradeWarning] {
&self.warnings
}
pub fn counts(&self) -> &DowngradeCounts {
&self.counts
}
pub fn is_clean(&self) -> bool {
self.warnings.is_empty()
}
pub(crate) fn push(&mut self, warning: DowngradeWarning) {
self.warnings.push(warning);
}
}
#[derive(Clone, Copy, Debug, Default)]
#[non_exhaustive]
pub struct DowngradeCounts {
pub components_to_component_definition: usize,
pub components_to_module_definition: usize,
pub components_split_into_both: usize,
pub sub_components_emitted: usize,
pub sequence_features_emitted: usize,
pub maps_to_reconstructed: usize,
pub identities_restored_from_backport: usize,
pub identities_synthesized: usize,
}
#[derive(Debug)]
#[non_exhaustive]
pub enum DowngradeError {
InvalidDefaultVersion(String),
}
impl std::fmt::Display for DowngradeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidDefaultVersion(msg) => {
write!(f, "invalid default version: {msg}")
}
}
}
}
impl std::error::Error for DowngradeError {}
pub fn sbol3_to_sbol2(
document: &Document,
options: DowngradeOptions,
) -> Result<(Graph, DowngradeReport), DowngradeError> {
if matches!(options.default_version.as_deref(), Some("")) {
return Err(DowngradeError::InvalidDefaultVersion(
"default version must be non-empty when set; use None to disable synthesis".to_string(),
));
}
let mut engine = Engine::new(document, options);
engine.preflight();
engine.convert();
Ok((Graph::new(engine.output_triples), engine.report))
}
impl Document {
pub fn downgrade_to_sbol2(&self) -> Result<(Graph, DowngradeReport), DowngradeError> {
sbol3_to_sbol2(self, DowngradeOptions::default())
}
pub fn downgrade_to_sbol2_with(
&self,
options: DowngradeOptions,
) -> Result<(Graph, DowngradeReport), DowngradeError> {
sbol3_to_sbol2(self, options)
}
}
struct Engine<'a> {
input: &'a Document,
options: DowngradeOptions,
versions: HashMap<String, String>,
preserved_versions: HashSet<String>,
persistent_identities: HashMap<String, String>,
backport_types: HashMap<String, String>,
backport_biopax_types: HashMap<String, HashSet<String>>,
biopax_variant_queue: HashMap<(String, String), Vec<String>>,
biopax_variant_cursor: HashMap<(String, String), usize>,
resolved_types: HashMap<String, String>,
resolved_type_sets: HashMap<String, HashSet<String>>,
subcomponent_targets: HashMap<String, String>,
top_levels: HashSet<String>,
iri_rewrites: HashMap<String, String>,
sa_collapses: HashMap<String, SaCollapseInfo>,
mapsto_reconstructions: HashMap<String, MapsToReconstruction>,
mapsto_constraints: HashSet<String>,
discarded_subjects: HashSet<String>,
fc_directions: HashMap<String, FcDirection>,
restored_fc_directions: HashSet<String>,
interface_subjects: HashSet<String>,
component_splits: HashMap<String, ComponentSplit>,
subcomponent_splits: HashMap<String, SubComponentSplit>,
participant_remap: HashMap<String, String>,
feature_parent: HashMap<String, String>,
used_iris: HashSet<String>,
dcterms_index: HashMap<(String, &'static str), HashSet<Term>>,
output_triples: Vec<Triple>,
report: DowngradeReport,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ComponentShape {
CdOnly,
MdOnly,
DualRole,
}
#[derive(Clone)]
struct ComponentSplit {
shape: ComponentShape,
cd_iri: String,
md_iri: String,
linking_fc_iri: Option<String>,
linking_fc_display_id: Option<String>,
cd_display_suffix: &'static str,
md_display_suffix: &'static str,
original_display_id: String,
}
#[derive(Clone)]
struct SubComponentSplit {
component_iri: String,
functional_component_iri: String,
module_iri: Option<String>,
}
#[derive(Clone, Copy, Debug)]
enum FcDirection {
In,
Out,
Inout,
NoneDirection,
}
#[derive(Clone, Copy)]
enum InterfaceFeatureKind {
Component,
FunctionalComponent,
Module,
}
impl FcDirection {
fn sbol2_iri(self) -> &'static str {
match self {
FcDirection::In => v2::SBOL2_DIRECTION_IN,
FcDirection::Out => v2::SBOL2_DIRECTION_OUT,
FcDirection::Inout => v2::SBOL2_DIRECTION_INOUT,
FcDirection::NoneDirection => v2::SBOL2_DIRECTION_NONE,
}
}
}
struct MapsToReconstruction {
carrier_v3: String,
display_id: String,
local_v3: String,
remote_v3: String,
refinement: Option<String>,
}
#[derive(Clone)]
struct PreservedSaTriple {
predicate: String,
object: Term,
}
struct SaCollapseInfo {
sa_display_id: String,
sa_iri_unversioned: String,
parent_component: String,
parent_cd: String,
locations: Vec<String>,
preserved_metadata: Vec<PreservedSaTriple>,
}