use std::path::PathBuf;
pub type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("could not open input file: {path}")]
Open {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("could not create output file: {path}")]
Create {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("failed to write {what}{}", OptionalPath(path))]
Write {
what: &'static str,
path: Option<PathBuf>,
#[source]
source: std::io::Error,
},
#[error("failed to parse {what}{}", OptionalPath(path))]
Parse {
what: &'static str,
path: Option<PathBuf>,
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("failed to serialize {what}")]
Serialize {
what: &'static str,
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("not a {what}: expected a <{expected}> root element, found <{found}>")]
WrongRoot {
what: &'static str,
expected: &'static str,
found: String,
},
#[error("not a {what}: the document has no root element")]
MissingRoot { what: &'static str },
#[error("<{element}> is missing {attribute}")]
MissingAttribute {
element: &'static str,
attribute: &'static str,
},
#[error("invalid {context}: {value:?}")]
InvalidNumber {
context: &'static str,
value: String,
},
#[error("non-finite {context}: {value:?}")]
NonFiniteNumber {
context: &'static str,
value: String,
},
#[error("incomplete {context}: {value:?}")]
IncompletePosition {
context: &'static str,
value: String,
},
#[error("{context} with too many coordinates: {value:?}")]
TooManyCoordinates {
context: &'static str,
value: String,
},
#[error("SUMO boolean value with no binary meaning (\"x\"/\"-\")")]
AmbiguousBool,
#[error("SUMO boundary must have 4 components: {value:?}")]
InvalidBoundary { value: String },
#[error("SUMO color must have 3 or 4 components: {value:?}")]
InvalidColor { value: String },
#[error("SUMO clock time must have 3 or 4 components: {value:?}")]
InvalidClockTime { value: String },
#[error("{element} sets both `{first}` and `{second}`, which are alternatives")]
ConflictingAttributes {
element: &'static str,
first: &'static str,
second: &'static str,
},
}
impl Error {
pub(crate) fn with_path(mut self, path: &std::path::Path) -> Self {
if let Self::Parse { path: slot, .. } | Self::Write { path: slot, .. } = &mut self {
*slot = Some(path.to_path_buf());
}
self
}
}
struct OptionalPath<'a>(&'a Option<PathBuf>);
impl std::fmt::Display for OptionalPath<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.0 {
Some(path) => write!(f, " in {}", path.display()),
None => Ok(()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn names_the_file_only_when_there_is_one() {
let with_path = Error::Parse {
what: "SUMO network",
path: Some(PathBuf::from("city.net.xml")),
source: "boom".into(),
};
assert_eq!(
with_path.to_string(),
"failed to parse SUMO network in city.net.xml"
);
let without_path = Error::Parse {
what: "SUMO network",
path: None,
source: "boom".into(),
};
assert_eq!(without_path.to_string(), "failed to parse SUMO network");
}
#[test]
fn keeps_the_underlying_error_reachable_as_a_source() {
use std::error::Error as _;
let error = Error::Parse {
what: "SUMO network",
path: None,
source: "the real detail".into(),
};
assert_eq!(error.source().unwrap().to_string(), "the real detail");
}
}