Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: box all errors, implement send sync #4

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 28 additions & 26 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::error::Error as StdError;
use std::io::{Error as IOError, ErrorKind as IOErrorKind};
use std::fmt;
use std::io::{Error as IOError, ErrorKind as IOErrorKind};

#[derive(Debug)]
pub enum ErrorKind {
Expand All @@ -9,45 +9,45 @@ pub enum ErrorKind {
PermissionDenied,
InvalidResponse,
IO(IOError),
Other(Box<dyn StdError>),
Other(Box<dyn StdError + Send + Sync>),
}

#[derive(Debug)]
pub struct Error {
kind: ErrorKind,
}

impl StdError for Error {}

impl Error {
pub fn new(k: ErrorKind) -> Error {
Error {
kind: k,
}
Error { kind: k }
}

pub fn kind(&self) -> &ErrorKind {
&self.kind
}

pub fn into_inner_io(self) -> Option<IOError> {
match self.kind {
ErrorKind::FileExists => None,
ErrorKind::DirectoryMissing => None,
ErrorKind::PermissionDenied => None,
ErrorKind::InvalidResponse => None,
ErrorKind::IO(err) => Some(err),
ErrorKind::Other(_) => None,
}
match self.kind {
ErrorKind::FileExists => None,
ErrorKind::DirectoryMissing => None,
ErrorKind::PermissionDenied => None,
ErrorKind::InvalidResponse => None,
ErrorKind::IO(err) => Some(err),
ErrorKind::Other(_) => None,
}
}

pub fn into_inner_other(self) -> Option<Box<dyn StdError>> {
match self.kind {
ErrorKind::FileExists => None,
ErrorKind::DirectoryMissing => None,
ErrorKind::PermissionDenied => None,
ErrorKind::InvalidResponse => None,
ErrorKind::IO(_) => None,
ErrorKind::Other(err) => Some(err),
}
pub fn into_inner_other(self) -> Option<Box<dyn StdError + Send + Sync>> {
match self.kind {
ErrorKind::FileExists => None,
ErrorKind::DirectoryMissing => None,
ErrorKind::PermissionDenied => None,
ErrorKind::InvalidResponse => None,
ErrorKind::IO(_) => None,
ErrorKind::Other(err) => Some(err),
}
}
}

Expand All @@ -65,19 +65,21 @@ impl From<IOError> for Error {
}
}

impl From<Box<dyn StdError>> for Error {
fn from(err: Box<dyn StdError>) -> Error {
impl From<Box<dyn StdError + Send + Sync>> for Error {
fn from(err: Box<dyn StdError + Send + Sync>) -> Error {
Error {
kind: ErrorKind::Other(err),
}
}
}
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.kind() {
ErrorKind::FileExists => write!(f, "File already exists"),
ErrorKind::DirectoryMissing => write!(f, "Destination path provided is not a valid directory"),
ErrorKind::DirectoryMissing => {
write!(f, "Destination path provided is not a valid directory")
}
ErrorKind::PermissionDenied => write!(f, "Cannot create file: permission denied"),
ErrorKind::InvalidResponse => write!(f, "Invalid response from the remote host"),
ErrorKind::IO(err) => err.fmt(f),
Expand Down
64 changes: 35 additions & 29 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ use bytes::Bytes;
use futures_util::stream::Stream;
use futures_util::StreamExt;

#[cfg(feature="sha256sum")]
use sha2::{Sha256, Digest};
#[cfg(feature = "sha256sum")]
use sha2::{Digest, Sha256};
use tokio_util::io::StreamReader;

use crate::error::{Error as TDSTDError, ErrorKind as TDSTDErrorKind};
Expand All @@ -39,7 +39,7 @@ pub struct AsyncDownload {
dst_path: PathBuf,
fname: String,
length: Option<u64>,
response_stream: Option<Box<S>>
response_stream: Option<Box<S>>,
}

impl AsyncDownload {
Expand All @@ -56,40 +56,38 @@ impl AsyncDownload {
dst_path: PathBuf::from(dst_path),
fname: String::from(fname),
length: None,
response_stream: None
response_stream: None,
}
}

/// Returns the length of the download in bytes. This should be called after calling [`get`]
/// or [`download`].
pub fn length(&self) -> Option<u64> {
self.length
self.length
}

/// Get the download URL, but do not download it. If successful, returns an `AsyncDownload`
/// object with a response stream, which you can then call [`download`] on. After this, the
/// length of the download should also be known and you can call [`length`] on it.
pub async fn get(mut self) -> Result<AsyncDownload, Box<dyn Error>> {
pub async fn get(mut self) -> Result<AsyncDownload, Box<dyn Error + Send + Sync>> {
self.get_non_consumable().await?;
Ok(self)
}

async fn get_non_consumable(&mut self) -> Result<(), Box<dyn Error>> {
let response = reqwest::get(self.url.clone())
.await?;
let content_length = response.headers().get("content-length").map_or(None,
|l| {
match l.to_str() {
async fn get_non_consumable(&mut self) -> Result<(), Box<dyn Error + Send + Sync>> {
let response = reqwest::get(self.url.clone()).await?;
let content_length =
response
.headers()
.get("content-length")
.map_or(None, |l| match l.to_str() {
Err(_) => None,
Ok(l_str) => {
l_str.parse::<u64>().ok()
}
}
});
self.response_stream = Some(Box::new(response
.error_for_status()?
.bytes_stream()
.map(|result| result.map_err(|e| IOError::new(ErrorKind::Other, e)))));
Ok(l_str) => l_str.parse::<u64>().ok(),
});
self.response_stream =
Some(Box::new(response.error_for_status()?.bytes_stream().map(
|result| result.map_err(|e| IOError::new(ErrorKind::Other, e)),
)));
self.length = content_length;
Ok(())
}
Expand All @@ -99,15 +97,20 @@ impl AsyncDownload {
/// Arguments:
/// * `cb` - An optional callback for reporting information about the download asynchronously.
/// The callback takes the position of the current download, in bytes.
pub async fn download(&mut self, cb: &Option<Box<dyn Fn(u64) -> ()>>) -> Result<(), TDSTDError> {
pub async fn download(
&mut self,
cb: &Option<Box<dyn Fn(u64) -> ()>>,
) -> Result<(), Box<dyn Error + Send + Sync>> {
if self.response_stream.is_none() {
self.get_non_consumable().await.map_err(|_| TDSTDError::new(TDSTDErrorKind::InvalidResponse))?;
self.get_non_consumable()
.await
.map_err(|_| TDSTDError::new(TDSTDErrorKind::InvalidResponse))?;
}
use tokio::io::{AsyncReadExt, AsyncWriteExt};

let fname = self.dst_path.join(self.fname.clone());
if fname.is_file() {
return Err(TDSTDError::new(TDSTDErrorKind::FileExists));
return Err(Box::new(TDSTDError::new(TDSTDErrorKind::FileExists)));
}

if self.dst_path.is_dir() {
Expand All @@ -130,23 +133,26 @@ impl AsyncDownload {
}
Ok(())
} else {
Err(TDSTDError::new(TDSTDErrorKind::DirectoryMissing))
Err(Box::new(TDSTDError::new(TDSTDErrorKind::DirectoryMissing)))
}
}

#[cfg(feature="sha256sum")]
#[cfg(feature = "sha256sum")]
/// Initiate the download and return a result with the sha256sum of the download contents.
/// Specify an optional callback.
///
/// Arguments:
/// * `cb` - An optional callback for reporting information about the download asynchronously.
/// The callback takes the position of the current download, in bytes.
pub async fn download_and_return_sha256sum(&mut self, cb: &Option<Box<dyn Fn(u64) -> ()>>) -> Result<Vec<u8>, TDSTDError> {
pub async fn download_and_return_sha256sum(
&mut self,
cb: &Option<Box<dyn Fn(u64) -> ()>>,
) -> Result<Vec<u8>, Box<dyn Error + Send + Sync>> {
use tokio::io::{AsyncReadExt, AsyncWriteExt};

let fname = self.dst_path.join(self.fname.clone());
if fname.is_file() {
return Err(TDSTDError::new(TDSTDErrorKind::FileExists));
return Err(Box::new(TDSTDError::new(TDSTDErrorKind::FileExists)));
}

if self.dst_path.is_dir() {
Expand All @@ -171,7 +177,7 @@ impl AsyncDownload {
}
Ok(hasher.finalize().to_vec())
} else {
Err(TDSTDError::new(TDSTDErrorKind::DirectoryMissing))
Err(Box::new(TDSTDError::new(TDSTDErrorKind::DirectoryMissing)))
}
}
}