1use rkyv::Archive;
2use serde::{Deserialize, Serialize};
3
4mod rkyv_json {
6 use rkyv::rancor::Fallible;
7 use rkyv::string::ArchivedString;
8 use rkyv::with::{ArchiveWith, DeserializeWith, SerializeWith};
9 use rkyv::{Archive, Place};
10
11 pub struct AsJsonString;
12
13 impl<T: serde::Serialize> ArchiveWith<T> for AsJsonString {
14 type Archived = ArchivedString;
15 type Resolver = <String as Archive>::Resolver;
16
17 fn resolve_with(field: &T, resolver: Self::Resolver, out: Place<Self::Archived>) {
18 let json = serde_json::to_string(field).expect("serde_json::to_string failed");
21 ArchivedString::resolve_from_str(&json, resolver, out);
22 }
23 }
24
25 impl<T: serde::Serialize, S: Fallible<Error: rkyv::rancor::Source> + rkyv::ser::Writer + ?Sized>
26 SerializeWith<T, S> for AsJsonString
27 {
28 fn serialize_with(field: &T, serializer: &mut S) -> Result<Self::Resolver, S::Error> {
29 let json = serde_json::to_string(field).expect("serde_json::to_string failed");
31 ArchivedString::serialize_from_str(&json, serializer)
32 }
33 }
34
35 impl<T: serde::de::DeserializeOwned, D: Fallible + ?Sized> DeserializeWith<ArchivedString, T, D>
36 for AsJsonString
37 {
38 fn deserialize_with(archived: &ArchivedString, _: &mut D) -> Result<T, D::Error> {
39 Ok(serde_json::from_str(archived.as_str()).expect("serde_json::from_str failed"))
41 }
42 }
43}
44
45#[derive(
48 Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Archive, rkyv::Serialize, rkyv::Deserialize,
49)]
50pub struct Position {
51 pub start: Point,
52 pub end: Point,
53}
54
55#[derive(
56 Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Archive, rkyv::Serialize, rkyv::Deserialize,
57)]
58pub struct Point {
59 pub line: usize,
60 pub column: usize,
61 pub offset: usize,
62}
63
64#[derive(
66 Debug, Clone, PartialEq, Serialize, Deserialize, Archive, rkyv::Serialize, rkyv::Deserialize,
67)]
68#[rkyv(serialize_bounds(
69 __S: rkyv::ser::Writer + rkyv::ser::Allocator + rkyv::rancor::Fallible<Error: rkyv::rancor::Source>,
70))]
71#[rkyv(deserialize_bounds(
72 __D: rkyv::rancor::Fallible<Error: rkyv::rancor::Source>,
73))]
74#[rkyv(bytecheck(bounds(
75 __C: rkyv::validation::ArchiveContext + rkyv::rancor::Fallible<Error: rkyv::rancor::Source>,
76)))]
77pub struct Root {
78 #[serde(rename = "type")]
79 pub node_type: RootType,
80 #[rkyv(with = rkyv::with::Map<rkyv_json::AsJsonString>)]
81 pub frontmatter: Option<serde_json::Value>,
82 #[rkyv(omit_bounds)]
83 pub children: Vec<Node>,
84 pub position: Position,
85}
86
87#[derive(
88 Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Archive, rkyv::Serialize, rkyv::Deserialize,
89)]
90pub enum RootType {
91 #[serde(rename = "root")]
92 Root,
93}
94
95#[derive(
97 Debug, Clone, PartialEq, Serialize, Deserialize, Archive, rkyv::Serialize, rkyv::Deserialize,
98)]
99#[rkyv(serialize_bounds(
100 __S: rkyv::ser::Writer + rkyv::ser::Allocator + rkyv::rancor::Fallible<Error: rkyv::rancor::Source>,
101))]
102#[rkyv(deserialize_bounds(
103 __D: rkyv::rancor::Fallible<Error: rkyv::rancor::Source>,
104))]
105#[rkyv(bytecheck(bounds(
106 __C: rkyv::validation::ArchiveContext + rkyv::rancor::Fallible<Error: rkyv::rancor::Source>,
107)))]
108#[serde(tag = "type")]
109pub enum Node {
110 #[serde(rename = "text")]
111 Text(#[rkyv(omit_bounds)] TextNode),
112 #[serde(rename = "code_inline")]
113 CodeInline(#[rkyv(omit_bounds)] TextNode),
114 #[serde(rename = "code_block")]
115 CodeBlock(#[rkyv(omit_bounds)] CodeBlockNode),
116 #[serde(rename = "paragraph")]
117 Paragraph(#[rkyv(omit_bounds)] StandardBlockNode),
118 #[serde(rename = "heading")]
119 Heading(#[rkyv(omit_bounds)] StandardBlockNode),
120 #[serde(rename = "list")]
121 List(#[rkyv(omit_bounds)] StandardBlockNode),
122 #[serde(rename = "list_item")]
123 ListItem(#[rkyv(omit_bounds)] StandardBlockNode),
124 #[serde(rename = "blockquote")]
125 Blockquote(#[rkyv(omit_bounds)] StandardBlockNode),
126 #[serde(rename = "thematic_break")]
127 ThematicBreak(#[rkyv(omit_bounds)] StandardBlockNode),
128 #[serde(rename = "html")]
129 Html(#[rkyv(omit_bounds)] StandardBlockNode),
130 #[serde(rename = "table")]
131 Table(#[rkyv(omit_bounds)] StandardBlockNode),
132 #[serde(rename = "table_row")]
133 TableRow(#[rkyv(omit_bounds)] StandardBlockNode),
134 #[serde(rename = "table_cell")]
135 TableCell(#[rkyv(omit_bounds)] StandardBlockNode),
136 #[serde(rename = "link")]
137 Link(#[rkyv(omit_bounds)] LinkNode),
138 #[serde(rename = "image")]
139 Image(#[rkyv(omit_bounds)] ImageNode),
140 #[serde(rename = "emphasis")]
141 Emphasis(#[rkyv(omit_bounds)] StandardBlockNode),
142 #[serde(rename = "strong")]
143 Strong(#[rkyv(omit_bounds)] StandardBlockNode),
144 #[serde(rename = "strikethrough")]
145 Strikethrough(#[rkyv(omit_bounds)] StandardBlockNode),
146 #[serde(rename = "footnote_definition")]
147 FootnoteDefinition(#[rkyv(omit_bounds)] FootnoteNode),
148 #[serde(rename = "footnote_reference")]
149 FootnoteReference(#[rkyv(omit_bounds)] FootnoteNode),
150 #[serde(rename = "math_inline")]
151 MathInline(#[rkyv(omit_bounds)] TextNode),
152 #[serde(rename = "math_display")]
153 MathDisplay(#[rkyv(omit_bounds)] TextNode),
154 #[serde(rename = "component")]
155 Component(#[rkyv(omit_bounds)] ComponentNode),
156 #[serde(rename = "variable")]
157 Variable(#[rkyv(omit_bounds)] VariableNode),
158 #[serde(rename = "error")]
159 Error(#[rkyv(omit_bounds)] ErrorNode),
160}
161
162#[derive(
164 Debug, Clone, PartialEq, Serialize, Deserialize, Archive, rkyv::Serialize, rkyv::Deserialize,
165)]
166#[rkyv(serialize_bounds(
167 __S: rkyv::ser::Writer + rkyv::ser::Allocator + rkyv::rancor::Fallible<Error: rkyv::rancor::Source>,
168))]
169#[rkyv(deserialize_bounds(
170 __D: rkyv::rancor::Fallible<Error: rkyv::rancor::Source>,
171))]
172#[rkyv(bytecheck(bounds(
173 __C: rkyv::validation::ArchiveContext + rkyv::rancor::Fallible<Error: rkyv::rancor::Source>,
174)))]
175pub struct StandardBlockNode {
176 #[serde(skip_serializing_if = "Option::is_none")]
177 pub depth: Option<u8>,
178 #[serde(skip_serializing_if = "Option::is_none")]
179 pub ordered: Option<bool>,
180 #[serde(skip_serializing_if = "Option::is_none")]
182 pub checked: Option<bool>,
183 #[serde(skip_serializing_if = "Option::is_none")]
185 pub id: Option<String>,
186 #[rkyv(omit_bounds)]
187 pub children: Vec<Node>,
188 pub position: Position,
189}
190
191#[derive(
193 Debug, Clone, PartialEq, Serialize, Deserialize, Archive, rkyv::Serialize, rkyv::Deserialize,
194)]
195#[rkyv(serialize_bounds(
196 __S: rkyv::ser::Writer + rkyv::ser::Allocator + rkyv::rancor::Fallible<Error: rkyv::rancor::Source>,
197))]
198#[rkyv(deserialize_bounds(
199 __D: rkyv::rancor::Fallible<Error: rkyv::rancor::Source>,
200))]
201#[rkyv(bytecheck(bounds(
202 __C: rkyv::validation::ArchiveContext + rkyv::rancor::Fallible<Error: rkyv::rancor::Source>,
203)))]
204pub struct ComponentNode {
205 pub name: String,
206 #[serde(rename = "isInline")]
207 pub is_inline: bool,
208 #[rkyv(omit_bounds)]
209 pub attributes: Vec<AttributeNode>,
210 #[rkyv(omit_bounds)]
211 pub children: Vec<Node>,
212 #[serde(
216 default,
217 rename = "rawContent",
218 skip_serializing_if = "String::is_empty"
219 )]
220 pub raw_content: String,
221 pub position: Position,
222}
223
224#[derive(
226 Debug, Clone, PartialEq, Serialize, Deserialize, Archive, rkyv::Serialize, rkyv::Deserialize,
227)]
228#[rkyv(serialize_bounds(
229 __S: rkyv::ser::Writer + rkyv::ser::Allocator + rkyv::rancor::Fallible<Error: rkyv::rancor::Source>,
230))]
231#[rkyv(deserialize_bounds(
232 __D: rkyv::rancor::Fallible<Error: rkyv::rancor::Source>,
233))]
234#[rkyv(bytecheck(bounds(
235 __C: rkyv::validation::ArchiveContext + rkyv::rancor::Fallible<Error: rkyv::rancor::Source>,
236)))]
237pub struct AttributeNode {
238 pub name: String,
239 #[rkyv(omit_bounds)]
240 pub value: AttributeValue,
241 pub position: Position,
242}
243
244#[derive(
246 Debug, Clone, PartialEq, Serialize, Deserialize, Archive, rkyv::Serialize, rkyv::Deserialize,
247)]
248#[rkyv(serialize_bounds(
249 __S: rkyv::ser::Writer + rkyv::ser::Allocator + rkyv::rancor::Fallible<Error: rkyv::rancor::Source>,
250))]
251#[rkyv(deserialize_bounds(
252 __D: rkyv::rancor::Fallible<Error: rkyv::rancor::Source>,
253))]
254#[rkyv(bytecheck(bounds(
255 __C: rkyv::validation::ArchiveContext + rkyv::rancor::Fallible<Error: rkyv::rancor::Source>,
256)))]
257#[serde(untagged)]
258pub enum AttributeValue {
259 Null,
260 Bool(bool),
261 Number(#[rkyv(with = rkyv_json::AsJsonString)] serde_json::Number),
262 String(String),
263 Array(#[rkyv(with = rkyv_json::AsJsonString)] Vec<serde_json::Value>),
264 Object(#[rkyv(with = rkyv_json::AsJsonString)] serde_json::Map<String, serde_json::Value>),
265 Variable(#[rkyv(omit_bounds)] VariableNode),
266}
267
268#[derive(
270 Debug, Clone, PartialEq, Serialize, Deserialize, Archive, rkyv::Serialize, rkyv::Deserialize,
271)]
272#[rkyv(serialize_bounds(
273 __S: rkyv::ser::Writer + rkyv::ser::Allocator + rkyv::rancor::Fallible<Error: rkyv::rancor::Source>,
274))]
275#[rkyv(deserialize_bounds(
276 __D: rkyv::rancor::Fallible<Error: rkyv::rancor::Source>,
277))]
278#[rkyv(bytecheck(bounds(
279 __C: rkyv::validation::ArchiveContext + rkyv::rancor::Fallible<Error: rkyv::rancor::Source>,
280)))]
281pub struct FootnoteNode {
282 pub label: String,
283 #[rkyv(omit_bounds)]
284 pub children: Vec<Node>,
285 pub position: Position,
286}
287
288#[derive(
290 Debug, Clone, PartialEq, Serialize, Deserialize, Archive, rkyv::Serialize, rkyv::Deserialize,
291)]
292#[rkyv(serialize_bounds(
293 __S: rkyv::ser::Writer + rkyv::ser::Allocator + rkyv::rancor::Fallible<Error: rkyv::rancor::Source>,
294))]
295#[rkyv(deserialize_bounds(
296 __D: rkyv::rancor::Fallible<Error: rkyv::rancor::Source>,
297))]
298#[rkyv(bytecheck(bounds(
299 __C: rkyv::validation::ArchiveContext + rkyv::rancor::Fallible<Error: rkyv::rancor::Source>,
300)))]
301pub struct LinkNode {
302 pub url: String,
303 #[serde(skip_serializing_if = "Option::is_none")]
304 pub title: Option<String>,
305 #[rkyv(omit_bounds)]
306 pub children: Vec<Node>,
307 pub position: Position,
308}
309
310#[derive(
312 Debug, Clone, PartialEq, Serialize, Deserialize, Archive, rkyv::Serialize, rkyv::Deserialize,
313)]
314#[rkyv(serialize_bounds(
315 __S: rkyv::ser::Writer + rkyv::ser::Allocator + rkyv::rancor::Fallible<Error: rkyv::rancor::Source>,
316))]
317#[rkyv(deserialize_bounds(
318 __D: rkyv::rancor::Fallible<Error: rkyv::rancor::Source>,
319))]
320#[rkyv(bytecheck(bounds(
321 __C: rkyv::validation::ArchiveContext + rkyv::rancor::Fallible<Error: rkyv::rancor::Source>,
322)))]
323pub struct ImageNode {
324 pub url: String,
325 #[serde(skip_serializing_if = "Option::is_none")]
326 pub title: Option<String>,
327 #[serde(skip_serializing_if = "Option::is_none")]
328 pub alt: Option<String>,
329 #[rkyv(omit_bounds)]
330 pub children: Vec<Node>,
331 pub position: Position,
332}
333
334#[derive(
336 Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Archive, rkyv::Serialize, rkyv::Deserialize,
337)]
338pub struct CodeBlockNode {
339 pub value: String,
340 #[serde(skip_serializing_if = "Option::is_none")]
341 pub lang: Option<String>,
342 #[serde(skip_serializing_if = "Option::is_none")]
343 pub meta: Option<String>,
344 pub position: Position,
345}
346
347#[derive(
349 Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Archive, rkyv::Serialize, rkyv::Deserialize,
350)]
351pub struct TextNode {
352 pub value: String,
353 pub position: Position,
354}
355
356#[derive(
358 Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Archive, rkyv::Serialize, rkyv::Deserialize,
359)]
360pub struct VariableNode {
361 pub path: String,
362 pub position: Position,
363}
364
365#[derive(
367 Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Archive, rkyv::Serialize, rkyv::Deserialize,
368)]
369pub struct ErrorNode {
370 pub message: String,
371 #[serde(rename = "rawContent")]
372 pub raw_content: String,
373 pub position: Position,
374}
375
376impl Node {
377 pub fn children_mut(&mut self) -> Option<&mut Vec<Node>> {
379 match self {
380 Node::Paragraph(b)
381 | Node::Heading(b)
382 | Node::List(b)
383 | Node::ListItem(b)
384 | Node::Blockquote(b)
385 | Node::Html(b)
386 | Node::Table(b)
387 | Node::TableRow(b)
388 | Node::TableCell(b)
389 | Node::Emphasis(b)
390 | Node::Strong(b)
391 | Node::Strikethrough(b)
392 | Node::ThematicBreak(b) => Some(&mut b.children),
393 Node::Link(l) => Some(&mut l.children),
394 Node::Image(i) => Some(&mut i.children),
395 Node::Component(c) => Some(&mut c.children),
396 Node::FootnoteDefinition(n) => Some(&mut n.children),
397 _ => None,
398 }
399 }
400
401 pub fn children(&self) -> Option<&[Node]> {
403 match self {
404 Node::Paragraph(b)
405 | Node::Heading(b)
406 | Node::List(b)
407 | Node::ListItem(b)
408 | Node::Blockquote(b)
409 | Node::Html(b)
410 | Node::Table(b)
411 | Node::TableRow(b)
412 | Node::TableCell(b)
413 | Node::Emphasis(b)
414 | Node::Strong(b)
415 | Node::Strikethrough(b)
416 | Node::ThematicBreak(b) => Some(&b.children),
417 Node::Link(l) => Some(&l.children),
418 Node::Image(i) => Some(&i.children),
419 Node::Component(c) => Some(&c.children),
420 Node::FootnoteDefinition(n) => Some(&n.children),
421 _ => None,
422 }
423 }
424}
425
426#[cfg(test)]
427mod tests {
428 use super::*;
429
430 fn pos(line: usize, col: usize, off: usize) -> Point {
431 Point {
432 line,
433 column: col,
434 offset: off,
435 }
436 }
437
438 fn span(sl: usize, sc: usize, so: usize, el: usize, ec: usize, eo: usize) -> Position {
439 Position {
440 start: pos(sl, sc, so),
441 end: pos(el, ec, eo),
442 }
443 }
444
445 #[test]
446 fn root_serializes_type_field() {
447 let root = Root {
448 node_type: RootType::Root,
449 frontmatter: None,
450 children: vec![],
451 position: span(1, 1, 0, 1, 1, 0),
452 };
453 let json = serde_json::to_value(&root).unwrap();
454 assert_eq!(json["type"], "root");
455 assert!(json["frontmatter"].is_null());
456 assert_eq!(json["children"], serde_json::json!([]));
457 }
458
459 #[test]
460 fn component_node_serializes_correctly() {
461 let node = Node::Component(ComponentNode {
462 name: "Badge".into(),
463 is_inline: false,
464 attributes: vec![
465 AttributeNode {
466 name: "status".into(),
467 value: AttributeValue::String("beta".into()),
468 position: span(1, 8, 7, 1, 22, 21),
469 },
470 AttributeNode {
471 name: "active".into(),
472 value: AttributeValue::Bool(true),
473 position: span(1, 23, 22, 1, 36, 35),
474 },
475 ],
476 children: vec![Node::Text(TextNode {
477 value: "New Feature".into(),
478 position: span(1, 37, 36, 1, 48, 47),
479 })],
480 raw_content: String::new(),
481 position: span(1, 1, 0, 1, 55, 54),
482 });
483
484 let json = serde_json::to_value(&node).unwrap();
485 assert_eq!(json["type"], "component");
486 assert_eq!(json["name"], "Badge");
487 assert_eq!(json["isInline"], false);
488 assert_eq!(json["attributes"][0]["name"], "status");
489 assert_eq!(json["attributes"][0]["value"], "beta");
490 assert_eq!(json["attributes"][1]["name"], "active");
491 assert_eq!(json["attributes"][1]["value"], true);
492 assert_eq!(json["children"][0]["type"], "text");
493 assert_eq!(json["children"][0]["value"], "New Feature");
494 }
495
496 #[test]
497 fn attribute_value_null_serializes_to_null() {
498 let val = AttributeValue::Null;
499 let json = serde_json::to_value(&val).unwrap();
500 assert!(json.is_null());
501 }
502
503 #[test]
504 fn attribute_value_number() {
505 let val = AttributeValue::Number(serde_json::Number::from(42));
506 let json = serde_json::to_value(&val).unwrap();
507 assert_eq!(json, 42);
508 }
509
510 #[test]
511 fn attribute_value_json_object() {
512 let mut map = serde_json::Map::new();
513 map.insert("type".into(), serde_json::Value::String("bar".into()));
514 let val = AttributeValue::Object(map);
515 let json = serde_json::to_value(&val).unwrap();
516 assert_eq!(json["type"], "bar");
517 }
518
519 #[test]
520 fn attribute_value_json_array() {
521 let val = AttributeValue::Array(vec![
522 serde_json::Value::from(10),
523 serde_json::Value::from(20),
524 ]);
525 let json = serde_json::to_value(&val).unwrap();
526 assert_eq!(json, serde_json::json!([10, 20]));
527 }
528
529 #[test]
530 fn variable_node_serializes() {
531 let node = Node::Variable(VariableNode {
532 path: "frontmatter.title".into(),
533 position: span(1, 1, 0, 1, 20, 19),
534 });
535 let json = serde_json::to_value(&node).unwrap();
536 assert_eq!(json["type"], "variable");
537 assert_eq!(json["path"], "frontmatter.title");
538 }
539
540 #[test]
541 fn error_node_serializes() {
542 let node = Node::Error(ErrorNode {
543 message: "Unclosed tag".into(),
544 raw_content: "<Notice>".into(),
545 position: span(1, 1, 0, 1, 9, 8),
546 });
547 let json = serde_json::to_value(&node).unwrap();
548 assert_eq!(json["type"], "error");
549 assert_eq!(json["message"], "Unclosed tag");
550 assert_eq!(json["rawContent"], "<Notice>");
551 }
552
553 #[test]
554 fn standard_block_omits_none_fields() {
555 let node = Node::Heading(StandardBlockNode {
556 depth: Some(2),
557 ordered: None,
558 checked: None,
559 id: None,
560 children: vec![],
561 position: span(1, 1, 0, 1, 10, 9),
562 });
563 let json = serde_json::to_value(&node).unwrap();
564 assert_eq!(json["depth"], 2);
565 assert!(json.get("ordered").is_none());
566 assert!(json.get("checked").is_none());
567 assert!(json.get("id").is_none());
568 }
569
570 #[test]
571 fn roundtrip_component_node() {
572 let original = Node::Component(ComponentNode {
573 name: "Chart".into(),
574 is_inline: true,
575 attributes: vec![],
576 children: vec![],
577 raw_content: String::new(),
578 position: span(1, 1, 0, 1, 10, 9),
579 });
580 let serialized = serde_json::to_string(&original).unwrap();
581 let deserialized: Node = serde_json::from_str(&serialized).unwrap();
582 assert_eq!(original, deserialized);
583 }
584}