use std::path::PathBuf;
#[cfg(feature = "error_reporting")]
use peace::miette::{self, SourceSpan};
#[cfg_attr(feature = "error_reporting", derive(peace::miette::Diagnostic))]
#[derive(Debug, thiserror::Error)]
pub enum FileDownloadError {
#[error("Failed to open destination file.")]
DestFileOpen(#[source] std::io::Error),
#[error("Failed to read destination file metadata.")]
DestMetadataRead(#[source] std::io::Error),
#[error("Failed to read destination file contents.")]
DestFileRead(#[source] std::io::Error),
#[error("Failed to create directories: `{}`.", dest_parent.display())]
#[cfg_attr(
feature = "error_reporting",
diagnostic(
code(peace_item_spec_file_download::dest_parent_dirs_create),
help(
"Ensure that `{}` is not a file, or rerun the command with a different path.",
dest_parent.display())),
)]
DestParentDirsCreate {
dest: PathBuf,
dest_parent: PathBuf,
#[cfg(feature = "error_reporting")]
#[source_code]
dest_display: String,
#[cfg(feature = "error_reporting")]
#[label]
parent_dirs_span: SourceSpan,
#[source]
error: std::io::Error,
},
#[error("Failed to open `{}` for writing.", dest.display())]
#[cfg_attr(
feature = "error_reporting",
diagnostic(
code(peace_item_spec_file_download::dest_file_create),
help(
"Ensure that `{}` is not a directory, or rerun the command with a different path.",
dest.display())),
)]
DestFileCreate {
#[cfg_attr(feature = "error_reporting", source_code)]
init_command_approx: String,
#[cfg(feature = "error_reporting")]
#[label = "defined here"]
dest_span: SourceSpan,
dest: PathBuf,
#[source]
error: std::io::Error,
},
#[error("Failed to delete destination file.")]
DestFileRemove(#[source] std::io::Error),
#[error("Failed to parse source URL.")]
SrcUrlParse(url::ParseError),
#[cfg_attr(
feature = "error_reporting",
diagnostic(
code(peace_item_spec_file_download::src_get),
help(
"Check that the URL is reachable: `curl {}`\nAre you connected to the internet?",
src
),
)
)]
#[error("Failed to download file.")]
SrcGet {
#[cfg(not(target_arch = "wasm32"))]
#[cfg_attr(feature = "error_reporting", source_code)]
init_command_approx: String,
#[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "error_reporting")]
#[label = "defined here"]
src_span: SourceSpan,
src: url::Url,
#[source]
error: reqwest::Error,
},
#[error("Failed to fetch source file metadata. Response status code: {status_code}")]
SrcFileUndetermined { status_code: reqwest::StatusCode },
#[error("Failed to read source file content.")]
SrcFileRead(#[source] reqwest::Error),
#[error("Failed to stream source file content.")]
ResponseBytesStream(#[source] reqwest::Error),
#[error("Failed to transfer source file content.")]
ResponseFileWrite(#[source] std::io::Error),
#[cfg(not(target_arch = "wasm32"))]
#[error("Failed to read current executable path.")]
CurrentExeRead(#[source] std::io::Error),
#[cfg(not(target_arch = "wasm32"))]
#[error("Failed to get current executable name from path.")]
CurrentExeNameRead,
#[cfg(not(target_arch = "wasm32"))]
#[error("Failed to format string in memory.")]
FormatString(#[source] std::fmt::Error),
#[cfg(target_arch = "wasm32")]
#[error("Failed to read bytes from response.")]
ResponseBytesRead(#[source] reqwest::Error),
#[cfg(target_arch = "wasm32")]
#[error("Failed to read text from response.")]
ResponseTextRead(#[source] reqwest::Error),
#[error("A `peace` runtime error occurred.")]
PeaceRtError(
#[cfg_attr(feature = "error_reporting", diagnostic_source)]
#[source]
#[from]
peace::rt_model::Error,
),
}
impl FileDownloadError {
#[cfg(not(target_arch = "wasm32"))]
#[allow(clippy::result_large_err)]
pub(crate) fn src_get(
src: url::Url,
dest: &std::path::Path,
error: reqwest::Error,
) -> Result<Self, Self> {
use std::{fmt::Write, path::Component};
#[cfg(feature = "error_reporting")]
use peace::miette::SourceOffset;
let mut init_command_approx = String::with_capacity(256);
let exe_path = std::env::current_exe().map_err(FileDownloadError::CurrentExeRead)?;
let exe_name = if let Some(Component::Normal(exe_name)) = exe_path.components().next_back()
{
exe_name
} else {
return Err(FileDownloadError::CurrentExeNameRead);
};
let exe_name = exe_name.to_string_lossy();
let dest_display = dest.display();
write!(&mut init_command_approx, "{exe_name} init ")
.map_err(FileDownloadError::FormatString)?;
#[cfg(feature = "error_reporting")]
let src_offset_col = init_command_approx.len();
write!(&mut init_command_approx, "{src}").map_err(FileDownloadError::FormatString)?;
#[cfg(feature = "error_reporting")]
let dest_offset_col = init_command_approx.len();
write!(&mut init_command_approx, " {dest_display}")
.map_err(FileDownloadError::FormatString)?;
#[cfg(feature = "error_reporting")]
let src_span = {
let loc_line = 1;
let start =
SourceOffset::from_location(&init_command_approx, loc_line, src_offset_col + 1);
let length = SourceOffset::from_location(
&init_command_approx,
loc_line,
dest_offset_col - src_offset_col + 1,
);
SourceSpan::new(start, length)
};
Err(FileDownloadError::SrcGet {
init_command_approx,
#[cfg(feature = "error_reporting")]
src_span,
src,
error,
})
}
#[cfg(target_arch = "wasm32")]
pub(crate) fn src_get(src: url::Url, error: reqwest::Error) -> Self {
FileDownloadError::SrcGet { src, error }
}
}