Skip to main content

mesh_llm_config/
diagnostic.rs

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}