use core::fmt;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct CustomBodyId {
pub catalog: String,
pub designation: String,
}
impl CustomBodyId {
pub fn new(catalog: impl Into<String>, designation: impl Into<String>) -> Self {
Self {
catalog: catalog.into(),
designation: designation.into(),
}
}
pub fn validate(&self) -> Result<(), CustomDefinitionValidationError> {
validate_canonical_text("custom body id", "catalog", &self.catalog)?;
validate_canonical_text("custom body id", "designation", &self.designation)?;
if self.catalog.contains(':') {
return Err(CustomDefinitionValidationError::contains_separator(
"custom body id",
"catalog",
':',
));
}
if self.designation.contains(':') {
return Err(CustomDefinitionValidationError::contains_separator(
"custom body id",
"designation",
':',
));
}
Ok(())
}
}
impl fmt::Display for CustomBodyId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.catalog, self.designation)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum CustomDefinitionValidationError {
Blank {
subject: &'static str,
field: String,
},
Padded {
subject: &'static str,
field: String,
},
ContainsSeparator {
subject: &'static str,
field: String,
separator: char,
},
DuplicateAlias {
subject: &'static str,
alias: String,
},
ReservedLabel {
subject: &'static str,
field: String,
label: String,
},
NonFinite {
subject: &'static str,
field: String,
},
IncompletePair {
subject: &'static str,
first: String,
second: String,
},
}
impl CustomDefinitionValidationError {
pub(crate) fn blank(subject: &'static str, field: impl Into<String>) -> Self {
Self::Blank {
subject,
field: field.into(),
}
}
pub(crate) fn padded(subject: &'static str, field: impl Into<String>) -> Self {
Self::Padded {
subject,
field: field.into(),
}
}
pub(crate) fn contains_separator(
subject: &'static str,
field: impl Into<String>,
separator: char,
) -> Self {
Self::ContainsSeparator {
subject,
field: field.into(),
separator,
}
}
pub(crate) fn duplicate_alias(subject: &'static str, alias: impl Into<String>) -> Self {
Self::DuplicateAlias {
subject,
alias: alias.into(),
}
}
pub(crate) fn reserved_label(
subject: &'static str,
field: impl Into<String>,
label: impl Into<String>,
) -> Self {
Self::ReservedLabel {
subject,
field: field.into(),
label: label.into(),
}
}
pub(crate) fn non_finite(subject: &'static str, field: impl Into<String>) -> Self {
Self::NonFinite {
subject,
field: field.into(),
}
}
pub(crate) fn incomplete_pair(
subject: &'static str,
first: impl Into<String>,
second: impl Into<String>,
) -> Self {
Self::IncompletePair {
subject,
first: first.into(),
second: second.into(),
}
}
pub fn summary_line(&self) -> String {
match self {
Self::Blank { subject, field } => format!("{subject} {field} must not be blank"),
Self::Padded { subject, field } => {
format!("{subject} {field} must not have leading or trailing whitespace")
}
Self::ContainsSeparator {
subject,
field,
separator,
} => format!("{subject} {field} must not contain '{separator}'"),
Self::DuplicateAlias { subject, alias } => {
format!("{subject} aliases must be unique: duplicate {alias}")
}
Self::ReservedLabel {
subject,
field,
label,
} => {
format!("{subject} {field} must not match a built-in label: {label}")
}
Self::NonFinite { subject, field } => format!("{subject} {field} must be finite"),
Self::IncompletePair {
subject,
first,
second,
} => format!("{subject} requires both {first} and {second} when one is present"),
}
}
}
impl fmt::Display for CustomDefinitionValidationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.summary_line())
}
}
impl std::error::Error for CustomDefinitionValidationError {}
pub(crate) fn validate_canonical_text(
subject: &'static str,
field: impl Into<String>,
value: &str,
) -> Result<(), CustomDefinitionValidationError> {
let field = field.into();
let trimmed = value.trim();
if trimmed.is_empty() {
return Err(CustomDefinitionValidationError::blank(subject, field));
}
if trimmed != value {
return Err(CustomDefinitionValidationError::padded(subject, field));
}
Ok(())
}