use std::fmt;
use crate::field::FieldSchema;
use crate::projection::ProjectionSpec;
use crate::CanonicalCondition;
use crate::RecoveryHint;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ErrorClass(&'static str);
impl ErrorClass {
pub const VALIDATION: Self = Self("validation");
pub const AUTHENTICATION: Self = Self("authentication");
pub const AUTHORIZATION: Self = Self("authorization");
pub const ROUTING: Self = Self("routing");
pub const CAPACITY: Self = Self("capacity");
pub const TIMEOUT: Self = Self("timeout");
pub const UNAVAILABLE: Self = Self("unavailable");
pub const IO: Self = Self("io");
pub const DATA_CORRUPTION: Self = Self("data_corruption");
pub const UNSUPPORTED: Self = Self("unsupported");
pub const INTERNAL: Self = Self("internal");
pub const BUG: Self = Self("bug");
#[inline]
pub const fn as_str(self) -> &'static str {
self.0
}
}
impl fmt::Display for ErrorClass {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ComponentId(&'static str);
impl ComponentId {
pub const CORE: Self = Self("core");
pub const PROTOCOL: Self = Self("protocol");
pub const ROUTE: Self = Self("route");
pub const AUTH: Self = Self("auth");
pub const CONTROLLER: Self = Self("controller");
pub const STORAGE: Self = Self("storage");
pub const RUNTIME: Self = Self("runtime");
pub const TRANSPORT: Self = Self("transport");
pub const BROKER: Self = Self("broker");
pub const CLIENT: Self = Self("client");
pub const PROXY: Self = Self("proxy");
pub const TOOLS: Self = Self("tools");
pub const OBSERVABILITY: Self = Self("observability");
#[inline]
pub const fn as_str(self) -> &'static str {
self.0
}
}
impl fmt::Display for ComponentId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum FaultAttribution {
Caller,
RemotePeer,
LocalResource,
Dependency,
Configuration,
Bug,
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Exposure {
Public,
Generic,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum BacktracePolicy {
Never,
OnDemand,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ErrorSeverity {
Debug,
Info,
Warn,
Error,
Critical,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ErrorCode(&'static str);
impl ErrorCode {
#[inline]
pub const fn try_new(value: &'static str) -> Option<Self> {
if is_valid_catalog_code(value) {
Some(Self(value))
} else {
None
}
}
#[inline]
pub const fn as_str(self) -> &'static str {
self.0
}
}
impl fmt::Display for ErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ErrorDescriptor {
code: ErrorCode,
class: ErrorClass,
condition: CanonicalCondition,
fault: FaultAttribution,
component: ComponentId,
public_message: &'static str,
severity: ErrorSeverity,
recovery_hint: RecoveryHint,
backtrace: BacktracePolicy,
exposure: Exposure,
projection: ProjectionSpec,
fields: &'static [FieldSchema],
}
impl ErrorDescriptor {
#[inline]
pub(crate) const fn try_new(
code: ErrorCode,
class: ErrorClass,
condition: CanonicalCondition,
fault: FaultAttribution,
component: ComponentId,
public_message: &'static str,
severity: ErrorSeverity,
recovery_hint: RecoveryHint,
backtrace: BacktracePolicy,
exposure: Exposure,
projection: ProjectionSpec,
fields: &'static [FieldSchema],
) -> Option<Self> {
if !valid_descriptor_fields(fields) {
return None;
}
Some(Self {
code,
class,
condition,
fault,
component,
public_message,
severity,
recovery_hint,
backtrace,
exposure,
projection,
fields,
})
}
#[inline]
pub const fn code(&self) -> ErrorCode {
self.code
}
#[inline]
pub const fn class(&self) -> ErrorClass {
self.class
}
#[inline]
pub const fn condition(&self) -> CanonicalCondition {
self.condition
}
#[inline]
pub const fn fault(&self) -> FaultAttribution {
self.fault
}
#[inline]
pub const fn component(&self) -> ComponentId {
self.component
}
#[inline]
pub const fn public_message(&self) -> &'static str {
self.public_message
}
#[inline]
pub const fn severity(&self) -> ErrorSeverity {
self.severity
}
#[inline]
pub const fn recovery_hint(&self) -> RecoveryHint {
self.recovery_hint
}
#[inline]
pub const fn backtrace_policy(&self) -> BacktracePolicy {
self.backtrace
}
#[inline]
pub const fn exposure(&self) -> Exposure {
self.exposure
}
#[inline]
pub const fn projection(&self) -> ProjectionSpec {
self.projection
}
#[inline]
pub const fn fields(&self) -> &'static [FieldSchema] {
self.fields
}
}
const fn valid_descriptor_fields(fields: &[FieldSchema]) -> bool {
if fields.len() > 16 {
return false;
}
let mut index = 0;
while index < fields.len() {
let mut other = index + 1;
while other < fields.len() {
if const_str_eq(fields[index].name(), fields[other].name()) {
return false;
}
other += 1;
}
index += 1;
}
true
}
const fn const_str_eq(left: &str, right: &str) -> bool {
let left = left.as_bytes();
let right = right.as_bytes();
if left.len() != right.len() {
return false;
}
let mut index = 0;
while index < left.len() {
if left[index] != right[index] {
return false;
}
index += 1;
}
true
}
const fn is_valid_catalog_code(value: &str) -> bool {
let bytes = value.as_bytes();
let mut index = 0;
let mut segment_count = 0;
let mut starts_segment = true;
while index < bytes.len() {
let byte = bytes[index];
if starts_segment {
if !byte.is_ascii_lowercase() {
return false;
}
starts_segment = false;
segment_count += 1;
} else if byte == b'.' {
starts_segment = true;
} else if !byte.is_ascii_lowercase() && !byte.is_ascii_digit() && byte != b'_' {
return false;
}
index += 1;
}
!starts_segment && segment_count >= 3
}
#[cfg(test)]
mod tests {
use crate::fields;
use super::*;
#[test]
fn descriptor_field_lists_reject_duplicate_names_and_capacity_overflow() {
let duplicate = [fields::OPERATION.schema(), fields::OPERATION_DIAGNOSTIC.schema()];
assert!(!valid_descriptor_fields(&duplicate));
let too_many = [fields::TOPIC.schema(); 17];
assert!(!valid_descriptor_fields(&too_many));
}
}