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(feature = "hash")]
172 pub fn hash(&self) -> String {
173 crate::hash::compute_hash(&self.code())
174 }
175}
176
177impl<C: ComponentId, P: PrimaryId> fmt::Display for Code<C, P> {
178 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179 write!(
180 f,
181 "{}.{}.{}.{:03}",
182 self.severity.as_char(),
183 self.component.as_str(),
184 self.primary.as_str(),
185 self.sequence
186 )
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193 use crate::traits::{ComponentId, PrimaryId};
194
195 #[cfg(feature = "std")]
196 use std::string::ToString;
197
198 #[cfg(not(feature = "std"))]
199 use alloc::string::ToString;
200
201 #[derive(Debug, Copy, Clone)]
202 enum TestComponent {
203 Crypto,
204 Pattern,
205 Io,
206 LongComponent,
207 }
208
209 impl ComponentId for TestComponent {
210 fn as_str(&self) -> &'static str {
211 match self {
212 TestComponent::Crypto => "CRYPTO",
213 TestComponent::Pattern => "PATTERN",
214 TestComponent::Io => "IO",
215 TestComponent::LongComponent => "LONGCOMPONNT",
216 }
217 }
218 }
219
220 #[derive(Debug, Copy, Clone)]
221 enum TestPrimary {
222 Salt,
223 Parse,
224 Fs,
225 LongPrimary,
226 }
227
228 impl PrimaryId for TestPrimary {
229 fn as_str(&self) -> &'static str {
230 match self {
231 TestPrimary::Salt => "SALT",
232 TestPrimary::Parse => "PARSE",
233 TestPrimary::Fs => "FS",
234 TestPrimary::LongPrimary => "LONGPRIMARY1",
235 }
236 }
237 }
238
239 #[test]
240 fn test_error_code_format() {
241 const CODE: Code<TestComponent, TestPrimary> =
242 Code::new(Severity::Error, TestComponent::Crypto, TestPrimary::Salt, 1);
243 assert_eq!(CODE.code(), "E.CRYPTO.SALT.001");
244 assert_eq!(CODE.severity(), Severity::Error);
245 assert_eq!(CODE.component_str(), "CRYPTO");
246 assert_eq!(CODE.primary_str(), "SALT");
247 assert_eq!(CODE.sequence(), 1);
248 }
249
250 #[test]
251 fn test_error_code_display() {
252 const CODE: Code<TestComponent, TestPrimary> = Code::new(
253 Severity::Warning,
254 TestComponent::Pattern,
255 TestPrimary::Parse,
256 5,
257 );
258 assert_eq!(CODE.to_string(), "W.PATTERN.PARSE.005");
259 }
260
261 #[cfg(feature = "hash")]
262 #[test]
263 fn test_error_code_hash() {
264 const CODE: Code<TestComponent, TestPrimary> =
265 Code::new(Severity::Error, TestComponent::Crypto, TestPrimary::Salt, 1);
266 let hash1 = CODE.hash();
267 let hash2 = CODE.hash();
268 assert_eq!(hash1, hash2); assert_eq!(hash1.len(), 5);
270 assert!(hash1.chars().all(|c| c.is_ascii_alphanumeric()));
271 }
272
273 #[test]
274 fn test_length_validation() {
275 const MIN: Code<TestComponent, TestPrimary> =
276 Code::new(Severity::Error, TestComponent::Io, TestPrimary::Fs, 1);
277 const MAX: Code<TestComponent, TestPrimary> = Code::new(
278 Severity::Error,
279 TestComponent::LongComponent,
280 TestPrimary::LongPrimary,
281 1,
282 );
283 assert_eq!(MIN.component_str(), "IO");
284 assert_eq!(MAX.component_str(), "LONGCOMPONNT");
285 }
286}