// zotapi - Rust wrapper for Sot API as implemented by Hubzilla // Copyright (C) 2018 Harald Eilertsen // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . use crate::error::Error; use reqwest::{ self, header::{ACCEPT, CONTENT_TYPE}, StatusCode, }; use serde::Serialize; use std::collections::BTreeMap; use std::fs::File; use std::io::Read; use url::Url; #[derive(Debug)] pub struct Client { inner: reqwest::Client, base_url: Url, user: String, pw: String, } pub fn client(url: &str, user: &str, pw: &str) -> Client { Client { inner: reqwest::Client::new(), base_url: Url::parse(url).unwrap().join("api/z/1.0/").unwrap(), user: String::from(user), pw: String::from(pw), } } impl Client { fn url(&self, path: &str, args: &T) -> String where T: Serialize + std::fmt::Debug, { let mut r = self.base_url.clone().join(path).unwrap(); if let Ok(a) = serde_qs::to_string(dbg!(args)) { r.set_query(Some(&a)); } r.to_string() } pub async fn fetch_stream(&self, path: &str, args: &T) -> Result where T: Serialize + std::fmt::Debug, { let url = dbg!(self.url(path, args)); let res = self.get(&url).send().await?; handle_result(res).await } pub async fn post_data(&self, path: &str, data: &T) -> Result where T: Serialize + std::fmt::Debug, { let url = dbg!(self.url(path, &())); let res = dbg!(self .inner .post(&url) .header(ACCEPT, "application/json") .header(CONTENT_TYPE, "application/x-www-form-urlencoded") .basic_auth(self.user.clone(), Some(self.pw.clone())) .body(serde_qs::to_string(&data)?)) //.form(&data)) .send().await?; handle_result(res).await } pub async fn post_multipart( &self, path: &str, data: &BTreeMap<&str, T>, files: &Vec<&str>, ) -> Result where T: ToString, { let url = dbg!(self.url(path, &())); let mut form = reqwest::multipart::Form::new(); for (key, value) in data.iter() { form = form.text(key.to_string(), value.to_string()); } for f in files.iter() { let mut pdata = vec![]; let path = std::path::Path::new(f); File::open(path)?.read_to_end(&mut pdata)?; let filename = String::from(path.file_name().unwrap().to_str().unwrap()); let p = reqwest::multipart::Part::bytes(pdata) .file_name(filename); form = form.part("media", p); } let res = self .inner .post(&url) .basic_auth(self.user.clone(), Some(self.pw.clone())) .multipart(form) .send() .await?; handle_result(res).await } /// Return a RequestBuilder object that's set up with the correct /// path and headers for performing a zot api request. pub fn get(&self, path: &str) -> reqwest::RequestBuilder { self.inner.get(&self.url(path, &())) .header(ACCEPT, "application/json") .basic_auth(self.user.clone(), Some(self.pw.clone())) } /// Return a RequestBuilder object that's set up with the correct /// path and headers for performing a zot api post request. pub fn post(&self, path: &str) -> reqwest::RequestBuilder { self.inner.post(&self.url(path, &())) .header(ACCEPT, "application/json") .basic_auth(self.user.clone(), Some(self.pw.clone())) } } // A common function for handling the response after a request. // // Consumes the response, and return it as a string or an error. async fn handle_result(res: reqwest::Response) -> Result { match res.status() { StatusCode::UNAUTHORIZED => Err(Error::Unauthorized), StatusCode::OK => { Ok(res.text().await?) } _ => { eprintln!("Received unknown status: {:?}", res.status()); Err(Error::Unknown) } } }