Skip to content

Commit ea9de5e

Browse files
committed
add consistency check
1 parent f4aa1b2 commit ea9de5e

File tree

3 files changed

+259
-2
lines changed

3 files changed

+259
-2
lines changed

src/lib.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ pub mod unit;
6565
/// Unit definitions composing multiple base units
6666
pub mod unitdef;
6767

68+
/// Error handling for SBML models
69+
pub mod sbmlerror;
70+
6871
/// Helper macros for working with SBML components
6972
pub mod macros;
7073

@@ -110,6 +113,7 @@ pub mod prelude {
110113
pub use crate::reader::*;
111114
pub use crate::rule::*;
112115
pub use crate::sbmldoc::*;
116+
pub use crate::sbmlerror::*;
113117
pub use crate::species::*;
114118
pub use crate::speciesref::*;
115119
pub use crate::traits::annotation::*;
@@ -160,6 +164,13 @@ pub(crate) mod sbmlcxx {
160164
generate!("SBMLWriter")
161165
generate!("SBMLReader")
162166

167+
// Validation types
168+
generate!("SBMLValidator")
169+
generate!("SBMLInternalValidator")
170+
generate!("SBMLError")
171+
generate!("SBMLErrorLog")
172+
generate!("XMLError")
173+
163174
// Container types
164175
generate!("ListOfParameters")
165176
generate!("ListOfUnitDefinitions")

src/sbmldoc.rs

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::{cell::RefCell, rc::Rc};
1010
use autocxx::{c_uint, WithinUniquePtr};
1111
use cxx::UniquePtr;
1212

13-
use crate::{cast::upcast, model::Model, sbmlcxx, traits::fromptr::FromPtr};
13+
use crate::{cast::upcast, model::Model, prelude::SBMLErrorLog, sbmlcxx, traits::fromptr::FromPtr};
1414

1515
/// A wrapper around libSBML's SBMLDocument class that provides a safe Rust interface.
1616
///
@@ -100,7 +100,7 @@ impl<'a> SBMLDocument<'a> {
100100
///
101101
/// # Returns
102102
/// A reference to the newly created Model
103-
pub fn create_model(&'a self, id: &str) -> Rc<Model<'a>> {
103+
pub fn create_model(&self, id: &str) -> Rc<Model<'a>> {
104104
let model = Rc::new(Model::new(self, id));
105105
self.model.borrow_mut().replace(Rc::clone(&model));
106106
model
@@ -132,6 +132,38 @@ impl<'a> SBMLDocument<'a> {
132132
String::new()
133133
}
134134
}
135+
136+
/// Checks the consistency of the SBML document.
137+
///
138+
/// This function performs a consistency check on the SBML document and returns
139+
/// a Result containing an error log if the document is not consistent. The [`SBMLErrorLog`]
140+
/// struct contains the validation status of the document and a list of all errors encountered
141+
/// during validation.
142+
///
143+
/// Users can simply check the `valid` field of the returned [`SBMLErrorLog`] to determine
144+
/// if the document is consistent. If not, they can iterate over the `errors` vector to
145+
///
146+
/// # Returns
147+
/// A [`SBMLErrorLog`] containing the validation status and errors of the document.
148+
pub fn check_consistency(&self) -> SBMLErrorLog {
149+
self.inner()
150+
.borrow_mut()
151+
.as_mut()
152+
.unwrap()
153+
.checkConsistency();
154+
155+
SBMLErrorLog::new(self)
156+
}
157+
}
158+
159+
impl<'a> std::fmt::Debug for SBMLDocument<'a> {
160+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161+
let mut ds = f.debug_struct("SBMLDocument");
162+
ds.field("level", &self.level());
163+
ds.field("version", &self.version());
164+
ds.field("model", &self.model());
165+
ds.finish()
166+
}
135167
}
136168

137169
impl<'a> Default for SBMLDocument<'a> {
@@ -142,6 +174,8 @@ impl<'a> Default for SBMLDocument<'a> {
142174

143175
#[cfg(test)]
144176
mod tests {
177+
use crate::prelude::SBMLErrorSeverity;
178+
145179
use super::*;
146180

147181
#[test]
@@ -175,4 +209,55 @@ mod tests {
175209
let xml_string = doc.to_xml_string();
176210
assert!(!xml_string.is_empty());
177211
}
212+
213+
#[test]
214+
fn test_sbmldoc_check_consistency() {
215+
let doc = SBMLDocument::default();
216+
let error_log = doc.check_consistency();
217+
assert!(error_log.valid);
218+
}
219+
220+
#[test]
221+
fn test_sbmldoc_check_consistency_invalid() {
222+
let doc = SBMLDocument::new(3, 2);
223+
let model = doc.create_model("model");
224+
225+
// Lets add a species without a compartment
226+
model
227+
.build_species("some")
228+
.initial_concentration(-10.0)
229+
.build();
230+
231+
let error_log = doc.check_consistency();
232+
let errors = error_log
233+
.errors
234+
.iter()
235+
.filter(|e| e.severity == SBMLErrorSeverity::Error)
236+
.count();
237+
238+
assert!(!error_log.valid);
239+
assert_eq!(errors, 1);
240+
241+
// Check that the error log contains the correct number of errors
242+
}
243+
244+
#[test]
245+
fn test_sbmldoc_check_consistency_warning() {
246+
let doc = SBMLDocument::new(3, 2);
247+
let model = doc.create_model("model");
248+
249+
// Lets create a parameter with nothing
250+
// This should throw a couple of warnings
251+
model.build_parameter("test").build();
252+
253+
let error_log = doc.check_consistency();
254+
let warnings = error_log
255+
.errors
256+
.iter()
257+
.filter(|e| e.severity == SBMLErrorSeverity::Warning)
258+
.count();
259+
260+
assert!(error_log.valid);
261+
assert_eq!(warnings, 4);
262+
}
178263
}

src/sbmlerror.rs

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
//! Error handling for SBML validation and consistency checking.
2+
//!
3+
//! This module provides types for representing and working with errors that occur
4+
//! during SBML document validation. It includes structures for individual errors
5+
//! and collections of errors, as well as severity levels to indicate the importance
6+
//! of each error.
7+
//!
8+
//! The main types in this module are:
9+
//! - `SBMLErrorLog`: A collection of validation errors from an SBML document
10+
//! - `SBMLError`: An individual validation error with detailed information
11+
//! - `SBMLErrorSeverity`: The severity level of an error (Error, Warning, etc.)
12+
13+
use std::pin::Pin;
14+
15+
use crate::{pin_ptr, sbmlcxx, SBMLDocument};
16+
17+
/// Represents a collection of SBML validation errors from a document.
18+
///
19+
/// This struct contains the validation status of an SBML document and
20+
/// a list of all errors encountered during validation.
21+
#[derive(Debug)]
22+
pub struct SBMLErrorLog {
23+
/// Indicates whether the document is valid (true) or has errors (false)
24+
pub valid: bool,
25+
/// Collection of all errors found during validation
26+
pub errors: Vec<SBMLError>,
27+
}
28+
29+
impl SBMLErrorLog {
30+
/// Creates a new error log from an SBML document.
31+
///
32+
/// Extracts all errors from the document's internal error log and
33+
/// determines if the document is valid based on the presence of
34+
/// errors with severity level Error or Fatal.
35+
///
36+
/// # Arguments
37+
/// * `document` - Reference to the SBML document to extract errors from
38+
///
39+
/// # Returns
40+
/// A new `SBMLErrorLog` containing all errors and validation status
41+
pub fn new(document: &SBMLDocument) -> Self {
42+
// Get the amount of errors for extraction
43+
let n_errors = document.inner().borrow().getNumErrors().0;
44+
45+
// Pin the error log to extract all errors
46+
let errorlog_ptr = document.inner().borrow_mut().pin_mut().getErrorLog();
47+
let errorlog = pin_ptr!(errorlog_ptr, sbmlcxx::SBMLErrorLog);
48+
49+
// Convert the errors to a Vec with pre-allocated capacity for efficiency
50+
let mut errors = Vec::with_capacity(n_errors as usize);
51+
for i in 0..n_errors {
52+
errors.push(SBMLError::new(errorlog.as_ref().getError(i.into())));
53+
}
54+
55+
// Document is invalid if it contains any Error or Fatal severity errors
56+
let has_errors = errors.iter().any(|error| {
57+
error.severity == SBMLErrorSeverity::Error || error.severity == SBMLErrorSeverity::Fatal
58+
});
59+
60+
Self {
61+
valid: !has_errors,
62+
errors,
63+
}
64+
}
65+
}
66+
67+
/// Represents a single SBML validation error.
68+
///
69+
/// Contains detailed information about an error encountered during
70+
/// SBML document validation, including its message, severity level,
71+
/// location in the document, and category.
72+
#[derive(Debug)]
73+
pub struct SBMLError {
74+
/// The error message describing the issue
75+
pub message: String,
76+
/// The severity level of the error
77+
pub severity: SBMLErrorSeverity,
78+
/// The line number where the error occurred
79+
pub line: u32,
80+
/// The column number where the error occurred
81+
pub column: u32,
82+
/// The category of the error (e.g., "SBML", "XML", etc.)
83+
pub category: String,
84+
}
85+
86+
impl SBMLError {
87+
/// Creates a new SBML error from a raw error pointer.
88+
///
89+
/// # Arguments
90+
/// * `error` - Pointer to the native SBMLError object
91+
///
92+
/// # Returns
93+
/// A new `SBMLError` with information extracted from the native error
94+
pub fn new(error: *const sbmlcxx::SBMLError) -> Self {
95+
let xml_error = error as *const sbmlcxx::XMLError;
96+
let xml_error = unsafe { Pin::new_unchecked(&*xml_error) };
97+
98+
let message = xml_error.as_ref().getMessage().to_string();
99+
let line = xml_error.as_ref().getLine().0;
100+
let column = xml_error.as_ref().getColumn().0;
101+
let category = xml_error.as_ref().getCategoryAsString().to_string();
102+
let severity = SBMLErrorSeverity::from(&*xml_error);
103+
104+
Self {
105+
message,
106+
severity,
107+
line,
108+
column,
109+
category,
110+
}
111+
}
112+
}
113+
114+
/// Represents the severity level of an SBML error.
115+
///
116+
/// SBML errors can have different severity levels, ranging from
117+
/// informational messages to fatal errors that prevent document processing.
118+
#[derive(Debug, PartialEq, Eq)]
119+
pub enum SBMLErrorSeverity {
120+
/// Standard error that indicates a problem with the SBML document
121+
Error,
122+
/// Severe error that prevents further processing
123+
Fatal,
124+
/// Warning that doesn't invalidate the document but should be addressed
125+
Warning,
126+
/// Informational message that doesn't indicate a problem
127+
Info,
128+
/// Internal error in the SBML library
129+
Internal,
130+
/// System-level error (e.g., file I/O problems)
131+
System,
132+
/// Unknown error type
133+
Unknown,
134+
}
135+
136+
impl From<&sbmlcxx::XMLError> for SBMLErrorSeverity {
137+
/// Converts a native XMLError to an SBMLErrorSeverity.
138+
///
139+
/// # Arguments
140+
/// * `xml_error` - Reference to the native XMLError
141+
///
142+
/// # Returns
143+
/// The corresponding SBMLErrorSeverity variant
144+
fn from(xml_error: &sbmlcxx::XMLError) -> Self {
145+
if xml_error.isError() {
146+
SBMLErrorSeverity::Error
147+
} else if xml_error.isWarning() {
148+
SBMLErrorSeverity::Warning
149+
} else if xml_error.isInternal() {
150+
SBMLErrorSeverity::Internal
151+
} else if xml_error.isFatal() {
152+
SBMLErrorSeverity::Fatal
153+
} else if xml_error.isSystem() {
154+
SBMLErrorSeverity::System
155+
} else if xml_error.isInfo() {
156+
SBMLErrorSeverity::Info
157+
} else {
158+
SBMLErrorSeverity::Unknown
159+
}
160+
}
161+
}

0 commit comments

Comments
 (0)