use std::{error::Error, io}; use log::debug; use super::{ api::{NewShareRequest, NewShareResponse, NotifyShareResponse, Uri}, file::{FileChecked, FileUploading, SharryFile}, }; pub trait Client { fn sharry_share_create( &self, uri: &Uri, alias_id: &str, data: NewShareRequest, ) -> Result; fn sharry_share_notify( &self, uri: &Uri, alias_id: &str, share_id: &str, ) -> Result<(), ClientError>; fn sharry_file_create( &self, uri: &Uri, alias_id: &str, share_id: &str, file: FileChecked, ) -> Result; // fn sharry_file_patch(&self); } #[derive(Debug, thiserror::Error)] 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), #[error("unexpected response status: {actual} (expected {expected})")] ResponseStatus { actual: u16, expected: u16 }, #[error("unexpected response content: {0}")] ResponseContent(String), // // #[error("could not parse offset header")] // ResponseOffset, } impl Client for ureq::Agent { fn sharry_share_create( &self, uri: &Uri, alias_id: &str, data: NewShareRequest, ) -> Result { 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::Request(e.to_string()))? .body_mut() .read_json::() .map_err(|e| ClientError::ResponseParsing(e.to_string()))? }; debug!("response: {res:?}"); if res.success && (res.message == "Share created.") { Ok(res.id) } else { Err(ClientError::ResponseContent(format!("{res:?}"))) } } 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::Request(e.to_string()))? .body_mut() .read_json::() .map_err(|e| ClientError::ResponseParsing(e.to_string()))? }; debug!("response: {res:?}"); Ok(()) } fn sharry_file_create( &self, uri: &Uri, alias_id: &str, share_id: &str, file: FileChecked, ) -> Result { 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::Request(e.to_string()))? }; 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::ResponseParsing("Location header not found".to_owned()))? .to_str() .map_err(|_| ClientError::ResponseParsing("Location header invalid".to_owned()))? .to_string(); debug!("patch uri: {location}"); Ok(FileUploading::new(file.into_path(), size, location)) } }