1use std::path::Path;
2
3use orion_error::OperationContext;
4use orion_error::report::DiagnosticReport;
5use orion_error::runtime::SourceFrame;
6
7pub mod key {
8 pub const CONFIG_KIND: &str = "config.kind";
9 pub const CONFIG_GROUP: &str = "config.group";
10 pub const CONFIG_SECTION: &str = "config.section";
11 pub const CONFIG_TYPE_NAME: &str = "config.type_name";
12 pub const CONFIG_LOADER: &str = "config.loader";
13
14 pub const FILE_PATH: &str = "file.path";
15 pub const DIR_PATH: &str = "dir.path";
16 pub const RESOURCE_PATH: &str = "resource.path";
17 pub const NETWORK_URL: &str = "network.url";
18
19 pub const RUNTIME_STAGE: &str = "runtime.stage";
20 pub const OPERATION_KIND: &str = "operation.kind";
21 pub const COMPONENT_KIND: &str = "component.kind";
22 pub const COMPONENT_NAME: &str = "component.name";
23
24 pub const HINT_CODE: &str = "hint.code";
25}
26
27pub trait MetaValue: Copy + Sized {
28 const KEY: &'static str;
29
30 fn as_str(self) -> &'static str;
31
32 fn parse(raw: &str) -> Option<Self>;
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum ConfigKind {
37 Wpsrc,
38 SinkRoute,
39 SinkDefaults,
40 ConnectorDef,
41 Engine,
42 Wpgen,
43 Knowdb,
44 SourceRuntime,
45 SinkRuntime,
46}
47
48impl MetaValue for ConfigKind {
49 const KEY: &'static str = key::CONFIG_KIND;
50
51 fn as_str(self) -> &'static str {
52 match self {
53 Self::Wpsrc => "wpsrc",
54 Self::SinkRoute => "sink_route",
55 Self::SinkDefaults => "sink_defaults",
56 Self::ConnectorDef => "connector_def",
57 Self::Engine => "engine",
58 Self::Wpgen => "wpgen",
59 Self::Knowdb => "knowdb",
60 Self::SourceRuntime => "source_runtime",
61 Self::SinkRuntime => "sink_runtime",
62 }
63 }
64
65 fn parse(raw: &str) -> Option<Self> {
66 Some(match raw {
67 "wpsrc" => Self::Wpsrc,
68 "sink_route" => Self::SinkRoute,
69 "sink_defaults" => Self::SinkDefaults,
70 "connector_def" => Self::ConnectorDef,
71 "engine" => Self::Engine,
72 "wpgen" => Self::Wpgen,
73 "knowdb" => Self::Knowdb,
74 "source_runtime" => Self::SourceRuntime,
75 "sink_runtime" => Self::SinkRuntime,
76 _ => return None,
77 })
78 }
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub enum ConfigGroup {
83 Infra,
84 Business,
85}
86
87impl MetaValue for ConfigGroup {
88 const KEY: &'static str = key::CONFIG_GROUP;
89
90 fn as_str(self) -> &'static str {
91 match self {
92 Self::Infra => "infra",
93 Self::Business => "business",
94 }
95 }
96
97 fn parse(raw: &str) -> Option<Self> {
98 Some(match raw {
99 "infra" => Self::Infra,
100 "business" => Self::Business,
101 _ => return None,
102 })
103 }
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107pub enum RuntimeStage {
108 GeneratorGenerate,
109 SupervisorMonitor,
110 CollectorRecovery,
111 OrchestratorConfigLoad,
112 SystemOperations,
113}
114
115impl MetaValue for RuntimeStage {
116 const KEY: &'static str = key::RUNTIME_STAGE;
117
118 fn as_str(self) -> &'static str {
119 match self {
120 Self::GeneratorGenerate => "generator.generate",
121 Self::SupervisorMonitor => "supervisor.monitor",
122 Self::CollectorRecovery => "collector.recovery",
123 Self::OrchestratorConfigLoad => "orchestrator.config.load",
124 Self::SystemOperations => "system_operations",
125 }
126 }
127
128 fn parse(raw: &str) -> Option<Self> {
129 Some(match raw {
130 "generator.generate" => Self::GeneratorGenerate,
131 "supervisor.monitor" => Self::SupervisorMonitor,
132 "collector.recovery" => Self::CollectorRecovery,
133 "orchestrator.config.load" => Self::OrchestratorConfigLoad,
134 "system_operations" => Self::SystemOperations,
135 _ => return None,
136 })
137 }
138}
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq)]
141pub enum OperationKind {
142 LoadConfigFile,
143 ParseConfig,
144 ValidateConfig,
145 ReadDir,
146 ReadDirEntry,
147 BuildSourceInstance,
148 BuildSinkInstance,
149 PluginValidate,
150 ReplayRescueFile,
151}
152
153impl MetaValue for OperationKind {
154 const KEY: &'static str = key::OPERATION_KIND;
155
156 fn as_str(self) -> &'static str {
157 match self {
158 Self::LoadConfigFile => "load_config_file",
159 Self::ParseConfig => "parse_config",
160 Self::ValidateConfig => "validate_config",
161 Self::ReadDir => "read_dir",
162 Self::ReadDirEntry => "read_dir_entry",
163 Self::BuildSourceInstance => "build_source_instance",
164 Self::BuildSinkInstance => "build_sink_instance",
165 Self::PluginValidate => "plugin_validate",
166 Self::ReplayRescueFile => "replay_rescue_file",
167 }
168 }
169
170 fn parse(raw: &str) -> Option<Self> {
171 Some(match raw {
172 "load_config_file" => Self::LoadConfigFile,
173 "parse_config" => Self::ParseConfig,
174 "validate_config" => Self::ValidateConfig,
175 "read_dir" => Self::ReadDir,
176 "read_dir_entry" => Self::ReadDirEntry,
177 "build_source_instance" => Self::BuildSourceInstance,
178 "build_sink_instance" => Self::BuildSinkInstance,
179 "plugin_validate" => Self::PluginValidate,
180 "replay_rescue_file" => Self::ReplayRescueFile,
181 _ => return None,
182 })
183 }
184}
185
186#[derive(Debug, Clone, Copy, PartialEq, Eq)]
187pub enum ComponentKind {
188 Source,
189 Sink,
190 Connector,
191 Generator,
192 Monitor,
193 Rescue,
194 Checkpoint,
195}
196
197impl MetaValue for ComponentKind {
198 const KEY: &'static str = key::COMPONENT_KIND;
199
200 fn as_str(self) -> &'static str {
201 match self {
202 Self::Source => "source",
203 Self::Sink => "sink",
204 Self::Connector => "connector",
205 Self::Generator => "generator",
206 Self::Monitor => "monitor",
207 Self::Rescue => "rescue",
208 Self::Checkpoint => "checkpoint",
209 }
210 }
211
212 fn parse(raw: &str) -> Option<Self> {
213 Some(match raw {
214 "source" => Self::Source,
215 "sink" => Self::Sink,
216 "connector" => Self::Connector,
217 "generator" => Self::Generator,
218 "monitor" => Self::Monitor,
219 "rescue" => Self::Rescue,
220 "checkpoint" => Self::Checkpoint,
221 _ => return None,
222 })
223 }
224}
225
226#[derive(Debug, Clone, Copy, PartialEq, Eq)]
227pub enum HintCode {
228 WpsrcTomlSchema,
229 SinkRouteTomlSchema,
230 SinkDefaultsTomlSchema,
231 KafkaFeatureRequired,
232 DuplicateSourceKey,
233 InvalidSyslogProtocol,
234}
235
236impl MetaValue for HintCode {
237 const KEY: &'static str = key::HINT_CODE;
238
239 fn as_str(self) -> &'static str {
240 match self {
241 Self::WpsrcTomlSchema => "wpsrc_toml_schema",
242 Self::SinkRouteTomlSchema => "sink_route_toml_schema",
243 Self::SinkDefaultsTomlSchema => "sink_defaults_toml_schema",
244 Self::KafkaFeatureRequired => "kafka_feature_required",
245 Self::DuplicateSourceKey => "duplicate_source_key",
246 Self::InvalidSyslogProtocol => "invalid_syslog_protocol",
247 }
248 }
249
250 fn parse(raw: &str) -> Option<Self> {
251 Some(match raw {
252 "wpsrc_toml_schema" => Self::WpsrcTomlSchema,
253 "sink_route_toml_schema" => Self::SinkRouteTomlSchema,
254 "sink_defaults_toml_schema" => Self::SinkDefaultsTomlSchema,
255 "kafka_feature_required" => Self::KafkaFeatureRequired,
256 "duplicate_source_key" => Self::DuplicateSourceKey,
257 "invalid_syslog_protocol" => Self::InvalidSyslogProtocol,
258 _ => return None,
259 })
260 }
261}
262
263pub trait OperationContextMetaExt: Sized {
264 fn with_meta_value<M: MetaValue>(self, value: M) -> Self;
265
266 fn with_file_path(self, path: &Path) -> Self;
267
268 fn with_dir_path(self, path: &Path) -> Self;
269
270 fn with_resource_path(self, path: &Path) -> Self;
271
272 fn with_network_url(self, url: impl Into<String>) -> Self;
273
274 fn with_component_name(self, name: impl Into<String>) -> Self;
275
276 fn with_config_type_name(self, name: impl Into<String>) -> Self;
277
278 fn with_config_section(self, section: impl Into<String>) -> Self;
279}
280
281impl OperationContextMetaExt for OperationContext {
282 fn with_meta_value<M: MetaValue>(mut self, value: M) -> Self {
283 self.record_meta(M::KEY, value.as_str());
284 self
285 }
286
287 fn with_file_path(mut self, path: &Path) -> Self {
288 self.record_meta(key::FILE_PATH, path.display().to_string());
289 self
290 }
291
292 fn with_dir_path(mut self, path: &Path) -> Self {
293 self.record_meta(key::DIR_PATH, path.display().to_string());
294 self
295 }
296
297 fn with_resource_path(mut self, path: &Path) -> Self {
298 self.record_meta(key::RESOURCE_PATH, path.display().to_string());
299 self
300 }
301
302 fn with_network_url(mut self, url: impl Into<String>) -> Self {
303 self.record_meta(key::NETWORK_URL, url.into());
304 self
305 }
306
307 fn with_component_name(mut self, name: impl Into<String>) -> Self {
308 self.record_meta(key::COMPONENT_NAME, name.into());
309 self
310 }
311
312 fn with_config_type_name(mut self, name: impl Into<String>) -> Self {
313 self.record_meta(key::CONFIG_TYPE_NAME, name.into());
314 self
315 }
316
317 fn with_config_section(mut self, section: impl Into<String>) -> Self {
318 self.record_meta(key::CONFIG_SECTION, section.into());
319 self
320 }
321}
322
323pub fn first_meta_str<'a>(report: &'a DiagnosticReport, key: &str) -> Option<&'a str> {
324 report.root_metadata.get_str(key).or_else(|| {
325 report
326 .source_frames
327 .iter()
328 .find_map(|frame| frame.metadata.get_str(key))
329 })
330}
331
332pub fn first_meta_enum<M: MetaValue>(report: &DiagnosticReport) -> Option<M> {
333 first_meta_str(report, M::KEY).and_then(M::parse)
334}
335
336pub fn first_meta_hint(report: &DiagnosticReport) -> Option<HintCode> {
337 first_meta_enum::<HintCode>(report)
338}
339
340pub fn frame_meta_enum<M: MetaValue>(frame: &SourceFrame) -> Option<M> {
341 frame.metadata.get_str(M::KEY).and_then(M::parse)
342}
343
344#[cfg(test)]
345mod tests {
346 use super::*;
347 use orion_error::types::ErrorMetadata;
348
349 #[test]
350 fn config_kind_round_trip() {
351 assert_eq!(
352 ConfigKind::parse(ConfigKind::Wpsrc.as_str()),
353 Some(ConfigKind::Wpsrc)
354 );
355 assert_eq!(
356 ConfigKind::parse(ConfigKind::SinkDefaults.as_str()),
357 Some(ConfigKind::SinkDefaults)
358 );
359 }
360
361 #[test]
362 fn operation_context_meta_ext_records_typed_and_path_metadata() {
363 let ctx = OperationContext::doing("load")
364 .with_meta_value(ConfigKind::Wpsrc)
365 .with_meta_value(OperationKind::LoadConfigFile)
366 .with_file_path(Path::new("/tmp/wpsrc.toml"))
367 .with_component_name("file_1");
368
369 assert_eq!(ctx.metadata().get_str(key::CONFIG_KIND), Some("wpsrc"));
370 assert_eq!(
371 ctx.metadata().get_str(key::OPERATION_KIND),
372 Some("load_config_file")
373 );
374 assert_eq!(
375 ctx.metadata().get_str(key::FILE_PATH),
376 Some("/tmp/wpsrc.toml")
377 );
378 assert_eq!(ctx.metadata().get_str(key::COMPONENT_NAME), Some("file_1"));
379 }
380
381 #[test]
382 fn first_meta_enum_prefers_root_metadata() {
383 let mut root = ErrorMetadata::new();
384 root.insert(key::CONFIG_KIND, ConfigKind::SinkRoute.as_str());
385
386 let report = DiagnosticReport {
387 reason: "configuration error".to_string(),
388 detail: None,
389 position: None,
390 want: None,
391 path: None,
392 context: Vec::new(),
393 root_metadata: root,
394 source_frames: Vec::new(),
395 };
396
397 assert_eq!(
398 first_meta_enum::<ConfigKind>(&report),
399 Some(ConfigKind::SinkRoute)
400 );
401 }
402
403 #[test]
404 fn frame_meta_enum_reads_typed_metadata() {
405 let mut metadata = ErrorMetadata::new();
406 metadata.insert(key::CONFIG_GROUP, ConfigGroup::Infra.as_str());
407
408 let frame = SourceFrame {
409 index: 0,
410 message: "configuration error".to_string(),
411 display: None,
412 debug: String::new(),
413 type_name: None,
414 error_code: None,
415 reason: None,
416 want: None,
417 path: None,
418 detail: None,
419 metadata,
420 is_root_cause: true,
421 };
422
423 assert_eq!(
424 frame_meta_enum::<ConfigGroup>(&frame),
425 Some(ConfigGroup::Infra)
426 );
427 }
428}