errgonomic/functions/
writeln_error.rs1use crate::{ErrVec, Prefixer, write_to_named_temp_file};
2use std::error::Error;
3use std::io;
4use std::io::{Write, stderr};
5
6pub fn writeln_error_to_writer_and_file(error: &(dyn Error + 'static), writer: &mut dyn Write) -> Result<(), io::Error> {
10 writeln_error_to_writer(error, writer, true)?;
11 writeln!(writer)?;
12 let error_debug = format!("{error:#?}");
13 let result = write_to_named_temp_file(error_debug.as_bytes());
14 match result {
15 Ok((_file, path_buf)) => {
16 writeln!(writer, "See the full error report:\nless {}", path_buf.display())
17 }
18 Err(other_error) => {
19 writeln!(writer, "{other_error:#?}")
20 }
21 }
22}
23
24pub fn writeln_error_to_writer(error: &(dyn Error + 'static), writer: &mut dyn Write, is_top_level: bool) -> Result<(), io::Error> {
28 let source = error;
29 if let Some(err_vec) = source.downcast_ref::<ErrVec>() {
30 if is_top_level {
31 writeln!(writer, "- {error}")?;
32 }
33 err_vec.inner.iter().try_for_each(|err| {
34 let mut prefixer = error_prefixer(writer);
35 writeln_error_to_writer(err.as_ref(), &mut prefixer, false)
36 })
37 } else {
38 writeln!(writer, "- {error}")?;
39 if let Some(source_new) = source.source() {
40 writeln_error_to_writer(source_new, writer, false)
41 } else {
42 Ok(())
43 }
44 }
45}
46
47pub fn eprintln_error(error: &(dyn Error + 'static)) {
49 let mut stderr = stderr().lock();
50 let result = writeln_error_to_writer_and_file(error, &mut stderr);
51 match result {
52 Ok(()) => (),
53 Err(err) => eprintln!("failed to write to stderr: {err:#?}"),
54 }
55}
56
57pub fn error_prefixer(writer: &mut dyn Write) -> Prefixer<'_> {
59 Prefixer::new(" * ", " ", writer)
60}
61
62#[cfg(test)]
63mod tests {
64 use crate::functions::writeln_error::tests::JsonSchemaNewError::InputMustBeObject;
65 use crate::{ErrVec, writeln_error_to_writer};
66 use thiserror::Error;
67
68 #[test]
69 fn must_write_error() {
70 let error = CliRunError::CommandRunFailed {
71 source: CommandRunError::I18nUpdateRunFailed {
72 source: I18nUpdateRunError::UpdateRowsFailed {
73 source: vec![
74 UpdateRowError::I18nRequestFailed {
75 source: I18nRequestError::JsonSchemaNewFailed {
76 source: InputMustBeObject {
77 input: "foo".to_string(),
78 },
79 },
80 row: Row::new("Foo"),
81 },
82 UpdateRowError::I18nRequestFailed {
83 source: I18nRequestError::RequestSendFailed {
84 source: tokio::io::Error::new(tokio::io::ErrorKind::AddrNotAvailable, "server at 239.143.73.1 did not respond"),
85 },
86 row: Row::new("Bar"),
87 },
88 ]
89 .into(),
90 },
91 },
92 };
93 let mut output = Vec::new();
94 writeln_error_to_writer(&error, &mut output, true).unwrap();
95 let string = String::from_utf8(output).unwrap();
96 assert_eq!(string, include_str!("writeln_error/fixtures/must_write_error.txt"))
97 }
98
99 #[derive(Error, Debug)]
100 pub enum CliRunError {
101 #[error("failed to run CLI command")]
102 CommandRunFailed { source: CommandRunError },
103 }
104
105 #[derive(Error, Debug)]
106 pub enum CommandRunError {
107 #[error("failed to run i18n update command")]
108 I18nUpdateRunFailed { source: I18nUpdateRunError },
109 }
110
111 #[derive(Error, Debug)]
112 pub enum I18nUpdateRunError {
113 #[error("failed to update {len} rows", len = source.len())]
114 UpdateRowsFailed { source: ErrVec },
115 }
116
117 #[derive(Error, Debug)]
118 pub enum UpdateRowError {
119 #[error("failed to send an i18n request for row '{row}'", row = row.name)]
120 I18nRequestFailed { source: I18nRequestError, row: Row },
121 }
122
123 #[derive(Error, Debug)]
124 pub enum I18nRequestError {
125 #[error("failed to construct a JSON schema")]
126 JsonSchemaNewFailed { source: JsonSchemaNewError },
127 #[error("failed to send a request")]
128 RequestSendFailed { source: tokio::io::Error },
129 }
130
131 #[derive(Error, Debug)]
132 pub enum JsonSchemaNewError {
133 #[error("input must be an object")]
134 InputMustBeObject { input: String },
135 }
136
137 #[derive(Debug)]
138 pub struct Row {
139 name: String,
140 }
141
142 impl Row {
143 pub fn new(name: impl Into<String>) -> Self {
144 Self {
145 name: name.into(),
146 }
147 }
148 }
149}