use crate::error::ParseError;
use core::fmt;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct MultiplicityInterval {
lower: u32,
upper: Option<u32>,
}
impl MultiplicityInterval {
pub const MANDATORY: Self = Self {
lower: 1,
upper: Some(1),
};
pub const OPTIONAL: Self = Self {
lower: 0,
upper: Some(1),
};
pub const PROHIBITED: Self = Self {
lower: 0,
upper: Some(0),
};
pub fn new(lower: u32, upper: Option<u32>) -> Result<Self, ParseError> {
if let Some(upper) = upper
&& upper < lower
{
return Err(ParseError::invariant(
"MULTIPLICITY_INTERVAL",
"upper bound below lower bound",
));
}
Ok(Self { lower, upper })
}
pub fn at_least(lower: u32) -> Result<Self, ParseError> {
Self::new(lower, None)
}
#[must_use]
pub const fn lower(&self) -> u32 {
self.lower
}
#[must_use]
pub const fn upper(&self) -> Option<u32> {
self.upper
}
#[must_use]
pub const fn is_open(&self) -> bool {
self.upper.is_none()
}
#[must_use]
pub const fn is_mandatory(&self) -> bool {
self.lower >= 1
}
#[must_use]
pub const fn is_prohibited(&self) -> bool {
matches!(self.upper, Some(0))
}
#[must_use]
pub const fn contains(&self, count: u32) -> bool {
count >= self.lower
&& match self.upper {
Some(upper) => count <= upper,
None => true,
}
}
#[must_use]
pub const fn narrows(&self, parent: &Self) -> bool {
if self.lower < parent.lower {
return false;
}
match (self.upper, parent.upper) {
(_, None) => true,
(None, Some(_)) => false,
(Some(mine), Some(theirs)) => mine <= theirs,
}
}
}
impl fmt::Display for MultiplicityInterval {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.upper {
Some(upper) => write!(f, "{}..{upper}", self.lower),
None => write!(f, "{}..*", self.lower),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Cardinality {
interval: MultiplicityInterval,
is_ordered: bool,
is_unique: bool,
}
impl Cardinality {
#[must_use]
pub const fn new(interval: MultiplicityInterval) -> Self {
Self {
interval,
is_ordered: false,
is_unique: false,
}
}
#[must_use]
pub const fn ordered(mut self) -> Self {
self.is_ordered = true;
self
}
#[must_use]
pub const fn unique(mut self) -> Self {
self.is_unique = true;
self
}
#[must_use]
pub const fn interval(&self) -> &MultiplicityInterval {
&self.interval
}
#[must_use]
pub const fn is_ordered(&self) -> bool {
self.is_ordered
}
#[must_use]
pub const fn is_unique(&self) -> bool {
self.is_unique
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_upper_bound_below_the_lower_one_is_refused() {
assert!(MultiplicityInterval::new(2, Some(1)).is_err());
assert!(MultiplicityInterval::new(2, Some(2)).is_ok());
}
#[test]
fn prohibited_is_distinguishable_from_optional() {
assert!(MultiplicityInterval::PROHIBITED.is_prohibited());
assert!(!MultiplicityInterval::OPTIONAL.is_prohibited());
assert!(!MultiplicityInterval::PROHIBITED.contains(1));
}
#[test]
fn narrowing_is_directional() {
let parent = MultiplicityInterval::new(0, Some(4)).unwrap();
let child = MultiplicityInterval::new(1, Some(2)).unwrap();
assert!(child.narrows(&parent));
assert!(!parent.narrows(&child));
let open = MultiplicityInterval::at_least(1).unwrap();
assert!(!open.narrows(&parent));
assert!(parent.narrows(&MultiplicityInterval::new(0, None).unwrap()));
}
#[test]
fn display_writes_openehr_multiplicity_syntax() {
assert_eq!(MultiplicityInterval::MANDATORY.to_string(), "1..1");
assert_eq!(
MultiplicityInterval::at_least(2).unwrap().to_string(),
"2..*"
);
}
}