agent_data_plane_config/lib.rs
1//! ADP-native configuration model: the typed target of configuration translation.
2//!
3//! This crate owns the domain-shaped model types that translation produces and that ADP runtime
4//! code consumes: `SalukiConfiguration { control, shared, domains }`, `ControlConfiguration`,
5//! `SharedConfiguration`, `DomainConfiguration` and its per-domain structs.
6//!
7//! It does not embed component config structs (those stay in `saluki-components`, built from this
8//! model). It depends on neither the raw configuration map nor the Datadog source model, so a
9//! consumer can depend on it without inheriting either.
10//!
11//! Every field is plain, source-agnostic data. There are no source key names in identifiers and no
12//! source serde (these structs are serialized for the `/config/internal` view but never
13//! deserialized from a source language; that is the source adapter's job).
14
15use std::fmt;
16
17use serde::Serialize;
18
19pub mod control;
20pub mod defaults;
21pub mod domains;
22pub mod live;
23pub mod shared;
24
25pub use control::{ControlConfiguration, ListenAddress, Logging};
26pub use domains::DomainConfiguration;
27pub use live::Live;
28pub use shared::SharedConfiguration;
29
30/// The complete ADP-native runtime configuration after translation.
31///
32/// Two writers fill it: the Datadog witness `drive` (schema fields) and `seed` (Saluki-only
33/// fields). They write disjoint fields. It is read by the orchestration layer (`control`) and by
34/// components at topology assembly (`shared` and `domains`).
35#[derive(Clone, Debug, Default, PartialEq, Serialize)]
36pub struct SalukiConfiguration {
37 /// Read first: decides which pipelines/topology to build. Orchestration layer only.
38 pub control: ControlConfiguration,
39 /// Cross-cutting values consumed by more than one domain, each with a single home.
40 pub shared: SharedConfiguration,
41 /// Per-domain resolved config, grouped by ownership domain.
42 pub domains: DomainConfiguration,
43}
44
45/// An error produced while translating a `DatadogConfiguration` or `SalukiOnly` value into a
46/// `SalukiConfiguration` value.
47#[derive(Debug)]
48pub struct Error {
49 context: String,
50 error: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
51}
52
53/// A boxed source error that can cross thread boundaries.
54pub type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
55
56impl Error {
57 /// Create an error that wraps an underlying error.
58 ///
59 /// Displays `{context}: {error}`.
60 pub fn new<S, E>(context: S, error: E) -> Self
61 where
62 S: Into<String>,
63 E: Into<BoxError>,
64 {
65 Self {
66 context: context.into(),
67 error: Some(error.into()),
68 }
69 }
70
71 /// Create an error without an underlying source error.
72 pub fn new_without_source<S>(context: S) -> Self
73 where
74 S: Into<String>,
75 {
76 Self {
77 context: context.into(),
78 error: None,
79 }
80 }
81}
82
83impl fmt::Display for Error {
84 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85 write!(f, "{}", self.context)?;
86 if let Some(error) = &self.error {
87 write!(f, ": {error}")?;
88 }
89 Ok(())
90 }
91}
92
93impl std::error::Error for Error {
94 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
95 self.error.as_deref().map(|s| s as &(dyn std::error::Error + 'static))
96 }
97}