pdfplumber_core/
validation.rs1use std::fmt;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum Severity {
15 Error,
17 Warning,
19}
20
21impl fmt::Display for Severity {
22 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23 match self {
24 Severity::Error => write!(f, "error"),
25 Severity::Warning => write!(f, "warning"),
26 }
27 }
28}
29
30#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct ValidationIssue {
37 pub severity: Severity,
39 pub code: String,
41 pub message: String,
43 pub location: Option<String>,
45}
46
47impl ValidationIssue {
48 pub fn new(severity: Severity, code: impl Into<String>, message: impl Into<String>) -> Self {
50 Self {
51 severity,
52 code: code.into(),
53 message: message.into(),
54 location: None,
55 }
56 }
57
58 pub fn with_location(
60 severity: Severity,
61 code: impl Into<String>,
62 message: impl Into<String>,
63 location: impl Into<String>,
64 ) -> Self {
65 Self {
66 severity,
67 code: code.into(),
68 message: message.into(),
69 location: Some(location.into()),
70 }
71 }
72
73 pub fn is_error(&self) -> bool {
75 self.severity == Severity::Error
76 }
77
78 pub fn is_warning(&self) -> bool {
80 self.severity == Severity::Warning
81 }
82}
83
84impl fmt::Display for ValidationIssue {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 write!(f, "[{}] {}: {}", self.severity, self.code, self.message)?;
87 if let Some(ref loc) = self.location {
88 write!(f, " (at {loc})")?;
89 }
90 Ok(())
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn severity_display() {
100 assert_eq!(Severity::Error.to_string(), "error");
101 assert_eq!(Severity::Warning.to_string(), "warning");
102 }
103
104 #[test]
105 fn severity_clone_and_eq() {
106 let s1 = Severity::Error;
107 let s2 = s1.clone();
108 assert_eq!(s1, s2);
109 assert_ne!(Severity::Error, Severity::Warning);
110 }
111
112 #[test]
113 fn validation_issue_new() {
114 let issue =
115 ValidationIssue::new(Severity::Error, "MISSING_TYPE", "catalog missing /Type key");
116 assert_eq!(issue.severity, Severity::Error);
117 assert_eq!(issue.code, "MISSING_TYPE");
118 assert_eq!(issue.message, "catalog missing /Type key");
119 assert!(issue.location.is_none());
120 assert!(issue.is_error());
121 assert!(!issue.is_warning());
122 }
123
124 #[test]
125 fn validation_issue_with_location() {
126 let issue = ValidationIssue::with_location(
127 Severity::Warning,
128 "MISSING_FONT",
129 "font /F1 not found in resources",
130 "page 2",
131 );
132 assert_eq!(issue.severity, Severity::Warning);
133 assert_eq!(issue.code, "MISSING_FONT");
134 assert_eq!(issue.message, "font /F1 not found in resources");
135 assert_eq!(issue.location.as_deref(), Some("page 2"));
136 assert!(!issue.is_error());
137 assert!(issue.is_warning());
138 }
139
140 #[test]
141 fn validation_issue_display_without_location() {
142 let issue = ValidationIssue::new(Severity::Error, "BROKEN_REF", "object 5 0 not found");
143 assert_eq!(
144 issue.to_string(),
145 "[error] BROKEN_REF: object 5 0 not found"
146 );
147 }
148
149 #[test]
150 fn validation_issue_display_with_location() {
151 let issue = ValidationIssue::with_location(
152 Severity::Warning,
153 "MISSING_FONT",
154 "font /F1 referenced but not defined",
155 "page 3",
156 );
157 assert_eq!(
158 issue.to_string(),
159 "[warning] MISSING_FONT: font /F1 referenced but not defined (at page 3)"
160 );
161 }
162
163 #[test]
164 fn validation_issue_clone_and_eq() {
165 let issue1 = ValidationIssue::new(Severity::Error, "TEST", "test message");
166 let issue2 = issue1.clone();
167 assert_eq!(issue1, issue2);
168 }
169}