use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Tolerance {
MissingVersionEntry,
UnknownVersionId {
raw: String,
},
VersionConflict {
declared: crate::BcfVersion,
observed: crate::BcfVersion,
},
UnreadableMarkup {
detail: String,
},
MarkupWithoutTopic,
TopicWithoutGuid,
CommentWithoutGuid,
TopicWithoutTitle,
DanglingReference {
target: String,
},
UnreadableViewpoint {
target: String,
detail: String,
},
BackslashSeparator {
name: String,
},
UnparseableDateTime {
field: &'static str,
raw: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Diagnostic {
pub entry: Option<String>,
pub tolerance: Tolerance,
}
impl Diagnostic {
#[must_use]
pub fn in_entry(entry: impl Into<String>, tolerance: Tolerance) -> Self {
Self {
entry: Some(entry.into()),
tolerance,
}
}
#[must_use]
pub fn in_archive(tolerance: Tolerance) -> Self {
Self {
entry: None,
tolerance,
}
}
}
impl fmt::Display for Tolerance {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Tolerance::MissingVersionEntry => {
f.write_str("no bcf.version entry; version inferred from shape")
}
Tolerance::UnknownVersionId { raw } => {
write!(f, "unrecognised bcf.version VersionId {raw:?}")
}
Tolerance::VersionConflict { declared, observed } => write!(
f,
"bcf.version declares {} but markup has {} shape",
declared.version_id(),
observed.version_id()
),
Tolerance::UnreadableMarkup { detail } => {
write!(f, "markup could not be parsed: {detail}")
}
Tolerance::MarkupWithoutTopic => {
f.write_str("markup document contains no Topic element")
}
Tolerance::TopicWithoutGuid => f.write_str("Topic has no Guid attribute"),
Tolerance::CommentWithoutGuid => f.write_str("Comment has no Guid attribute"),
Tolerance::TopicWithoutTitle => f.write_str("Topic has no Title"),
Tolerance::DanglingReference { target } => {
write!(f, "referenced entry {target:?} is not in the archive")
}
Tolerance::UnreadableViewpoint { target, detail } => {
write!(f, "viewpoint {target:?} could not be read: {detail}")
}
Tolerance::BackslashSeparator { name } => {
write!(f, "entry {name:?} uses backslash separators")
}
Tolerance::UnparseableDateTime { field, raw } => {
write!(f, "{field} is not a valid dateTime: {raw:?}")
}
}
}
}
impl fmt::Display for Diagnostic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.entry {
Some(entry) => write!(f, "{entry}: {}", self.tolerance),
None => write!(f, "{}", self.tolerance),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::BcfVersion;
#[test]
fn entry_scoped_diagnostics_name_their_entry() {
let d = Diagnostic::in_entry("topic/markup.bcf", Tolerance::TopicWithoutGuid);
assert_eq!(d.entry.as_deref(), Some("topic/markup.bcf"));
assert!(d.to_string().starts_with("topic/markup.bcf: "), "{d}");
}
#[test]
fn archive_scoped_diagnostics_have_no_entry_prefix() {
let d = Diagnostic::in_archive(Tolerance::MissingVersionEntry);
assert_eq!(d.entry, None);
assert!(!d.to_string().contains(both_separator()), "{d}");
}
fn both_separator() -> &'static str {
".bcf: "
}
#[test]
fn version_conflict_reports_both_sides() {
let msg = Tolerance::VersionConflict {
declared: BcfVersion::V2_1,
observed: BcfVersion::V2_0,
}
.to_string();
assert!(msg.contains("2.1") && msg.contains("2.0"), "{msg}");
}
#[test]
fn unknown_version_ids_are_quoted_verbatim() {
let msg = Tolerance::UnknownVersionId { raw: "4.2".into() }.to_string();
assert!(msg.contains("\"4.2\""), "{msg}");
}
}