|
1 | 1 | // Copyright 2021-Present Datadog, Inc. https://www.datadoghq.com/ |
2 | 2 | // SPDX-License-Identifier: Apache-2.0 |
3 | 3 |
|
| 4 | +#[cfg(feature = "client")] |
| 5 | +use crate::file_storage::ParseFile; |
4 | 6 | use crate::{ |
5 | 7 | config::{ |
6 | 8 | self, agent_config::AgentConfigFile, agent_task::AgentTaskFile, dynamic::DynamicConfigFile, |
7 | 9 | }, |
8 | 10 | RemoteConfigPath, RemoteConfigProduct, RemoteConfigSource, |
9 | 11 | }; |
10 | | -use std::any::Any; |
11 | | -use std::collections::HashMap; |
12 | 12 |
|
13 | | -/// Opaque parsed payload for a remote config product. Product crates implement this on their own |
14 | | -/// types and export a [`ProductParser`] factory; the RC crate stores and distributes the results |
15 | | -/// without knowing their concrete type. |
16 | | -pub trait RemoteConfigParsedData: Send + Sync + 'static { |
17 | | - fn as_any(&self) -> &dyn Any; |
18 | | - fn product(&self) -> RemoteConfigProduct; |
19 | | -} |
20 | | - |
21 | | -/// A product-specific parser: converts raw bytes into a parsed payload. |
22 | | -pub type ProductParser = |
23 | | - Box<dyn Fn(&[u8]) -> anyhow::Result<Box<dyn RemoteConfigParsedData>> + Send + Sync>; |
24 | | - |
25 | | -/// Maps [`RemoteConfigProduct`] variants to their parser functions. |
| 13 | +/// Parsed payload for the products owned by `datadog-remote-config`. |
26 | 14 | /// |
27 | | -/// Consumers build a registry (optionally starting from [`default_registry`]) and inject it into |
28 | | -/// the file storage or fetcher. Products with no registered parser return [`IgnoredProduct`] |
29 | | -/// so callers can track the config without processing its contents. |
30 | | -pub struct ParserRegistry { |
31 | | - parsers: HashMap<RemoteConfigProduct, ProductParser>, |
| 15 | +/// Consumers that only care about these products can use this enum directly via |
| 16 | +/// [`BuiltinProductsParser`]. Consumers that need additional products should define their own |
| 17 | +/// enum and [`ParseFile`] implementation. |
| 18 | +#[derive(Debug)] |
| 19 | +#[allow(clippy::large_enum_variant)] |
| 20 | +pub enum BuiltinProducts { |
| 21 | + AgentConfig(AgentConfigFile), |
| 22 | + AgentTask(AgentTaskFile), |
| 23 | + ApmTracing(DynamicConfigFile), |
| 24 | + Other(RemoteConfigProduct), |
32 | 25 | } |
33 | 26 |
|
34 | | -impl ParserRegistry { |
35 | | - pub fn new() -> Self { |
36 | | - ParserRegistry { |
37 | | - parsers: HashMap::new(), |
| 27 | +impl BuiltinProducts { |
| 28 | + pub fn product(&self) -> RemoteConfigProduct { |
| 29 | + match self { |
| 30 | + BuiltinProducts::AgentConfig(_) => RemoteConfigProduct::AgentConfig, |
| 31 | + BuiltinProducts::AgentTask(_) => RemoteConfigProduct::AgentTask, |
| 32 | + BuiltinProducts::ApmTracing(_) => RemoteConfigProduct::ApmTracing, |
| 33 | + BuiltinProducts::Other(p) => *p, |
38 | 34 | } |
39 | 35 | } |
40 | 36 |
|
41 | | - /// Register a parser for a product. Replaces any existing parser for that product. |
42 | | - pub fn register(&mut self, product: RemoteConfigProduct, parser: ProductParser) { |
43 | | - self.parsers.insert(product, parser); |
44 | | - } |
45 | | - |
46 | | - /// Parse `data` for `product`. Returns [`IgnoredProduct`] (not an error) when no parser is |
47 | | - /// registered, so callers can still track the config in their bookkeeping structures. |
48 | | - pub fn parse( |
49 | | - &self, |
50 | | - product: RemoteConfigProduct, |
51 | | - data: &[u8], |
52 | | - ) -> anyhow::Result<Box<dyn RemoteConfigParsedData>> { |
53 | | - match self.parsers.get(&product) { |
54 | | - Some(parser) => parser(data), |
55 | | - None => Ok(Box::new(IgnoredProduct(product))), |
56 | | - } |
57 | | - } |
58 | | -} |
59 | | - |
60 | | -impl Default for ParserRegistry { |
61 | | - fn default() -> Self { |
62 | | - Self::new() |
63 | | - } |
64 | | -} |
65 | | - |
66 | | -// ── Implementations for RC-internal product types ──────────────────────────── |
67 | | - |
68 | | -/// Sentinel returned by [`ParserRegistry::parse`] when no parser is registered for a product. |
69 | | -/// Consumers that want to handle only specific products can downcast and ignore this type. |
70 | | -pub struct IgnoredProduct(pub RemoteConfigProduct); |
71 | | - |
72 | | -impl RemoteConfigParsedData for IgnoredProduct { |
73 | | - fn as_any(&self) -> &dyn Any { |
74 | | - self |
75 | | - } |
76 | | - fn product(&self) -> RemoteConfigProduct { |
77 | | - self.0 |
| 37 | + pub fn try_parse(product: RemoteConfigProduct, data: &[u8]) -> anyhow::Result<BuiltinProducts> { |
| 38 | + Ok(match product { |
| 39 | + RemoteConfigProduct::AgentConfig => { |
| 40 | + BuiltinProducts::AgentConfig(config::agent_config::parse_json(data)?) |
| 41 | + } |
| 42 | + RemoteConfigProduct::AgentTask => { |
| 43 | + BuiltinProducts::AgentTask(config::agent_task::parse_json(data)?) |
| 44 | + } |
| 45 | + RemoteConfigProduct::ApmTracing => { |
| 46 | + BuiltinProducts::ApmTracing(config::dynamic::parse_json(data)?) |
| 47 | + } |
| 48 | + other => BuiltinProducts::Other(other), |
| 49 | + }) |
78 | 50 | } |
79 | 51 | } |
80 | 52 |
|
81 | | -impl RemoteConfigParsedData for DynamicConfigFile { |
82 | | - fn as_any(&self) -> &dyn Any { |
83 | | - self |
84 | | - } |
85 | | - fn product(&self) -> RemoteConfigProduct { |
86 | | - RemoteConfigProduct::ApmTracing |
87 | | - } |
88 | | -} |
| 53 | +/// [`ParseFile`] implementation for [`BuiltinProducts`]. Use this with [`RawFileStorage`] when |
| 54 | +/// no extra products beyond the RC-internal set need parsing. |
| 55 | +/// |
| 56 | +/// [`RawFileStorage`]: crate::file_storage::RawFileStorage |
| 57 | +#[cfg(feature = "client")] |
| 58 | +#[derive(Clone, Default)] |
| 59 | +pub struct BuiltinProductsParser; |
89 | 60 |
|
90 | | -impl RemoteConfigParsedData for AgentConfigFile { |
91 | | - fn as_any(&self) -> &dyn Any { |
92 | | - self |
93 | | - } |
94 | | - fn product(&self) -> RemoteConfigProduct { |
95 | | - RemoteConfigProduct::AgentConfig |
96 | | - } |
97 | | -} |
| 61 | +#[cfg(feature = "client")] |
| 62 | +impl ParseFile for BuiltinProductsParser { |
| 63 | + type Parsed = anyhow::Result<BuiltinProducts>; |
98 | 64 |
|
99 | | -impl RemoteConfigParsedData for AgentTaskFile { |
100 | | - fn as_any(&self) -> &dyn Any { |
101 | | - self |
102 | | - } |
103 | | - fn product(&self) -> RemoteConfigProduct { |
104 | | - RemoteConfigProduct::AgentTask |
| 65 | + fn parse(&self, path: &RemoteConfigPath, contents: Vec<u8>) -> Self::Parsed { |
| 66 | + BuiltinProducts::try_parse(path.product, &contents) |
105 | 67 | } |
106 | 68 | } |
107 | 69 |
|
108 | | -/// Returns a registry pre-loaded with parsers for the RC-internal products: |
109 | | -/// [`RemoteConfigProduct::AgentConfig`], [`RemoteConfigProduct::AgentTask`], and |
110 | | -/// [`RemoteConfigProduct::ApmTracing`]. |
| 70 | +/// A parsed remote config file along with metadata extracted from its path. |
111 | 71 | /// |
112 | | -/// Consumers that need additional product parsers (live-debugger, FFE, …) should call |
113 | | -/// [`ParserRegistry::register`] on the returned registry before use. |
114 | | -pub fn default_registry() -> ParserRegistry { |
115 | | - let mut registry = ParserRegistry::new(); |
116 | | - registry.register( |
117 | | - RemoteConfigProduct::AgentConfig, |
118 | | - Box::new(|data: &[u8]| { |
119 | | - let parsed = config::agent_config::parse_json(data)?; |
120 | | - Ok(Box::new(parsed) as Box<dyn RemoteConfigParsedData>) |
121 | | - }), |
122 | | - ); |
123 | | - registry.register( |
124 | | - RemoteConfigProduct::AgentTask, |
125 | | - Box::new(|data: &[u8]| { |
126 | | - let parsed = config::agent_task::parse_json(data)?; |
127 | | - Ok(Box::new(parsed) as Box<dyn RemoteConfigParsedData>) |
128 | | - }), |
129 | | - ); |
130 | | - registry.register( |
131 | | - RemoteConfigProduct::ApmTracing, |
132 | | - Box::new(|data: &[u8]| { |
133 | | - let parsed = config::dynamic::parse_json(data)?; |
134 | | - Ok(Box::new(parsed) as Box<dyn RemoteConfigParsedData>) |
135 | | - }), |
136 | | - ); |
137 | | - registry |
138 | | -} |
139 | | - |
140 | | -// ── RemoteConfigValue ───────────────────────────────────────────────────────── |
141 | | - |
142 | | -pub struct RemoteConfigValue { |
| 72 | +/// `T` is the consumer-defined parsed-payload enum; for the built-in set, use |
| 73 | +/// [`BuiltinProducts`]. |
| 74 | +pub struct RemoteConfigValue<T> { |
143 | 75 | pub source: RemoteConfigSource, |
144 | | - pub data: Box<dyn RemoteConfigParsedData>, |
| 76 | + pub data: T, |
145 | 77 | pub config_id: String, |
146 | 78 | pub name: String, |
147 | 79 | } |
148 | 80 |
|
149 | | -impl std::fmt::Debug for RemoteConfigValue { |
| 81 | +impl<T: std::fmt::Debug> std::fmt::Debug for RemoteConfigValue<T> { |
150 | 82 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
151 | 83 | f.debug_struct("RemoteConfigValue") |
152 | 84 | .field("source", &self.source) |
153 | | - .field("product", &self.data.product()) |
| 85 | + .field("data", &self.data) |
154 | 86 | .field("config_id", &self.config_id) |
155 | 87 | .field("name", &self.name) |
156 | 88 | .finish() |
157 | 89 | } |
158 | 90 | } |
159 | 91 |
|
160 | | -impl RemoteConfigValue { |
161 | | - pub fn try_parse(path: &str, data: &[u8], registry: &ParserRegistry) -> anyhow::Result<Self> { |
| 92 | +impl<T> RemoteConfigValue<T> { |
| 93 | + pub fn try_parse( |
| 94 | + path: &str, |
| 95 | + data: &[u8], |
| 96 | + parse: impl FnOnce(RemoteConfigProduct, &[u8]) -> anyhow::Result<T>, |
| 97 | + ) -> anyhow::Result<Self> { |
162 | 98 | let path = RemoteConfigPath::try_parse(path)?; |
163 | | - let data = registry.parse(path.product, data)?; |
| 99 | + let parsed = parse(path.product, data)?; |
164 | 100 | Ok(RemoteConfigValue { |
165 | 101 | source: path.source, |
166 | | - data, |
| 102 | + data: parsed, |
167 | 103 | config_id: path.config_id.to_string(), |
168 | 104 | name: path.name.to_string(), |
169 | 105 | }) |
|
0 commit comments