1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
//! Writing calendar todos with the day sent as a bare date.
//!
//! `starts_at` goes on the wire as `YYYY-MM-DD`, which HEY casts in the reader's time zone.
//! The generated [`CalendarTodoPayload`](crate::models::CalendarTodoPayload) types it as an
//! instant instead, and an instant at UTC midnight lands on the previous day once HEY casts
//! it — so these two send the body themselves rather than through the generated payload.
use serde_json::{Map, Value, json};
use crate::error::Error;
use crate::generated::routes;
use crate::generated::types::Recording;
use crate::types::Date;
pub use crate::generated::services::calendar_todos::*;
/// What an edit changes about a todo. A field left unset is left alone: HEY applies what it
/// is sent and keeps the rest, so a rename carries a title and says nothing about the day.
#[derive(Debug, Clone, Default, PartialEq)]
pub struct TodoChanges {
/// A new title. An empty one is no title, and changes nothing.
pub title: Option<String>,
/// The day the todo moves to.
pub starts_at: Option<Date>,
/// Whether the todo is in focus.
pub focused: Option<bool>,
}
impl CalendarTodos<'_> {
/// Creates a todo, filed on a day. No day files it on today where this machine is.
pub async fn create_todo(
&self,
title: &str,
starts_at: Option<Date>,
) -> Result<Recording, Error> {
let starts_at = starts_at.unwrap_or_else(Date::today);
let body = json!({ "calendar_todo": { "title": title, "starts_at": starts_at } });
let mut operation = self.client().operation(&routes::CREATE_CALENDAR_TODO, &[]);
operation.json(&body)?;
self.client().send(operation).await
}
/// Edits a todo. `todo_id` is the recording's id.
///
/// Changing nothing is refused rather than sent: an empty payload asks HEY to do nothing
/// and answers as though it had done something.
pub async fn update_todo(
&self,
todo_id: i64,
changes: &TodoChanges,
) -> Result<Recording, Error> {
let fields = changed_fields(changes);
if fields.is_empty() {
return Err(Error::usage(format!(
"update calendar todo {todo_id}: nothing to change"
)));
}
let mut operation = self
.client()
.operation(&routes::UPDATE_CALENDAR_TODO, &[&todo_id]);
operation.resource_id(todo_id);
operation.json(&json!({ "calendar_todo": fields }))?;
self.client().send(operation).await
}
}
fn changed_fields(changes: &TodoChanges) -> Map<String, Value> {
let mut fields = Map::new();
// An empty title is no title: HEY refuses a todo without one, so a `Some("")` is left
// out and reads as changing nothing rather than as clearing it.
if let Some(title) = changes.title.as_deref().filter(|title| !title.is_empty()) {
fields.insert("title".to_string(), json!(title));
}
if let Some(starts_at) = changes.starts_at {
fields.insert("starts_at".to_string(), json!(starts_at));
}
if let Some(focused) = changes.focused {
fields.insert("focused".to_string(), json!(focused));
}
fields
}