oxide_update_engine_types/
spec.rs1use crate::schema::RustTypeInfo;
6use anyhow::anyhow;
7use indent_write::fmt::IndentWriter;
8use serde::{Serialize, de::DeserializeOwned};
9use std::{fmt, fmt::Write};
10
11pub trait EngineSpec: Send + 'static {
15 fn spec_name() -> String;
18
19 type Component: Clone
21 + fmt::Debug
22 + DeserializeOwned
23 + Serialize
24 + Eq
25 + Send
26 + Sync;
27
28 type StepId: Clone
30 + fmt::Debug
31 + DeserializeOwned
32 + Serialize
33 + Eq
34 + Send
35 + Sync;
36
37 type StepMetadata: Clone
42 + fmt::Debug
43 + DeserializeOwned
44 + Serialize
45 + Eq
46 + Send
47 + Sync;
48
49 type ProgressMetadata: Clone
54 + fmt::Debug
55 + DeserializeOwned
56 + Serialize
57 + Eq
58 + Send
59 + Sync;
60
61 type CompletionMetadata: Clone
66 + fmt::Debug
67 + DeserializeOwned
68 + Serialize
69 + Eq
70 + Send
71 + Sync;
72
73 type SkippedMetadata: Clone
78 + fmt::Debug
79 + DeserializeOwned
80 + Serialize
81 + Eq
82 + Send
83 + Sync;
84
85 type Error: AsError + fmt::Debug + Send + Sync;
92
93 fn rust_type_info() -> Option<RustTypeInfo> {
100 None
101 }
102}
103
104#[cfg(feature = "schemars08")]
117pub trait JsonSchemaEngineSpec:
118 EngineSpec<
119 Component: schemars::JsonSchema,
120 StepId: schemars::JsonSchema,
121 StepMetadata: schemars::JsonSchema,
122 ProgressMetadata: schemars::JsonSchema,
123 CompletionMetadata: schemars::JsonSchema,
124 SkippedMetadata: schemars::JsonSchema,
125 > + schemars::JsonSchema
126{
127}
128
129#[cfg(feature = "schemars08")]
130impl<S> JsonSchemaEngineSpec for S
131where
132 S: EngineSpec + schemars::JsonSchema,
133 S::Component: schemars::JsonSchema,
134 S::StepId: schemars::JsonSchema,
135 S::StepMetadata: schemars::JsonSchema,
136 S::ProgressMetadata: schemars::JsonSchema,
137 S::CompletionMetadata: schemars::JsonSchema,
138 S::SkippedMetadata: schemars::JsonSchema,
139{
140}
141
142pub struct GenericSpec(());
149
150#[cfg(feature = "schemars08")]
151impl schemars::JsonSchema for GenericSpec {
152 fn schema_name() -> String {
153 "GenericSpec".to_owned()
154 }
155
156 fn json_schema(
157 _: &mut schemars::r#gen::SchemaGenerator,
158 ) -> schemars::schema::Schema {
159 schemars::schema::Schema::Bool(true)
160 }
161}
162
163impl EngineSpec for GenericSpec {
164 fn spec_name() -> String {
165 "GenericSpec".to_owned()
166 }
167
168 type Component = serde_json::Value;
169 type StepId = serde_json::Value;
170 type StepMetadata = serde_json::Value;
171 type ProgressMetadata = serde_json::Value;
172 type CompletionMetadata = serde_json::Value;
173 type SkippedMetadata = serde_json::Value;
174 type Error = SerializableError;
175
176 fn rust_type_info() -> Option<RustTypeInfo> {
177 Some(RustTypeInfo {
178 crate_name: crate::schema::CRATE_NAME,
179 version: crate::schema::VERSION,
180 path: crate::schema::GENERIC_SPEC_PATH,
181 })
182 }
183}
184
185#[derive(Clone, Debug)]
191pub struct SerializableError {
192 message: String,
193 source: Option<Box<SerializableError>>,
194}
195
196impl SerializableError {
197 pub fn new(error: &dyn std::error::Error) -> Self {
199 Self {
200 message: format!("{}", error),
201 source: error.source().map(|s| Box::new(Self::new(s))),
202 }
203 }
204
205 pub fn from_message_and_causes(
208 message: String,
209 causes: Vec<String>,
210 ) -> Self {
211 let mut next = None;
215 for cause in causes.into_iter().rev() {
216 let error = Self { message: cause, source: next.map(Box::new) };
217 next = Some(error);
218 }
219 Self { message, source: next.map(Box::new) }
220 }
221
222 pub fn message(&self) -> &str {
224 &self.message
225 }
226
227 pub fn sources(&self) -> SerializableErrorSources<'_> {
229 SerializableErrorSources { current: self.source.as_deref() }
230 }
231}
232
233impl fmt::Display for SerializableError {
234 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235 f.write_str(&self.message)
236 }
237}
238
239impl std::error::Error for SerializableError {
240 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
241 self.source.as_ref().map(|s| s as &(dyn std::error::Error + 'static))
242 }
243}
244
245#[derive(Debug)]
247pub struct SerializableErrorSources<'a> {
248 current: Option<&'a SerializableError>,
249}
250
251impl<'a> Iterator for SerializableErrorSources<'a> {
252 type Item = &'a SerializableError;
253
254 fn next(&mut self) -> Option<Self::Item> {
255 let current = self.current?;
256 self.current = current.source.as_deref();
257 Some(current)
258 }
259}
260
261mod serializable_error_serde {
262 use super::*;
263 use serde::Deserialize;
264
265 #[derive(Serialize, Deserialize)]
266 struct Ser {
267 message: String,
268 causes: Vec<String>,
269 }
270
271 impl Serialize for SerializableError {
272 fn serialize<S: serde::Serializer>(
273 &self,
274 serializer: S,
275 ) -> Result<S::Ok, S::Error> {
276 let mut causes = Vec::new();
277 let mut cause = self.source.as_ref();
278 while let Some(c) = cause {
279 causes.push(c.message.clone());
280 cause = c.source.as_ref();
281 }
282
283 let serialized = Ser { message: self.message.clone(), causes };
284 serialized.serialize(serializer)
285 }
286 }
287
288 impl<'de> Deserialize<'de> for SerializableError {
289 fn deserialize<D: serde::Deserializer<'de>>(
290 deserializer: D,
291 ) -> Result<Self, D::Error> {
292 let serialized = Ser::deserialize(deserializer)?;
293 Ok(SerializableError::from_message_and_causes(
294 serialized.message,
295 serialized.causes,
296 ))
297 }
298 }
299}
300
301impl AsError for SerializableError {
302 fn as_error(&self) -> &(dyn std::error::Error + 'static) {
303 self
304 }
305}
306
307pub trait AsError: fmt::Debug + Send + Sync + 'static {
311 fn as_error(&self) -> &(dyn std::error::Error + 'static);
312}
313
314impl AsError for anyhow::Error {
315 fn as_error(&self) -> &(dyn std::error::Error + 'static) {
316 self.as_ref()
317 }
318}
319
320pub fn merge_anyhow_list<I>(errors: I) -> anyhow::Error
327where
328 I: IntoIterator<Item = anyhow::Error>,
329{
330 let mut iter = errors.into_iter().peekable();
331 let Some(first_error) = iter.next() else {
333 panic!("error_list_to_anyhow called with no errors");
335 };
336
337 if iter.peek().is_none() {
338 return first_error;
340 }
341
342 let mut out = String::new();
344 let mut nerrors = 0;
345 for error in std::iter::once(first_error).chain(iter) {
346 if nerrors > 0 {
347 writeln!(&mut out).unwrap();
350 }
351 nerrors += 1;
352 let mut current = error.as_error();
353
354 let mut writer = IndentWriter::new_skip_initial(" ", &mut out);
355 write!(writer, "Error: {current}").unwrap();
356
357 while let Some(cause) = current.source() {
358 writeln!(&mut out).unwrap();
361
362 let mut writer =
364 IndentWriter::new_skip_initial(" ", &mut out);
365 write!(writer, " - {cause}").unwrap();
366 current = cause;
367 }
368 }
369 anyhow!(out).context(format!("{nerrors} errors encountered"))
370}
371
372#[cfg(test)]
373mod tests {
374 use super::*;
375 use indoc::indoc;
376
377 #[test]
378 fn test_merge_anyhow_list() {
379 unsafe {
385 std::env::set_var("RUST_BACKTRACE", "0");
386 }
387
388 let error = anyhow!("base").context("parent").context("root");
390
391 let merged = merge_anyhow_list(vec![error]);
392 assert_eq!(
393 format!("{:?}", merged),
394 indoc! {"
395 root
396
397 Caused by:
398 0: parent
399 1: base"
400 },
401 );
402
403 let error1 =
405 anyhow!("base1").context("parent1\nparent1 line2").context("root1");
406 let error2 = anyhow!("base2").context("parent2").context("root2");
407
408 let merged = merge_anyhow_list(vec![error1, error2]);
409 let merged_debug = format!("{:?}", merged);
410 println!("merged debug: {}", merged_debug);
411
412 assert_eq!(
413 merged_debug,
414 indoc! {"
415 2 errors encountered
416
417 Caused by:
418 Error: root1
419 - parent1
420 parent1 line2
421 - base1
422 Error: root2
423 - parent2
424 - base2"
425 },
426 );
427
428 let error3 = merged.context("overall root");
431 let error3_debug = format!("{:?}", error3);
432 println!("error3 debug: {}", error3_debug);
433 assert_eq!(
434 error3_debug,
435 indoc! {"
436 overall root
437
438 Caused by:
439 0: 2 errors encountered
440 1: Error: root1
441 - parent1
442 parent1 line2
443 - base1
444 Error: root2
445 - parent2
446 - base2"
447 },
448 );
449 }
450}