Merge branch 'develop' into feature/hashing

- with "clippy fix" and "cargo fmt"
This commit is contained in:
Jörn-Michael Miehe 2025-06-25 21:07:24 +00:00
commit 1f9c247439
12 changed files with 171 additions and 146 deletions

3
.vscode/tasks.json vendored
View file

@ -32,6 +32,9 @@
"command": "clippy", "command": "clippy",
"args": [ "args": [
"--fix", "--fix",
"--lib",
"--bin",
"shrupl",
"--allow-dirty", "--allow-dirty",
"--allow-staged", "--allow-staged",
"--", "--",

View file

@ -6,9 +6,10 @@ use log::{debug, info, warn};
use crate::{ use crate::{
cachefile::CacheFile, cachefile::CacheFile,
cli::Cli, cli::Cli,
error,
file::{Chunk, FileTrait}, file::{Chunk, FileTrait},
output::new_progressbar, output::new_progressbar,
sharry::{self, Client}, sharry::Client,
}; };
pub struct AppState { pub struct AppState {
@ -41,12 +42,12 @@ impl AppState {
} }
} }
pub fn try_resume(args: &Cli) -> sharry::Result<Self> { pub fn try_resume(args: &Cli) -> error::Result<Self> {
fn check_hash<'a>(file: &'a impl FileTrait<'a>, bar: &ProgressBar) -> sharry::Result<()> { fn check_hash<'a>(file: &'a impl FileTrait<'a>, bar: &ProgressBar) -> error::Result<()> {
bar.set_message(format!("checking {:?}", file.get_name())); bar.set_message(format!("checking {:?}", file.get_name()));
match file.check_hash(|bytes| bar.inc(bytes)) { match file.check_hash(|bytes| bar.inc(bytes)) {
Ok(true) => Ok(()), Ok(true) => Ok(()),
Ok(false) => Err(sharry::ClientError::unknown(format!( Ok(false) => Err(error::Error::unknown(format!(
"Hash mismatch for file {:?}!", "Hash mismatch for file {:?}!",
file.get_name() file.get_name()
))), ))),
@ -90,7 +91,7 @@ impl AppState {
Ok(Self::new(new_http(args.get_timeout()), inner)) Ok(Self::new(new_http(args.get_timeout()), inner))
} }
pub fn from_args(args: &Cli) -> sharry::Result<Self> { pub fn from_args(args: &Cli) -> error::Result<Self> {
// TODO CLI switch begin // TODO CLI switch begin
let mut files = args.files.clone(); let mut files = args.files.clone();
@ -148,7 +149,7 @@ impl AppState {
self.with_progressbar(f, true); self.with_progressbar(f, true);
} }
fn next_chunk<'t>(&mut self, buffer: &'t mut [u8]) -> sharry::Result<Option<Chunk<'t>>> { fn next_chunk<'t>(&mut self, buffer: &'t mut [u8]) -> error::Result<Option<Chunk<'t>>> {
if self.inner.get_uploading(&self.http)?.is_none() { if self.inner.get_uploading(&self.http)?.is_none() {
return Ok(None); return Ok(None);
} }
@ -164,7 +165,7 @@ impl AppState {
Ok(Some(chunk)) Ok(Some(chunk))
} }
pub fn upload_chunk(&mut self, buffer: &mut [u8]) -> sharry::Result<bool> { pub fn upload_chunk(&mut self, buffer: &mut [u8]) -> error::Result<bool> {
let Some(chunk) = self.next_chunk(buffer)? else { let Some(chunk) = self.next_chunk(buffer)? else {
self.inner self.inner
.share_notify(&self.http) .share_notify(&self.http)
@ -185,6 +186,7 @@ impl AppState {
Ok(self.inner.peek_uploading().is_none() && self.inner.queue().is_empty()) Ok(self.inner.peek_uploading().is_none() && self.inner.queue().is_empty())
} }
#[must_use]
pub fn rewind_chunk(mut self) -> Option<Self> { pub fn rewind_chunk(mut self) -> Option<Self> {
self.inner = self.inner.rewind_chunk()?; self.inner = self.inner.rewind_chunk()?;
@ -196,7 +198,7 @@ impl AppState {
self.drop_progressbar(ProgressBar::abandon); self.drop_progressbar(ProgressBar::abandon);
} }
pub fn rebuild_share(self, args: &Cli) -> sharry::Result<Self> { pub fn rebuild_share(self, args: &Cli) -> error::Result<Self> {
let share_id = let share_id =
self.http self.http
.share_create(&args.get_uri(), &args.alias, args.get_share_request())?; .share_create(&args.get_uri(), &args.alias, args.get_share_request())?;
@ -215,4 +217,8 @@ impl AppState {
pub fn discard(self) -> io::Result<()> { pub fn discard(self) -> io::Result<()> {
self.inner.discard() self.inner.discard()
} }
pub fn clear_any(args: &Cli) {
CacheFile::clear_any(args);
}
} }

View file

@ -1,11 +1,3 @@
mod appstate;
mod cachefile;
mod cli;
mod file;
mod impl_ureq;
mod output;
mod sharry;
use std::{ use std::{
process, process,
sync::{ sync::{
@ -16,12 +8,12 @@ use std::{
use clap::Parser; use clap::Parser;
use console::{StyledObject, style}; use console::{StyledObject, style};
use log::{info, trace}; use log::{debug, info, trace};
use appstate::AppState; use shrupl::{
use cli::Cli; AppState, Cli, error,
use output::{Log, SHRUPL}; output::{self, Log, SHRUPL},
use sharry::{ClientError, Parameter}; };
fn main() { fn main() {
let args = Cli::parse(); let args = Cli::parse();
@ -56,7 +48,7 @@ fn main() {
let mut state = resumed let mut state = resumed
.inspect_err(|e| { .inspect_err(|e| {
cachefile::CacheFile::clear_any(&args); AppState::clear_any(&args);
Log::handle(e); Log::handle(e);
info!("could not resume from hash {:?}: {e}", args.get_hash()); info!("could not resume from hash {:?}: {e}", args.get_hash());
}) })
@ -94,18 +86,18 @@ fn main() {
Err(e) => { Err(e) => {
Log::handle(&e); Log::handle(&e);
if let ClientError::InvalidParameter(p) = e { if let error::Error::InvalidParameter(p) = e {
match p { match p {
// Error 404 (File not found) // Error 404 (File not found)
Parameter::FileID(fid) => { error::Parameter::FileID(fid) => {
trace!("requeueing file {fid:?}"); debug!("requeueing file {fid:?}");
state.abort_upload(); state.abort_upload();
} }
// Error 404 (Share not found) // Error 404 (Share not found)
Parameter::ShareID(sid) => { error::Parameter::ShareID(sid) => {
// TODO ask // TODO ask
trace!("rebuilding share {sid:?}"); debug!("rebuilding share {sid:?}");
// rebuild share // rebuild share
let Ok(s) = state.rebuild_share(&args) else { let Ok(s) = state.rebuild_share(&args) else {
@ -122,7 +114,7 @@ fn main() {
}; };
tries += 1; tries += 1;
trace!("State rewound, retrying last chunk (tries: {tries})"); debug!("State rewound, retrying last chunk (tries: {tries})");
state = s; state = s;
} }
} }

View file

@ -10,8 +10,9 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
cli::Cli, cli::Cli,
error,
file::{self, Chunk}, file::{self, Chunk},
sharry::{self, Client, Uri}, sharry::{Client, Uri},
}; };
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@ -74,7 +75,7 @@ impl CacheFile {
pub fn get_uploading( pub fn get_uploading(
&mut self, &mut self,
client: &impl Client, client: &impl Client,
) -> sharry::Result<Option<&mut file::Uploading>> { ) -> error::Result<Option<&mut file::Uploading>> {
if self.uploading.is_some() { if self.uploading.is_some() {
Ok(self.uploading.as_mut()) Ok(self.uploading.as_mut())
} else if let Some(chk) = self.files.pop_front() { } else if let Some(chk) = self.files.pop_front() {
@ -128,11 +129,11 @@ impl CacheFile {
); );
} }
pub fn share_notify(&self, client: &impl Client) -> sharry::Result<()> { pub fn share_notify(&self, client: &impl Client) -> error::Result<()> {
client.share_notify(&self.uri, &self.alias_id, &self.share_id) client.share_notify(&self.uri, &self.alias_id, &self.share_id)
} }
pub fn file_patch(&self, client: &impl Client, chunk: &Chunk) -> sharry::Result<()> { pub fn file_patch(&self, client: &impl Client, chunk: &Chunk) -> error::Result<()> {
client.file_patch(&self.uri, &self.alias_id, &self.share_id, chunk) client.file_patch(&self.uri, &self.alias_id, &self.share_id, chunk)
} }

View file

@ -107,14 +107,17 @@ where
} }
impl Cli { impl Cli {
#[must_use]
pub fn get_timeout(&self) -> Option<Duration> { pub fn get_timeout(&self) -> Option<Duration> {
(!self.timeout.is_zero()).then_some(self.timeout) (!self.timeout.is_zero()).then_some(self.timeout)
} }
#[must_use]
pub fn get_uri(&self) -> Uri { pub fn get_uri(&self) -> Uri {
Uri::new(&self.protocol, &self.url) Uri::new(&self.protocol, &self.url)
} }
#[must_use]
pub fn may_retry(&self, tries: u32) -> bool { pub fn may_retry(&self, tries: u32) -> bool {
match self.retry_limit { match self.retry_limit {
0 => true, 0 => true,
@ -122,10 +125,12 @@ impl Cli {
} }
} }
#[must_use]
pub fn get_share_request(&self) -> NewShareRequest { pub fn get_share_request(&self) -> NewShareRequest {
NewShareRequest::new(&self.name, self.description.as_ref(), self.max_views) NewShareRequest::new(&self.name, self.description.as_ref(), self.max_views)
} }
#[must_use]
pub fn get_level_filter(&self) -> LevelFilter { pub fn get_level_filter(&self) -> LevelFilter {
match self.verbose { match self.verbose {
0 => LevelFilter::Error, 0 => LevelFilter::Error,
@ -140,6 +145,7 @@ impl Cli {
self.files.iter().map(FileTrait::get_name).collect() self.files.iter().map(FileTrait::get_name).collect()
} }
#[must_use]
pub fn get_hash(&self) -> String { pub fn get_hash(&self) -> String {
let mut hasher = Blake2b::new().hash_length(16).to_state(); let mut hasher = Blake2b::new().hash_length(16).to_state();

76
src/error.rs Normal file
View file

@ -0,0 +1,76 @@
use std::fmt;
#[derive(Debug, thiserror::Error)]
pub enum Parameter {
#[error("given URI {0:?}")]
Uri(String),
#[error("given Alias ID {0:?}")]
AliasID(String),
#[error("stored Share ID {0:?}")]
ShareID(String),
#[error("stored File ID {0:?}")]
FileID(String),
}
impl Parameter {
fn is_fatal(&self) -> bool {
matches!(self, Self::Uri(_) | Self::AliasID(_))
}
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
StdIo(#[from] std::io::Error),
#[error("response error: {0}")]
Response(String),
#[error("Invalid {0}")]
InvalidParameter(Parameter),
#[error("Unknown error: {0}")]
Unknown(String),
}
#[allow(clippy::needless_pass_by_value)]
fn into_string(val: impl ToString) -> String {
val.to_string()
}
impl Error {
pub fn res_status_check<T>(actual: T, expected: T) -> Result<()>
where
T: PartialEq + fmt::Display + Copy,
{
if actual == expected {
Ok(())
} else {
Err(Self::Response(format!(
"unexpected status: {actual} (expected {expected})"
)))
}
}
pub fn response(e: impl ToString) -> Self {
Self::Response(into_string(e))
}
pub fn unknown(e: impl ToString) -> Self {
Self::Unknown(into_string(e))
}
#[must_use]
pub fn is_fatal(&self) -> bool {
match self {
Self::InvalidParameter(p) => p.is_fatal(),
Self::Unknown(_) => true,
_ => false,
}
}
}
pub type Result<T> = std::result::Result<T, Error>;

View file

@ -5,7 +5,7 @@ use std::{
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::sharry; use crate::{error, sharry};
use super::{FileTrait, Uploading}; use super::{FileTrait, Uploading};
@ -81,7 +81,7 @@ impl Checked {
uri: &sharry::Uri, uri: &sharry::Uri,
alias_id: &str, alias_id: &str,
share_id: &str, share_id: &str,
) -> sharry::Result<Uploading> { ) -> error::Result<Uploading> {
let file_id = client.file_create(uri, alias_id, share_id, &self)?; let file_id = client.file_create(uri, alias_id, share_id, &self)?;
Ok(Uploading::new(self.path, self.size, self.hash, file_id)) Ok(Uploading::new(self.path, self.size, self.hash, file_id))

View file

@ -1,8 +1,9 @@
use log::{debug, trace}; use log::{debug, trace};
use crate::{ use crate::{
error,
file::{self, FileTrait}, file::{self, FileTrait},
sharry::{self, ClientError, Uri}, sharry::{self, Uri},
}; };
fn find_cause( fn find_cause(
@ -10,22 +11,22 @@ fn find_cause(
alias_id: &str, alias_id: &str,
share_id: Option<&str>, share_id: Option<&str>,
file_id: Option<&str>, file_id: Option<&str>,
) -> impl FnOnce(ureq::Error) -> ClientError { ) -> impl FnOnce(ureq::Error) -> error::Error {
move |error| match error { move |error| match error {
ureq::Error::StatusCode(403) => { ureq::Error::StatusCode(403) => {
trace!("HTTP Error 403: Alias not found!"); trace!("HTTP Error 403: Alias not found!");
ClientError::InvalidParameter(sharry::Parameter::AliasID(alias_id.to_owned())) error::Error::InvalidParameter(error::Parameter::AliasID(alias_id.to_owned()))
} }
ureq::Error::StatusCode(404) => { ureq::Error::StatusCode(404) => {
trace!("HTTP Error 404: Share and/or file may have been deleted!"); trace!("HTTP Error 404: Share and/or file may have been deleted!");
if let Some(file_id) = file_id { if let Some(file_id) = file_id {
ClientError::InvalidParameter(sharry::Parameter::FileID(file_id.to_owned())) error::Error::InvalidParameter(error::Parameter::FileID(file_id.to_owned()))
} else if let Some(share_id) = share_id { } else if let Some(share_id) = share_id {
ClientError::InvalidParameter(sharry::Parameter::ShareID(share_id.to_owned())) error::Error::InvalidParameter(error::Parameter::ShareID(share_id.to_owned()))
} else { } else {
ClientError::unknown(error) error::Error::InvalidParameter(error::Parameter::Uri(uri.to_string()))
} }
} }
ureq::Error::Io(error) => { ureq::Error::Io(error) => {
@ -33,7 +34,7 @@ fn find_cause(
if let Some(msg) = error.get_ref().map(ToString::to_string) { if let Some(msg) = error.get_ref().map(ToString::to_string) {
if msg.starts_with("failed to lookup address information") { if msg.starts_with("failed to lookup address information") {
ClientError::InvalidParameter(sharry::Parameter::Uri(uri.to_string())) error::Error::InvalidParameter(error::Parameter::Uri(uri.to_string()))
} else { } else {
error.into() error.into()
} }
@ -41,7 +42,7 @@ fn find_cause(
error.into() error.into()
} }
} }
error => ClientError::unknown(error), error => error::Error::unknown(error),
} }
} }
@ -51,7 +52,7 @@ impl sharry::Client for ureq::Agent {
uri: &Uri, uri: &Uri,
alias_id: &str, alias_id: &str,
data: sharry::NewShareRequest, data: sharry::NewShareRequest,
) -> sharry::Result<String> { ) -> error::Result<String> {
let res = { let res = {
let endpoint = uri.share_create(); let endpoint = uri.share_create();
@ -62,11 +63,11 @@ impl sharry::Client for ureq::Agent {
.map_err(find_cause(uri, alias_id, None, None))?; .map_err(find_cause(uri, alias_id, None, None))?;
trace!("{endpoint:?} response: {res:?}"); trace!("{endpoint:?} response: {res:?}");
ClientError::res_status_check(res.status(), ureq::http::StatusCode::OK)?; error::Error::res_status_check(res.status(), ureq::http::StatusCode::OK)?;
res.body_mut() res.body_mut()
.read_json::<sharry::NewShareResponse>() .read_json::<sharry::NewShareResponse>()
.map_err(ClientError::response)? .map_err(error::Error::response)?
}; };
debug!("{res:?}"); debug!("{res:?}");
@ -76,11 +77,11 @@ impl sharry::Client for ureq::Agent {
Ok(res.id) Ok(res.id)
} else { } else {
Err(ClientError::response(format!("{res:?}"))) Err(error::Error::response(format!("{res:?}")))
} }
} }
fn share_notify(&self, uri: &Uri, alias_id: &str, share_id: &str) -> sharry::Result<()> { fn share_notify(&self, uri: &Uri, alias_id: &str, share_id: &str) -> error::Result<()> {
let res = { let res = {
let endpoint = uri.share_notify(share_id); let endpoint = uri.share_notify(share_id);
@ -91,11 +92,11 @@ impl sharry::Client for ureq::Agent {
.map_err(find_cause(uri, alias_id, Some(share_id), None))?; .map_err(find_cause(uri, alias_id, Some(share_id), None))?;
trace!("{endpoint:?} response: {res:?}"); trace!("{endpoint:?} response: {res:?}");
ClientError::res_status_check(res.status(), ureq::http::StatusCode::OK)?; error::Error::res_status_check(res.status(), ureq::http::StatusCode::OK)?;
res.body_mut() res.body_mut()
.read_json::<sharry::NotifyShareResponse>() .read_json::<sharry::NotifyShareResponse>()
.map_err(ClientError::response)? .map_err(error::Error::response)?
}; };
debug!("{res:?}"); debug!("{res:?}");
@ -109,7 +110,7 @@ impl sharry::Client for ureq::Agent {
alias_id: &str, alias_id: &str,
share_id: &str, share_id: &str,
file: &file::Checked, file: &file::Checked,
) -> sharry::Result<String> { ) -> error::Result<String> {
let res = { let res = {
let endpoint = uri.file_create(share_id); let endpoint = uri.file_create(share_id);
@ -122,14 +123,14 @@ impl sharry::Client for ureq::Agent {
.map_err(find_cause(uri, alias_id, Some(share_id), None))?; .map_err(find_cause(uri, alias_id, Some(share_id), None))?;
trace!("{endpoint:?} response: {res:?}"); trace!("{endpoint:?} response: {res:?}");
ClientError::res_status_check(res.status(), ureq::http::StatusCode::CREATED)?; error::Error::res_status_check(res.status(), ureq::http::StatusCode::CREATED)?;
res res
}; };
let location = (res.headers().get("Location")) let location = (res.headers().get("Location"))
.ok_or_else(|| ClientError::response("Location header not found"))? .ok_or_else(|| error::Error::response("Location header not found"))?
.to_str() .to_str()
.map_err(ClientError::response)? .map_err(error::Error::response)?
.to_string(); .to_string();
let file_id = Self::get_file_id(&location)?; let file_id = Self::get_file_id(&location)?;
@ -145,7 +146,7 @@ impl sharry::Client for ureq::Agent {
alias_id: &str, alias_id: &str,
share_id: &str, share_id: &str,
chunk: &file::Chunk, chunk: &file::Chunk,
) -> sharry::Result<()> { ) -> error::Result<()> {
let res = { let res = {
let endpoint = uri.file_patch(share_id, chunk.get_file_id()); let endpoint = uri.file_patch(share_id, chunk.get_file_id());
@ -162,21 +163,21 @@ impl sharry::Client for ureq::Agent {
))?; ))?;
trace!("{endpoint:?} response: {res:?}"); trace!("{endpoint:?} response: {res:?}");
ClientError::res_status_check(res.status(), ureq::http::StatusCode::NO_CONTENT)?; error::Error::res_status_check(res.status(), ureq::http::StatusCode::NO_CONTENT)?;
res res
}; };
let res_offset = (res.headers().get("Upload-Offset")) let res_offset = (res.headers().get("Upload-Offset"))
.ok_or_else(|| ClientError::response("Upload-Offset header not found"))? .ok_or_else(|| error::Error::response("Upload-Offset header not found"))?
.to_str() .to_str()
.map_err(ClientError::response)? .map_err(error::Error::response)?
.parse::<u64>() .parse::<u64>()
.map_err(ClientError::response)?; .map_err(error::Error::response)?;
if chunk.get_behind() == res_offset { if chunk.get_behind() == res_offset {
Ok(()) Ok(())
} else { } else {
Err(ClientError::response(format!( Err(error::Error::response(format!(
"Unexpected Upload-Offset: {} (expected {})", "Unexpected Upload-Offset: {} (expected {})",
res_offset, res_offset,
chunk.get_behind() chunk.get_behind()

14
src/lib.rs Normal file
View file

@ -0,0 +1,14 @@
// TODO fix with documentation
#![allow(clippy::missing_errors_doc)]
mod appstate;
mod cachefile;
mod cli;
pub mod error;
mod file;
mod impl_ureq;
pub mod output;
mod sharry;
pub use appstate::AppState;
pub use cli::Cli;

View file

@ -5,12 +5,11 @@ use dialoguer::{Select, theme::ColorfulTheme};
use indicatif::{ProgressBar, ProgressStyle}; use indicatif::{ProgressBar, ProgressStyle};
use log::{error, info}; use log::{error, info};
use crate::sharry;
type StaticStyled<'t> = LazyLock<StyledObject<&'t str>>; type StaticStyled<'t> = LazyLock<StyledObject<&'t str>>;
pub static SHRUPL: StaticStyled = LazyLock::new(|| style("ShrUpl").yellow().bold()); pub static SHRUPL: StaticStyled = LazyLock::new(|| style("ShrUpl").yellow().bold());
#[must_use]
pub fn prompt_continue() -> bool { pub fn prompt_continue() -> bool {
let prompt = format!( let prompt = format!(
"This operation has previously been stopped. {}", "This operation has previously been stopped. {}",
@ -44,6 +43,8 @@ where
strs.iter().map(|&s| f(style(s)).to_string()).collect() strs.iter().map(|&s| f(style(s)).to_string()).collect()
} }
#[must_use]
#[allow(clippy::missing_panics_doc)]
pub fn new_progressbar() -> ProgressBar { pub fn new_progressbar() -> ProgressBar {
ProgressBar::hidden().with_style( ProgressBar::hidden().with_style(
ProgressStyle::with_template(&format!( ProgressStyle::with_template(&format!(
@ -74,7 +75,7 @@ impl Log {
process::exit(1); process::exit(1);
} }
pub fn handle(e: &sharry::ClientError) { pub fn handle(e: &crate::error::Error) {
if e.is_fatal() { if e.is_fatal() {
// react to fatal error // react to fatal error
error!("fatal error: {e:?}"); error!("fatal error: {e:?}");

View file

@ -1,15 +1,14 @@
use std::{fmt, sync::LazyLock}; use std::sync::LazyLock;
use log::trace; use log::trace;
use regex::Regex; use regex::Regex;
use thiserror::Error;
use crate::file; use crate::{error, file};
use super::api::{NewShareRequest, Uri}; use super::api::{NewShareRequest, Uri};
pub trait Client { pub trait Client {
fn get_file_id(uri: &str) -> super::Result<&str> { fn get_file_id(uri: &str) -> error::Result<&str> {
/// Pattern breakdown: /// Pattern breakdown:
/// - `^([^:/?#]+)://` scheme (anything but `:/?#`) + `"://"` /// - `^([^:/?#]+)://` scheme (anything but `:/?#`) + `"://"`
/// - `([^/?#]+)` authority/host (anything but `/?#`) /// - `([^/?#]+)` authority/host (anything but `/?#`)
@ -33,7 +32,7 @@ pub trait Client {
{ {
Ok(fid) Ok(fid)
} else { } else {
Err(super::ClientError::unknown(format!( Err(error::Error::unknown(format!(
"Could not extract File ID from {uri:?}" "Could not extract File ID from {uri:?}"
))) )))
} }
@ -44,9 +43,9 @@ pub trait Client {
uri: &Uri, uri: &Uri,
alias_id: &str, alias_id: &str,
data: NewShareRequest, data: NewShareRequest,
) -> super::Result<String>; ) -> error::Result<String>;
fn share_notify(&self, uri: &Uri, alias_id: &str, share_id: &str) -> super::Result<()>; fn share_notify(&self, uri: &Uri, alias_id: &str, share_id: &str) -> error::Result<()>;
fn file_create( fn file_create(
&self, &self,
@ -54,7 +53,7 @@ pub trait Client {
alias_id: &str, alias_id: &str,
share_id: &str, share_id: &str,
file: &file::Checked, file: &file::Checked,
) -> super::Result<String>; ) -> error::Result<String>;
fn file_patch( fn file_patch(
&self, &self,
@ -62,7 +61,7 @@ pub trait Client {
alias_id: &str, alias_id: &str,
share_id: &str, share_id: &str,
chunk: &file::Chunk, chunk: &file::Chunk,
) -> super::Result<()>; ) -> error::Result<()>;
} }
// TODO move into tests subdir // TODO move into tests subdir
@ -85,75 +84,3 @@ pub trait Client {
// assert!(Client::get_file_id(bad).is_err()); // assert!(Client::get_file_id(bad).is_err());
// } // }
// } // }
#[derive(Debug, Error)]
pub enum Parameter {
#[error("given URI {0:?}")]
Uri(String),
#[error("given Alias ID {0:?}")]
AliasID(String),
#[error("stored Share ID {0:?}")]
ShareID(String),
#[error("stored File ID {0:?}")]
FileID(String),
}
impl Parameter {
fn is_fatal(&self) -> bool {
matches!(self, Self::Uri(_) | Self::AliasID(_))
}
}
#[derive(Debug, Error)]
pub enum ClientError {
#[error(transparent)]
StdIo(#[from] std::io::Error),
#[error("response error: {0}")]
Response(String),
#[error("Invalid {0}")]
InvalidParameter(Parameter),
#[error("{0}")]
Unknown(String),
}
#[allow(clippy::needless_pass_by_value)]
fn into_string(val: impl ToString) -> String {
val.to_string()
}
impl ClientError {
pub fn res_status_check<T>(actual: T, expected: T) -> super::Result<()>
where
T: PartialEq + fmt::Display + Copy,
{
if actual == expected {
Ok(())
} else {
Err(Self::Response(format!(
"unexpected status: {actual} (expected {expected})"
)))
}
}
pub fn response(e: impl ToString) -> Self {
Self::Response(into_string(e))
}
pub fn unknown(e: impl ToString) -> Self {
Self::Unknown(into_string(e))
}
pub fn is_fatal(&self) -> bool {
match self {
Self::InvalidParameter(p) => p.is_fatal(),
Self::Unknown(_) => true,
_ => false,
}
}
}

View file

@ -2,6 +2,4 @@ mod api;
mod client; mod client;
pub use api::{NewShareRequest, NewShareResponse, NotifyShareResponse, Uri}; pub use api::{NewShareRequest, NewShareResponse, NotifyShareResponse, Uri};
pub use client::{Client, ClientError, Parameter}; pub use client::Client;
pub type Result<T> = std::result::Result<T, ClientError>;