1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7macro_rules! diagnostic_text_newtype {
8 ($name:ident) => {
9 #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
10 pub struct $name(String);
11
12 impl $name {
13 pub fn new(input: &str) -> Result<Self, PhpDiagnosticError> {
14 let trimmed = input.trim();
15 if trimmed.is_empty() {
16 Err(PhpDiagnosticError::Empty)
17 } else {
18 Ok(Self(trimmed.to_string()))
19 }
20 }
21
22 pub fn as_str(&self) -> &str {
23 &self.0
24 }
25 }
26
27 impl fmt::Display for $name {
28 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
29 formatter.write_str(self.as_str())
30 }
31 }
32 };
33}
34
35diagnostic_text_newtype!(DiagnosticMessage);
36diagnostic_text_newtype!(DiagnosticSource);
37
38#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
40pub enum PhpErrorLevel {
41 Error,
42 Warning,
43 Parse,
44 Notice,
45 CoreError,
46 CoreWarning,
47 CompileError,
48 CompileWarning,
49 UserError,
50 UserWarning,
51 UserNotice,
52 Deprecated,
53 UserDeprecated,
54 RecoverableError,
55}
56
57impl PhpErrorLevel {
58 pub const fn as_str(self) -> &'static str {
59 match self {
60 Self::Error => "E_ERROR",
61 Self::Warning => "E_WARNING",
62 Self::Parse => "E_PARSE",
63 Self::Notice => "E_NOTICE",
64 Self::CoreError => "E_CORE_ERROR",
65 Self::CoreWarning => "E_CORE_WARNING",
66 Self::CompileError => "E_COMPILE_ERROR",
67 Self::CompileWarning => "E_COMPILE_WARNING",
68 Self::UserError => "E_USER_ERROR",
69 Self::UserWarning => "E_USER_WARNING",
70 Self::UserNotice => "E_USER_NOTICE",
71 Self::Deprecated => "E_DEPRECATED",
72 Self::UserDeprecated => "E_USER_DEPRECATED",
73 Self::RecoverableError => "E_RECOVERABLE_ERROR",
74 }
75 }
76
77 pub const fn severity(self) -> PhpSeverity {
78 match self {
79 Self::Error
80 | Self::Parse
81 | Self::CoreError
82 | Self::CompileError
83 | Self::UserError
84 | Self::RecoverableError => PhpSeverity::Error,
85 Self::Warning | Self::CoreWarning | Self::CompileWarning | Self::UserWarning => {
86 PhpSeverity::Warning
87 },
88 Self::Deprecated | Self::UserDeprecated => PhpSeverity::Deprecated,
89 Self::Notice | Self::UserNotice => PhpSeverity::Info,
90 }
91 }
92}
93
94impl fmt::Display for PhpErrorLevel {
95 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
96 formatter.write_str(self.as_str())
97 }
98}
99
100#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
102pub enum PhpSeverity {
103 Error,
104 Warning,
105 Info,
106 Deprecated,
107}
108
109impl PhpSeverity {
110 pub const fn as_str(self) -> &'static str {
111 match self {
112 Self::Error => "error",
113 Self::Warning => "warning",
114 Self::Info => "info",
115 Self::Deprecated => "deprecated",
116 }
117 }
118}
119
120impl fmt::Display for PhpSeverity {
121 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
122 formatter.write_str(self.as_str())
123 }
124}
125
126#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
128pub enum PhpErrorKind {
129 Runtime,
130 Parse,
131 Compile,
132 Core,
133 User,
134 Deprecation,
135 Unknown,
136}
137
138impl PhpErrorKind {
139 pub const fn as_str(self) -> &'static str {
140 match self {
141 Self::Runtime => "runtime",
142 Self::Parse => "parse",
143 Self::Compile => "compile",
144 Self::Core => "core",
145 Self::User => "user",
146 Self::Deprecation => "deprecation",
147 Self::Unknown => "unknown",
148 }
149 }
150}
151
152impl fmt::Display for PhpErrorKind {
153 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
154 formatter.write_str(self.as_str())
155 }
156}
157
158impl FromStr for PhpErrorKind {
159 type Err = PhpDiagnosticError;
160
161 fn from_str(input: &str) -> Result<Self, Self::Err> {
162 match input.trim().to_ascii_lowercase().as_str() {
163 "runtime" => Ok(Self::Runtime),
164 "parse" => Ok(Self::Parse),
165 "compile" => Ok(Self::Compile),
166 "core" => Ok(Self::Core),
167 "user" => Ok(Self::User),
168 "deprecation" | "deprecated" => Ok(Self::Deprecation),
169 "unknown" => Ok(Self::Unknown),
170 "" => Err(PhpDiagnosticError::Empty),
171 _ => Err(PhpDiagnosticError::UnknownLabel),
172 }
173 }
174}
175
176#[derive(Clone, Debug, Eq, PartialEq)]
178pub struct PhpDiagnostic {
179 kind: PhpErrorKind,
180 severity: PhpSeverity,
181 message: DiagnosticMessage,
182 source: Option<DiagnosticSource>,
183}
184
185impl PhpDiagnostic {
186 pub const fn new(
187 kind: PhpErrorKind,
188 severity: PhpSeverity,
189 message: DiagnosticMessage,
190 ) -> Self {
191 Self {
192 kind,
193 severity,
194 message,
195 source: None,
196 }
197 }
198
199 pub fn with_source(mut self, source: DiagnosticSource) -> Self {
200 self.source = Some(source);
201 self
202 }
203
204 pub const fn kind(&self) -> PhpErrorKind {
205 self.kind
206 }
207
208 pub const fn severity(&self) -> PhpSeverity {
209 self.severity
210 }
211
212 pub const fn message(&self) -> &DiagnosticMessage {
213 &self.message
214 }
215
216 pub const fn source(&self) -> Option<&DiagnosticSource> {
217 self.source.as_ref()
218 }
219}
220
221#[derive(Clone, Copy, Debug, Eq, PartialEq)]
223pub enum PhpDiagnosticError {
224 Empty,
225 UnknownLabel,
226}
227
228impl fmt::Display for PhpDiagnosticError {
229 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
230 match self {
231 Self::Empty => formatter.write_str("PHP diagnostic metadata cannot be empty"),
232 Self::UnknownLabel => formatter.write_str("unknown PHP diagnostic metadata label"),
233 }
234 }
235}
236
237impl Error for PhpDiagnosticError {}
238
239#[cfg(test)]
240mod tests {
241 use super::{
242 DiagnosticMessage, DiagnosticSource, PhpDiagnostic, PhpDiagnosticError, PhpErrorKind,
243 PhpErrorLevel, PhpSeverity,
244 };
245
246 #[test]
247 fn maps_error_level_to_severity() {
248 assert_eq!(PhpErrorLevel::UserWarning.severity(), PhpSeverity::Warning);
249 assert_eq!(
250 PhpErrorLevel::Deprecated.severity(),
251 PhpSeverity::Deprecated
252 );
253 }
254
255 #[test]
256 fn builds_diagnostic_metadata() -> Result<(), PhpDiagnosticError> {
257 let diagnostic = PhpDiagnostic::new(
258 PhpErrorKind::Runtime,
259 PhpSeverity::Error,
260 DiagnosticMessage::new("Undefined variable")?,
261 )
262 .with_source(DiagnosticSource::new("example.php:10")?);
263
264 assert_eq!(diagnostic.severity(), PhpSeverity::Error);
265 assert_eq!(
266 diagnostic.source().expect("source").as_str(),
267 "example.php:10"
268 );
269 Ok(())
270 }
271}