Skip to main content

wp_error/
diagnostic_meta.rs

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