client.rs
1 use std::{fmt, path::Path}; 2 3 use anyhow::Result; 4 use headers::{Authorization, HeaderMapExt}; 5 use reqwest::{ 6 Url, 7 header::{HeaderMap, HeaderValue}, 8 multipart, 9 }; 10 use serde::Deserialize; 11 12 use crate::model::{Claim, Projects, Stats}; 13 14 static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),); 15 16 pub struct Client { 17 client: reqwest::Client, 18 host: Url, 19 } 20 21 impl Client { 22 pub fn new(host: String, api_key: Option<String>) -> Result<Self> { 23 let mut headers = HeaderMap::new(); 24 25 if let Some(api_key) = api_key { 26 headers.typed_insert(Authorization::bearer(&api_key)?); 27 headers.insert("docat-api-key", api_key.parse()?); 28 } 29 30 Ok(Self { 31 client: reqwest::Client::builder() 32 .user_agent(APP_USER_AGENT) 33 .default_headers(headers) 34 .build()?, 35 host: host.parse()?, 36 }) 37 } 38 39 pub async fn stats(&self) -> Result<Stats> { 40 let url = self.url(format_args!("stats"))?; 41 let resp = self.client.get(url).send().await?.error_for_status()?; 42 43 resp.json().await.map_err(Into::into) 44 } 45 46 pub async fn projects(&self) -> Result<Projects> { 47 let url = self.url(format_args!("projects"))?; 48 let resp = self.client.get(url).send().await?.error_for_status()?; 49 50 resp.json().await.map_err(Into::into) 51 } 52 53 pub async fn push(&self, project: &str, version: &str, docs_path: &Path) -> Result<()> { 54 let url = self.url(format_args!("{project}/{version}"))?; 55 let form = multipart::Form::new().file("file", docs_path).await?; 56 57 self.client 58 .post(url) 59 .multipart(form) 60 .send() 61 .await? 62 .error_for_status()?; 63 64 Ok(()) 65 } 66 67 pub async fn claim(&self, project: &str) -> Result<Claim> { 68 let url = self.url(format_args!("{project}/claim"))?; 69 let resp = self.client.get(url).send().await?.error_for_status()?; 70 71 resp.json().await.map_err(Into::into) 72 } 73 74 pub async fn push_icon(&self, project: &str, icon_path: &Path) -> Result<()> { 75 let url = self.url(format_args!("{project}/icon"))?; 76 let form = multipart::Form::new().file("file", icon_path).await?; 77 78 self.client 79 .post(url) 80 .multipart(form) 81 .send() 82 .await? 83 .error_for_status()?; 84 85 Ok(()) 86 } 87 88 pub async fn delete(&self, project: &str, version: &str) -> Result<()> { 89 let url = self.url(format_args!("{project}/{version}"))?; 90 self.client.delete(url).send().await?.error_for_status()?; 91 92 Ok(()) 93 } 94 95 pub async fn tag(&self, project: &str, version: &str, tag: &str) -> Result<()> { 96 let url = self.url(format_args!("{project}/{version}/tags/{tag}"))?; 97 self.client.put(url).send().await?.error_for_status()?; 98 99 Ok(()) 100 } 101 102 pub async fn rename(&self, project: &str, new_name: &str) -> Result<()> { 103 let url = self.url(format_args!("{project}/rename/{new_name}"))?; 104 self.client.put(url).send().await?.error_for_status()?; 105 106 Ok(()) 107 } 108 109 pub async fn hide(&self, project: &str, version: &str) -> Result<()> { 110 let url = self.url(format_args!("{project}/{version}/hide"))?; 111 self.client.post(url).send().await?.error_for_status()?; 112 113 Ok(()) 114 } 115 116 pub async fn show(&self, project: &str, version: &str) -> Result<()> { 117 let url = self.url(format_args!("{project}/{version}/show"))?; 118 self.client.post(url).send().await?.error_for_status()?; 119 120 Ok(()) 121 } 122 123 fn url(&self, args: fmt::Arguments<'_>) -> Result<Url> { 124 self.host 125 .join(&format!("/api/v1/{args}")) 126 .map_err(Into::into) 127 } 128 }