shrupl/src/sharry/client.rs

160 lines
4.1 KiB
Rust
Raw Normal View History

use std::{fmt, sync::LazyLock};
2025-06-08 01:20:41 +00:00
use log::trace;
use regex::Regex;
use thiserror::Error;
2025-06-08 01:20:41 +00:00
use crate::file;
use super::api::{NewShareRequest, Uri};
2025-06-08 01:20:41 +00:00
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!(
2025-06-18 14:49:30 +00:00
"Could not extract File ID from {uri:?}"
)))
}
}
2025-06-08 01:20:41 +00:00
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<()>;
2025-06-08 01:20:41 +00:00
fn file_create(
2025-06-08 17:13:01 +00:00
&self,
uri: &Uri,
2025-06-08 17:13:01 +00:00
alias_id: &str,
share_id: &str,
file: &file::Checked,
) -> super::Result<String>;
2025-06-08 01:20:41 +00:00
fn file_patch(
&self,
uri: &Uri,
alias_id: &str,
share_id: &str,
chunk: &file::Chunk,
) -> super::Result<()>;
2025-06-08 01:20:41 +00:00
}
// TODO move into tests subdir
// #[cfg(test)]
// mod tests {
// use super::*;
2025-06-08 01:20:41 +00:00
// #[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());
2025-06-08 17:13:01 +00:00
// 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:?}")]
2025-06-18 14:49:30 +00:00
Uri(String),
#[error("given Alias ID {0:?}")]
AliasID(String),
#[error("stored Share ID {0:?}")]
ShareID(String),
#[error("stored File ID {0:?}")]
FileID(String),
2025-06-08 01:20:41 +00:00
}
impl Parameter {
fn is_fatal(&self) -> bool {
2025-06-18 14:49:30 +00:00
matches!(self, Self::Uri(_) | Self::AliasID(_))
}
}
#[derive(Debug, Error)]
pub enum ClientError {
#[error(transparent)]
StdIo(#[from] std::io::Error),
2025-06-08 01:20:41 +00:00
#[error("response error: {0}")]
Response(String),
#[error("Invalid {0}")]
InvalidParameter(Parameter),
2025-06-08 01:20:41 +00:00
#[error("Unknown error: {0}")]
Unknown(String),
}
2025-06-08 01:20:41 +00:00
2025-06-18 18:14:39 +00:00
#[allow(clippy::needless_pass_by_value)]
2025-06-18 14:49:30 +00:00
fn into_string(val: impl ToString) -> String {
2025-06-18 18:14:39 +00:00
val.to_string()
2025-06-18 14:49:30 +00:00
}
impl ClientError {
pub fn res_status_check<T>(actual: T, expected: T) -> super::Result<()>
where
T: PartialEq + fmt::Display + Copy,
{
if actual == expected {
Ok(())
2025-06-08 01:20:41 +00:00
} else {
Err(Self::Response(format!(
"unexpected status: {actual} (expected {expected})"
)))
2025-06-08 01:20:41 +00:00
}
}
pub fn response(e: impl ToString) -> Self {
2025-06-18 14:49:30 +00:00
Self::Response(into_string(e))
2025-06-08 01:20:41 +00:00
}
2025-06-08 17:13:01 +00:00
pub fn unknown(e: impl ToString) -> Self {
2025-06-18 14:49:30 +00:00
Self::Unknown(into_string(e))
}
pub fn is_fatal(&self) -> bool {
match self {
Self::InvalidParameter(p) => p.is_fatal(),
Self::Unknown(_) => true,
_ => false,
}
2025-06-08 17:13:01 +00:00
}
2025-06-08 01:20:41 +00:00
}