Skip to main content

freedom_models/
task.rs

1#[cfg(feature = "serde")]
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use strum::{AsRefStr, EnumString};
5use time::OffsetDateTime;
6use url::Url;
7
8use crate::Hateoas;
9
10#[cfg(feature = "serde")]
11use super::utils;
12
13#[cfg_attr(
14    feature = "serde",
15    derive(Serialize, Deserialize),
16    serde(rename_all = "camelCase")
17)]
18#[derive(Debug, Clone, PartialEq, Eq)]
19#[cfg_attr(not(feature = "unstable"), non_exhaustive)]
20pub struct PayloadStatus {
21    /// A boolean describing the availability of the Payload API
22    pub payload_enabled: bool,
23    /// If `payload_enabled == false`, this field will contain a short descriptor of why
24    pub reason: Option<String>,
25}
26
27#[cfg_attr(
28    feature = "serde",
29    derive(Serialize, Deserialize),
30    serde(rename_all = "SCREAMING_SNAKE_CASE")
31)]
32#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, AsRefStr, EnumString)]
33#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
34#[cfg_attr(not(feature = "unstable"), non_exhaustive)]
35pub enum TaskStatusType {
36    Received,
37    Pending,
38    Scheduled,
39    Rejected,
40    Denied,
41    MovedVis,
42    Bumped,
43    Moved,
44    Cancelled,
45    SystemError,
46    QueuedPass,
47    Configured,
48    Downlinking,
49    Recording,
50    CompletedPass,
51    DataCloud,
52    Processing,
53    ReadyCustom,
54    ProcessedCustom,
55    ErrorCustom,
56    Completed,
57    CompletedError,
58    PushedToCustomer,
59    ErrorPushToCustomer,
60    Invoiced,
61    Paid,
62}
63
64#[cfg_attr(
65    feature = "serde",
66    derive(Serialize, Deserialize),
67    serde(rename_all = "camelCase")
68)]
69#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
70#[cfg_attr(not(feature = "unstable"), non_exhaustive)]
71pub struct TaskStatus {
72    #[cfg_attr(feature = "serde", serde(with = "crate::utils::timestamp"))]
73    pub created: OffsetDateTime,
74    pub status: TaskStatusType,
75    pub reason: String,
76}
77
78#[cfg_attr(
79    feature = "serde",
80    derive(Serialize, Deserialize),
81    serde(rename_all = "SCREAMING_SNAKE_CASE")
82)]
83#[derive(
84    Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, AsRefStr, EnumString,
85)]
86#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
87#[cfg_attr(not(feature = "unstable"), non_exhaustive)]
88pub enum Polarization {
89    #[default]
90    Right,
91    Left,
92    Vertical,
93    Horizontal,
94}
95
96#[cfg_attr(
97    feature = "serde",
98    derive(Serialize, Deserialize),
99    serde(rename_all = "SCREAMING_SNAKE_CASE")
100)]
101#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, AsRefStr, EnumString)]
102#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
103#[cfg_attr(not(feature = "unstable"), non_exhaustive)]
104pub enum TaskType {
105    Before,
106    After,
107    Test,
108    Around,
109    Exact,
110}
111
112#[cfg_attr(
113    feature = "serde",
114    derive(Serialize, Deserialize),
115    serde(rename_all = "camelCase")
116)]
117#[derive(Debug, Clone, PartialEq, Eq)]
118#[cfg_attr(not(feature = "unstable"), non_exhaustive)]
119pub struct Task {
120    #[cfg_attr(feature = "serde", serde(with = "time::serde::iso8601"))]
121    pub created: OffsetDateTime,
122    #[cfg_attr(
123        feature = "serde",
124        serde(default, with = "time::serde::iso8601::option")
125    )]
126    pub modified: Option<OffsetDateTime>,
127    pub found_visibility: bool,
128    /// Unavailable for user accounts
129    #[cfg_attr(feature = "serde", serde(default))]
130    pub internal_meta_data: Option<HashMap<String, String>>,
131    /// Unavailable for user accounts
132    #[cfg_attr(feature = "serde", serde(default))]
133    pub score: Option<u8>,
134    #[cfg_attr(feature = "serde", serde(with = "time::serde::iso8601"))]
135    pub start: OffsetDateTime,
136    #[cfg_attr(feature = "serde", serde(with = "time::serde::iso8601"))]
137    pub end: OffsetDateTime,
138    #[cfg_attr(
139        feature = "serde",
140        serde(default, with = "time::serde::iso8601::option")
141    )]
142    pub visibility_start: Option<OffsetDateTime>,
143    #[cfg_attr(
144        feature = "serde",
145        serde(default, with = "time::serde::iso8601::option")
146    )]
147    pub visibility_end: Option<OffsetDateTime>,
148    pub billable: bool,
149    pub duration_in_seconds: u32,
150    pub task_within_config_window: bool,
151    pub duration: String,
152    pub file_results: Vec<String>,
153    #[cfg_attr(feature = "serde", serde(default))]
154    pub meta_data: Option<HashMap<String, String>>,
155    #[cfg_attr(
156        feature = "serde",
157        serde(rename = "_links", with = "utils::links::serde", default)
158    )]
159    pub links: HashMap<String, Url>,
160}
161
162impl Hateoas for Task {
163    fn get_links(&self) -> &HashMap<String, url::Url> {
164        &self.links
165    }
166
167    fn get_links_mut(&mut self) -> &mut HashMap<String, url::Url> {
168        &mut self.links
169    }
170}
171
172#[cfg_attr(
173    feature = "serde",
174    derive(Serialize, Deserialize),
175    serde(rename_all = "camelCase")
176)]
177#[derive(Debug, Clone, PartialEq, Eq)]
178#[cfg_attr(not(feature = "unstable"), non_exhaustive)]
179pub struct TaskStatusEvent {
180    pub task_request_id: i32,
181    pub task_request_uri: String,
182    pub status_changes: Vec<TaskStatus>,
183}
184
185impl TaskStatusEvent {
186    pub fn latest_status(&self) -> Option<&TaskStatus> {
187        self.status_changes
188            .iter()
189            .max_by_key(|status| status.created)
190    }
191}
192
193#[cfg_attr(
194    feature = "serde",
195    derive(Serialize, Deserialize),
196    serde(rename_all = "camelCase")
197)]
198#[derive(Debug, Clone, PartialEq, Eq)]
199#[cfg_attr(not(feature = "unstable"), non_exhaustive)]
200pub struct TaskRequest {
201    #[cfg_attr(feature = "serde", serde(with = "time::serde::iso8601"))]
202    pub created: OffsetDateTime,
203    #[cfg_attr(
204        feature = "serde",
205        serde(default, with = "time::serde::iso8601::option")
206    )]
207    pub modified: Option<OffsetDateTime>,
208    /// Unavailable for user accounts
209    #[cfg_attr(feature = "serde", serde(default))]
210    pub internal_meta_data: Option<HashMap<String, String>>,
211    #[cfg_attr(feature = "serde", serde(rename = "type"))]
212    pub task_type: TaskType,
213    #[cfg_attr(feature = "serde", serde(default))]
214    pub hours_of_flex: u32,
215    pub duration: u32,
216    pub minimum_duration: u32,
217    #[cfg_attr(feature = "serde", serde(with = "time::serde::iso8601"))]
218    pub target_date: OffsetDateTime,
219    #[cfg_attr(feature = "serde", serde(with = "time::serde::iso8601"))]
220    pub earliest_start: OffsetDateTime,
221    #[cfg_attr(feature = "serde", serde(with = "time::serde::iso8601"))]
222    pub latest_start: OffsetDateTime,
223    pub transmitting: bool,
224    #[cfg_attr(feature = "serde", serde(default))]
225    pub test_file: Option<String>,
226    pub status_changes: Vec<TaskStatus>,
227    pub task_active: bool,
228    pub task_request_scheduled: bool,
229    pub task_request_cancelled: bool,
230    pub flex: bool,
231    pub latest_status_change: TaskStatus,
232    #[cfg_attr(feature = "serde", serde(default))]
233    pub meta_data: Option<HashMap<String, String>>,
234    #[cfg_attr(
235        feature = "serde",
236        serde(rename = "_links", with = "utils::links::serde", default)
237    )]
238    pub links: HashMap<String, Url>,
239}
240
241impl Hateoas for TaskRequest {
242    fn get_links(&self) -> &HashMap<String, url::Url> {
243        &self.links
244    }
245
246    fn get_links_mut(&mut self) -> &mut HashMap<String, url::Url> {
247        &mut self.links
248    }
249}
250
251#[cfg(all(test, feature = "serde"))]
252mod tests {
253    use super::*;
254
255    #[test]
256    fn task_status_event() {
257        let json = r#"
258{
259  "taskRequestId": 189348,
260  "taskRequestUri": "https://test-api.atlasground.com/api/requests/189348",
261  "statusChanges": [
262    {
263      "created": "2025-08-01T19:55:07.061Z",
264      "status": "RECEIVED",
265      "reason": "Saved to Database and awaiting scheduling"
266    },
267    {
268      "created": "2025-08-01T19:55:07.674Z",
269      "status": "SCHEDULED",
270      "reason": "Test Task Scheduled"
271    },
272    {
273      "created": "2025-08-01T19:55:08.986Z",
274      "status": "QUEUED_PASS",
275      "reason": "Pass has been queued to execute"
276    }
277  ]
278}"#;
279        let event: TaskStatusEvent = serde_json::from_str(json).unwrap();
280        assert_eq!(
281            event.latest_status().unwrap().status,
282            TaskStatusType::QueuedPass
283        );
284    }
285
286    #[test]
287    fn task_status_event_float_timestamp() {
288        use time::macros::datetime;
289
290        let json = r#"
291{
292  "taskRequestId": 190029,
293  "taskRequestUri": "https://test-api.atlasground.com/api/requests/190029",
294  "statusChanges": [
295    {
296      "created": 1754409946.362000000,
297      "status": "RECEIVED",
298      "reason": "Saved to Database and awaiting scheduling"
299    }
300  ]
301}"#;
302        let event: TaskStatusEvent = serde_json::from_str(json).unwrap();
303        assert_eq!(
304            datetime!(2025 - 08 - 05 16:05:46.361_999_989).assume_utc(),
305            event.status_changes[0].created
306        );
307    }
308}