use crate::base::{Interval, Real};
use crate::error::ParseError;
use crate::{am::Cardinality, am::MultiplicityInterval};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum NodeIdSyntax {
Adl2,
Adl14,
}
impl NodeIdSyntax {
#[must_use]
pub fn of(code: &str) -> Option<Self> {
let digits = code
.strip_prefix("id")
.or_else(|| code.strip_prefix("at"))
.or_else(|| code.strip_prefix("ac"))?;
if digits.is_empty() {
return None;
}
let mut segments = digits.split('.');
let first = segments.next()?;
if !first.chars().all(|c| c.is_ascii_digit()) {
return None;
}
for segment in segments {
if segment.is_empty() || !segment.chars().all(|c| c.is_ascii_digit()) {
return None;
}
}
if first.len() > 1 && first.starts_with('0') {
Some(Self::Adl14)
} else {
Some(Self::Adl2)
}
}
#[must_use]
pub fn specialisation_depth(code: &str) -> usize {
code.matches('.').count()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CAttribute {
rm_attribute_name: String,
existence: MultiplicityInterval,
cardinality: Option<Cardinality>,
children: Vec<CObject>,
}
impl CAttribute {
pub fn single(
rm_attribute_name: impl Into<String>,
existence: MultiplicityInterval,
children: Vec<CObject>,
) -> Result<Self, ParseError> {
let rm_attribute_name = rm_attribute_name.into();
if rm_attribute_name.is_empty() {
return Err(ParseError::invariant("C_ATTRIBUTE", "empty attribute name"));
}
Ok(Self {
rm_attribute_name,
existence,
cardinality: None,
children,
})
}
pub fn container(
rm_attribute_name: impl Into<String>,
existence: MultiplicityInterval,
cardinality: Cardinality,
children: Vec<CObject>,
) -> Result<Self, ParseError> {
let mut attribute = Self::single(rm_attribute_name, existence, children)?;
let required: u32 = attribute
.children
.iter()
.map(|child| child.occurrences().lower())
.sum();
if let Some(upper) = cardinality.interval().upper()
&& required > upper
{
return Err(ParseError::invariant(
"C_ATTRIBUTE",
"children require more occurrences than the cardinality permits",
));
}
attribute.cardinality = Some(cardinality);
Ok(attribute)
}
#[must_use]
pub fn rm_attribute_name(&self) -> &str {
&self.rm_attribute_name
}
#[must_use]
pub const fn existence(&self) -> &MultiplicityInterval {
&self.existence
}
#[must_use]
pub const fn cardinality(&self) -> Option<&Cardinality> {
self.cardinality.as_ref()
}
#[must_use]
pub fn children(&self) -> &[CObject] {
&self.children
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "_type")]
#[non_exhaustive]
pub enum CObject {
#[serde(rename = "C_COMPLEX_OBJECT")]
Complex(CComplexObject),
#[serde(rename = "C_PRIMITIVE_OBJECT")]
Primitive(CPrimitiveObject),
#[serde(rename = "ARCHETYPE_SLOT")]
Slot(ArchetypeSlot),
#[serde(rename = "C_ARCHETYPE_ROOT")]
ArchetypeRoot(CArchetypeRoot),
}
impl CObject {
#[must_use]
pub fn rm_type_name(&self) -> &str {
match self {
Self::Complex(o) => &o.rm_type_name,
Self::Primitive(o) => &o.rm_type_name,
Self::Slot(o) => &o.rm_type_name,
Self::ArchetypeRoot(o) => &o.rm_type_name,
}
}
#[must_use]
pub fn node_id(&self) -> Option<&str> {
match self {
Self::Complex(o) => o.node_id.as_deref(),
Self::Primitive(o) => o.node_id.as_deref(),
Self::Slot(o) => Some(&o.node_id),
Self::ArchetypeRoot(o) => o.node_id.as_deref(),
}
}
#[must_use]
pub const fn occurrences(&self) -> &MultiplicityInterval {
match self {
Self::Complex(o) => &o.occurrences,
Self::Primitive(o) => &o.occurrences,
Self::Slot(o) => &o.occurrences,
Self::ArchetypeRoot(o) => &o.occurrences,
}
}
#[must_use]
pub fn attributes(&self) -> &[CAttribute] {
match self {
Self::Complex(o) => &o.attributes,
Self::ArchetypeRoot(o) => &o.attributes,
Self::Primitive(_) | Self::Slot(_) => &[],
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CComplexObject {
rm_type_name: String,
node_id: Option<String>,
occurrences: MultiplicityInterval,
attributes: Vec<CAttribute>,
}
impl CComplexObject {
pub fn new(
rm_type_name: impl Into<String>,
node_id: Option<String>,
occurrences: MultiplicityInterval,
attributes: Vec<CAttribute>,
) -> Result<Self, ParseError> {
let rm_type_name = rm_type_name.into();
if rm_type_name.is_empty() {
return Err(ParseError::invariant("C_COMPLEX_OBJECT", "empty rm_type_name"));
}
if let Some(code) = node_id.as_deref()
&& NodeIdSyntax::of(code).is_none()
{
return Err(ParseError::new(
"C_COMPLEX_OBJECT",
"node_id is not an id-, at- or ac-code",
code,
));
}
let mut seen: Vec<&str> = Vec::with_capacity(attributes.len());
for attribute in &attributes {
if seen.contains(&attribute.rm_attribute_name()) {
return Err(ParseError::invariant("C_COMPLEX_OBJECT", "VOKU"));
}
seen.push(attribute.rm_attribute_name());
}
Ok(Self {
rm_type_name,
node_id,
occurrences,
attributes,
})
}
#[must_use]
pub fn rm_type_name(&self) -> &str {
&self.rm_type_name
}
#[must_use]
pub fn node_id(&self) -> Option<&str> {
self.node_id.as_deref()
}
#[must_use]
pub const fn occurrences(&self) -> &MultiplicityInterval {
&self.occurrences
}
#[must_use]
pub fn attributes(&self) -> &[CAttribute] {
&self.attributes
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CPrimitiveObject {
rm_type_name: String,
node_id: Option<String>,
occurrences: MultiplicityInterval,
constraint: CPrimitive,
}
impl CPrimitiveObject {
#[must_use]
pub fn new(
rm_type_name: impl Into<String>,
occurrences: MultiplicityInterval,
constraint: CPrimitive,
) -> Self {
Self {
rm_type_name: rm_type_name.into(),
node_id: None,
occurrences,
constraint,
}
}
#[must_use]
pub const fn constraint(&self) -> &CPrimitive {
&self.constraint
}
#[must_use]
pub fn rm_type_name(&self) -> &str {
&self.rm_type_name
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "_type")]
#[non_exhaustive]
pub enum CPrimitive {
#[serde(rename = "C_BOOLEAN")]
Boolean {
allow_true: bool,
allow_false: bool,
},
#[serde(rename = "C_STRING")]
String {
list: Vec<String>,
pattern: Option<String>,
},
#[serde(rename = "C_INTEGER")]
Integer {
list: Vec<i64>,
range: Option<Interval<i64>>,
},
#[serde(rename = "C_REAL")]
Real {
list: Vec<Real>,
range: Option<Interval<Real>>,
},
#[serde(rename = "C_TERMINOLOGY_CODE")]
TerminologyCode {
constraint: Option<String>,
code_list: Vec<String>,
},
#[serde(rename = "C_UNSUPPORTED")]
Unsupported {
rm_type_name: String,
source: String,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ArchetypeSlot {
rm_type_name: String,
node_id: String,
occurrences: MultiplicityInterval,
includes: Vec<String>,
excludes: Vec<String>,
}
impl ArchetypeSlot {
pub fn new(
rm_type_name: impl Into<String>,
node_id: impl Into<String>,
occurrences: MultiplicityInterval,
) -> Result<Self, ParseError> {
let node_id = node_id.into();
if NodeIdSyntax::of(&node_id).is_none() {
return Err(ParseError::new(
"ARCHETYPE_SLOT",
"node_id is not an id-, at- or ac-code",
&node_id,
));
}
Ok(Self {
rm_type_name: rm_type_name.into(),
node_id,
occurrences,
includes: Vec::new(),
excludes: Vec::new(),
})
}
#[must_use]
pub fn including(mut self, assertion: impl Into<String>) -> Self {
self.includes.push(assertion.into());
self
}
#[must_use]
pub fn excluding(mut self, assertion: impl Into<String>) -> Self {
self.excludes.push(assertion.into());
self
}
#[must_use]
pub fn node_id(&self) -> &str {
&self.node_id
}
#[must_use]
pub fn includes(&self) -> &[String] {
&self.includes
}
#[must_use]
pub fn excludes(&self) -> &[String] {
&self.excludes
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CArchetypeRoot {
rm_type_name: String,
node_id: Option<String>,
archetype_ref: String,
occurrences: MultiplicityInterval,
attributes: Vec<CAttribute>,
}
impl CArchetypeRoot {
pub fn new(
rm_type_name: impl Into<String>,
archetype_ref: impl Into<String>,
occurrences: MultiplicityInterval,
) -> Result<Self, ParseError> {
let archetype_ref = archetype_ref.into();
if archetype_ref.is_empty() {
return Err(ParseError::invariant(
"C_ARCHETYPE_ROOT",
"empty archetype reference",
));
}
Ok(Self {
rm_type_name: rm_type_name.into(),
node_id: None,
archetype_ref,
occurrences,
attributes: Vec::new(),
})
}
#[must_use]
pub fn archetype_ref(&self) -> &str {
&self.archetype_ref
}
}
#[cfg(test)]
mod tests {
use super::*;
fn element(node_id: &str) -> CObject {
CObject::Complex(
CComplexObject::new(
"ELEMENT",
Some(node_id.to_owned()),
MultiplicityInterval::MANDATORY,
Vec::new(),
)
.unwrap(),
)
}
#[test]
fn two_constraints_on_one_attribute_are_refused() {
let dup = CComplexObject::new(
"OBSERVATION",
Some("id1".to_owned()),
MultiplicityInterval::MANDATORY,
vec![
CAttribute::single("data", MultiplicityInterval::MANDATORY, Vec::new()).unwrap(),
CAttribute::single("data", MultiplicityInterval::MANDATORY, Vec::new()).unwrap(),
],
);
assert_eq!(dup.unwrap_err().reason, "VOKU");
}
#[test]
fn a_cardinality_that_cannot_hold_its_children_is_refused() {
let err = CAttribute::container(
"items",
MultiplicityInterval::MANDATORY,
Cardinality::new(MultiplicityInterval::OPTIONAL),
vec![element("at0001"), element("at0002")],
)
.unwrap_err();
assert_eq!(
err.reason,
"children require more occurrences than the cardinality permits"
);
}
#[test]
fn a_malformed_node_id_is_refused_at_construction() {
assert!(
CComplexObject::new(
"ELEMENT",
Some("node-4".to_owned()),
MultiplicityInterval::MANDATORY,
Vec::new()
)
.is_err()
);
let malformed = "banana";
assert!(ArchetypeSlot::new("SECTION", malformed, MultiplicityInterval::OPTIONAL).is_err());
}
#[test]
fn both_node_id_syntaxes_are_recognised_and_kept_apart() {
assert_eq!(NodeIdSyntax::of("id1"), Some(NodeIdSyntax::Adl2));
assert_eq!(NodeIdSyntax::of("at0000"), Some(NodeIdSyntax::Adl14));
assert_eq!(NodeIdSyntax::specialisation_depth("id1.1.2"), 2);
assert_eq!(NodeIdSyntax::specialisation_depth("at0004"), 0);
}
}