use crate::Severity;
use crate::traits::{ComponentId, PrimaryId};
use core::fmt;
#[cfg(feature = "std")]
use std::{format, string::String};
#[cfg(not(feature = "std"))]
use alloc::{format, string::String};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Code<C: ComponentId, P: PrimaryId> {
severity: Severity,
component: C,
primary: P,
sequence: u16,
}
impl<C: ComponentId, P: PrimaryId> Code<C, P> {
pub const fn new(severity: Severity, component: C, primary: P, sequence: u16) -> Self {
assert!(sequence <= 999, "Sequence must be <= 999");
Self {
severity,
component,
primary,
sequence,
}
}
pub const fn error(component: C, primary: P, sequence: u16) -> Self {
Self::new(Severity::Error, component, primary, sequence)
}
pub const fn warning(component: C, primary: P, sequence: u16) -> Self {
Self::new(Severity::Warning, component, primary, sequence)
}
pub const fn critical(component: C, primary: P, sequence: u16) -> Self {
Self::new(Severity::Critical, component, primary, sequence)
}
pub const fn blocked(component: C, primary: P, sequence: u16) -> Self {
Self::new(Severity::Blocked, component, primary, sequence)
}
pub const fn help(component: C, primary: P, sequence: u16) -> Self {
Self::new(Severity::Help, component, primary, sequence)
}
pub const fn success(component: C, primary: P, sequence: u16) -> Self {
Self::new(Severity::Success, component, primary, sequence)
}
pub const fn completed(component: C, primary: P, sequence: u16) -> Self {
Self::new(Severity::Completed, component, primary, sequence)
}
pub const fn info(component: C, primary: P, sequence: u16) -> Self {
Self::new(Severity::Info, component, primary, sequence)
}
pub const fn trace(component: C, primary: P, sequence: u16) -> Self {
Self::new(Severity::Trace, component, primary, sequence)
}
pub fn code(&self) -> String {
format!(
"{}.{}.{}.{:03}",
self.severity.as_char(),
self.component.as_str(),
self.primary.as_str(),
self.sequence
)
}
pub fn write_code(&self, f: &mut impl fmt::Write) -> fmt::Result {
write!(
f,
"{}.{}.{}.{:03}",
self.severity.as_char(),
self.component.as_str(),
self.primary.as_str(),
self.sequence
)
}
pub const fn severity(&self) -> Severity {
self.severity
}
pub const fn component(&self) -> C {
self.component
}
pub fn component_str(&self) -> &'static str {
self.component.as_str()
}
pub const fn primary(&self) -> P {
self.primary
}
pub fn primary_str(&self) -> &'static str {
self.primary.as_str()
}
pub const fn sequence(&self) -> u16 {
self.sequence
}
#[cfg(all(feature = "runtime-hash", feature = "std"))]
pub fn hash(&self) -> String {
waddling_errors_hash::compute_wdp_hash(&self.code())
}
#[cfg(feature = "runtime-hash")]
pub fn hash_with_config(&self, config: &waddling_errors_hash::HashConfig) -> String {
use waddling_errors_hash::compute_hash_with_config;
compute_hash_with_config(&self.code(), config)
}
}
impl<C: ComponentId, P: PrimaryId> fmt::Display for Code<C, P> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}.{}.{}.{:03}",
self.severity.as_char(),
self.component.as_str(),
self.primary.as_str(),
self.sequence
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::traits::{ComponentId, PrimaryId};
#[cfg(feature = "std")]
use std::string::ToString;
#[cfg(not(feature = "std"))]
use alloc::string::ToString;
#[derive(Debug, Copy, Clone)]
enum TestComponent {
Crypto,
Pattern,
Io,
LongComponent,
}
impl ComponentId for TestComponent {
fn as_str(&self) -> &'static str {
match self {
TestComponent::Crypto => "CRYPTO",
TestComponent::Pattern => "PATTERN",
TestComponent::Io => "IO",
TestComponent::LongComponent => "LONGCOMPONNT",
}
}
}
#[derive(Debug, Copy, Clone)]
enum TestPrimary {
Salt,
Parse,
Fs,
LongPrimary,
}
impl PrimaryId for TestPrimary {
fn as_str(&self) -> &'static str {
match self {
TestPrimary::Salt => "SALT",
TestPrimary::Parse => "PARSE",
TestPrimary::Fs => "FS",
TestPrimary::LongPrimary => "LONGPRIMARY1",
}
}
}
#[test]
fn test_error_code_format() {
const CODE: Code<TestComponent, TestPrimary> =
Code::new(Severity::Error, TestComponent::Crypto, TestPrimary::Salt, 1);
assert_eq!(CODE.code(), "E.CRYPTO.SALT.001");
assert_eq!(CODE.severity(), Severity::Error);
assert_eq!(CODE.component_str(), "CRYPTO");
assert_eq!(CODE.primary_str(), "SALT");
assert_eq!(CODE.sequence(), 1);
}
#[test]
fn test_error_code_display() {
const CODE: Code<TestComponent, TestPrimary> = Code::new(
Severity::Warning,
TestComponent::Pattern,
TestPrimary::Parse,
5,
);
assert_eq!(CODE.to_string(), "W.PATTERN.PARSE.005");
}
#[cfg(feature = "runtime-hash")]
#[test]
fn test_error_code_hash() {
const CODE: Code<TestComponent, TestPrimary> =
Code::new(Severity::Error, TestComponent::Crypto, TestPrimary::Salt, 1);
let hash1 = CODE.hash();
let hash2 = CODE.hash();
assert_eq!(hash1, hash2); assert_eq!(hash1.len(), 5);
assert!(hash1.chars().all(|c| c.is_ascii_alphanumeric()));
}
#[test]
fn test_length_validation() {
const MIN: Code<TestComponent, TestPrimary> =
Code::new(Severity::Error, TestComponent::Io, TestPrimary::Fs, 1);
const MAX: Code<TestComponent, TestPrimary> = Code::new(
Severity::Error,
TestComponent::LongComponent,
TestPrimary::LongPrimary,
1,
);
assert_eq!(MIN.component_str(), "IO");
assert_eq!(MAX.component_str(), "LONGCOMPONNT");
}
}