datadog_agent_config/
translate_error.rs

1//! The error types recorded by the configuration translator and surfaced by the witness driver.
2
3use std::fmt;
4
5/// An error produced while translating a witnessed Datadog configuration value into the
6/// ADP-native model. Names the offending Datadog key, and optionally a human-readable note
7/// and/or the underlying error object.
8#[derive(Debug)]
9pub struct TranslateError {
10    key: String,
11    context: Option<String>,
12    error: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
13}
14
15pub type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
16
17impl TranslateError {
18    /// Create a translation error wrapping an underlying error.
19    ///
20    /// Produces `error translating config key `{key}`: {error}`.
21    pub fn new<S, E>(key: S, error: E) -> Self
22    where
23        S: Into<String>,
24        E: Into<BoxError>,
25    {
26        Self {
27            key: key.into(),
28            context: None,
29            error: Some(error.into()),
30        }
31    }
32
33    /// Create a translation error wrapping an underlying error and adding a contextual message.
34    ///
35    /// Produces `error translating config key `{key}`, {context}: {error}`.
36    pub fn new_with_context<S1, S2, E>(key: S1, context: S2, error: E) -> Self
37    where
38        S1: Into<String>,
39        S2: Into<String>,
40        E: Into<BoxError>,
41    {
42        Self {
43            key: key.into(),
44            context: Some(context.into()),
45            error: Some(error.into()),
46        }
47    }
48
49    /// Create a translation error when no underlying error object exists.
50    ///
51    /// Produces `error translating config key `{key}`, {message}`.
52    pub fn new_with_message<S1, S2>(key: S1, message: S2) -> Self
53    where
54        S1: Into<String>,
55        S2: Into<String>,
56    {
57        Self {
58            key: key.into(),
59            context: Some(message.into()),
60            error: None,
61        }
62    }
63}
64
65impl fmt::Display for TranslateError {
66    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67        write!(f, "error translating config key `{}`", self.key)?;
68        if let Some(context) = &self.context {
69            write!(f, ", {context}")?;
70        }
71        if let Some(error) = &self.error {
72            write!(f, ": {error}")?;
73        }
74        Ok(())
75    }
76}
77
78impl std::error::Error for TranslateError {
79    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
80        self.error.as_deref().map(|s| s as &(dyn std::error::Error + 'static))
81    }
82}
83
84/// Every error [`drive`][crate::drive] recorded, non-empty by construction.
85///
86/// A collection of failures has no single `source`. Only [`drive`][crate::drive] constructs it,
87/// and only when at least one key failed.
88#[derive(Debug)]
89pub struct TranslateErrors(Vec<TranslateError>);
90
91impl TranslateErrors {
92    /// Wraps the recorded errors. Callers must pass a non-empty vec.
93    pub(crate) fn new(errors: Vec<TranslateError>) -> Self {
94        debug_assert!(!errors.is_empty(), "TranslateErrors must be non-empty");
95        Self(errors)
96    }
97}
98
99impl fmt::Display for TranslateErrors {
100    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101        write!(f, "{} config translation error(s):", self.0.len())?;
102        for error in &self.0 {
103            write!(f, "\n  - {error}")?;
104        }
105        Ok(())
106    }
107}
108
109impl std::error::Error for TranslateErrors {}