use crate::limits::TransactionLimits;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct DiagnosticCode(String);
impl DiagnosticCode {
pub const MAX_BYTES: usize = 64;
pub fn try_new(value: impl Into<String>) -> Result<Self, SafeDiagnosticError> {
let s = value.into();
if s.is_empty() {
return Err(SafeDiagnosticError::EmptyCode);
}
if s.len() > Self::MAX_BYTES {
return Err(SafeDiagnosticError::CodeTooLong);
}
if s.chars().any(|c| c.is_control()) {
return Err(SafeDiagnosticError::ControlCharacter);
}
Ok(Self(s))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SafeDiagnostic {
pub code: DiagnosticCode,
pub message: Option<String>,
}
impl SafeDiagnostic {
pub fn try_new(
code: impl Into<String>,
message: Option<impl Into<String>>,
max_message_bytes: usize,
) -> Result<Self, SafeDiagnosticError> {
let code = DiagnosticCode::try_new(code)?;
let message = match message {
None => None,
Some(m) => {
let mut s = m.into();
if s.chars().any(|c| c.is_control()) {
return Err(SafeDiagnosticError::ControlCharacter);
}
if s.len() > max_message_bytes {
s.truncate(max_message_bytes);
while !s.is_char_boundary(s.len()) {
s.pop();
}
}
if s.is_empty() {
None
} else {
Some(s)
}
}
};
Ok(Self { code, message })
}
pub fn try_new_default(
code: impl Into<String>,
message: Option<impl Into<String>>,
) -> Result<Self, SafeDiagnosticError> {
let limits = TransactionLimits::default();
Self::try_new(code, message, limits.max_diagnostic_bytes)
}
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
pub enum SafeDiagnosticError {
#[error("diagnostic code must be non-empty")]
EmptyCode,
#[error("diagnostic code exceeds maximum length")]
CodeTooLong,
#[error("diagnostic must not contain control characters")]
ControlCharacter,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn redacts_by_truncation_not_secret_content() {
let long = "x".repeat(2000);
let d = SafeDiagnostic::try_new("ok", Some(long), 32).unwrap();
assert!(d.message.as_ref().unwrap().len() <= 32);
}
}