#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Occurrence {
Required,
Prohibited,
Optional,
}
impl Occurrence {
pub const DEFAULT: Occurrence = Occurrence::Required;
#[must_use]
pub fn from_cardinality(value: &str) -> Option<Occurrence> {
match value {
"required" => Some(Occurrence::Required),
"prohibited" => Some(Occurrence::Prohibited),
"optional" => Some(Occurrence::Optional),
_ => None,
}
}
#[must_use]
pub fn as_cardinality(self) -> &'static str {
match self {
Occurrence::Required => "required",
Occurrence::Prohibited => "prohibited",
Occurrence::Optional => "optional",
}
}
#[must_use]
pub fn from_xs_occurs(min: u32, max: Option<u32>) -> Occurrence {
if max == Some(0) {
Occurrence::Prohibited
} else if min == 0 {
Occurrence::Optional
} else {
Occurrence::Required
}
}
#[must_use]
pub fn permits_presence(self) -> bool {
!matches!(self, Occurrence::Prohibited)
}
}
impl Default for Occurrence {
fn default() -> Self {
Occurrence::DEFAULT
}
}
impl core::fmt::Display for Occurrence {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.as_cardinality())
}
}
#[cfg(test)]
mod tests {
use super::*;
const ALL: [Occurrence; 3] = [
Occurrence::Required,
Occurrence::Prohibited,
Occurrence::Optional,
];
#[test]
fn cardinality_round_trips() {
for occurrence in ALL {
assert_eq!(
Occurrence::from_cardinality(occurrence.as_cardinality()),
Some(occurrence)
);
}
}
#[test]
fn unknown_cardinality_is_rejected() {
for bad in ["", "Required", "REQUIRED", "mandatory", "forbidden"] {
assert!(
Occurrence::from_cardinality(bad).is_none(),
"`{bad}` must not parse"
);
}
}
#[test]
fn revisions_agree_on_equal_intent() {
assert_eq!(
Occurrence::from_xs_occurs(1, Some(1)),
Occurrence::from_cardinality("required").expect("required")
);
assert_eq!(
Occurrence::from_xs_occurs(0, Some(1)),
Occurrence::from_cardinality("optional").expect("optional")
);
assert_eq!(
Occurrence::from_xs_occurs(0, Some(0)),
Occurrence::from_cardinality("prohibited").expect("prohibited")
);
}
#[test]
fn unbounded_maximum_is_required_not_optional() {
assert_eq!(Occurrence::from_xs_occurs(1, None), Occurrence::Required);
assert_eq!(Occurrence::from_xs_occurs(0, None), Occurrence::Optional);
}
#[test]
fn zero_maximum_is_prohibited_even_with_zero_minimum() {
assert_eq!(
Occurrence::from_xs_occurs(0, Some(0)),
Occurrence::Prohibited
);
}
#[test]
fn default_is_required() {
assert_eq!(Occurrence::default(), Occurrence::Required);
assert_eq!(Occurrence::DEFAULT, Occurrence::Required);
}
#[test]
fn only_prohibited_forbids_presence() {
assert!(Occurrence::Required.permits_presence());
assert!(Occurrence::Optional.permits_presence());
assert!(!Occurrence::Prohibited.permits_presence());
}
#[test]
fn display_matches_cardinality() {
for occurrence in ALL {
assert_eq!(occurrence.to_string(), occurrence.as_cardinality());
}
}
}