Skip to main content

sbol/
lib.rs

1//! Umbrella facade for the sbol-rs ecosystem.
2//!
3//! This crate re-exports the SBOL 2 implementation as [`v2`], the SBOL 3
4//! implementation as [`v3`], and the SBOL 2 ⇄ SBOL 3 conversion API as
5//! [`convert`]. It also adds version detection plus a version-neutral
6//! [`AnyDocument`] handle over the underlying RDF layer.
7#![forbid(unsafe_code)]
8
9pub use sbol_core;
10pub use sbol_rdf;
11pub use sbol_rdf::{Graph as RdfGraph, Iri, Literal, RdfFormat, Resource, Term, Triple};
12
13/// SBOL 2 ⇄ SBOL 3 conversion.
14pub use sbol_convert as convert;
15/// SBOL 2 implementation.
16pub use sbol2 as v2;
17/// SBOL 3 implementation.
18pub use sbol3 as v3;
19
20const SBOL_V2_NS: &str = "http://sbols.org/v2#";
21const SBOL_V3_NS: &str = "http://sbols.org/v3#";
22
23/// The SBOL major version a document is expressed in.
24#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
25#[non_exhaustive]
26pub enum SbolVersion {
27    /// SBOL 2 (`http://sbols.org/v2#`).
28    V2,
29    /// SBOL 3 (`http://sbols.org/v3#`).
30    V3,
31}
32
33/// Detects the SBOL major version of an already-parsed RDF graph. SBOL 3
34/// evidence takes precedence over SBOL 2 evidence; returns `None` when the
35/// graph carries neither namespace.
36pub fn detect_version_in_graph(graph: &RdfGraph) -> Option<SbolVersion> {
37    let mut saw_v2 = false;
38    for triple in graph.triples() {
39        if triple.predicate.as_str().starts_with(SBOL_V3_NS) {
40            return Some(SbolVersion::V3);
41        }
42        if let Some(iri) = triple.object.as_iri() {
43            if iri.as_str().starts_with(SBOL_V3_NS) {
44                return Some(SbolVersion::V3);
45            }
46            if iri.as_str().starts_with(SBOL_V2_NS) {
47                saw_v2 = true;
48            }
49        }
50        if triple.predicate.as_str().starts_with(SBOL_V2_NS) {
51            saw_v2 = true;
52        }
53    }
54    saw_v2.then_some(SbolVersion::V2)
55}
56
57/// Parses an in-memory RDF serialization and detects its SBOL major version.
58/// Returns `None` when the input does not parse or carries no SBOL namespace.
59pub fn detect_version(input: &str, format: RdfFormat) -> Option<SbolVersion> {
60    RdfGraph::parse(input, format)
61        .ok()
62        .and_then(|g| detect_version_in_graph(&g))
63}
64
65/// A version-neutral handle over a parsed SBOL document. It wraps an SBOL 3
66/// [`Document`](v3::Document); SBOL 2 documents are handled directly through
67/// `v2::Document`. The enum is `#[non_exhaustive]` so further arms can be
68/// added without a breaking change.
69#[non_exhaustive]
70pub enum AnyDocument {
71    /// An SBOL 3 document.
72    V3(v3::Document),
73}
74
75impl AnyDocument {
76    /// Returns the SBOL major version of the wrapped document.
77    pub fn version(&self) -> SbolVersion {
78        match self {
79            AnyDocument::V3(_) => SbolVersion::V3,
80        }
81    }
82
83    /// Serializes the wrapped document in the given RDF format.
84    pub fn write(&self, format: RdfFormat) -> Result<String, v3::WriteError> {
85        match self {
86            AnyDocument::V3(d) => d.write(format),
87        }
88    }
89
90    /// Borrows the wrapped document as SBOL 3, if it is SBOL 3.
91    pub fn as_v3(&self) -> Option<&v3::Document> {
92        match self {
93            AnyDocument::V3(d) => Some(d),
94        }
95    }
96
97    /// Consumes the handle and returns the SBOL 3 document, if it is SBOL 3.
98    pub fn into_v3(self) -> Option<v3::Document> {
99        match self {
100            AnyDocument::V3(d) => Some(d),
101        }
102    }
103}
104
105pub mod prelude {
106    //! Re-exports for most sbol-rs code: the SBOL 3 prelude plus the umbrella
107    //! version-detection surface.
108    pub use crate::v3::prelude::*;
109    pub use crate::{AnyDocument, SbolVersion, detect_version};
110}