create struct sharry::api::FileID

This commit is contained in:
Jörn-Michael Miehe 2025-06-25 23:15:33 +00:00
parent f2b063ba85
commit 63057b805c
7 changed files with 110 additions and 78 deletions

View file

@ -1,5 +1,7 @@
use std::fmt;
use crate::sharry;
#[derive(Debug, thiserror::Error)]
pub enum Parameter {
#[error("given URI {0:?}")]
@ -11,8 +13,8 @@ pub enum Parameter {
#[error("stored Share ID {0:?}")]
ShareID(String),
#[error("stored File ID {0:?}")]
FileID(String),
#[error("stored {0:?}")]
FileID(sharry::FileID),
}
impl Parameter {

View file

@ -1,7 +1,9 @@
use std::fmt;
use crate::sharry;
pub struct Chunk<'t> {
file_id: String,
file_id: sharry::FileID,
offset: u64,
data: &'t [u8],
}
@ -17,7 +19,7 @@ impl fmt::Debug for Chunk<'_> {
}
impl<'t> Chunk<'t> {
pub fn new(file_id: String, offset: u64, data: &'t [u8]) -> Self {
pub fn new(file_id: sharry::FileID, offset: u64, data: &'t [u8]) -> Self {
Self {
file_id,
offset,
@ -25,7 +27,7 @@ impl<'t> Chunk<'t> {
}
}
pub fn get_file_id(&self) -> &str {
pub fn get_file_id(&self) -> &sharry::FileID {
&self.file_id
}

View file

@ -7,6 +7,8 @@ use std::{
use log::warn;
use serde::{Deserialize, Serialize};
use crate::sharry;
use super::{Checked, Chunk, FileTrait};
#[derive(Serialize, Deserialize, Debug)]
@ -17,14 +19,19 @@ pub struct Uploading {
size: u64,
/// hash of that file
hash: Option<String>,
file_id: String,
file_id: sharry::FileID,
#[serde(skip)]
last_offset: Option<u64>,
offset: u64,
}
impl Uploading {
pub(super) fn new(path: PathBuf, size: u64, hash: Option<String>, file_id: String) -> Self {
pub(super) fn new(
path: PathBuf,
size: u64,
hash: Option<String>,
file_id: sharry::FileID,
) -> Self {
Self {
path,
size,

View file

@ -3,14 +3,14 @@ use log::{debug, trace};
use crate::{
error,
file::{self, FileTrait},
sharry::{self, Uri},
sharry::{self, FileID, Uri},
};
fn find_cause(
uri: &Uri,
alias_id: &str,
share_id: Option<&str>,
file_id: Option<&str>,
file_id: Option<&FileID>,
) -> impl FnOnce(ureq::Error) -> error::Error {
move |error| match error {
ureq::Error::StatusCode(403) => {
@ -22,7 +22,7 @@ fn find_cause(
trace!("HTTP Error 404: Share and/or file may have been deleted!");
if let Some(file_id) = file_id {
error::Error::InvalidParameter(error::Parameter::FileID(file_id.to_owned()))
error::Error::InvalidParameter(error::Parameter::FileID(file_id.clone()))
} else if let Some(share_id) = share_id {
error::Error::InvalidParameter(error::Parameter::ShareID(share_id.to_owned()))
} else {
@ -110,7 +110,7 @@ impl sharry::Client for ureq::Agent {
alias_id: &str,
share_id: &str,
file: &file::Checked,
) -> error::Result<String> {
) -> error::Result<FileID> {
let res = {
let endpoint = uri.file_create(share_id);
@ -133,11 +133,7 @@ impl sharry::Client for ureq::Agent {
.map_err(error::Error::response)?
.to_string();
let file_id = Self::get_file_id(&location)?;
debug!("location: {location:?}, file_id: {file_id:?}");
Ok(file_id.to_owned())
FileID::try_from(location)
}
fn file_patch(

View file

@ -1,8 +1,11 @@
use std::fmt;
use std::{fmt, sync::LazyLock};
use log::trace;
use log::{debug, trace};
use regex::Regex;
use serde::{Deserialize, Serialize};
use crate::error;
#[derive(Serialize, Deserialize, Debug)]
pub struct Uri(String);
@ -41,11 +44,89 @@ impl Uri {
self.endpoint(format_args!("alias/upload/{share_id}/files/tus"))
}
pub fn file_patch(&self, share_id: &str, file_id: &str) -> String {
pub fn file_patch(&self, share_id: &str, file_id: &FileID) -> String {
self.endpoint(format_args!("alias/upload/{share_id}/files/tus/{file_id}"))
}
}
// pub struct AliasID(String);
// pub struct ShareID(String);
#[derive(Serialize, Deserialize, Clone)]
pub struct FileID(String);
impl fmt::Display for FileID {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl fmt::Debug for FileID {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("File ID").field(&self.0).finish()
}
}
impl TryFrom<String> for FileID {
type Error = error::Error;
fn try_from(value: String) -> error::Result<Self> {
/// 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")
});
trace!("TryFrom {value:?}");
if let Some(fid) = UPLOAD_URL_RE
.captures(&value)
.and_then(|caps| caps.name("fid").map(|m| m.as_str()))
{
let result = Self(fid.to_owned());
debug!("{result:?}");
Ok(result)
} else {
Err(error::Error::unknown(format!(
"Could not extract File ID from {value:?}"
)))
}
}
}
// 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(Serialize, Debug)]
#[allow(non_snake_case)]
pub struct NewShareRequest {

View file

@ -1,43 +1,8 @@
use std::sync::LazyLock;
use log::trace;
use regex::Regex;
use crate::{error, file};
use super::api::{NewShareRequest, Uri};
use super::api::{FileID, NewShareRequest, Uri};
pub trait Client {
fn get_file_id(uri: &str) -> error::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(error::Error::unknown(format!(
"Could not extract File ID from {uri:?}"
)))
}
}
fn share_create(
&self,
uri: &Uri,
@ -53,7 +18,7 @@ pub trait Client {
alias_id: &str,
share_id: &str,
file: &file::Checked,
) -> error::Result<String>;
) -> error::Result<FileID>;
fn file_patch(
&self,
@ -63,24 +28,3 @@ pub trait Client {
chunk: &file::Chunk,
) -> error::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());
// }
// }

View file

@ -1,5 +1,5 @@
mod api;
mod client;
pub use api::{NewShareRequest, NewShareResponse, NotifyShareResponse, Uri};
pub use api::{FileID, NewShareRequest, NewShareResponse, NotifyShareResponse, Uri};
pub use client::Client;