trello/
card.rs

1use crate::client::Client;
2use crate::formatting::header;
3use crate::label::Label;
4use crate::trello_error::TrelloError;
5use crate::trello_object::{Renderable, TrelloObject};
6
7use serde::Deserialize;
8use std::str::FromStr;
9
10type Result<T> = std::result::Result<T, TrelloError>;
11
12// https://developers.trello.com/reference/#card-object
13#[derive(Deserialize, Debug, Eq, PartialEq, Clone)]
14#[serde(rename_all = "camelCase")]
15pub struct Card {
16    pub id: String,
17    pub name: String,
18    pub desc: String,
19    pub closed: bool,
20    pub url: String,
21    pub labels: Option<Vec<Label>>,
22}
23
24impl TrelloObject for Card {
25    fn get_type() -> String {
26        String::from("Card")
27    }
28
29    fn get_name(&self) -> &str {
30        &self.name
31    }
32
33    fn get_fields() -> &'static [&'static str] {
34        &["id", "name", "desc", "labels", "closed", "url"]
35    }
36}
37
38impl Renderable for Card {
39    fn render(&self) -> String {
40        [header(&self.name, "=").as_str(), &self.desc].join("\n")
41    }
42}
43
44#[derive(Debug, PartialEq, Eq)]
45pub struct CardContents {
46    pub name: String,
47    pub desc: String,
48}
49
50impl FromStr for CardContents {
51    type Err = TrelloError;
52
53    /// Takes a buffer of contents that represent a Card render and parses
54    /// it into a CardContents structure. This is similar to a deserialization process
55    /// except this is quite unstructured and is not very strict in order to allow
56    /// the user to more easily edit card contents.
57    /// ```
58    /// # fn main() -> Result<(), trello::TrelloError> {
59    /// let buffer = "Hello World\n===\nThis is my card";
60    /// let card_contents: trello::CardContents = buffer.parse()?;
61    ///
62    /// assert_eq!(
63    ///     card_contents,
64    ///     trello::CardContents {
65    ///         name: String::from("Hello World"),
66    ///         desc: String::from("This is my card"),
67    ///     },
68    /// );
69    /// # Ok(())
70    /// # }
71    /// ```
72    /// Invalid data will result in an appropriate error being returned.
73    fn from_str(value: &str) -> Result<CardContents> {
74        // this is guaranteed to give at least one result
75        let mut contents = value.split('\n').collect::<Vec<&str>>();
76        trace!("{:?}", contents);
77
78        // first line should *always* be the name of the card
79        let mut name = vec![contents.remove(0)];
80
81        // continue generating the name until we find a line entirely composed of '='
82        // we cannot calculate header() here because we allow the user the benefit of not
83        // having to add or remove characters in case the name grows or shrinks in size
84        let mut found = false;
85        while !contents.is_empty() {
86            let line = contents.remove(0);
87
88            if line.chars().take_while(|c| c == &'=').collect::<String>() != line {
89                name.push(line);
90            } else {
91                found = true;
92                break;
93            }
94        }
95
96        if !found {
97            return Err(TrelloError::CardParse(
98                "Unable to find name delimiter '===='".to_owned(),
99            ));
100        }
101
102        let name = name.join("\n");
103        // The rest of the contents is assumed to be the description
104        let desc = contents.join("\n");
105
106        Ok(CardContents { name, desc })
107    }
108}
109
110impl Card {
111    pub fn new(id: &str, name: &str, desc: &str, labels: Option<Vec<Label>>, url: &str) -> Card {
112        Card {
113            id: String::from(id),
114            name: String::from(name),
115            desc: String::from(desc),
116            url: String::from(url),
117            labels,
118            closed: false,
119        }
120    }
121
122    pub fn get(client: &Client, card_id: &str) -> Result<Card> {
123        let url = client.get_trello_url(&format!("/1/cards/{}", card_id), &[])?;
124
125        Ok(reqwest::get(url)?.error_for_status()?.json()?)
126    }
127
128    pub fn create(client: &Client, list_id: &str, card: &Card) -> Result<Card> {
129        let url = client.get_trello_url("/1/cards/", &[])?;
130
131        let params: [(&str, &str); 3] = [
132            ("name", &card.name),
133            ("desc", &card.desc),
134            ("idList", list_id),
135        ];
136
137        Ok(reqwest::Client::new()
138            .post(url)
139            .form(&params)
140            .send()?
141            .error_for_status()?
142            .json()?)
143    }
144
145    pub fn open(client: &Client, card_id: &str) -> Result<Card> {
146        let url = client.get_trello_url(&format!("/1/cards/{}", &card_id), &[])?;
147
148        let params = [("closed", "false")];
149
150        Ok(reqwest::Client::new()
151            .put(url)
152            .form(&params)
153            .send()?
154            .error_for_status()?
155            .json()?)
156    }
157
158    pub fn update(client: &Client, card: &Card) -> Result<Card> {
159        let url = client.get_trello_url(&format!("/1/cards/{}/", &card.id), &[])?;
160
161        let params = [
162            ("name", &card.name),
163            ("desc", &card.desc),
164            ("closed", &card.closed.to_string()),
165        ];
166
167        Ok(reqwest::Client::new()
168            .put(url)
169            .form(&params)
170            .send()?
171            .error_for_status()?
172            .json()?)
173    }
174
175    pub fn get_all(client: &Client, list_id: &str) -> Result<Vec<Card>> {
176        let url = client.get_trello_url(
177            &format!("/1/lists/{}/cards/", list_id),
178            &[("fields", &Card::get_fields().join(","))],
179        )?;
180        Ok(reqwest::get(url)?.error_for_status()?.json()?)
181    }
182}