agent_data_plane_config/provenance.rs
1//! [`ConfigValue`]: a configuration value paired with the reason it holds that value.
2
3use serde::{Serialize, Serializer};
4
5/// Whether a configuration value was set explicitly, or merely defaulted.
6///
7/// A configuration source generally supplies every setting it knows about, including settings nobody
8/// configured, so a value on its own cannot say whether anything set it. A setting whose meaning
9/// depends on that question needs the distinction; a setting that only needs an effective value can
10/// ignore it.
11#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
12pub enum Provenance {
13 /// Nothing set the setting, so it holds a default.
14 ///
15 /// This is the provenance of a value the model defaulted itself and of a value a source supplied
16 /// from its own defaults.
17 #[default]
18 Default,
19
20 /// Some input set this value: a configuration file, an environment variable, remote
21 /// configuration, and so on.
22 Explicit,
23}
24
25/// A configuration value together with the reason it holds that value.
26///
27/// Most model fields are plain values, because their effective value is all a consumer needs. Use
28/// `ConfigValue<T>` for a setting whose behavior depends on whether the value was set explicitly,
29/// rather than on the value alone. The primary intake URL is the canonical example: the Core Agent
30/// supplies `dd_url` at its schema default even when the operator configured only `site`, so the URL
31/// alone cannot say whether it should override `site`.
32///
33/// The value and its provenance are independent, and both are always available. A defaulted setting
34/// holds its default value with [`Provenance::Default`], rather than holding no value, so a consumer
35/// never has to restate a default the configuration layer already resolved:
36///
37/// ```ignore
38/// if endpoints.dd_url.is_explicit() {
39/// resolve_verbatim(&endpoints.dd_url.value)
40/// } else {
41/// resolve_from_site(&endpoints.site.value)
42/// }
43/// ```
44///
45/// Equality covers both fields: a value that keeps its contents but becomes explicit is a change, and
46/// a [`Live`](crate::Live) view of it wakes.
47///
48/// Serialization emits the value alone, so the shape of a serialized
49/// [`SalukiConfiguration`](crate::SalukiConfiguration) does not depend on which fields track
50/// provenance.
51///
52/// Prefer `ConfigValue<T>` over `ConfigValue<Option<T>>`. Reaching for the inner `Option` usually
53/// means it is duplicating the provenance: a `T` with [`Provenance::Default`] already expresses "no
54/// one configured this", and the nested form leaves two different ways to say so.
55#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
56pub struct ConfigValue<T> {
57 /// The effective value.
58 pub value: T,
59
60 /// Whether an input set [`value`](Self::value), or it is a default.
61 pub provenance: Provenance,
62}
63
64impl<T> ConfigValue<T> {
65 /// Creates a value with the given provenance.
66 pub fn new(value: T, provenance: Provenance) -> Self {
67 Self { value, provenance }
68 }
69
70 /// Creates a value that an input set explicitly.
71 pub fn explicit(value: T) -> Self {
72 Self::new(value, Provenance::Explicit)
73 }
74
75 /// Creates a default value that nothing set.
76 pub fn defaulted(value: T) -> Self {
77 Self::new(value, Provenance::Default)
78 }
79
80 /// Returns whether an input set this value explicitly.
81 ///
82 /// A setting that acts as an override is in force only when this is true: a defaulted value
83 /// expresses no intent to override anything.
84 pub fn is_explicit(&self) -> bool {
85 self.provenance == Provenance::Explicit
86 }
87}
88
89impl<T: Serialize> Serialize for ConfigValue<T> {
90 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
91 self.value.serialize(serializer)
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::{ConfigValue, Provenance};
98
99 #[test]
100 fn a_defaulted_value_keeps_its_effective_value() {
101 let value = ConfigValue::defaulted("https://app.datadoghq.com".to_string());
102
103 assert!(!value.is_explicit());
104 // The effective value survives, so a consumer need not restate the default.
105 assert_eq!(value.value, "https://app.datadoghq.com");
106 }
107
108 #[test]
109 fn an_explicit_value_reports_itself_as_explicit() {
110 let value = ConfigValue::explicit("https://custom.example.com".to_string());
111
112 assert!(value.is_explicit());
113 assert_eq!(value.value, "https://custom.example.com");
114 }
115
116 #[test]
117 fn provenance_participates_in_equality() {
118 // A `Live` view projecting a `ConfigValue` must wake when a value becomes explicit even though
119 // its contents are unchanged, so provenance cannot be excluded from equality.
120 assert_ne!(ConfigValue::defaulted(0u64), ConfigValue::explicit(0u64));
121 }
122
123 #[test]
124 fn the_model_default_is_a_defaulted_value() {
125 assert_eq!(ConfigValue::<u64>::default(), ConfigValue::defaulted(0));
126 assert_eq!(Provenance::default(), Provenance::Default);
127 }
128}