1use std::collections::HashSet;
2
3use serde::{
4 Deserialize, Serialize,
5 de::{self, Deserializer},
6 ser::{Error as _, SerializeMap, Serializer},
7};
8
9pub(crate) mod converter;
10mod macros;
11mod text;
12
13pub use converter::inlines_to_string;
14pub use macros::*;
15pub use text::*;
16
17use crate::{Anchor, BlockMetadata, ElementAttributes, Image, Source, StemNotation, Title};
18
19#[non_exhaustive]
24#[derive(Clone, Debug, PartialEq)]
25pub enum InlineNode {
26 PlainText(Plain),
28 RawText(Raw),
30 VerbatimText(Verbatim),
32 BoldText(Bold),
33 ItalicText(Italic),
34 MonospaceText(Monospace),
35 HighlightText(Highlight),
36 SubscriptText(Subscript),
37 SuperscriptText(Superscript),
38 CurvedQuotationText(CurvedQuotation),
39 CurvedApostropheText(CurvedApostrophe),
40 StandaloneCurvedApostrophe(StandaloneCurvedApostrophe),
41 LineBreak(LineBreak),
42 InlineAnchor(Anchor),
43 Macro(InlineMacro),
44 CalloutRef(CalloutRef),
46}
47
48#[non_exhaustive]
86#[derive(Clone, Debug, PartialEq, Serialize)]
87pub enum InlineMacro {
88 Footnote(Footnote),
90 Icon(Icon),
92 Image(Box<Image>),
94 Keyboard(Keyboard),
96 Button(Button),
98 Menu(Menu),
100 Url(Url),
102 Link(Link),
104 Mailto(Mailto),
106 Autolink(Autolink),
108 CrossReference(CrossReference),
110 Pass(Pass),
112 Stem(Stem),
114 IndexTerm(IndexTerm),
116}
117
118macro_rules! serialize_inline_format {
121 ($map:expr, $value:expr, $variant:literal) => {{
122 $map.serialize_entry("name", "span")?;
123 $map.serialize_entry("type", "inline")?;
124 $map.serialize_entry("variant", $variant)?;
125 $map.serialize_entry("form", &$value.form)?;
126 if let Some(role) = &$value.role {
127 $map.serialize_entry("role", role)?;
128 }
129 if let Some(id) = &$value.id {
130 $map.serialize_entry("id", id)?;
131 }
132 $map.serialize_entry("inlines", &$value.content)?;
133 $map.serialize_entry("location", &$value.location)?;
134 }};
135}
136
137impl Serialize for InlineNode {
138 #[allow(clippy::too_many_lines)]
139 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
140 where
141 S: Serializer,
142 {
143 let mut map = serializer.serialize_map(None)?;
144
145 match self {
146 InlineNode::PlainText(plain) => {
147 map.serialize_entry("name", "text")?;
148 map.serialize_entry("type", "string")?;
149 map.serialize_entry("value", &plain.content)?;
150 map.serialize_entry("location", &plain.location)?;
151 }
152 InlineNode::RawText(raw) => {
153 map.serialize_entry("name", "raw")?;
154 map.serialize_entry("type", "string")?;
155 map.serialize_entry("value", &raw.content)?;
156 map.serialize_entry("location", &raw.location)?;
157 }
158 InlineNode::VerbatimText(verbatim) => {
159 map.serialize_entry("name", "text")?;
162 map.serialize_entry("type", "string")?;
163 map.serialize_entry("value", &verbatim.content)?;
164 map.serialize_entry("location", &verbatim.location)?;
165 }
166 InlineNode::HighlightText(highlight) => {
167 serialize_inline_format!(map, highlight, "mark");
168 }
169 InlineNode::ItalicText(italic) => {
170 serialize_inline_format!(map, italic, "emphasis");
171 }
172 InlineNode::BoldText(bold) => {
173 serialize_inline_format!(map, bold, "strong");
174 }
175 InlineNode::MonospaceText(monospace) => {
176 serialize_inline_format!(map, monospace, "code");
177 }
178 InlineNode::SubscriptText(subscript) => {
179 serialize_inline_format!(map, subscript, "subscript");
180 }
181 InlineNode::SuperscriptText(superscript) => {
182 serialize_inline_format!(map, superscript, "superscript");
183 }
184 InlineNode::CurvedQuotationText(curved_quotation) => {
185 serialize_inline_format!(map, curved_quotation, "curved_quotation");
186 }
187 InlineNode::CurvedApostropheText(curved_apostrophe) => {
188 serialize_inline_format!(map, curved_apostrophe, "curved_apostrophe");
189 }
190 InlineNode::StandaloneCurvedApostrophe(standalone) => {
191 map.serialize_entry("name", "curved_apostrophe")?;
192 map.serialize_entry("type", "string")?;
193 map.serialize_entry("location", &standalone.location)?;
194 }
195 InlineNode::LineBreak(line_break) => {
196 map.serialize_entry("name", "break")?;
197 map.serialize_entry("type", "inline")?;
198 map.serialize_entry("location", &line_break.location)?;
199 }
200 InlineNode::InlineAnchor(anchor) => {
201 map.serialize_entry("name", "anchor")?;
202 map.serialize_entry("type", "inline")?;
203 map.serialize_entry("id", &anchor.id)?;
204 if let Some(xreflabel) = &anchor.xreflabel {
205 map.serialize_entry("xreflabel", xreflabel)?;
206 }
207 map.serialize_entry("location", &anchor.location)?;
208 }
209 InlineNode::Macro(macro_node) => {
210 serialize_inline_macro::<S>(macro_node, &mut map)?;
211 }
212 InlineNode::CalloutRef(callout_ref) => {
213 map.serialize_entry("name", "callout_reference")?;
214 map.serialize_entry("type", "inline")?;
215 map.serialize_entry("variant", &callout_ref.kind)?;
216 map.serialize_entry("number", &callout_ref.number)?;
217 map.serialize_entry("location", &callout_ref.location)?;
218 }
219 }
220 map.end()
221 }
222}
223
224fn serialize_inline_macro<S>(
225 macro_node: &InlineMacro,
226 map: &mut S::SerializeMap,
227) -> Result<(), S::Error>
228where
229 S: Serializer,
230{
231 match macro_node {
232 InlineMacro::Footnote(f) => serialize_footnote::<S>(f, map),
233 InlineMacro::Icon(i) => serialize_icon::<S>(i, map),
234 InlineMacro::Image(i) => serialize_image::<S>(i, map),
235 InlineMacro::Keyboard(k) => serialize_keyboard::<S>(k, map),
236 InlineMacro::Button(b) => serialize_button::<S>(b, map),
237 InlineMacro::Menu(m) => serialize_menu::<S>(m, map),
238 InlineMacro::Url(u) => serialize_url::<S>(u, map),
239 InlineMacro::Mailto(m) => serialize_mailto::<S>(m, map),
240 InlineMacro::Link(l) => serialize_link::<S>(l, map),
241 InlineMacro::Autolink(a) => serialize_autolink::<S>(a, map),
242 InlineMacro::CrossReference(x) => serialize_xref::<S>(x, map),
243 InlineMacro::Stem(s) => serialize_stem::<S>(s, map),
244 InlineMacro::IndexTerm(i) => serialize_indexterm::<S>(i, map),
245 InlineMacro::Pass(_) => Err(S::Error::custom(
246 "inline passthrough macros are not part of the ASG specification and cannot be serialized",
247 )),
248 }
249}
250
251fn serialize_footnote<S>(f: &Footnote, map: &mut S::SerializeMap) -> Result<(), S::Error>
252where
253 S: Serializer,
254{
255 map.serialize_entry("name", "footnote")?;
256 map.serialize_entry("type", "inline")?;
257 map.serialize_entry("id", &f.id)?;
258 map.serialize_entry("inlines", &f.content)?;
259 map.serialize_entry("location", &f.location)
260}
261
262fn serialize_icon<S>(i: &Icon, map: &mut S::SerializeMap) -> Result<(), S::Error>
263where
264 S: Serializer,
265{
266 map.serialize_entry("name", "icon")?;
267 map.serialize_entry("type", "inline")?;
268 map.serialize_entry("target", &i.target)?;
269 if !i.attributes.is_empty() {
270 map.serialize_entry("attributes", &i.attributes)?;
271 }
272 map.serialize_entry("location", &i.location)
273}
274
275fn serialize_image<S>(i: &Image, map: &mut S::SerializeMap) -> Result<(), S::Error>
276where
277 S: Serializer,
278{
279 map.serialize_entry("name", "image")?;
280 map.serialize_entry("type", "inline")?;
281 map.serialize_entry("title", &i.title)?;
282 map.serialize_entry("target", &i.source)?;
283 map.serialize_entry("location", &i.location)
284}
285
286fn serialize_keyboard<S>(k: &Keyboard, map: &mut S::SerializeMap) -> Result<(), S::Error>
287where
288 S: Serializer,
289{
290 map.serialize_entry("name", "keyboard")?;
291 map.serialize_entry("type", "inline")?;
292 map.serialize_entry("keys", &k.keys)?;
293 map.serialize_entry("location", &k.location)
294}
295
296fn serialize_button<S>(b: &Button, map: &mut S::SerializeMap) -> Result<(), S::Error>
297where
298 S: Serializer,
299{
300 map.serialize_entry("name", "button")?;
301 map.serialize_entry("type", "inline")?;
302 map.serialize_entry("label", &b.label)?;
303 map.serialize_entry("location", &b.location)
304}
305
306fn serialize_menu<S>(m: &Menu, map: &mut S::SerializeMap) -> Result<(), S::Error>
307where
308 S: Serializer,
309{
310 map.serialize_entry("name", "menu")?;
311 map.serialize_entry("type", "inline")?;
312 map.serialize_entry("target", &m.target)?;
313 if !m.items.is_empty() {
314 map.serialize_entry("items", &m.items)?;
315 }
316 map.serialize_entry("location", &m.location)
317}
318
319fn serialize_url<S>(u: &Url, map: &mut S::SerializeMap) -> Result<(), S::Error>
320where
321 S: Serializer,
322{
323 map.serialize_entry("name", "ref")?;
324 map.serialize_entry("type", "inline")?;
325 map.serialize_entry("variant", "link")?;
326 map.serialize_entry("target", &u.target)?;
327 map.serialize_entry("location", &u.location)?;
328 map.serialize_entry("attributes", &u.attributes)
329}
330
331fn serialize_mailto<S>(m: &Mailto, map: &mut S::SerializeMap) -> Result<(), S::Error>
332where
333 S: Serializer,
334{
335 map.serialize_entry("name", "ref")?;
336 map.serialize_entry("type", "inline")?;
337 map.serialize_entry("variant", "mailto")?;
338 map.serialize_entry("target", &m.target)?;
339 map.serialize_entry("location", &m.location)?;
340 map.serialize_entry("attributes", &m.attributes)
341}
342
343fn serialize_link<S>(l: &Link, map: &mut S::SerializeMap) -> Result<(), S::Error>
344where
345 S: Serializer,
346{
347 map.serialize_entry("name", "ref")?;
348 map.serialize_entry("type", "inline")?;
349 map.serialize_entry("variant", "link")?;
350 map.serialize_entry("target", &l.target)?;
351 map.serialize_entry("location", &l.location)?;
352 map.serialize_entry("attributes", &l.attributes)
353}
354
355fn serialize_autolink<S>(a: &Autolink, map: &mut S::SerializeMap) -> Result<(), S::Error>
356where
357 S: Serializer,
358{
359 map.serialize_entry("name", "ref")?;
360 map.serialize_entry("type", "inline")?;
361 map.serialize_entry("variant", "autolink")?;
362 map.serialize_entry("target", &a.url)?;
363 map.serialize_entry("location", &a.location)
364}
365
366fn serialize_xref<S>(x: &CrossReference, map: &mut S::SerializeMap) -> Result<(), S::Error>
367where
368 S: Serializer,
369{
370 map.serialize_entry("name", "xref")?;
371 map.serialize_entry("type", "inline")?;
372 map.serialize_entry("target", &x.target)?;
373 if let Some(text) = &x.text {
374 map.serialize_entry("text", text)?;
375 }
376 map.serialize_entry("location", &x.location)
377}
378
379fn serialize_stem<S>(s: &Stem, map: &mut S::SerializeMap) -> Result<(), S::Error>
380where
381 S: Serializer,
382{
383 map.serialize_entry("name", "stem")?;
384 map.serialize_entry("type", "inline")?;
385 map.serialize_entry("content", &s.content)?;
386 map.serialize_entry("notation", &s.notation)?;
387 map.serialize_entry("location", &s.location)
388}
389
390fn serialize_indexterm<S>(i: &IndexTerm, map: &mut S::SerializeMap) -> Result<(), S::Error>
391where
392 S: Serializer,
393{
394 map.serialize_entry("name", "indexterm")?;
395 map.serialize_entry("type", "inline")?;
396 map.serialize_entry("term", i.term())?;
397 if let Some(secondary) = i.secondary() {
398 map.serialize_entry("secondary", secondary)?;
399 }
400 if let Some(tertiary) = i.tertiary() {
401 map.serialize_entry("tertiary", tertiary)?;
402 }
403 map.serialize_entry("visible", &i.is_visible())?;
404 map.serialize_entry("location", &i.location)
405}
406
407#[derive(Default, Deserialize)]
413#[serde(default)]
414struct RawInlineFields {
415 name: Option<String>,
416 r#type: Option<String>,
417 value: Option<String>,
418 variant: Option<String>,
419 form: Option<Form>,
420 location: Option<crate::Location>,
421 inlines: Option<Vec<InlineNode>>,
422 title: Option<Vec<InlineNode>>,
423 target: Option<serde_json::Value>,
424 attributes: Option<ElementAttributes>,
425 role: Option<String>,
426 id: Option<String>,
427 text: Option<String>,
428 items: Option<Vec<String>>,
429 keys: Option<Vec<String>>,
430 label: Option<String>,
431 content: Option<String>,
432 notation: Option<StemNotation>,
433 substitutions: Option<HashSet<crate::Substitution>>,
434 xreflabel: Option<String>,
435 bracketed: Option<bool>,
436 number: Option<usize>,
437 term: Option<String>,
439 secondary: Option<String>,
440 tertiary: Option<String>,
441 visible: Option<bool>,
442}
443
444fn construct_plain_text<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
449 Ok(InlineNode::PlainText(Plain {
450 content: raw.value.ok_or_else(|| E::missing_field("value"))?,
451 location: raw.location.ok_or_else(|| E::missing_field("location"))?,
452 }))
453}
454
455fn construct_raw_text<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
456 Ok(InlineNode::RawText(Raw {
457 content: raw.value.ok_or_else(|| E::missing_field("value"))?,
458 location: raw.location.ok_or_else(|| E::missing_field("location"))?,
459 }))
460}
461
462fn construct_verbatim_text<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
463 Ok(InlineNode::VerbatimText(Verbatim {
464 content: raw.value.ok_or_else(|| E::missing_field("value"))?,
465 location: raw.location.ok_or_else(|| E::missing_field("location"))?,
466 }))
467}
468
469fn construct_standalone_curved_apostrophe<E: de::Error>(
470 raw: RawInlineFields,
471) -> Result<InlineNode, E> {
472 Ok(InlineNode::StandaloneCurvedApostrophe(
473 StandaloneCurvedApostrophe {
474 location: raw.location.ok_or_else(|| E::missing_field("location"))?,
475 },
476 ))
477}
478
479fn construct_line_break<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
480 Ok(InlineNode::LineBreak(LineBreak {
481 location: raw.location.ok_or_else(|| E::missing_field("location"))?,
482 }))
483}
484
485fn construct_anchor<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
486 Ok(InlineNode::InlineAnchor(Anchor {
487 id: raw.id.ok_or_else(|| E::missing_field("id"))?,
488 xreflabel: raw.xreflabel,
489 location: raw.location.ok_or_else(|| E::missing_field("location"))?,
490 }))
491}
492
493fn construct_icon<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
494 let target_val = raw.target.ok_or_else(|| E::missing_field("target"))?;
495 let target: Source = serde_json::from_value(target_val).map_err(E::custom)?;
496 Ok(InlineNode::Macro(InlineMacro::Icon(Icon {
497 attributes: raw.attributes.unwrap_or_default(),
498 target,
499 location: raw.location.ok_or_else(|| E::missing_field("location"))?,
500 })))
501}
502
503fn construct_image<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
504 let title = Title::new(raw.title.ok_or_else(|| E::missing_field("title"))?);
505 let target_val = raw.target.ok_or_else(|| E::missing_field("target"))?;
506 let source: Source = serde_json::from_value(target_val).map_err(E::custom)?;
507 Ok(InlineNode::Macro(InlineMacro::Image(Box::new(Image {
508 title,
509 source,
510 metadata: BlockMetadata::default(),
511 location: raw.location.ok_or_else(|| E::missing_field("location"))?,
512 }))))
513}
514
515fn construct_footnote<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
516 let inlines = raw.inlines.ok_or_else(|| E::missing_field("inlines"))?;
517 Ok(InlineNode::Macro(InlineMacro::Footnote(Footnote {
518 id: raw.id,
519 content: inlines,
520 number: 0,
521 location: raw.location.ok_or_else(|| E::missing_field("location"))?,
522 })))
523}
524
525fn construct_keyboard<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
526 Ok(InlineNode::Macro(InlineMacro::Keyboard(Keyboard {
527 keys: raw.keys.ok_or_else(|| E::missing_field("keys"))?,
528 location: raw.location.ok_or_else(|| E::missing_field("location"))?,
529 })))
530}
531
532fn construct_button<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
533 Ok(InlineNode::Macro(InlineMacro::Button(Button {
534 label: raw.label.ok_or_else(|| E::missing_field("label"))?,
535 location: raw.location.ok_or_else(|| E::missing_field("location"))?,
536 })))
537}
538
539fn construct_menu<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
540 let target_val = raw.target.ok_or_else(|| E::missing_field("target"))?;
541 let target: String = serde_json::from_value(target_val).map_err(E::custom)?;
542 Ok(InlineNode::Macro(InlineMacro::Menu(Menu {
543 target,
544 items: raw.items.unwrap_or_default(),
545 location: raw.location.ok_or_else(|| E::missing_field("location"))?,
546 })))
547}
548
549fn construct_stem<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
550 Ok(InlineNode::Macro(InlineMacro::Stem(Stem {
551 content: raw.content.ok_or_else(|| E::missing_field("content"))?,
552 notation: raw.notation.ok_or_else(|| E::missing_field("notation"))?,
553 location: raw.location.ok_or_else(|| E::missing_field("location"))?,
554 })))
555}
556
557fn construct_indexterm<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
558 let term = raw.term.ok_or_else(|| E::missing_field("term"))?;
559 let visible = raw.visible.ok_or_else(|| E::missing_field("visible"))?;
560 let location = raw.location.ok_or_else(|| E::missing_field("location"))?;
561
562 let kind = if visible {
563 IndexTermKind::Flow(term)
564 } else {
565 IndexTermKind::Concealed {
566 term,
567 secondary: raw.secondary,
568 tertiary: raw.tertiary,
569 }
570 };
571
572 Ok(InlineNode::Macro(InlineMacro::IndexTerm(IndexTerm {
573 kind,
574 location,
575 })))
576}
577
578fn construct_xref<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
579 let target_val = raw.target.ok_or_else(|| E::missing_field("target"))?;
580 let target: String = serde_json::from_value(target_val).map_err(E::custom)?;
581 Ok(InlineNode::Macro(InlineMacro::CrossReference(
582 crate::model::CrossReference {
583 target,
584 text: raw.text,
585 location: raw.location.ok_or_else(|| E::missing_field("location"))?,
586 },
587 )))
588}
589
590fn construct_ref<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
591 let variant = raw.variant.ok_or_else(|| E::missing_field("variant"))?;
592 let target_val = raw.target.ok_or_else(|| E::missing_field("target"))?;
593 let target: Source = serde_json::from_value(target_val).map_err(E::custom)?;
594 let location = raw.location.ok_or_else(|| E::missing_field("location"))?;
595
596 match variant.as_str() {
597 "url" => Ok(InlineNode::Macro(InlineMacro::Url(Url {
598 text: vec![],
599 attributes: raw.attributes.unwrap_or_default(),
600 target,
601 location,
602 }))),
603 "link" => Ok(InlineNode::Macro(InlineMacro::Link(Link {
604 text: None,
605 attributes: raw.attributes.unwrap_or_default(),
606 target,
607 location,
608 }))),
609 "mailto" => Ok(InlineNode::Macro(InlineMacro::Mailto(Mailto {
610 text: vec![],
611 attributes: raw.attributes.unwrap_or_default(),
612 target,
613 location,
614 }))),
615 "autolink" => Ok(InlineNode::Macro(InlineMacro::Autolink(Autolink {
616 url: target,
617 bracketed: raw.bracketed.unwrap_or(false),
618 location,
619 }))),
620 "pass" => Ok(InlineNode::Macro(InlineMacro::Pass(Pass {
621 text: raw.text,
622 substitutions: raw.substitutions.unwrap_or_default(),
623 location,
624 kind: PassthroughKind::default(),
625 }))),
626 _ => {
627 tracing::error!(variant = %variant, "invalid inline macro variant");
628 Err(E::custom("invalid inline macro variant"))
629 }
630 }
631}
632
633fn construct_span<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
634 let variant = raw.variant.ok_or_else(|| E::missing_field("variant"))?;
635 let inlines = raw.inlines.ok_or_else(|| E::missing_field("inlines"))?;
636 let location = raw.location.ok_or_else(|| E::missing_field("location"))?;
637 let role = raw.role;
638 let id = raw.id;
639
640 match variant.as_str() {
641 "strong" => Ok(InlineNode::BoldText(Bold {
642 role,
643 id,
644 form: raw.form.unwrap_or(Form::Constrained),
645 content: inlines,
646 location,
647 })),
648 "emphasis" => Ok(InlineNode::ItalicText(Italic {
649 role,
650 id,
651 form: raw.form.unwrap_or(Form::Constrained),
652 content: inlines,
653 location,
654 })),
655 "code" => Ok(InlineNode::MonospaceText(Monospace {
656 role,
657 id,
658 form: raw.form.unwrap_or(Form::Constrained),
659 content: inlines,
660 location,
661 })),
662 "mark" => Ok(InlineNode::HighlightText(Highlight {
663 role,
664 id,
665 form: raw.form.unwrap_or(Form::Constrained),
666 content: inlines,
667 location,
668 })),
669 "subscript" => Ok(InlineNode::SubscriptText(Subscript {
670 role,
671 id,
672 form: raw.form.unwrap_or(Form::Unconstrained),
673 content: inlines,
674 location,
675 })),
676 "superscript" => Ok(InlineNode::SuperscriptText(Superscript {
677 role,
678 id,
679 form: raw.form.unwrap_or(Form::Unconstrained),
680 content: inlines,
681 location,
682 })),
683 "curved_quotation" => Ok(InlineNode::CurvedQuotationText(CurvedQuotation {
684 role,
685 id,
686 form: raw.form.unwrap_or(Form::Unconstrained),
687 content: inlines,
688 location,
689 })),
690 "curved_apostrophe" => Ok(InlineNode::CurvedApostropheText(CurvedApostrophe {
691 role,
692 id,
693 form: raw.form.unwrap_or(Form::Unconstrained),
694 content: inlines,
695 location,
696 })),
697 _ => {
698 tracing::error!(variant = %variant, "invalid inline node variant");
699 Err(E::custom("invalid inline node variant"))
700 }
701 }
702}
703
704fn construct_callout_ref<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
705 let variant = raw.variant.ok_or_else(|| E::missing_field("variant"))?;
706 let number = raw.number.ok_or_else(|| E::missing_field("number"))?;
707 let location = raw.location.ok_or_else(|| E::missing_field("location"))?;
708
709 let kind = match variant.as_str() {
710 "explicit" => CalloutRefKind::Explicit,
711 "auto" => CalloutRefKind::Auto,
712 _ => {
713 tracing::error!(variant = %variant, "invalid callout ref variant");
714 return Err(E::custom("invalid callout ref variant"));
715 }
716 };
717
718 Ok(InlineNode::CalloutRef(CalloutRef {
719 kind,
720 number,
721 location,
722 }))
723}
724
725fn dispatch_inline<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
727 let name = raw.name.clone().ok_or_else(|| E::missing_field("name"))?;
728 let ty = raw.r#type.clone().ok_or_else(|| E::missing_field("type"))?;
729
730 match (name.as_str(), ty.as_str()) {
731 ("text", "string") => construct_plain_text(raw),
732 ("raw", "string") => construct_raw_text(raw),
733 ("verbatim", "string") => construct_verbatim_text(raw),
734 ("curved_apostrophe", "string") => construct_standalone_curved_apostrophe(raw),
735 ("break", "inline") => construct_line_break(raw),
736 ("anchor", "inline") => construct_anchor(raw),
737 ("icon", "inline") => construct_icon(raw),
738 ("image", "inline") => construct_image(raw),
739 ("footnote", "inline") => construct_footnote(raw),
740 ("keyboard", "inline") => construct_keyboard(raw),
741 ("btn" | "button", "inline") => construct_button(raw),
742 ("menu", "inline") => construct_menu(raw),
743 ("stem", "inline") => construct_stem(raw),
744 ("indexterm", "inline") => construct_indexterm(raw),
745 ("xref", "inline") => construct_xref(raw),
746 ("ref", "inline") => construct_ref(raw),
747 ("span", "inline") => construct_span(raw),
748 ("callout_reference", "inline") => construct_callout_ref(raw),
749 _ => {
750 tracing::error!(name = %name, r#type = %ty, "invalid inline node");
751 Err(E::custom("invalid inline node"))
752 }
753 }
754}
755
756impl<'de> Deserialize<'de> for InlineNode {
757 fn deserialize<D>(deserializer: D) -> Result<InlineNode, D::Error>
758 where
759 D: Deserializer<'de>,
760 {
761 let raw: RawInlineFields = RawInlineFields::deserialize(deserializer)?;
762 dispatch_inline(raw)
763 }
764}