redacted-error 0.1.0

Stable public error messages with debug-only diagnostic detail
Documentation
  • Coverage
  • 100%
    16 out of 16 items documented6 out of 15 items with examples
  • Size
  • Source code size: 30.97 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 655.99 kB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 14s Average build duration of successful builds.
  • all releases: 11s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • chutuananh2k/redacted-error
    0 0 0
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • chutuananh2k
redacted-error-0.1.0 has been yanked.

redacted-error

Stable public error messages with debug-only diagnostic detail.

redacted-error is a small facade for Rust errors that cross crate, process, API, or protocol boundaries. It lets applications expose stable public error codes and messages while preserving rich diagnostics in debug builds.

Features

  • message! for displayable static public-facing messages.
  • message_string! for owned public-facing messages.
  • detail!, detail, and display for debug-only runtime diagnostics.
  • ErrorCode and PublicError traits for stable public API responses.
  • impl_redacted_debug! to keep release Debug from leaking enum fields.
  • Optional static string obfuscation behind the default obfuscate feature.

The public API does not expose the obfuscation implementation. Today the default backend is obfstr; later releases can replace that backend without requiring callers to rename macros.

message! returns a Message wrapper, not a borrowed &'static str. That is intentional: it keeps backend-specific lifetime behavior out of caller code. Use message_string! when a trait or response type needs an owned String.

Usage

[dependencies]
redacted-error = "0.1"

Disable static string obfuscation:

[dependencies]
redacted-error = { version = "0.1", default-features = false }

Example

use redacted_error::message as m;
use thiserror::Error;

#[cfg_attr(debug_assertions, derive(Debug))]
#[derive(Error)]
pub enum TransportError {
    #[cfg_attr(debug_assertions, error("{prefix} {0}", prefix = m!("listener bind failed:")))]
    #[cfg_attr(not(debug_assertions), error("{}", m!("listener bind failed")))]
    ListenerBindFailed(String),
}

redacted_error::impl_redacted_debug!(TransportError);

impl redacted_error::ErrorCode for TransportError {
    fn code(&self) -> &'static str {
        match self {
            Self::ListenerBindFailed(_) => "transport.listener_bind_failed",
        }
    }
}

impl redacted_error::PublicError for TransportError {
    fn public_message(&self) -> redacted_error::Message {
        match self {
            Self::ListenerBindFailed(_) => redacted_error::message!("listener bind failed"),
        }
    }
}

fn bind_failed(addr: &str, err: impl std::fmt::Display) -> TransportError {
    TransportError::ListenerBindFailed(redacted_error::detail!(
        "{addr}: {err}"
    ))
}

In debug builds, Display can include diagnostic detail:

listener bind failed: 127.0.0.1:8080: address already in use

In release builds, Display and Debug stay public-safe:

listener bind failed

For API responses, use stable structured fields rather than parsing Display:

{
  "code": "transport.listener_bind_failed",
  "message": "listener bind failed"
}

Security

This crate is a leakage-reduction tool, not a confidentiality boundary.

  • The obfuscate feature only raises the bar against trivial strings-style inspection of compiled binaries. It is not a defense against a debugger, dynamic instrumentation, symbol tables, or any motivated reverse engineer. Do not treat obfuscated literals as secret.
  • Diagnostic-detail stripping is gated on cfg(debug_assertions). Cargo's standard release profile turns this off, but [profile.release] debug-assertions = true re-enables it — and with it, every detail! / display / detail call leaks runtime detail in release builds. Avoid that combination if redaction matters.
  • The detail! macro skips evaluating its format arguments in release. The detail and display free functions still evaluate (and drop) their argument; prefer the macro when the argument has nontrivial cost or side effects.

Migration Notes

For existing code using obfstr in error messages:

use obfstr::obfstr as s;

Prefer:

use redacted_error::message as s;

Then replace dynamic error details with redacted_error::detail! or redacted_error::display.

For code currently using the local error-detail crate, the intended mapping is:

Local API Public crate API
error_detail::obfstr!("...") redacted_error::message!("...")
error_detail::obfstring!("...") redacted_error::message_string!("...")
error_detail::detail!("...") redacted_error::detail!("...")
error_detail::display(err) redacted_error::display(err)
error_detail::ErrorCode redacted_error::ErrorCode
error_detail::impl_redacted_debug! redacted_error::impl_redacted_debug!

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.