use std::error::Error as StdError;
use std::fmt;
fn main() {
for error in [
metadata::run(),
metadata::parse(b"\xff"),
metadata::parse(b"no json here"),
metadata::rejected("error: no such command: `metadata`"),
]
.map(|result| result.expect_err("every stub path fails"))
{
println!("{error}");
if error.is_start_failure() {
println!(" -> could not start the process, retriable");
} else if error.is_bad_output() {
println!(" -> the process ran but its output was unusable");
}
}
}
#[derive(Debug)]
struct JsonSyntaxError;
impl fmt::Display for JsonSyntaxError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("expected value at line 1 column 1")
}
}
impl StdError for JsonSyntaxError {}
mod metadata {
use std::error::Error as StdError;
use std::str::{Utf8Error, from_utf8};
use std::string::FromUtf8Error;
use super::JsonSyntaxError;
#[ohno::error]
#[display("`cargo metadata` exited with an error: {stderr}")]
struct CargoMetadataError {
stderr: String,
}
#[ohno::error]
#[from(std::io::Error)]
#[display("failed to start `cargo metadata`")]
struct StartError;
#[ohno::error]
#[from(Utf8Error)]
#[display("cannot convert the stdout of `cargo metadata`")]
struct StdoutError;
#[ohno::error]
#[from(FromUtf8Error)]
#[display("cannot convert the stderr of `cargo metadata`")]
struct StderrError;
#[ohno::error]
#[from(JsonSyntaxError)]
#[display("failed to interpret `cargo metadata`'s json")]
struct JsonError;
#[ohno::error]
#[display("could not find any json in the output of `cargo metadata`")]
struct NoJsonError;
#[ohno::error]
#[from(CargoMetadataError)]
#[from(StartError)]
#[from(StdoutError)]
#[from(StderrError)]
#[from(JsonError)]
#[from(NoJsonError)]
pub(crate) struct Error;
impl Error {
pub(crate) fn is_start_failure(&self) -> bool {
self.source().is_some_and(<dyn StdError>::is::<StartError>)
}
pub(crate) fn is_bad_output(&self) -> bool {
self.source().is_some_and(|source| {
source.is::<StdoutError>() || source.is::<StderrError>() || source.is::<JsonError>() || source.is::<NoJsonError>()
})
}
}
pub(crate) fn run() -> Result<String, Error> {
let stdout = start()?;
parse(&stdout)
}
pub(crate) fn parse(stdout: &[u8]) -> Result<String, Error> {
let text = decode(stdout)?;
Ok(locate_json(text)?.to_owned())
}
pub(crate) fn rejected(stderr: &str) -> Result<String, Error> {
Err(CargoMetadataError::new(stderr).into())
}
fn start() -> Result<Vec<u8>, StartError> {
Err(std::io::Error::new(std::io::ErrorKind::NotFound, "no such file or directory").into())
}
fn decode(stdout: &[u8]) -> Result<&str, StdoutError> {
Ok(from_utf8(stdout)?)
}
fn locate_json(text: &str) -> Result<&str, NoJsonError> {
text.find('{').map(|start| &text[start..]).ok_or_else(NoJsonError::new)
}
}