shrupl/src/sharry/client.rs

158 lines
4.2 KiB
Rust
Raw Normal View History

use std::{error::Error, fmt::Display, io};
2025-06-08 01:20:41 +00:00
use log::debug;
use thiserror::Error;
2025-06-08 01:20:41 +00:00
2025-06-08 17:13:01 +00:00
use super::{
api::{NewShareRequest, NewShareResponse, NotifyShareResponse, Uri},
file::{FileChecked, FileUploading, SharryFile},
};
2025-06-08 01:20:41 +00:00
pub trait Client {
fn sharry_share_create(
&self,
uri: &Uri,
alias_id: &str,
data: NewShareRequest,
) -> Result<String, ClientError>;
fn sharry_share_notify(
&self,
uri: &Uri,
alias_id: &str,
share_id: &str,
) -> Result<(), ClientError>;
2025-06-08 17:13:01 +00:00
fn sharry_file_create(
&self,
uri: &Uri,
alias_id: &str,
share_id: &str,
file: FileChecked,
) -> Result<FileUploading, ClientError>;
2025-06-08 01:20:41 +00:00
// fn sharry_file_patch(&self);
}
#[derive(Debug, Error)]
2025-06-08 01:20:41 +00:00
pub enum ClientError {
#[error("file I/O error: {0}")]
FileIO(#[from] io::Error),
#[error("network request failed: {0}")]
Request(String),
#[error("response parsing failed: {0}")]
ResponseParsing(String),
2025-06-08 17:13:01 +00:00
#[error("unexpected response status: {actual} (expected {expected})")]
ResponseStatus { actual: u16, expected: u16 },
2025-06-08 01:20:41 +00:00
#[error("unexpected response content: {0}")]
ResponseContent(String),
}
impl ClientError {
fn req_err(msg: impl Display) -> Self {
ClientError::Request(msg.to_string())
}
fn res_parse_err(msg: impl Display) -> Self {
ClientError::ResponseParsing(msg.to_string())
}
fn res_content_err(msg: impl Display) -> Self {
ClientError::ResponseContent(msg.to_string())
}
2025-06-08 01:20:41 +00:00
}
impl Client for ureq::Agent {
fn sharry_share_create(
&self,
uri: &Uri,
alias_id: &str,
data: NewShareRequest,
) -> Result<String, ClientError> {
let res = {
let endpoint = uri.get_endpoint("alias/upload/new");
self.post(endpoint)
.header("Sharry-Alias", alias_id)
.send_json(data)
.map_err(|e| ClientError::req_err(e))?
2025-06-08 01:20:41 +00:00
.body_mut()
.read_json::<NewShareResponse>()
.map_err(|e| ClientError::res_parse_err(e))?
2025-06-08 01:20:41 +00:00
};
debug!("response: {res:?}");
if res.success && (res.message == "Share created.") {
Ok(res.id)
} else {
Err(ClientError::res_content_err(format!("{res:?}")))
2025-06-08 01:20:41 +00:00
}
}
fn sharry_share_notify(
&self,
uri: &Uri,
alias_id: &str,
share_id: &str,
) -> Result<(), ClientError> {
let res = {
let endpoint = uri.get_endpoint(format!("alias/mail/notify/{}", share_id));
self.post(endpoint)
.header("Sharry-Alias", alias_id)
.send_empty()
.map_err(|e| ClientError::req_err(e))?
2025-06-08 01:20:41 +00:00
.body_mut()
.read_json::<NotifyShareResponse>()
.map_err(|e| ClientError::res_parse_err(e))?
2025-06-08 01:20:41 +00:00
};
debug!("response: {res:?}");
Ok(())
}
2025-06-08 17:13:01 +00:00
fn sharry_file_create(
&self,
uri: &Uri,
alias_id: &str,
share_id: &str,
file: FileChecked,
) -> Result<FileUploading, ClientError> {
let size = file.get_size();
let res = {
let endpoint = uri.get_endpoint(format!("alias/upload/{}/files/tus", share_id));
self.post(endpoint)
.header("Sharry-Alias", alias_id)
.header("Sharry-File-Name", file.get_name())
.header("Upload-Length", size)
.send_empty()
.map_err(|e| ClientError::req_err(e))?
2025-06-08 17:13:01 +00:00
};
if res.status() != ureq::http::StatusCode::CREATED {
return Err(ClientError::ResponseStatus {
actual: res.status().as_u16(),
expected: ureq::http::StatusCode::CREATED.as_u16(),
});
}
let location = (res.headers().get("Location"))
.ok_or_else(|| ClientError::res_parse_err("Location header not found"))?
2025-06-08 17:13:01 +00:00
.to_str()
.map_err(|e| ClientError::res_parse_err(e))?
2025-06-08 17:13:01 +00:00
.to_string();
debug!("patch uri: {location}");
Ok(FileUploading::new(file.into_path(), size, location))
}
2025-06-08 01:20:41 +00:00
}