filesvc-rs 1.0.0

Wrapper for the https://files.vc file hosting service
Documentation
use std::{fs, path::Path, time::Duration};

use derive_builder::Builder;
use reqwest::{Body, Method, StatusCode};
use thiserror::Error;

impl<'a> super::Client<'a> {
    pub async fn upload_web_file<P: AsRef<Path>>(
        &self,
        path: P,
        upload: Upload,
    ) -> Result<(String, String), UploadError> {
        let bytes = fs::read(path)?;
        self.upload_web(bytes, upload).await
    }

    pub async fn upload_web<'b, B: Into<Body>>(
        &self,
        body: B,
        upload: Upload,
    ) -> Result<(String, String), UploadError> {
        let mut request = self
            .http
            .request(
                Method::PUT,
                format!("{}{}/", upload.protocol.to_string(), self.base_uri),
            )
            .body(body);

        if let Some(filename) = upload.filename {
            request = request.query(&[("filename", filename)]);
        }

        if let Some(randomizefn) = upload.randomizefn {
            request = request.query(&[("randomizefn", randomizefn as i32)]);
        }

        if let Some(expire) = upload.expire {
            request = request.query(&[("expire", expire.as_secs() / 60)]);
        }

        if let Some(autodestroy) = upload.autodestroy {
            request = request.query(&[("autodestroy", autodestroy as i32)]);
        }

        if let Some(shorturl) = upload.shorturl {
            request = request.query(&[("shorturl", shorturl as i32)]);
        }

        let response = request.send().await?;

        let content = match response.status() {
            StatusCode::OK => response.text().await?,
            StatusCode::PAYLOAD_TOO_LARGE => return Err(UploadError::PayloadTooLarge),
            status => return Err(UploadError::UnknownStatusCode(status)),
        };

        if !content.contains("[Admin]") || !content.contains("[Download]") {
            return Err(UploadError::MalformedResponse(content));
        }

        let mut lines = content.lines().skip(1);

        let admin_url = lines
            .next()
            .ok_or(UploadError::MalformedResponse(content.to_owned()))?
            .strip_suffix(" [Admin]")
            .ok_or(UploadError::MalformedResponse(content.to_owned()))?
            .to_string();
        let download_url = lines
            .next()
            .ok_or(UploadError::MalformedResponse(content.to_owned()))?
            .strip_suffix(" [Download]")
            .ok_or(UploadError::MalformedResponse(content.to_owned()))?
            .to_string();

        Ok((admin_url, download_url))
    }
}

#[derive(Debug, Error)]
pub enum UploadError {
    #[error("The payload you are trying to upload is too large")]
    PayloadTooLarge,
    #[error("The server returned an unknown status code {0}")]
    UnknownStatusCode(StatusCode),
    #[error("The server returned a malformed response {0}")]
    MalformedResponse(String),
    #[error(transparent)]
    Io(#[from] std::io::Error),
    #[error(transparent)]
    Http(#[from] reqwest::Error),
}

#[derive(Default, Debug, Clone, Builder)]
#[builder(setter(into), default)]
pub struct Upload {
    protocol: UploadProtocol,
    filename: Option<String>,
    randomizefn: Option<bool>,
    expire: Option<Duration>,
    autodestroy: Option<bool>,
    shorturl: Option<bool>,
}

impl Upload {
    pub fn builder() -> UploadBuilder {
        UploadBuilder::default()
    }
}

#[derive(Default, Debug, Clone)]
pub enum UploadMethod {
    #[default]
    Put,
    Post,
}

#[derive(Default, Debug, Clone)]
pub enum UploadProtocol {
    #[default]
    Https,
    Http,
}

impl ToString for UploadProtocol {
    fn to_string(&self) -> String {
        match self {
            UploadProtocol::Https => "https://".to_string(),
            UploadProtocol::Http => "http://".to_string(),
        }
    }
}