// 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::{client::Client, error::Error}; use reqwest::{ self, header::{CONTENT_TYPE}, }; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use std::fs::File; use std::io::Read; /// Data type for values that an Item can hold. #[derive(Debug, Serialize)] #[serde(untagged)] pub enum ItemData<'a> { /// A single value, either textual or numeric. Value(&'a str), /// A list ov values. List(Vec<&'a str>), } impl<'a> From<&'a str> for ItemData<'a> { fn from(orig: &'a str) -> ItemData<'a> { ItemData::Value(orig) } } impl<'a> ToString for ItemData<'a> { fn to_string(&self) -> String { match &self { ItemData::Value(s) => s.to_string(), ItemData::List(v) => v.join(","), } } } impl<'a> ItemData<'a> { /// Push a new value into pub fn push(&mut self, value: &'a str) { match self { ItemData::Value(_) => std::panic!("Pushing to a simple value is not allowed."), ItemData::List(v) => v.push(value), }; } } #[test] fn convert_itemdata_list_to_a_string() { let l = ItemData::List(vec!["one", "two", "everything"]); assert_eq!(l.to_string(), "one,two,everything"); } #[test] fn convert_itemdata_list_with_one_member_to_a_string() { let l = ItemData::List(vec!["one"]); assert_eq!(l.to_string(), "one"); } #[derive(Debug, Deserialize)] pub struct ItemCreatedResponse { pub success: bool, pub item_id: u32, } /// A structure to help you create an item in a declarative way. /// /// Typical usage: /// /// ```no_run /// # async { /// let client = zotapi::client("https://myhub.com", "mychannel", "mypw"); /// let new_post = zotapi::item() /// .title("A title") /// .body("The body of the post") /// .file("/my/photo.jpg") /// .create(&client) /// .await?; /// # Ok::<(), zotapi::Error>(()) /// # }; /// ``` /// ``` #[derive(Debug)] pub struct ItemBuilder<'a> { data: BTreeMap<&'a str, ItemData<'a>>, files: Vec<&'a str>, } pub fn item<'a>() -> ItemBuilder<'a> { ItemBuilder { data: BTreeMap::new(), files: vec![], } } impl<'a> ItemBuilder<'a> { /// Add a title to the post pub fn title(&mut self, text: &'a str) -> &mut ItemBuilder<'a> { self.data.insert("title", ItemData::Value(text)); self } /// Add the body of the post pub fn body(&mut self, text: &'a str) -> &mut ItemBuilder<'a> { self.data.insert("body", ItemData::Value(text)); self } /// Add a file attachment to the post pub fn file(&mut self, fname: &'a str) -> &mut ItemBuilder<'a> { self.files.push(fname); self } /// Set groups allowed to access item pub fn group_allow(&mut self, group: &'a str) -> &mut ItemBuilder<'a> { let groups_allow = self .data .entry("groups_allow") .or_insert(ItemData::List(Vec::<&str>::new())); groups_allow.push(group); self } /// Create the item by poting it to the server pub async fn create(&self, client: &Client) -> Result { dbg!(self); let mut req = client.post("item/update"); if self.files.is_empty() { req = req .header(CONTENT_TYPE, "application/x-www-form-urlencoded") .body(serde_qs::to_string(&self.data)?) } else { let mut form = reqwest::multipart::Form::new(); for (key, value) in self.data.iter() { form = form.text(key.to_string(), value.to_string()); } for f in self.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); } req = req.multipart(form) } Ok(serde_json::from_str(&req.send().await?.text().await?)?) } } #[test] fn add_group_to_list_of_groups_allowed() { let mut item = item(); item.group_allow("test"); match item.data.get("groups_allow") { Some(ItemData::List(v)) => assert_eq!(v.len(), 1), Some(ItemData::Value(s)) => assert!(false, "Expected a list, found value: {}", s), None => assert!(false, "List not found!"), }; }