1use std::fmt;
2
3use serde::{Deserialize, Serialize};
4
5pub const ITEM_SCHEMA: &str = "agd.item/v1";
6pub const AGENDA_ENTRY_SCHEMA: &str = "agd.agenda-entry/v1";
7pub const MUTATION_SCHEMA: &str = "agd.mutation/v1";
8pub const ERROR_SCHEMA: &str = "agd.error/v1";
9
10#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
11pub struct Item {
12 pub schema: String,
13 #[serde(rename = "ref")]
14 pub item_ref: String,
15 pub id: Option<String>,
16 pub file: String,
17 pub line: usize,
18 pub olpath: Vec<String>,
19 pub todo: Option<String>,
20 pub done: bool,
21 pub priority: Option<String>,
22 pub title: String,
23 pub tags: Vec<String>,
24 pub inherited_tags: Vec<String>,
25 pub category: String,
26 pub planning: Planning,
27}
28
29#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
30pub struct Planning {
31 pub scheduled: Option<PlanningTimestamp>,
32 pub deadline: Option<PlanningTimestamp>,
33 pub closed: Option<PlanningTimestamp>,
34}
35
36#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
37pub struct PlanningTimestamp {
38 pub date: String,
39 pub time: Option<String>,
40 pub end_time: Option<String>,
41 pub repeater: Option<String>,
42 pub warning: Option<String>,
43 #[serde(skip_serializing_if = "Option::is_none")]
44 pub raw: Option<String>,
45}
46
47#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
48pub struct AgendaEntry {
49 pub schema: String,
50 pub item: Item,
51 pub occurrence: Occurrence,
52}
53
54#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
55pub struct Occurrence {
56 #[serde(rename = "type")]
57 pub kind: OccurrenceType,
58 pub date: String,
59 pub time: Option<String>,
60 pub extra: String,
61}
62
63#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
64#[serde(rename_all = "kebab-case")]
65pub enum OccurrenceType {
66 Scheduled,
67 PastScheduled,
68 Deadline,
69 UpcomingDeadline,
70 Timestamp,
71 Block,
72 Closed,
73 Todo,
74}
75
76#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
77pub struct Mutation {
78 pub schema: String,
79 pub ok: bool,
80 pub item: Item,
81 pub diff: String,
82 pub journal_id: String,
83}
84
85#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
86pub struct ErrorResponse {
87 pub schema: String,
88 pub code: String,
89 pub message: String,
90 #[serde(skip_serializing_if = "Option::is_none")]
91 pub current: Option<Item>,
92}
93
94#[derive(Debug)]
95pub struct AgdError {
96 response: Box<ErrorResponse>,
97 source: Option<Box<dyn std::error::Error + Send + Sync>>,
98}
99
100impl AgdError {
101 pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
102 Self {
103 response: Box::new(ErrorResponse {
104 schema: ERROR_SCHEMA.to_string(),
105 code: code.into(),
106 message: message.into(),
107 current: None,
108 }),
109 source: None,
110 }
111 }
112
113 pub fn with_current(mut self, current: Item) -> Self {
114 self.response.current = Some(current);
115 self
116 }
117
118 pub fn with_source(
119 code: impl Into<String>,
120 message: impl Into<String>,
121 source: impl std::error::Error + Send + Sync + 'static,
122 ) -> Self {
123 Self {
124 response: Box::new(ErrorResponse {
125 schema: ERROR_SCHEMA.to_string(),
126 code: code.into(),
127 message: message.into(),
128 current: None,
129 }),
130 source: Some(Box::new(source)),
131 }
132 }
133
134 pub fn code(&self) -> &str {
135 &self.response.code
136 }
137
138 pub fn response(&self) -> &ErrorResponse {
139 &self.response
140 }
141
142 pub fn into_response(self) -> ErrorResponse {
143 *self.response
144 }
145}
146
147impl fmt::Display for AgdError {
148 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
149 formatter.write_str(&self.response.message)
150 }
151}
152
153impl std::error::Error for AgdError {
154 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
155 self.source
156 .as_deref()
157 .map(|source| source as &(dyn std::error::Error + 'static))
158 }
159}
160
161impl From<std::io::Error> for AgdError {
162 fn from(error: std::io::Error) -> Self {
163 Self::with_source("io", error.to_string(), error)
164 }
165}