Skip to main content

zohohorrorshow/models/tasklist/
mod.rs

1use serde::{Deserialize, Serialize};
2use tracing::warn;
3
4use crate::errors::*;
5use crate::request::{FilterOptions, ModelRequest, RequestDetails, RequestParameters};
6use std::collections::HashMap;
7
8use super::IntString;
9
10pub mod task;
11
12pub(crate) fn model_path(
13    portal: impl std::fmt::Display,
14    project: impl std::fmt::Display,
15) -> String {
16    format!("portal/{}/projects/{}/tasklists/", portal, project)
17}
18
19#[derive(Clone, Debug)]
20pub struct TasklistRequest(RequestDetails);
21
22impl TasklistRequest {
23    pub fn new(access_token: &str, model_path: &str, id: Option<i64>) -> Self {
24        TasklistRequest(RequestDetails::new(access_token, model_path, id))
25    }
26
27    pub fn iter_get(self) -> TasklistIterator {
28        TasklistIterator::new(self)
29    }
30}
31
32impl ModelRequest for TasklistRequest {
33    fn uri(&self) -> String {
34        self.0.uri()
35    }
36
37    fn params(&self) -> Option<HashMap<String, String>> {
38        self.0.params()
39    }
40
41    fn access_token(&self) -> String {
42        self.0.access_token()
43    }
44
45    fn filter(mut self, param: (impl FilterOptions + std::fmt::Display)) -> Self {
46        self.0 = self.0.filter(&param);
47        self
48    }
49}
50
51impl RequestParameters for TasklistRequest {
52    type ModelCollection = ZohoTasklists;
53    type NewModel = NewTasklist;
54}
55
56pub enum Filter {
57    Index(usize),
58    Range(i8),
59    Flag(Flag),
60    Milestone(usize),
61}
62
63#[derive(Clone, Debug, Deserialize, Serialize)]
64pub enum Flag {
65    #[serde(rename = "internal")]
66    Internal,
67    #[serde(rename = "external")]
68    External,
69}
70
71impl std::fmt::Display for Flag {
72    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73        let str_rep = match self {
74            Flag::Internal => "internal",
75            Flag::External => "external",
76        };
77
78        write!(f, "{}", str_rep)
79    }
80}
81
82impl FilterOptions for Filter {
83    fn key(&self) -> String {
84        match self {
85            Filter::Index(_) => "index".to_owned(),
86            Filter::Range(_) => "range".to_owned(),
87            Filter::Flag(_) => "flag".to_owned(),
88            Filter::Milestone(_) => "milestone_id".to_owned(),
89        }
90    }
91}
92
93impl std::fmt::Display for Filter {
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        let str_rep = match self {
96            Filter::Index(index) => index.to_string(),
97            Filter::Range(range) => range.to_string(),
98            Filter::Flag(flag) => flag.to_string(),
99            Filter::Milestone(id) => id.to_string(),
100        };
101
102        write!(f, "{}", str_rep)
103    }
104}
105
106#[derive(Clone, Debug, Serialize, Deserialize, Default)]
107pub struct ZohoTasklists {
108    #[serde(rename = "tasklists")]
109    pub tasklists: Vec<Tasklist>,
110}
111
112#[derive(Clone, Debug, Deserialize, Serialize)]
113pub struct NewTasklist {
114    name: String,
115    milestone_id: usize,
116    flag: Flag,
117}
118
119// TODO(Xymist): Implement Tasklist::tasks() to create a new request to fetch all tasks for a tasklist
120#[derive(Clone, Debug, Serialize, Deserialize, Default)]
121pub struct Tasklist {
122    #[serde(rename = "id")]
123    pub id: i64,
124    #[serde(rename = "name")]
125    pub name: String,
126    #[serde(rename = "milestone")]
127    pub milestone: Milestone,
128    #[serde(rename = "completed")]
129    pub completed: bool,
130    #[serde(rename = "created_time")]
131    pub created_time: String,
132    #[serde(rename = "created_time_long")]
133    pub created_time_long: i64,
134    #[serde(rename = "rolled")]
135    pub rolled: bool,
136    #[serde(rename = "sequence")]
137    pub sequence: i64,
138    #[serde(rename = "view_type")]
139    pub view_type: Option<String>,
140    #[serde(rename = "link")]
141    pub link: TasklistLink,
142}
143
144#[derive(Clone, Debug, Serialize, Deserialize, Default)]
145pub struct TasklistLink {
146    #[serde(rename = "self")]
147    pub link: Link,
148    #[serde(rename = "task")]
149    pub task: Link,
150}
151
152#[derive(Clone, Debug, Serialize, Deserialize, Default)]
153pub struct Link {
154    #[serde(rename = "url")]
155    pub url: String,
156}
157
158#[derive(Clone, Debug, Serialize, Deserialize, Default)]
159pub struct Milestone {
160    #[serde(rename = "id")]
161    pub id: i64,
162    #[serde(rename = "link")]
163    pub link: Option<MilestoneLink>,
164    #[serde(rename = "name")]
165    pub name: String,
166    #[serde(rename = "owner_name")]
167    pub owner_name: Option<String>,
168    #[serde(rename = "owner_id")]
169    pub owner_id: Option<IntString>,
170    #[serde(rename = "flag")]
171    pub flag: Option<String>,
172    // #[serde(rename = "start_date")]
173    // pub start_date: String,
174    // #[serde(rename = "start_date_long")]
175    // pub start_date_long: i64,
176    // #[serde(rename = "end_date")]
177    // pub end_date: String,
178    // #[serde(rename = "end_date_long")]
179    // pub end_date_long: i64,
180    // #[serde(rename = "status")]
181    // pub status: String,
182}
183
184#[derive(Clone, Debug, Serialize, Deserialize, Default)]
185pub struct MilestoneLink {
186    #[serde(rename = "self")]
187    pub link: Link,
188    #[serde(rename = "status")]
189    pub status: Link,
190}
191
192#[derive(Clone, Debug)]
193pub struct TasklistIterator {
194    pub items: <Vec<Tasklist> as IntoIterator>::IntoIter,
195    pub last_full: bool,
196    pub request: TasklistRequest,
197    pub start_index: usize,
198}
199
200impl TasklistIterator {
201    pub fn new(request: TasklistRequest) -> TasklistIterator {
202        TasklistIterator {
203            items: Vec::new().into_iter(),
204            last_full: true,
205            request,
206            start_index: 0,
207        }
208    }
209
210    fn range(&self) -> i8 {
211        match self.request.params() {
212            Some(params) => match params.get("range") {
213                Some(range_string) => range_string.parse::<i8>().unwrap_or(100),
214                None => 100,
215            },
216            None => 100,
217        }
218    }
219
220    pub fn try_next(&mut self) -> Result<Option<Tasklist>> {
221        // If there are still items in the local cache from the last request, use the next one of those.
222        if let Some(tasklist) = self.items.next() {
223            return Ok(Some(tasklist));
224        }
225
226        // If we didn't get a full 100 (the default number to retrieve) the last time, then we must have
227        // run out in Zoho; don't request any more.
228        if !self.last_full {
229            return Ok(None);
230        }
231
232        let returned_tasklists = self
233            .request
234            .clone()
235            .filter(Filter::Index(self.start_index))
236            .get();
237
238        match returned_tasklists {
239            Ok(Some(tasklist_list)) => {
240                self.last_full = tasklist_list.tasklists.len() as i8 == self.range();
241
242                self.start_index += tasklist_list.tasklists.len();
243
244                self.items = tasklist_list.tasklists.into_iter();
245
246                Ok(self.items.next())
247            }
248            Ok(None) => {
249                self.last_full = false;
250                Ok(None)
251            }
252            Err(err) => {
253                self.last_full = false;
254                Err(err)
255            }
256        }
257    }
258}
259
260impl Iterator for TasklistIterator {
261    type Item = Result<Tasklist>;
262
263    fn next(&mut self) -> Option<Self::Item> {
264        match self.try_next() {
265            Ok(Some(val)) => Some(Ok(val)),
266            Ok(None) => None,
267            Err(err) => {
268                warn!(
269                    "Fetching Tasklists from Zoho experienced an error: {:?}",
270                    err
271                );
272                None
273            }
274        }
275    }
276}