#![forbid(unsafe_code)]
use std::{
fmt::{self, Display, Formatter},
fs::{self, File},
io::{self, BufWriter, Write},
path::{Path, PathBuf},
};
use serde::Serialize;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ArtifactPath {
requested: PathBuf,
}
impl ArtifactPath {
pub fn try_new(requested: PathBuf) -> Result<Self, ArtifactOutputError> {
if requested.as_os_str().is_empty() {
return Err(ArtifactOutputError::EmptyPath);
}
Ok(Self { requested })
}
pub fn as_path(&self) -> &Path {
&self.requested
}
pub fn create_writer(&self) -> Result<BufWriter<File>, ArtifactOutputError> {
if let Some(parent) = self
.requested
.parent()
.filter(|parent| !parent.as_os_str().is_empty())
{
fs::create_dir_all(parent).map_err(|source| ArtifactOutputError::CreateParent {
path: parent.to_owned(),
source,
})?;
}
let file = File::create(&self.requested).map_err(|source| ArtifactOutputError::Open {
path: self.requested.clone(),
source,
})?;
Ok(BufWriter::new(file))
}
pub fn flush_error(&self, source: io::Error) -> ArtifactOutputError {
ArtifactOutputError::Flush {
path: self.requested.clone(),
source,
}
}
}
pub fn write_json_output(
value: &impl Serialize,
path: Option<&ArtifactPath>,
) -> Result<(), ArtifactOutputError> {
if let Some(path) = path {
let mut writer = path.create_writer()?;
serde_json::to_writer_pretty(&mut writer, value).map_err(|source| {
ArtifactOutputError::Serialize {
destination: ArtifactDestination::Path(path.as_path().to_owned()),
source,
}
})?;
writer.flush().map_err(|source| path.flush_error(source))?;
} else {
let stdout = io::stdout();
let mut handle = stdout.lock();
serde_json::to_writer_pretty(&mut handle, value).map_err(|source| {
ArtifactOutputError::Serialize {
destination: ArtifactDestination::Stdout,
source,
}
})?;
writeln!(handle).map_err(|source| ArtifactOutputError::WriteStdout { source })?;
}
Ok(())
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ArtifactDestination {
Stdout,
Path(PathBuf),
}
impl Display for ArtifactDestination {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::Stdout => formatter.write_str("stdout"),
Self::Path(path) => write!(formatter, "{}", path.display()),
}
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ArtifactOutputError {
#[error("artifact path must not be empty")]
EmptyPath,
#[error("failed to create artifact directory {path:?}: {source}")]
CreateParent {
path: PathBuf,
#[source]
source: io::Error,
},
#[error("failed to open artifact {path:?} for writing: {source}")]
Open {
path: PathBuf,
#[source]
source: io::Error,
},
#[error("failed to serialize JSON to {destination}: {source}")]
Serialize {
destination: ArtifactDestination,
#[source]
source: serde_json::Error,
},
#[error("failed to flush artifact {path:?}: {source}")]
Flush {
path: PathBuf,
#[source]
source: io::Error,
},
#[error("failed to write JSON to stdout: {source}")]
WriteStdout {
#[source]
source: io::Error,
},
}