use connectrpc::ConnectError;
#[derive(Debug)]
pub enum DomainErrorKind {
NotFound,
Conflict(String),
Internal(String),
}
pub trait IntoDomainErrorKind {
fn kind(&self) -> DomainErrorKind;
}
pub fn domain_to_connect<E: IntoDomainErrorKind>(err: &E) -> ConnectError {
match err.kind() {
DomainErrorKind::NotFound => ConnectError::not_found("not found"),
DomainErrorKind::Conflict(msg) => ConnectError::already_exists(msg),
DomainErrorKind::Internal(detail) => {
tracing::error!(error = %detail, "internal domain error");
ConnectError::internal("internal error")
}
}
}
#[cfg(test)]
mod tests {
use super::*;
struct TestErr(DomainErrorKind);
impl IntoDomainErrorKind for TestErr {
fn kind(&self) -> DomainErrorKind {
match &self.0 {
DomainErrorKind::NotFound => DomainErrorKind::NotFound,
DomainErrorKind::Conflict(s) => DomainErrorKind::Conflict(s.clone()),
DomainErrorKind::Internal(s) => DomainErrorKind::Internal(s.clone()),
}
}
}
fn debug_str(e: &ConnectError) -> String {
format!("{e:?}")
}
#[test]
fn not_found_maps_to_not_found() {
let err = domain_to_connect(&TestErr(DomainErrorKind::NotFound));
let s = debug_str(&err);
assert!(
s.contains("not_found") || s.contains("NotFound"),
"expected not_found code, got: {s}"
);
}
#[test]
fn conflict_maps_to_already_exists() {
let err = domain_to_connect(&TestErr(DomainErrorKind::Conflict("dup key".into())));
let s = debug_str(&err);
assert!(
s.contains("already_exists") || s.contains("AlreadyExists"),
"expected already_exists code, got: {s}"
);
}
#[test]
fn internal_maps_to_internal_without_detail() {
let err = domain_to_connect(&TestErr(DomainErrorKind::Internal("secret detail".into())));
let s = debug_str(&err);
assert!(
s.contains("internal") || s.contains("Internal"),
"expected internal code, got: {s}"
);
assert!(
!s.contains("secret detail"),
"internal detail must not be surfaced to caller"
);
}
}