1use crate::ConfigPath;
2use serde::{Deserialize, Serialize};
3
4#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
5#[serde(rename_all = "snake_case")]
6pub enum ConfigDiagnosticSeverity {
7 #[default]
8 Error,
9 Warning,
10 Info,
11}
12
13#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
14#[serde(rename_all = "snake_case")]
15pub enum ConfigDiagnosticSource {
16 #[default]
17 Validation,
18 Schema,
19 Plugin,
20 Compatibility,
21}
22
23#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
24#[serde(rename_all = "snake_case")]
25pub enum ConfigDiagnosticSchemaSource {
26 BuiltIn,
27 Engine,
28 Plugin,
29}
30
31#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
32#[serde(rename_all = "snake_case")]
33pub enum ConfigDiagnosticCode {
34 InvalidValue,
35 MissingRequiredValue,
36 UnsupportedField,
37 RejectedField,
38 AliasApplied,
39 MisplacedField,
40 UnknownField,
41 SchemaUnavailable,
42 LegacyUnvalidatedConfig,
43 UnsupportedSchemaVersion,
44}
45
46#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
47pub struct ConfigDiagnostic {
48 pub code: ConfigDiagnosticCode,
49 pub severity: ConfigDiagnosticSeverity,
50 pub source: ConfigDiagnosticSource,
51 #[serde(default, skip_serializing_if = "Option::is_none")]
52 pub schema_source: Option<ConfigDiagnosticSchemaSource>,
53 #[serde(default, skip_serializing_if = "Option::is_none")]
54 pub path: Option<ConfigPath>,
55 #[serde(default, skip_serializing_if = "Option::is_none")]
56 pub canonical_path: Option<ConfigPath>,
57 pub message: String,
58 #[serde(default, skip_serializing_if = "Option::is_none")]
59 pub help: Option<String>,
60}
61
62impl ConfigDiagnostic {
63 pub fn new(
64 code: ConfigDiagnosticCode,
65 severity: ConfigDiagnosticSeverity,
66 source: ConfigDiagnosticSource,
67 message: impl Into<String>,
68 ) -> Self {
69 Self {
70 code,
71 severity,
72 source,
73 schema_source: None,
74 path: None,
75 canonical_path: None,
76 message: message.into(),
77 help: None,
78 }
79 }
80
81 pub fn error(
82 code: ConfigDiagnosticCode,
83 source: ConfigDiagnosticSource,
84 message: impl Into<String>,
85 ) -> Self {
86 Self::new(code, ConfigDiagnosticSeverity::Error, source, message)
87 }
88
89 pub fn warning(
90 code: ConfigDiagnosticCode,
91 source: ConfigDiagnosticSource,
92 message: impl Into<String>,
93 ) -> Self {
94 Self::new(code, ConfigDiagnosticSeverity::Warning, source, message)
95 }
96
97 pub fn at_path(mut self, path: ConfigPath) -> Self {
98 self.path = Some(path);
99 self
100 }
101
102 pub fn with_schema_source(mut self, schema_source: ConfigDiagnosticSchemaSource) -> Self {
103 self.schema_source = Some(schema_source);
104 self
105 }
106
107 pub fn with_canonical_path(mut self, canonical_path: ConfigPath) -> Self {
108 self.canonical_path = Some(canonical_path);
109 self
110 }
111
112 pub fn with_help(mut self, help: impl Into<String>) -> Self {
113 self.help = Some(help.into());
114 self
115 }
116
117 pub fn legacy_message(&self) -> &str {
118 &self.message
119 }
120}
121
122pub fn invalid_value_diagnostic(path: ConfigPath, message: impl Into<String>) -> ConfigDiagnostic {
123 ConfigDiagnostic::error(
124 ConfigDiagnosticCode::InvalidValue,
125 ConfigDiagnosticSource::Validation,
126 message,
127 )
128 .with_schema_source(ConfigDiagnosticSchemaSource::BuiltIn)
129 .at_path(path)
130}
131
132pub fn unsupported_field_diagnostic(
133 path: ConfigPath,
134 message: impl Into<String>,
135) -> ConfigDiagnostic {
136 ConfigDiagnostic::error(
137 ConfigDiagnosticCode::UnsupportedField,
138 ConfigDiagnosticSource::Schema,
139 message,
140 )
141 .with_schema_source(ConfigDiagnosticSchemaSource::BuiltIn)
142 .at_path(path.clone())
143 .with_canonical_path(path)
144}
145
146pub fn rejected_field_diagnostic(path: ConfigPath, message: impl Into<String>) -> ConfigDiagnostic {
147 ConfigDiagnostic::error(
148 ConfigDiagnosticCode::RejectedField,
149 ConfigDiagnosticSource::Schema,
150 message,
151 )
152 .with_schema_source(ConfigDiagnosticSchemaSource::BuiltIn)
153 .at_path(path.clone())
154 .with_canonical_path(path)
155}
156
157pub fn alias_diagnostic(
158 used_path: ConfigPath,
159 canonical_path: ConfigPath,
160 message: impl Into<String>,
161) -> ConfigDiagnostic {
162 ConfigDiagnostic::warning(
163 ConfigDiagnosticCode::AliasApplied,
164 ConfigDiagnosticSource::Compatibility,
165 message,
166 )
167 .with_schema_source(ConfigDiagnosticSchemaSource::BuiltIn)
168 .at_path(used_path)
169 .with_canonical_path(canonical_path)
170}
171
172pub(crate) type DiagnosticResult = std::result::Result<(), ConfigDiagnostic>;
173
174pub fn legacy_validation_error_text(diagnostics: &[ConfigDiagnostic]) -> String {
175 diagnostics
176 .iter()
177 .map(ConfigDiagnostic::legacy_message)
178 .collect::<Vec<_>>()
179 .join("\n")
180}