#![forbid(unsafe_code)]
#[cfg(feature = "audit-schema")]
pub mod audit;
pub mod model;
pub mod occurrence;
pub mod read;
pub mod version;
pub mod write;
#[cfg(feature = "audit-schema")]
pub use audit::{audit, AuditCode, AuditFinding, Severity};
pub use model::{
Applicability, Attribute, Classification, Entity, Facet, Ids, IfcVersion, Info, Material,
PartOf, Property, Relation, Requirement, Requirements, Restriction, Specification, Value,
};
pub use occurrence::Occurrence;
pub use read::{from_slice, from_str, ErrorKind, ReadError};
pub use version::{detect_version, Signal, VersionSignals};
pub use write::{to_string, to_writer, WriteError, WriteErrorKind};
pub const NAMESPACE: &str = "http://standards.buildingsmart.org/IDS";
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum IdsVersion {
Draft0_9,
Draft0_9_6,
Draft0_9_7,
Ids1_0,
}
impl IdsVersion {
pub const CURRENT: IdsVersion = IdsVersion::Ids1_0;
pub const ALL: [IdsVersion; 4] = [
IdsVersion::Draft0_9,
IdsVersion::Draft0_9_6,
IdsVersion::Draft0_9_7,
IdsVersion::Ids1_0,
];
#[must_use]
pub fn is_approved(self) -> bool {
matches!(self, IdsVersion::Ids1_0)
}
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
IdsVersion::Draft0_9 => "0.9",
IdsVersion::Draft0_9_6 => "0.9.6",
IdsVersion::Draft0_9_7 => "0.9.7",
IdsVersion::Ids1_0 => "1.0",
}
}
}
impl core::fmt::Display for IdsVersion {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseIdsVersionError {
input: String,
}
impl ParseIdsVersionError {
#[must_use]
pub fn input(&self) -> &str {
&self.input
}
}
impl core::fmt::Display for ParseIdsVersionError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"`{}` is not a published IDS version (expected one of 0.9, 0.9.6, 0.9.7, 1.0)",
self.input
)
}
}
impl std::error::Error for ParseIdsVersionError {}
impl core::str::FromStr for IdsVersion {
type Err = ParseIdsVersionError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.trim() {
"0.9" | "0.9.0" => Ok(IdsVersion::Draft0_9),
"0.9.6" => Ok(IdsVersion::Draft0_9_6),
"0.9.7" => Ok(IdsVersion::Draft0_9_7),
"1.0" | "1.0.0" => Ok(IdsVersion::Ids1_0),
other => Err(ParseIdsVersionError {
input: other.to_owned(),
}),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const ALL: [IdsVersion; 4] = IdsVersion::ALL;
#[test]
fn all_lists_every_variant() {
for version in ALL {
match version {
IdsVersion::Draft0_9
| IdsVersion::Draft0_9_6
| IdsVersion::Draft0_9_7
| IdsVersion::Ids1_0 => {}
}
}
assert_eq!(ALL.len(), 4);
let unique: std::collections::BTreeSet<_> = ALL.iter().collect();
assert_eq!(unique.len(), ALL.len(), "ALL contains a duplicate");
}
#[test]
fn all_is_ordered_oldest_first() {
assert!(ALL.windows(2).all(|w| w[0] < w[1]));
}
#[test]
fn only_1_0_is_approved() {
assert!(IdsVersion::Ids1_0.is_approved());
assert!(!IdsVersion::Draft0_9.is_approved());
assert!(!IdsVersion::Draft0_9_6.is_approved());
assert!(!IdsVersion::Draft0_9_7.is_approved());
}
#[test]
fn current_is_the_newest_version() {
assert_eq!(IdsVersion::CURRENT, IdsVersion::Ids1_0);
assert!(IdsVersion::Ids1_0 > IdsVersion::Draft0_9_7);
assert!(IdsVersion::Draft0_9_7 > IdsVersion::Draft0_9);
}
#[test]
fn every_version_round_trips_through_text() {
for version in ALL {
assert_eq!(
version.as_str().parse::<IdsVersion>(),
Ok(version),
"{version} did not round trip"
);
}
}
#[test]
fn display_matches_as_str() {
for version in ALL {
assert_eq!(version.to_string(), version.as_str());
}
}
#[test]
fn version_strings_are_distinct() {
let mut seen = std::collections::BTreeSet::new();
for version in ALL {
assert!(seen.insert(version.as_str()), "duplicate {version}");
}
}
#[test]
fn schema_file_version_spelling_parses() {
assert_eq!("1.0.0".parse(), Ok(IdsVersion::Ids1_0));
assert_eq!("0.9.0".parse(), Ok(IdsVersion::Draft0_9));
}
#[test]
fn surrounding_whitespace_is_tolerated() {
assert_eq!(" 1.0\n".parse(), Ok(IdsVersion::Ids1_0));
}
#[test]
fn unknown_versions_are_rejected_and_quote_the_input() {
let err = "0.8".parse::<IdsVersion>().expect_err("must reject 0.8");
assert_eq!(err.input(), "0.8");
assert!(err.to_string().contains("0.8"), "{err}");
for bad in ["", "1", "1.0.1", "v1.0", "IDS1.0"] {
assert!(
bad.parse::<IdsVersion>().is_err(),
"`{bad}` must not parse as a published version"
);
}
}
}