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}