shrupl/src/sharry/client.rs

159 lines
4.1 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use std::{fmt, sync::LazyLock};
use log::trace;
use regex::Regex;
use thiserror::Error;
use crate::file;
use super::api::{NewShareRequest, Uri};
pub trait Client {
fn get_file_id(uri: &str) -> super::Result<&str> {
/// Pattern breakdown:
/// - `^([^:/?#]+)://` scheme (anything but `:/?#`) + `"://"`
/// - `([^/?#]+)` authority/host (anything but `/?#`)
/// - `/api/v2/alias/upload/` literal path segment
/// - `([^/]+)` capture SID (one or more non-slash chars)
/// - `/files/tus/` literal path segment
/// - `(?P<fid>[^/]+)` capture FID (one or more non-slash chars)
/// - `$` end of string
static UPLOAD_URL_RE: LazyLock<Regex> = LazyLock::new(|| {
trace!("compiling UPLOAD_URL_RE");
Regex::new(
r"^([^:/?#]+)://([^/?#]+)/api/v2/alias/upload/[^/]+/files/tus/(?P<fid>[^/]+)$",
)
.expect("Regex compilation failed")
});
if let Some(fid) = UPLOAD_URL_RE
.captures(uri)
.and_then(|caps| caps.name("fid").map(|m| m.as_str()))
{
Ok(fid)
} else {
Err(super::ClientError::unknown(format!(
"Could not extract File ID from {uri:?}"
)))
}
}
fn share_create(
&self,
uri: &Uri,
alias_id: &str,
data: NewShareRequest,
) -> super::Result<String>;
fn share_notify(&self, uri: &Uri, alias_id: &str, share_id: &str) -> super::Result<()>;
fn file_create(
&self,
uri: &Uri,
alias_id: &str,
share_id: &str,
file: &file::Checked,
) -> super::Result<String>;
fn file_patch(
&self,
uri: &Uri,
alias_id: &str,
share_id: &str,
chunk: &file::Chunk,
) -> super::Result<()>;
}
// TODO move into tests subdir
// #[cfg(test)]
// mod tests {
// use super::*;
// #[test]
// fn test_get_file_id() {
// let good = "https://example.com/api/v2/alias/upload/SID123/files/tus/FID456";
// let good = Client::get_file_id(good);
// assert!(good.is_ok());
// assert_eq!(good.unwrap(), "FID456");
// let bad = "https://example.com/api/v2/alias/upload//files/tus/FID456"; // missing SID
// assert!(Client::get_file_id(bad).is_err());
// let bad: &'static str = "https://example.com/api/v2/alias/upload/SID123/files/tus/"; // missing FID
// 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("Unknown 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,
}
}
}