schematic/config/
error.rs1use super::merger::MergeError;
2use super::parser::ParserError;
3#[cfg(feature = "validate")]
4use super::validator::ValidatorError;
5use miette::Diagnostic;
6use starbase_styles::{Style, Stylize};
7use std::fmt::Display;
8use std::path::PathBuf;
9use thiserror::Error;
10
11#[derive(Error, Debug, Diagnostic)]
13pub enum ConfigError {
14 #[error(transparent)]
15 Handler(#[from] Box<HandlerError>),
16
17 #[error(transparent)]
18 Merge(#[from] Box<MergeError>),
19
20 #[diagnostic(code(config::enums::invalid_fallback))]
21 #[error("Invalid fallback variant {}, unable to parse type.", .0.style(Style::Symbol))]
22 EnumInvalidFallback(String),
23
24 #[diagnostic(code(config::enums::unknown_variant))]
25 #[error("Unknown enum variant {}.", .0.style(Style::Id))]
26 EnumUnknownVariant(String),
27
28 #[diagnostic(code(config::extends::no_source_code))]
29 #[error("Unable to extend, expected a file path or secure URL.")]
30 ExtendsFromNoCode,
31
32 #[diagnostic(code(config::extends::only_parent_file))]
33 #[error("Extending from a file is only allowed if the parent source is also a file.")]
34 ExtendsFromParentFileOnly,
35
36 #[diagnostic(code(config::url::https_only))]
37 #[error("Only secure URLs are allowed, received {}.", .0.style(Style::Url))]
38 HttpsOnly(String),
39
40 #[diagnostic(code(config::code::invalid))]
41 #[error("Invalid code block used as a source.")]
42 InvalidCode,
43
44 #[diagnostic(code(config::default::invalid))]
45 #[error("Invalid default value. {0}")]
46 InvalidDefaultValue(String),
47
48 #[diagnostic(code(config::file::invalid))]
49 #[error("Invalid file path used as a source.")]
50 InvalidFile,
51
52 #[diagnostic(code(config::url::invalid))]
53 #[error("Invalid URL used as a source.")]
54 InvalidUrl,
55
56 #[diagnostic(code(config::file::missing), help("Is the path absolute?"))]
57 #[error("File path {} does not exist.", .0.style(Style::Path))]
58 MissingFile(PathBuf),
59
60 #[diagnostic(
61 code(config::format::no_matching),
62 help("Is there a format registered for the file extension?")
63 )]
64 #[error(
65 "Unable to parse {} as there's no matching source format for extension {}.",
66 .src.style(Style::Path),
67 .ext.style(Style::File)
68 )]
69 NoMatchingFormat { src: String, ext: String },
70
71 #[diagnostic(code(config::file::read_failed))]
72 #[error("Failed to read file {}.", .path.style(Style::Path))]
73 ReadFileFailed {
74 path: PathBuf,
75 #[source]
76 error: Box<std::io::Error>,
77 },
78
79 #[cfg(feature = "url")]
80 #[diagnostic(code(config::url::read_failed))]
81 #[error("Failed to read URL {}.", .url.style(Style::Url))]
82 ReadUrlFailed {
83 url: String,
84 #[source]
85 error: Box<reqwest::Error>,
86 },
87
88 #[cfg(feature = "json")]
89 #[diagnostic(code(config::json::failed))]
90 #[error("Failed to strip comments from {}.", .file.style(Style::File))]
91 JsonStripCommentsFailed {
92 file: String,
93 #[source]
94 error: Box<std::io::Error>,
95 },
96
97 #[cfg(feature = "pkl")]
98 #[diagnostic(code(config::pkl::failed))]
99 #[error("Failed to evaluate Pkl file {}.", .path.style(Style::Path))]
100 PklEvalFailed {
101 path: PathBuf,
102 #[source]
103 error: Box<rpkl::Error>,
104 },
105
106 #[cfg(feature = "pkl")]
107 #[diagnostic(code(config::pkl::file_required))]
108 #[error("Pkl requires local file paths to evaluate, received a code snippet or URL.")]
109 PklFileRequired,
110
111 #[cfg(feature = "pkl")]
112 #[diagnostic(code(config::pkl::binary_required))]
113 #[error(
114 "Pkl configuration requires the {} binary to be installed and available.\nLearn more: {}",
115 "pkl".style(Style::Shell),
116 "https://pkl-lang.org/main/current/pkl-cli/index.html".style(Style::Url)
117 )]
118 PklRequired,
119
120 #[diagnostic(code(config::parse::failed))]
122 #[error("Failed to parse {}.", .location.style(Style::File))]
123 Parser {
124 location: String,
125
126 #[diagnostic_source]
127 #[source]
128 error: Box<ParserError>,
129
130 #[help]
131 help: Option<String>,
132 },
133
134 #[cfg(feature = "validate")]
136 #[diagnostic(code(config::validate::failed))]
137 #[error("Failed to validate {}.", .location.style(Style::File))]
138 Validator {
139 location: String,
140
141 #[diagnostic_source]
142 #[source]
143 error: Box<ValidatorError>,
144
145 #[help]
146 help: Option<String>,
147 },
148}
149
150impl ConfigError {
151 pub fn to_full_string(&self) -> String {
154 let mut message = self.to_string();
155
156 let mut push_end = || {
157 if !message.ends_with('\n') {
158 if !message.ends_with('.') && !message.ends_with(':') {
159 message.push('.');
160 }
161 message.push(' ');
162 }
163 };
164
165 match self {
166 #[cfg(feature = "pkl")]
167 ConfigError::PklEvalFailed { error: inner, .. } => {
168 push_end();
169 message.push_str(&inner.to_string());
170 }
171 ConfigError::ReadFileFailed { error: inner, .. } => {
172 push_end();
173 message.push_str(&inner.to_string());
174 }
175 #[cfg(feature = "url")]
176 ConfigError::ReadUrlFailed { error: inner, .. } => {
177 push_end();
178 message.push_str(&inner.to_string());
179 }
180 ConfigError::Parser { error: inner, .. } => {
181 push_end();
182 message.push_str(&inner.to_string());
183 }
184 #[cfg(feature = "validate")]
185 ConfigError::Validator { error: inner, .. } => {
186 push_end();
187 for error in &inner.errors {
188 message.push_str(format!("\n {error}").as_str());
189 }
190 }
191 _ => {}
192 };
193
194 message.trim().to_string()
195 }
196}
197
198impl From<HandlerError> for ConfigError {
199 fn from(error: HandlerError) -> ConfigError {
200 ConfigError::Handler(Box::new(error))
201 }
202}
203
204impl From<MergeError> for ConfigError {
205 fn from(error: MergeError) -> ConfigError {
206 ConfigError::Merge(Box::new(error))
207 }
208}
209
210impl From<ParserError> for ConfigError {
211 fn from(error: ParserError) -> ConfigError {
212 ConfigError::Parser {
213 location: String::new(),
214 error: Box::new(error),
215 help: None,
216 }
217 }
218}
219
220#[derive(Error, Debug, Diagnostic)]
222#[error("{0}")]
223pub struct HandlerError(pub String);
224
225impl HandlerError {
226 pub fn new<T: Display>(message: T) -> Self {
227 Self(message.to_string())
228 }
229}