1use crate::Severity;
4use core::fmt;
5
6#[cfg(feature = "std")]
7use std::{format, string::String};
8
9#[cfg(not(feature = "std"))]
10use alloc::{format, string::String};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26pub struct Code {
27 severity: Severity,
28 component: &'static str,
29 primary: &'static str,
30 sequence: u16,
31}
32
33impl Code {
34 const MAX_LEN: usize = 12;
35 const MIN_LEN: usize = 2;
36
37 pub const fn new(
43 severity: Severity,
44 component: &'static str,
45 primary: &'static str,
46 sequence: u16,
47 ) -> Self {
48 assert!(sequence <= 999, "Sequence must be <= 999");
49
50 let component_len = component.len();
51 assert!(
52 component_len >= Self::MIN_LEN && component_len <= Self::MAX_LEN,
53 "Component must be 2-12 characters"
54 );
55
56 let primary_len = primary.len();
57 assert!(
58 primary_len >= Self::MIN_LEN && primary_len <= Self::MAX_LEN,
59 "Primary must be 2-12 characters"
60 );
61
62 Self {
63 severity,
64 component,
65 primary,
66 sequence,
67 }
68 }
69
70 pub const fn error(component: &'static str, primary: &'static str, sequence: u16) -> Self {
72 Self::new(Severity::Error, component, primary, sequence)
73 }
74
75 pub const fn warning(component: &'static str, primary: &'static str, sequence: u16) -> Self {
77 Self::new(Severity::Warning, component, primary, sequence)
78 }
79
80 pub const fn critical(component: &'static str, primary: &'static str, sequence: u16) -> Self {
82 Self::new(Severity::Critical, component, primary, sequence)
83 }
84
85 pub const fn blocked(component: &'static str, primary: &'static str, sequence: u16) -> Self {
87 Self::new(Severity::Blocked, component, primary, sequence)
88 }
89
90 pub const fn help(component: &'static str, primary: &'static str, sequence: u16) -> Self {
92 Self::new(Severity::Help, component, primary, sequence)
93 }
94
95 pub const fn success(component: &'static str, primary: &'static str, sequence: u16) -> Self {
97 Self::new(Severity::Success, component, primary, sequence)
98 }
99
100 pub const fn completed(component: &'static str, primary: &'static str, sequence: u16) -> Self {
102 Self::new(Severity::Completed, component, primary, sequence)
103 }
104
105 pub const fn info(component: &'static str, primary: &'static str, sequence: u16) -> Self {
107 Self::new(Severity::Info, component, primary, sequence)
108 }
109
110 pub const fn trace(component: &'static str, primary: &'static str, sequence: u16) -> Self {
112 Self::new(Severity::Trace, component, primary, sequence)
113 }
114
115 pub fn code(&self) -> String {
117 format!(
118 "{}.{}.{}.{:03}",
119 self.severity.as_char(),
120 self.component,
121 self.primary,
122 self.sequence
123 )
124 }
125
126 pub fn write_code(&self, f: &mut impl fmt::Write) -> fmt::Result {
130 write!(
131 f,
132 "{}.{}.{}.{:03}",
133 self.severity.as_char(),
134 self.component,
135 self.primary,
136 self.sequence
137 )
138 }
139
140 pub const fn severity(&self) -> Severity {
142 self.severity
143 }
144
145 pub const fn component(&self) -> &'static str {
147 self.component
148 }
149
150 pub const fn primary(&self) -> &'static str {
152 self.primary
153 }
154
155 pub const fn sequence(&self) -> u16 {
157 self.sequence
158 }
159
160 #[cfg(feature = "hash")]
165 pub fn hash(&self) -> String {
166 crate::hash::compute_hash(&self.code())
167 }
168}
169
170impl fmt::Display for Code {
171 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172 write!(
173 f,
174 "{}.{}.{}.{:03}",
175 self.severity.as_char(),
176 self.component,
177 self.primary,
178 self.sequence
179 )
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[cfg(feature = "std")]
188 use std::string::ToString;
189
190 #[cfg(not(feature = "std"))]
191 use alloc::string::ToString;
192
193 #[test]
194 fn test_error_code_format() {
195 const CODE: Code = Code::new(Severity::Error, "CRYPTO", "SALT", 1);
196 assert_eq!(CODE.code(), "E.CRYPTO.SALT.001");
197 assert_eq!(CODE.severity(), Severity::Error);
198 assert_eq!(CODE.component(), "CRYPTO");
199 assert_eq!(CODE.primary(), "SALT");
200 assert_eq!(CODE.sequence(), 1);
201 }
202
203 #[test]
204 fn test_error_code_display() {
205 const CODE: Code = Code::new(Severity::Warning, "PATTERN", "PARSE", 5);
206 assert_eq!(CODE.to_string(), "W.PATTERN.PARSE.005");
207 }
208
209 #[cfg(feature = "hash")]
210 #[test]
211 fn test_error_code_hash() {
212 const CODE: Code = Code::new(Severity::Error, "CRYPTO", "SALT", 1);
213 let hash1 = CODE.hash();
214 let hash2 = CODE.hash();
215 assert_eq!(hash1, hash2); assert_eq!(hash1.len(), 5);
217 assert!(hash1.chars().all(|c| c.is_ascii_alphanumeric()));
218 }
219
220 #[test]
221 fn test_length_validation() {
222 const MIN: Code = Code::new(Severity::Error, "IO", "FS", 1);
223 const MAX: Code = Code::new(Severity::Error, "LONGCOMPONNT", "LONGPRIMARY1", 1);
224 assert_eq!(MIN.component(), "IO");
225 assert_eq!(MAX.component(), "LONGCOMPONNT");
226 }
227}