1use std::fmt;
4
5use crate::document::{DvgwDocument, DvgwMessageType};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
14pub enum Severity {
15 Info,
17 Warning,
19 Error,
21}
22
23impl Severity {
24 #[must_use]
26 pub fn as_str(self) -> &'static str {
27 match self {
28 Self::Info => "info",
29 Self::Warning => "warning",
30 Self::Error => "error",
31 }
32 }
33}
34
35impl fmt::Display for Severity {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 f.write_str(self.as_str())
38 }
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
43#[cfg_attr(feature = "serde", derive(serde::Serialize))]
44#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
45#[non_exhaustive]
46pub struct DvgwIssue {
47 pub severity: Severity,
49 pub message: String,
51 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
53 pub rule_id: Option<&'static str>,
54 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
56 pub segment_tag: Option<&'static str>,
57 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
59 pub suggestion: Option<String>,
60}
61
62impl DvgwIssue {
63 pub(crate) fn new(severity: Severity, message: impl Into<String>) -> Self {
64 Self {
65 severity,
66 message: message.into(),
67 rule_id: None,
68 segment_tag: None,
69 suggestion: None,
70 }
71 }
72
73 pub(crate) fn with_rule(mut self, rule_id: &'static str) -> Self {
74 self.rule_id = Some(rule_id);
75 self
76 }
77
78 pub(crate) fn with_segment(mut self, tag: &'static str) -> Self {
79 self.segment_tag = Some(tag);
80 self
81 }
82
83 pub(crate) fn with_suggestion(mut self, text: impl Into<String>) -> Self {
84 self.suggestion = Some(text.into());
85 self
86 }
87}
88
89impl fmt::Display for DvgwIssue {
90 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91 write!(f, "[{}]", self.severity)?;
92 if let Some(rule) = self.rule_id {
93 write!(f, " {rule}")?;
94 }
95 if let Some(tag) = self.segment_tag {
96 write!(f, " ({tag})")?;
97 }
98 write!(f, ": {}", self.message)
99 }
100}
101
102#[derive(Debug, Clone)]
104#[cfg_attr(feature = "serde", derive(serde::Serialize))]
105#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
106#[non_exhaustive]
107pub struct DvgwReport {
108 pub message_type: DvgwMessageType,
110 pub document: DvgwDocument,
112 pub message_ref: String,
114 pub issues: Vec<DvgwIssue>,
116}
117
118impl DvgwReport {
119 pub(crate) fn new(
120 message_type: DvgwMessageType,
121 document: DvgwDocument,
122 message_ref: String,
123 issues: Vec<DvgwIssue>,
124 ) -> Self {
125 Self {
126 message_type,
127 document,
128 message_ref,
129 issues,
130 }
131 }
132
133 #[must_use]
135 pub fn is_valid(&self) -> bool {
136 !self.issues.iter().any(|i| i.severity == Severity::Error)
137 }
138
139 pub fn errors(&self) -> impl Iterator<Item = &DvgwIssue> {
141 self.issues.iter().filter(|i| i.severity == Severity::Error)
142 }
143
144 pub fn warnings(&self) -> impl Iterator<Item = &DvgwIssue> {
146 self.issues
147 .iter()
148 .filter(|i| i.severity == Severity::Warning)
149 }
150
151 pub fn result(self) -> Result<Self, Self> {
158 if self.is_valid() { Ok(self) } else { Err(self) }
159 }
160}
161
162impl fmt::Display for DvgwReport {
163 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164 write!(
165 f,
166 "{} ({}) {}: {} error(s), {} warning(s)",
167 self.message_type,
168 self.document.code(),
169 self.message_ref,
170 self.errors().count(),
171 self.warnings().count()
172 )
173 }
174}