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 success(component: &'static str, primary: &'static str, sequence: u16) -> Self {
92 Self::new(Severity::Success, component, primary, sequence)
93 }
94
95 pub const fn completed(component: &'static str, primary: &'static str, sequence: u16) -> Self {
97 Self::new(Severity::Completed, component, primary, sequence)
98 }
99
100 pub const fn info(component: &'static str, primary: &'static str, sequence: u16) -> Self {
102 Self::new(Severity::Info, component, primary, sequence)
103 }
104
105 pub const fn trace(component: &'static str, primary: &'static str, sequence: u16) -> Self {
107 Self::new(Severity::Trace, component, primary, sequence)
108 }
109
110 pub fn code(&self) -> String {
112 format!(
113 "{}.{}.{}.{:03}",
114 self.severity.as_char(),
115 self.component,
116 self.primary,
117 self.sequence
118 )
119 }
120
121 pub fn write_code(&self, f: &mut impl fmt::Write) -> fmt::Result {
125 write!(
126 f,
127 "{}.{}.{}.{:03}",
128 self.severity.as_char(),
129 self.component,
130 self.primary,
131 self.sequence
132 )
133 }
134
135 pub const fn severity(&self) -> Severity {
137 self.severity
138 }
139
140 pub const fn component(&self) -> &'static str {
142 self.component
143 }
144
145 pub const fn primary(&self) -> &'static str {
147 self.primary
148 }
149
150 pub const fn sequence(&self) -> u16 {
152 self.sequence
153 }
154
155 #[cfg(feature = "hash")]
160 pub fn hash(&self) -> String {
161 crate::hash::compute_hash(&self.code())
162 }
163}
164
165impl fmt::Display for Code {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167 write!(
168 f,
169 "{}.{}.{}.{:03}",
170 self.severity.as_char(),
171 self.component,
172 self.primary,
173 self.sequence
174 )
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[cfg(feature = "std")]
183 use std::string::ToString;
184
185 #[cfg(not(feature = "std"))]
186 use alloc::string::ToString;
187
188 #[test]
189 fn test_error_code_format() {
190 const CODE: Code = Code::new(Severity::Error, "CRYPTO", "SALT", 1);
191 assert_eq!(CODE.code(), "E.CRYPTO.SALT.001");
192 assert_eq!(CODE.severity(), Severity::Error);
193 assert_eq!(CODE.component(), "CRYPTO");
194 assert_eq!(CODE.primary(), "SALT");
195 assert_eq!(CODE.sequence(), 1);
196 }
197
198 #[test]
199 fn test_error_code_display() {
200 const CODE: Code = Code::new(Severity::Warning, "PATTERN", "PARSE", 5);
201 assert_eq!(CODE.to_string(), "W.PATTERN.PARSE.005");
202 }
203
204 #[cfg(feature = "hash")]
205 #[test]
206 fn test_error_code_hash() {
207 const CODE: Code = Code::new(Severity::Error, "CRYPTO", "SALT", 1);
208 let hash1 = CODE.hash();
209 let hash2 = CODE.hash();
210 assert_eq!(hash1, hash2); assert_eq!(hash1.len(), 5);
212 assert!(hash1.chars().all(|c| c.is_ascii_alphanumeric()));
213 }
214
215 #[test]
216 fn test_length_validation() {
217 const MIN: Code = Code::new(Severity::Error, "IO", "FS", 1);
218 const MAX: Code = Code::new(Severity::Error, "LONGCOMPONNT", "LONGPRIMARY1", 1);
219 assert_eq!(MIN.component(), "IO");
220 assert_eq!(MAX.component(), "LONGCOMPONNT");
221 }
222}