1use std::str::FromStr;
26
27use serde::{Deserialize, Serialize};
28use serde_json::{Map as JsonMap, Value as JsonValue};
29
30use super::payload::{Payload, PayloadItem};
31use super::Card;
32use crate::value::QuillValue;
33use crate::version::QuillReference;
34
35#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
38#[serde(tag = "type", rename_all = "lowercase")]
39pub enum PayloadItemWire {
40 Field {
42 key: String,
43 value: JsonValue,
44 #[serde(default)]
46 fill: bool,
47 },
48 Comment {
50 text: String,
51 #[serde(default)]
53 inline: bool,
54 },
55}
56
57#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
64#[serde(rename_all = "camelCase", deny_unknown_fields)]
65pub struct CardWire {
66 #[serde(default)]
69 pub kind: String,
70 #[serde(default, skip_serializing_if = "Option::is_none")]
73 pub quill: Option<String>,
74 #[serde(default, skip_serializing_if = "Option::is_none")]
76 pub id: Option<String>,
77 #[serde(default, skip_serializing_if = "Option::is_none")]
79 pub ext: Option<JsonMap<String, JsonValue>>,
80 #[serde(default, alias = "payload_items")]
82 pub payload_items: Vec<PayloadItemWire>,
83 #[serde(default)]
85 pub body: String,
86}
87
88#[derive(Debug, Clone, PartialEq, Eq)]
90pub enum WireError {
91 InvalidQuillReference { value: String, reason: String },
93}
94
95impl std::fmt::Display for WireError {
96 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97 match self {
98 WireError::InvalidQuillReference { value, reason } => {
99 write!(f, "invalid `quill` reference {value:?}: {reason}")
100 }
101 }
102 }
103}
104
105impl std::error::Error for WireError {}
106
107impl From<&Card> for CardWire {
108 fn from(card: &Card) -> Self {
109 let mut wire = CardWire {
110 kind: String::new(),
111 quill: None,
112 id: None,
113 ext: None,
114 payload_items: Vec::new(),
115 body: card.body().to_string(),
116 };
117 for item in card.payload().items() {
118 match item {
119 PayloadItem::Quill { reference } => wire.quill = Some(reference.to_string()),
120 PayloadItem::Kind { value } => wire.kind = value.clone(),
121 PayloadItem::Id { value } => wire.id = Some(value.clone()),
122 PayloadItem::Ext { value, .. } => wire.ext = Some(value.clone()),
123 PayloadItem::Field {
124 key, value, fill, ..
125 } => wire.payload_items.push(PayloadItemWire::Field {
126 key: key.clone(),
127 value: value.as_json().clone(),
128 fill: *fill,
129 }),
130 PayloadItem::Comment { text, inline } => {
131 wire.payload_items.push(PayloadItemWire::Comment {
132 text: text.clone(),
133 inline: *inline,
134 })
135 }
136 }
137 }
138 wire
139 }
140}
141
142impl TryFrom<CardWire> for Card {
143 type Error = WireError;
144
145 fn try_from(wire: CardWire) -> Result<Self, Self::Error> {
146 let items = wire
147 .payload_items
148 .into_iter()
149 .map(|item| match item {
150 PayloadItemWire::Field { key, value, fill } => PayloadItem::Field {
151 key,
152 value: QuillValue::from_json(value),
153 fill,
154 nested_comments: Vec::new(),
155 },
156 PayloadItemWire::Comment { text, inline } => PayloadItem::Comment { text, inline },
157 })
158 .collect::<Vec<_>>();
159
160 let mut payload = Payload::from_items(items);
164 if let Some(value) = wire.quill {
165 let reference = QuillReference::from_str(&value)
166 .map_err(|reason| WireError::InvalidQuillReference { value, reason })?;
167 payload.set_quill(reference);
168 }
169 if !wire.kind.is_empty() {
170 payload.set_kind(wire.kind);
171 }
172 if let Some(id) = wire.id {
173 payload.set_id(id);
174 }
175 if let Some(ext) = wire.ext {
176 payload.set_ext(ext);
177 }
178 Ok(Card::from_parts(payload, wire.body))
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185 use serde_json::json;
186
187 #[test]
189 fn card_wire_round_trips_fields_and_comment() {
190 let mut payload = Payload::from_items(vec![
191 PayloadItem::comment("a note"),
192 PayloadItem::field("title", QuillValue::from_json(json!("Hi"))),
193 PayloadItem::Field {
194 key: "count".to_string(),
195 value: QuillValue::from_json(json!(3)),
196 fill: true,
197 nested_comments: Vec::new(),
198 },
199 ]);
200 payload.set_kind("note");
201 let card = Card::from_parts(payload, "body text".to_string());
202
203 let wire = CardWire::from(&card);
204 assert_eq!(wire.kind, "note");
205 assert_eq!(wire.payload_items.len(), 3);
206
207 let back = Card::try_from(wire).expect("wire → card");
208 assert_eq!(back, card, "Card → wire → Card must be identity");
209 }
210
211 #[test]
213 fn card_wire_round_trips_quill() {
214 let mut payload = Payload::from_index_map(Default::default());
215 payload.set_quill("memo@1.2.3".parse().unwrap());
216 payload.set_kind("main");
217 let card = Card::from_parts(payload, String::new());
218
219 let wire = CardWire::from(&card);
220 assert_eq!(wire.quill.as_deref(), Some("memo@1.2.3"));
221
222 let back = Card::try_from(wire).expect("wire → card");
223 assert_eq!(back, card);
224 }
225
226 #[test]
228 fn card_wire_json_shape() {
229 let card = Card::try_from(CardWire {
230 kind: "note".to_string(),
231 quill: None,
232 id: None,
233 ext: None,
234 payload_items: vec![PayloadItemWire::Field {
235 key: "x".to_string(),
236 value: json!(1),
237 fill: false,
238 }],
239 body: String::new(),
240 })
241 .unwrap();
242 let json = serde_json::to_value(CardWire::from(&card)).unwrap();
243 assert_eq!(json["kind"], json!("note"));
244 assert_eq!(json["payloadItems"][0]["type"], json!("field"));
245 assert_eq!(json["payloadItems"][0]["key"], json!("x"));
246 assert!(json.get("quill").is_none(), "absent quill is omitted");
247 }
248
249 #[test]
251 fn card_wire_rejects_bad_quill() {
252 let err = Card::try_from(CardWire {
253 kind: String::new(),
254 quill: Some("@nope".to_string()),
255 id: None,
256 ext: None,
257 payload_items: Vec::new(),
258 body: String::new(),
259 })
260 .unwrap_err();
261 assert!(matches!(err, WireError::InvalidQuillReference { .. }));
262 }
263}