Skip to main content

wp_error/
diagnostic_meta.rs

1use std::path::Path;
2
3use orion_error::OperationContext;
4use orion_error::report::DiagnosticReport;
5use orion_error::runtime::source::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
325        .context()
326        .iter()
327        .find_map(|ctx| ctx.metadata().get_str(key))
328}
329
330pub fn first_meta_enum<M: MetaValue>(report: &DiagnosticReport) -> Option<M> {
331    first_meta_str(report, M::KEY).and_then(M::parse)
332}
333
334pub fn first_meta_hint(report: &DiagnosticReport) -> Option<HintCode> {
335    first_meta_enum::<HintCode>(report)
336}
337
338pub fn frame_meta_enum<M: MetaValue>(frame: &SourceFrame) -> Option<M> {
339    frame.metadata.get_str(M::KEY).and_then(M::parse)
340}
341
342#[cfg(test)]
343mod tests {
344    use super::*;
345    use orion_error::UnifiedReason;
346    use orion_error::conversion::ToStructError;
347    use orion_error::runtime::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 ctx = OperationContext::doing("load").with_meta_value(ConfigKind::SinkRoute);
384        let err = UnifiedReason::core_conf()
385            .to_err()
386            .with_detail("configuration error")
387            .with_context(&ctx);
388        let report = err.report();
389
390        assert_eq!(
391            first_meta_enum::<ConfigKind>(&report),
392            Some(ConfigKind::SinkRoute)
393        );
394    }
395
396    #[test]
397    fn frame_meta_enum_reads_typed_metadata() {
398        let mut metadata = ErrorMetadata::new();
399        metadata.insert(key::CONFIG_GROUP, ConfigGroup::Infra.as_str());
400
401        let frame = SourceFrame {
402            index: 0,
403            message: "configuration error".into(),
404            display: None,
405            debug: None,
406            type_name: None,
407            error_code: None,
408            reason: None,
409            path: None,
410            detail: None,
411            context_fields: Vec::new(),
412            metadata,
413            is_root_cause: true,
414        };
415
416        assert_eq!(
417            frame_meta_enum::<ConfigGroup>(&frame),
418            Some(ConfigGroup::Infra)
419        );
420    }
421}