Skip to content

Commit 3a5b9bc

Browse files
committed
improved error messages
uses miette for error reporting and extends the error messages to hopefully be more usefull
1 parent eae7b16 commit 3a5b9bc

File tree

7 files changed

+212
-72
lines changed

7 files changed

+212
-72
lines changed

cargo-espflash/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ categories = [
2424
]
2525

2626
[dependencies]
27-
anyhow = "1.0"
27+
miette = "2"
2828
cargo_metadata = "0.14"
2929
clap = "2.33"
3030
espflash = { version = "*", path = "../espflash" }

cargo-espflash/src/main.rs

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use anyhow::{anyhow, bail, Context};
21
use cargo_metadata::Message;
32
use clap::{App, Arg, SubCommand};
4-
use espflash::{Config, Flasher, PartitionTable};
3+
use espflash::{Config, Error, Flasher, PartitionTable};
4+
use miette::{bail, miette, IntoDiagnostic, Result, WrapErr};
55
use serial::{BaudRate, SerialPort};
66

77
use std::{
@@ -14,7 +14,7 @@ use std::{
1414
mod cargo_config;
1515
use cargo_config::has_build_std;
1616

17-
fn main() -> anyhow::Result<()> {
17+
fn main() -> Result<()> {
1818
let mut app = App::new(env!("CARGO_PKG_NAME"))
1919
.bin_name("cargo")
2020
.subcommand(
@@ -84,7 +84,7 @@ fn main() -> anyhow::Result<()> {
8484
let matches = match matches.subcommand_matches("espflash") {
8585
Some(matches) => matches,
8686
None => {
87-
app.print_help()?;
87+
app.print_help().into_diagnostic()?;
8888
exit(0);
8989
}
9090
};
@@ -99,7 +99,7 @@ fn main() -> anyhow::Result<()> {
9999
} else if let Some(serial) = config.connection.serial {
100100
serial
101101
} else {
102-
app.print_help()?;
102+
app.print_help().into_diagnostic()?;
103103
exit(0);
104104
};
105105

@@ -120,15 +120,19 @@ fn main() -> anyhow::Result<()> {
120120
// Attempt to open the serial port and set its initial baud rate.
121121
println!("Serial port: {}", port);
122122
println!("Connecting...\n");
123-
let mut serial = serial::open(&port).context(format!("Failed to open serial port {}", port))?;
124-
serial.reconfigure(&|settings| {
125-
settings.set_baud_rate(BaudRate::Baud115200)?;
126-
Ok(())
127-
})?;
123+
let mut serial = serial::open(&port)
124+
.map_err(Error::from)
125+
.wrap_err_with(|| format!("Failed to open serial port {}", port))?;
126+
serial
127+
.reconfigure(&|settings| {
128+
settings.set_baud_rate(BaudRate::Baud115200)?;
129+
Ok(())
130+
})
131+
.into_diagnostic()?;
128132

129133
// Parse the baud rate if provided as as a command-line argument.
130134
let speed = if let Some(speed) = matches.value_of("speed") {
131-
let speed = speed.parse::<usize>()?;
135+
let speed = speed.parse::<usize>().into_diagnostic()?;
132136
Some(BaudRate::from_speed(speed))
133137
} else {
134138
None
@@ -145,8 +149,8 @@ fn main() -> anyhow::Result<()> {
145149
// If the '--bootloader' option is provided, load the binary file at the
146150
// specified path.
147151
let bootloader = if let Some(path) = matches.value_of("bootloader") {
148-
let path = fs::canonicalize(path)?;
149-
let data = fs::read(path)?;
152+
let path = fs::canonicalize(path).into_diagnostic()?;
153+
let data = fs::read(path).into_diagnostic()?;
150154
Some(data)
151155
} else {
152156
None
@@ -155,8 +159,8 @@ fn main() -> anyhow::Result<()> {
155159
// If the '--partition-table' option is provided, load the partition table from
156160
// the CSV at the specified path.
157161
let partition_table = if let Some(path) = matches.value_of("partition_table") {
158-
let path = fs::canonicalize(path)?;
159-
let data = fs::read_to_string(path)?;
162+
let path = fs::canonicalize(path).into_diagnostic()?;
163+
let data = fs::read_to_string(path).into_diagnostic()?;
160164

161165
match PartitionTable::try_from_str(data) {
162166
Ok(t) => Some(t),
@@ -167,7 +171,7 @@ fn main() -> anyhow::Result<()> {
167171
};
168172

169173
// Read the ELF data from the build path and load it to the target.
170-
let elf_data = fs::read(path.unwrap())?;
174+
let elf_data = fs::read(path.unwrap()).into_diagnostic()?;
171175
if matches.is_present("ram") {
172176
flasher.load_elf_to_ram(&elf_data)?;
173177
} else {
@@ -183,7 +187,7 @@ fn board_info(flasher: &Flasher) {
183187
println!("Flash size: {}", flasher.flash_size());
184188
}
185189

186-
fn build(release: bool, example: Option<&str>, features: Option<&str>) -> anyhow::Result<PathBuf> {
190+
fn build(release: bool, example: Option<&str>, features: Option<&str>) -> Result<PathBuf> {
187191
// The 'build-std' unstable cargo feature is required to enable
188192
// cross-compilation. If it has not been set then we cannot build the
189193
// application.
@@ -219,8 +223,10 @@ fn build(release: bool, example: Option<&str>, features: Option<&str>) -> anyhow
219223
.args(&["--message-format", "json-diagnostic-rendered-ansi"])
220224
.stdout(Stdio::piped())
221225
.stderr(Stdio::inherit())
222-
.spawn()?
223-
.wait_with_output()?;
226+
.spawn()
227+
.into_diagnostic()?
228+
.wait_with_output()
229+
.into_diagnostic()?;
224230

225231
// Parse build output.
226232
let messages = Message::parse_stream(&output.stdout[..]);
@@ -229,7 +235,7 @@ fn build(release: bool, example: Option<&str>, features: Option<&str>) -> anyhow
229235
let mut target_artifact = None;
230236

231237
for message in messages {
232-
match message? {
238+
match message.into_diagnostic()? {
233239
Message::CompilerArtifact(artifact) => {
234240
if artifact.executable.is_some() {
235241
if target_artifact.is_some() {
@@ -266,7 +272,7 @@ fn build(release: bool, example: Option<&str>, features: Option<&str>) -> anyhow
266272
target_artifact
267273
.unwrap()
268274
.executable
269-
.ok_or_else(|| anyhow!("artifact executable path is missing"))?,
275+
.ok_or_else(|| miette!("artifact executable path is missing"))?,
270276
);
271277

272278
Ok(artifact_path)

espflash/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ strum_macros = "0.21.1"
3333
csv = "1.1.6"
3434
regex = "1.5.4"
3535
flate2 = "1"
36+
miette = "2"
3637

3738
[dev-dependencies]
3839
pretty_assertions = "0.7.1"

espflash/src/connection.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::thread::sleep;
33
use std::time::Duration;
44

55
use crate::encoder::SlipEncoder;
6-
use crate::error::{Error, RomError};
6+
use crate::error::{ConnectionError, Error, RomError};
77
use binread::io::Cursor;
88
use binread::{BinRead, BinReaderExt};
99
use serial::{BaudRate, SerialPort, SerialPortSettings};
@@ -135,7 +135,7 @@ impl Connection {
135135
}
136136
}
137137
}
138-
Err(Error::ConnectionFailed)
138+
Err(Error::Connection(ConnectionError::ConnectionFailed))
139139
}
140140

141141
fn read(&mut self) -> Result<Vec<u8>, Error> {

espflash/src/error.rs

Lines changed: 145 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,120 @@
1+
use miette::Diagnostic;
12
use slip_codec::Error as SlipError;
3+
use std::io;
24
use thiserror::Error;
35

4-
#[derive(Error, Debug)]
6+
#[derive(Error, Debug, Diagnostic)]
57
#[non_exhaustive]
68
pub enum Error {
9+
#[error("Error while connecting to device")]
10+
#[diagnostic(transparent)]
11+
Connection(#[source] ConnectionError),
12+
#[error("Communication error while flashing device")]
13+
#[diagnostic(transparent)]
14+
Flashing(#[source] ConnectionError),
15+
#[error("Supplied elf image is not valid")]
16+
#[diagnostic(
17+
code(espflash::invalid_elf),
18+
help("Try running `cargo clean` and rebuilding the image")
19+
)]
20+
InvalidElf,
21+
#[error("Supplied elf image can not be ran from ram")]
22+
#[diagnostic(
23+
code(espflash::not_ram_loadable),
24+
help("Either build the binary to be all in ram or remove the `--ram` option to load the image to flash")
25+
)]
26+
ElfNotRamLoadable,
27+
#[error("The bootloader returned an error")]
28+
#[diagnostic(transparent)]
29+
RomError(#[source] RomError),
30+
#[error("Chip not recognized, supported chip types are esp8266, esp32 and esp32-c3")]
31+
#[diagnostic(
32+
code(espflash::unrecognized_chip),
33+
help("If your chip is supported, try hard-resetting the device and try again")
34+
)]
35+
UnrecognizedChip,
36+
#[error(
37+
"Flash chip not supported, flash id: {0:#x}, flash sizes from 1 to 16MB are supported"
38+
)]
39+
#[diagnostic(code(espflash::unrecognized_flash))]
40+
UnsupportedFlash(u8),
41+
#[error("Failed to connect to on-device flash")]
42+
#[diagnostic(code(espflash::flash_connect))]
43+
FlashConnect,
44+
}
45+
46+
#[derive(Error, Debug, Diagnostic)]
47+
#[non_exhaustive]
48+
pub enum ConnectionError {
749
#[error("IO error while using serial port: {0}")]
8-
Serial(#[from] serial::core::Error),
50+
#[diagnostic(code(espflash::serial_error))]
51+
Serial(#[source] serial::core::Error),
952
#[error("Failed to connect to the device")]
53+
#[diagnostic(
54+
code(espflash::connection_failed),
55+
help("Ensure that the device is connected and the reset and boot pins are not being held down")
56+
)]
1057
ConnectionFailed,
58+
#[error("Serial port not found")]
59+
#[diagnostic(
60+
code(espflash::connection_failed),
61+
help("Ensure that the device is connected and your host recognizes the serial adapter")
62+
)]
63+
DeviceNotFound,
1164
#[error("Timeout while running command")]
65+
#[diagnostic(code(espflash::timeout))]
1266
Timeout,
13-
#[error("Invalid SLIP framing")]
67+
#[error("Received packet has invalid SLIP framing")]
68+
#[diagnostic(
69+
code(espflash::slip_framing),
70+
help("Try hard-resetting the device and try again, if the error persists your rom might be corrupted")
71+
)]
1472
FramingError,
15-
#[error("Packet to large for buffer")]
73+
#[error("Received packet to large for buffer")]
74+
#[diagnostic(
75+
code(espflash::oversized_packet),
76+
help("Try hard-resetting the device and try again, if the error persists your rom might be corrupted")
77+
)]
1678
OverSizedPacket,
17-
#[error("elf image is not valid")]
18-
InvalidElf,
19-
#[error("elf image can not be ran from ram")]
20-
ElfNotRamLoadable,
21-
#[error("bootloader returned an error: {0:?}")]
22-
RomError(RomError),
23-
#[error("chip not recognized, supported chip types are esp8266 and esp32")]
24-
UnrecognizedChip,
25-
#[error("flash chip not supported, flash id: {0:#x}")]
26-
UnsupportedFlash(u8),
2779
}
2880

29-
impl From<std::io::Error> for Error {
30-
fn from(err: std::io::Error) -> Self {
31-
Self::Serial(serial::core::Error::from(err))
81+
impl From<serial::Error> for ConnectionError {
82+
fn from(err: serial::Error) -> Self {
83+
match err.kind() {
84+
serial::ErrorKind::Io(kind) => from_error_kind(kind, err),
85+
serial::ErrorKind::NoDevice => ConnectionError::DeviceNotFound,
86+
_ => ConnectionError::Serial(err),
87+
}
3288
}
3389
}
3490

35-
impl From<SlipError> for Error {
91+
impl From<serial::Error> for Error {
92+
fn from(err: serial::Error) -> Self {
93+
Self::Connection(err.into())
94+
}
95+
}
96+
97+
impl From<io::Error> for ConnectionError {
98+
fn from(err: io::Error) -> Self {
99+
from_error_kind(err.kind(), err)
100+
}
101+
}
102+
103+
impl From<io::Error> for Error {
104+
fn from(err: io::Error) -> Self {
105+
Self::Connection(err.into())
106+
}
107+
}
108+
109+
fn from_error_kind<E: Into<serial::Error>>(kind: io::ErrorKind, err: E) -> ConnectionError {
110+
match kind {
111+
io::ErrorKind::TimedOut => ConnectionError::Timeout,
112+
io::ErrorKind::NotFound => ConnectionError::DeviceNotFound,
113+
_ => ConnectionError::Serial(err.into()),
114+
}
115+
}
116+
117+
impl From<SlipError> for ConnectionError {
36118
fn from(err: SlipError) -> Self {
37119
match err {
38120
SlipError::FramingError => Self::FramingError,
@@ -43,26 +125,54 @@ impl From<SlipError> for Error {
43125
}
44126
}
45127

46-
impl From<binread::Error> for Error {
128+
impl From<SlipError> for Error {
129+
fn from(err: SlipError) -> Self {
130+
Self::Connection(err.into())
131+
}
132+
}
133+
134+
impl From<binread::Error> for ConnectionError {
47135
fn from(err: binread::Error) -> Self {
48136
match err {
49-
binread::Error::Io(e) => Error::from(e),
137+
binread::Error::Io(e) => ConnectionError::from(e),
50138
_ => unreachable!(),
51139
}
52140
}
53141
}
54142

55-
#[derive(Copy, Clone, Debug)]
143+
impl From<binread::Error> for Error {
144+
fn from(err: binread::Error) -> Self {
145+
Self::Connection(err.into())
146+
}
147+
}
148+
149+
#[derive(Copy, Clone, Debug, Error, Diagnostic)]
56150
#[allow(dead_code)]
57151
#[repr(u8)]
58152
pub enum RomError {
153+
#[error("Invalid message received")]
154+
#[diagnostic(code(espflash::rom::invalid_message))]
59155
InvalidMessage = 0x05,
156+
#[error("Bootloader failed to execute command")]
157+
#[diagnostic(code(espflash::rom::failed))]
60158
FailedToAct = 0x06,
159+
#[error("Received message has invalid crc")]
160+
#[diagnostic(code(espflash::rom::crc))]
61161
InvalidCrc = 0x07,
162+
#[error("Bootloader failed to write to flash")]
163+
#[diagnostic(code(espflash::rom::flash_write))]
62164
FlashWriteError = 0x08,
165+
#[error("Bootloader failed to read from flash")]
166+
#[diagnostic(code(espflash::rom::flash_read))]
63167
FlashReadError = 0x09,
168+
#[error("Invalid length for flash read")]
169+
#[diagnostic(code(espflash::rom::flash_read_length))]
64170
FlashReadLengthError = 0x0a,
171+
#[error("Malformed compressed data received")]
172+
#[diagnostic(code(espflash::rom::deflate))]
65173
DeflateError = 0x0b,
174+
#[error("Other")]
175+
#[diagnostic(code(espflash::rom::other))]
66176
Other = 0xff,
67177
}
68178

@@ -80,3 +190,17 @@ impl From<u8> for RomError {
80190
}
81191
}
82192
}
193+
194+
pub(crate) trait ResultExt {
195+
/// mark an error as having occurred during the flashing stage
196+
fn flashing(self) -> Self;
197+
}
198+
199+
impl<T> ResultExt for Result<T, Error> {
200+
fn flashing(self) -> Self {
201+
match self {
202+
Err(Error::Connection(err)) => Err(Error::Flashing(err)),
203+
res => res,
204+
}
205+
}
206+
}

0 commit comments

Comments
 (0)