1use crate::Severity;
4use crate::traits::{ComponentId, PrimaryId};
5use core::fmt;
6
7#[cfg(feature = "std")]
8use std::{format, string::String};
9
10#[cfg(not(feature = "std"))]
11use alloc::{format, string::String};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
43pub struct Code<C: ComponentId, P: PrimaryId> {
44 severity: Severity,
45 component: C,
46 primary: P,
47 sequence: u16,
48}
49
50impl<C: ComponentId, P: PrimaryId> Code<C, P> {
51 pub const fn new(severity: Severity, component: C, primary: P, sequence: u16) -> Self {
57 assert!(sequence <= 999, "Sequence must be <= 999");
58
59 Self {
60 severity,
61 component,
62 primary,
63 sequence,
64 }
65 }
66
67 pub const fn error(component: C, primary: P, sequence: u16) -> Self {
69 Self::new(Severity::Error, component, primary, sequence)
70 }
71
72 pub const fn warning(component: C, primary: P, sequence: u16) -> Self {
74 Self::new(Severity::Warning, component, primary, sequence)
75 }
76
77 pub const fn critical(component: C, primary: P, sequence: u16) -> Self {
79 Self::new(Severity::Critical, component, primary, sequence)
80 }
81
82 pub const fn blocked(component: C, primary: P, sequence: u16) -> Self {
84 Self::new(Severity::Blocked, component, primary, sequence)
85 }
86
87 pub const fn help(component: C, primary: P, sequence: u16) -> Self {
89 Self::new(Severity::Help, component, primary, sequence)
90 }
91
92 pub const fn success(component: C, primary: P, sequence: u16) -> Self {
94 Self::new(Severity::Success, component, primary, sequence)
95 }
96
97 pub const fn completed(component: C, primary: P, sequence: u16) -> Self {
99 Self::new(Severity::Completed, component, primary, sequence)
100 }
101
102 pub const fn info(component: C, primary: P, sequence: u16) -> Self {
104 Self::new(Severity::Info, component, primary, sequence)
105 }
106
107 pub const fn trace(component: C, primary: P, sequence: u16) -> Self {
109 Self::new(Severity::Trace, component, primary, sequence)
110 }
111
112 pub fn code(&self) -> String {
114 format!(
115 "{}.{}.{}.{:03}",
116 self.severity.as_char(),
117 self.component.as_str(),
118 self.primary.as_str(),
119 self.sequence
120 )
121 }
122
123 pub fn write_code(&self, f: &mut impl fmt::Write) -> fmt::Result {
127 write!(
128 f,
129 "{}.{}.{}.{:03}",
130 self.severity.as_char(),
131 self.component.as_str(),
132 self.primary.as_str(),
133 self.sequence
134 )
135 }
136
137 pub const fn severity(&self) -> Severity {
139 self.severity
140 }
141
142 pub const fn component(&self) -> C {
144 self.component
145 }
146
147 pub fn component_str(&self) -> &'static str {
149 self.component.as_str()
150 }
151
152 pub const fn primary(&self) -> P {
154 self.primary
155 }
156
157 pub fn primary_str(&self) -> &'static str {
159 self.primary.as_str()
160 }
161
162 pub const fn sequence(&self) -> u16 {
164 self.sequence
165 }
166
167 #[cfg(all(feature = "runtime-hash", feature = "std"))]
201 pub fn hash(&self) -> String {
202 use waddling_errors_hash::{compute_hash_with_config, load_global_config};
203 let config = load_global_config();
204 compute_hash_with_config(&self.code(), &config)
205 }
206
207 #[cfg(feature = "runtime-hash")]
241 pub fn hash_with_config(&self, config: &waddling_errors_hash::HashConfig) -> String {
242 use waddling_errors_hash::compute_hash_with_config;
243 compute_hash_with_config(&self.code(), config)
244 }
245}
246
247impl<C: ComponentId, P: PrimaryId> fmt::Display for Code<C, P> {
248 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
249 write!(
250 f,
251 "{}.{}.{}.{:03}",
252 self.severity.as_char(),
253 self.component.as_str(),
254 self.primary.as_str(),
255 self.sequence
256 )
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263 use crate::traits::{ComponentId, PrimaryId};
264
265 #[cfg(feature = "std")]
266 use std::string::ToString;
267
268 #[cfg(not(feature = "std"))]
269 use alloc::string::ToString;
270
271 #[derive(Debug, Copy, Clone)]
272 enum TestComponent {
273 Crypto,
274 Pattern,
275 Io,
276 LongComponent,
277 }
278
279 impl ComponentId for TestComponent {
280 fn as_str(&self) -> &'static str {
281 match self {
282 TestComponent::Crypto => "CRYPTO",
283 TestComponent::Pattern => "PATTERN",
284 TestComponent::Io => "IO",
285 TestComponent::LongComponent => "LONGCOMPONNT",
286 }
287 }
288 }
289
290 #[derive(Debug, Copy, Clone)]
291 enum TestPrimary {
292 Salt,
293 Parse,
294 Fs,
295 LongPrimary,
296 }
297
298 impl PrimaryId for TestPrimary {
299 fn as_str(&self) -> &'static str {
300 match self {
301 TestPrimary::Salt => "SALT",
302 TestPrimary::Parse => "PARSE",
303 TestPrimary::Fs => "FS",
304 TestPrimary::LongPrimary => "LONGPRIMARY1",
305 }
306 }
307 }
308
309 #[test]
310 fn test_error_code_format() {
311 const CODE: Code<TestComponent, TestPrimary> =
312 Code::new(Severity::Error, TestComponent::Crypto, TestPrimary::Salt, 1);
313 assert_eq!(CODE.code(), "E.CRYPTO.SALT.001");
314 assert_eq!(CODE.severity(), Severity::Error);
315 assert_eq!(CODE.component_str(), "CRYPTO");
316 assert_eq!(CODE.primary_str(), "SALT");
317 assert_eq!(CODE.sequence(), 1);
318 }
319
320 #[test]
321 fn test_error_code_display() {
322 const CODE: Code<TestComponent, TestPrimary> = Code::new(
323 Severity::Warning,
324 TestComponent::Pattern,
325 TestPrimary::Parse,
326 5,
327 );
328 assert_eq!(CODE.to_string(), "W.PATTERN.PARSE.005");
329 }
330
331 #[cfg(feature = "runtime-hash")]
332 #[test]
333 fn test_error_code_hash() {
334 const CODE: Code<TestComponent, TestPrimary> =
335 Code::new(Severity::Error, TestComponent::Crypto, TestPrimary::Salt, 1);
336 let hash1 = CODE.hash();
337 let hash2 = CODE.hash();
338 assert_eq!(hash1, hash2); assert_eq!(hash1.len(), 5);
340 assert!(hash1.chars().all(|c| c.is_ascii_alphanumeric()));
341 }
342
343 #[test]
344 fn test_length_validation() {
345 const MIN: Code<TestComponent, TestPrimary> =
346 Code::new(Severity::Error, TestComponent::Io, TestPrimary::Fs, 1);
347 const MAX: Code<TestComponent, TestPrimary> = Code::new(
348 Severity::Error,
349 TestComponent::LongComponent,
350 TestPrimary::LongPrimary,
351 1,
352 );
353 assert_eq!(MIN.component_str(), "IO");
354 assert_eq!(MAX.component_str(), "LONGCOMPONNT");
355 }
356}