1use serde::{Deserialize, Serialize};
25
26use super::inline::WireInline;
27use super::range::Range;
28
29#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
34#[serde(tag = "kind", rename_all = "snake_case")]
35#[non_exhaustive]
36pub enum WireNode {
37 Document {
38 range: Range,
39 #[serde(skip_serializing_if = "Option::is_none")]
40 origin: Option<String>,
41 children: Vec<WireNode>,
42 },
43 Session {
44 range: Range,
45 #[serde(skip_serializing_if = "Option::is_none")]
46 origin: Option<String>,
47 title: String,
48 #[serde(skip_serializing_if = "Option::is_none")]
49 marker: Option<String>,
50 children: Vec<WireNode>,
51 },
52 Definition {
53 range: Range,
54 #[serde(skip_serializing_if = "Option::is_none")]
55 origin: Option<String>,
56 subject: String,
57 children: Vec<WireNode>,
58 },
59 Paragraph {
60 range: Range,
61 #[serde(skip_serializing_if = "Option::is_none")]
62 origin: Option<String>,
63 inlines: Vec<WireInline>,
64 },
65 List {
66 range: Range,
67 #[serde(skip_serializing_if = "Option::is_none")]
68 origin: Option<String>,
69 marker_style: String,
70 items: Vec<WireListItem>,
71 },
72 Verbatim {
73 range: Range,
74 #[serde(skip_serializing_if = "Option::is_none")]
75 origin: Option<String>,
76 label: String,
77 params: serde_json::Value,
78 body_text: String,
79 #[serde(default, skip_serializing_if = "String::is_empty")]
84 subject: String,
85 #[serde(default = "default_verbatim_mode")]
91 mode: String,
92 },
93 Table {
94 range: Range,
95 #[serde(skip_serializing_if = "Option::is_none")]
96 origin: Option<String>,
97 caption: String,
98 header_rows: u32,
99 align: String,
100 rows: Vec<WireRow>,
101 #[serde(default, skip_serializing_if = "Vec::is_empty")]
102 footnotes: Vec<WireFootnote>,
103 },
104 Annotation {
105 range: Range,
106 #[serde(skip_serializing_if = "Option::is_none")]
107 origin: Option<String>,
108 label: String,
109 params: serde_json::Value,
110 body: serde_json::Value,
115 },
116 Blank {
117 range: Range,
118 #[serde(skip_serializing_if = "Option::is_none")]
119 origin: Option<String>,
120 },
121}
122
123#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
125pub struct WireListItem {
126 pub range: Range,
127 pub inlines: Vec<WireInline>,
128 #[serde(default, skip_serializing_if = "Vec::is_empty")]
129 pub children: Vec<WireNode>,
130}
131
132#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
134pub struct WireRow {
135 pub cells: Vec<WireTableCell>,
136}
137
138#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
142pub struct WireTableCell {
143 pub inlines: Vec<WireInline>,
144 #[serde(default = "one")]
145 pub colspan: u32,
146 #[serde(default = "one")]
147 pub rowspan: u32,
148}
149
150fn one() -> u32 {
151 1
152}
153
154fn default_verbatim_mode() -> String {
155 "inflow".to_string()
156}
157
158#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
160pub struct WireFootnote {
161 pub marker: String,
162 pub inlines: Vec<WireInline>,
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 use crate::wire::range::Position;
169
170 fn r(s_l: u32, s_c: u32, e_l: u32, e_c: u32) -> Range {
171 Range::new(Position::new(s_l, s_c), Position::new(e_l, e_c))
172 }
173
174 #[test]
175 fn paragraph_round_trips() {
176 let p = WireNode::Paragraph {
177 range: r(0, 0, 0, 5),
178 origin: None,
179 inlines: vec![WireInline::Text {
180 text: "hello".into(),
181 }],
182 };
183 let s = serde_json::to_string(&p).unwrap();
184 let back: WireNode = serde_json::from_str(&s).unwrap();
185 assert_eq!(back, p);
186 }
187
188 #[test]
189 fn paragraph_kind_in_serialized_form() {
190 let p = WireNode::Paragraph {
191 range: r(0, 0, 0, 5),
192 origin: None,
193 inlines: vec![],
194 };
195 let s = serde_json::to_string(&p).unwrap();
196 assert!(s.contains(r#""kind":"paragraph""#));
197 }
198
199 #[test]
200 fn document_with_children() {
201 let d = WireNode::Document {
202 range: r(0, 0, 10, 0),
203 origin: Some("doc.lex".into()),
204 children: vec![WireNode::Paragraph {
205 range: r(0, 0, 0, 3),
206 origin: None,
207 inlines: vec![WireInline::Text { text: "x".into() }],
208 }],
209 };
210 let s = serde_json::to_string(&d).unwrap();
211 assert!(s.contains(r#""origin":"doc.lex""#));
212 let back: WireNode = serde_json::from_str(&s).unwrap();
213 assert_eq!(back, d);
214 }
215
216 #[test]
217 fn annotation_with_lex_body() {
218 let a = WireNode::Annotation {
219 range: r(3, 0, 6, 0),
220 origin: None,
221 label: "acme.commenting".into(),
222 params: serde_json::json!({"role": "editor"}),
223 body: serde_json::json!({
224 "kind": "block",
225 "children": []
226 }),
227 };
228 let s = serde_json::to_string(&a).unwrap();
229 let back: WireNode = serde_json::from_str(&s).unwrap();
230 assert_eq!(back, a);
231 }
232
233 #[test]
234 fn verbatim_carries_label_and_body_text() {
235 let v = WireNode::Verbatim {
236 range: r(0, 0, 4, 0),
237 origin: None,
238 label: "rust".into(),
239 params: serde_json::json!({}),
240 body_text: "fn main() {}".into(),
241 subject: "Code:".into(),
242 mode: "inflow".into(),
243 };
244 let s = serde_json::to_string(&v).unwrap();
245 let back: WireNode = serde_json::from_str(&s).unwrap();
246 assert_eq!(back, v);
247 }
248
249 #[test]
250 fn verbatim_mode_field_defaults_to_inflow_on_deserialise() {
251 let payload = r#"{
255 "kind":"verbatim",
256 "range":{"start":[0,0],"end":[4,0]},
257 "label":"rust",
258 "params":{},
259 "body_text":"x"
260 }"#;
261 let v: WireNode = serde_json::from_str(payload).unwrap();
262 match v {
263 WireNode::Verbatim {
264 ref mode,
265 ref subject,
266 ..
267 } => {
268 assert_eq!(mode, "inflow");
269 assert_eq!(subject, "");
270 }
271 _ => panic!("expected Verbatim"),
272 }
273 }
274
275 #[test]
276 fn list_with_items() {
277 let l = WireNode::List {
278 range: r(0, 0, 2, 0),
279 origin: None,
280 marker_style: "dash".into(),
281 items: vec![
282 WireListItem {
283 range: r(0, 0, 0, 5),
284 inlines: vec![WireInline::Text { text: "a".into() }],
285 children: vec![],
286 },
287 WireListItem {
288 range: r(1, 0, 1, 5),
289 inlines: vec![WireInline::Text { text: "b".into() }],
290 children: vec![],
291 },
292 ],
293 };
294 let s = serde_json::to_string(&l).unwrap();
295 let back: WireNode = serde_json::from_str(&s).unwrap();
296 assert_eq!(back, l);
297 }
298}