1use std::borrow::Cow;
4
5use serde::de;
6use serde::ser::SerializeMap;
7use serde::{Deserialize, Serialize};
8
9use super::Text;
10use crate::extensions::ExtensionBlock;
11
12#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
17pub struct Content {
18 pub version: String,
20
21 pub blocks: Vec<Block>,
23}
24
25impl Content {
26 #[must_use]
28 pub fn new(blocks: Vec<Block>) -> Self {
29 Self {
30 version: crate::SPEC_VERSION.to_string(),
31 blocks,
32 }
33 }
34
35 #[must_use]
37 pub fn empty() -> Self {
38 Self::new(Vec::new())
39 }
40
41 #[must_use]
43 pub fn is_empty(&self) -> bool {
44 self.blocks.is_empty()
45 }
46
47 #[must_use]
49 pub fn len(&self) -> usize {
50 self.blocks.len()
51 }
52}
53
54impl Default for Content {
55 fn default() -> Self {
56 Self::empty()
57 }
58}
59
60#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
62#[serde(rename_all = "camelCase")]
63pub struct BlockAttributes {
64 #[serde(default, skip_serializing_if = "Option::is_none")]
66 pub dir: Option<String>,
67
68 #[serde(default, skip_serializing_if = "Option::is_none")]
70 pub lang: Option<String>,
71
72 #[serde(default, skip_serializing_if = "Option::is_none")]
74 pub writing_mode: Option<WritingMode>,
75}
76
77impl BlockAttributes {
78 #[must_use]
80 pub fn is_empty(&self) -> bool {
81 self.dir.is_none() && self.lang.is_none() && self.writing_mode.is_none()
82 }
83}
84
85#[derive(Debug, Clone, PartialEq)]
96pub enum Block {
97 Paragraph {
99 id: Option<String>,
101
102 children: Vec<Text>,
104
105 attributes: BlockAttributes,
107 },
108
109 Heading {
111 id: Option<String>,
113
114 level: u8,
116
117 children: Vec<Text>,
119
120 attributes: BlockAttributes,
122 },
123
124 List {
126 id: Option<String>,
128
129 ordered: bool,
131
132 start: Option<u32>,
134
135 children: Vec<Block>,
137
138 attributes: BlockAttributes,
140 },
141
142 ListItem {
144 id: Option<String>,
146
147 checked: Option<bool>,
149
150 children: Vec<Block>,
152
153 attributes: BlockAttributes,
155 },
156
157 Blockquote {
159 id: Option<String>,
161
162 children: Vec<Block>,
164
165 attributes: BlockAttributes,
167 },
168
169 CodeBlock {
171 id: Option<String>,
173
174 language: Option<String>,
176
177 highlighting: Option<String>,
179
180 tokens: Option<Vec<CodeToken>>,
182
183 children: Vec<Text>,
185
186 attributes: BlockAttributes,
188 },
189
190 HorizontalRule {
192 id: Option<String>,
194 },
195
196 Image(ImageBlock),
198
199 Table {
201 id: Option<String>,
203
204 children: Vec<Block>,
206
207 attributes: BlockAttributes,
209 },
210
211 TableRow {
213 id: Option<String>,
215
216 header: bool,
218
219 children: Vec<Block>,
221
222 attributes: BlockAttributes,
224 },
225
226 TableCell(TableCellBlock),
228
229 Math(MathBlock),
231
232 Break {
234 id: Option<String>,
236 },
237
238 DefinitionList(DefinitionListBlock),
240
241 DefinitionItem {
243 id: Option<String>,
245
246 children: Vec<Block>,
248
249 attributes: BlockAttributes,
251 },
252
253 DefinitionTerm {
255 id: Option<String>,
257
258 children: Vec<Text>,
260
261 attributes: BlockAttributes,
263 },
264
265 DefinitionDescription {
267 id: Option<String>,
269
270 children: Vec<Block>,
272
273 attributes: BlockAttributes,
275 },
276
277 Measurement(MeasurementBlock),
279
280 Signature(SignatureBlock),
282
283 Svg(SvgBlock),
285
286 Barcode(BarcodeBlock),
288
289 Figure(FigureBlock),
291
292 FigCaption(FigCaptionBlock),
294
295 Admonition(AdmonitionBlock),
297
298 Extension(ExtensionBlock),
304}
305
306#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
308pub struct ImageBlock {
309 #[serde(default, skip_serializing_if = "Option::is_none")]
311 pub id: Option<String>,
312
313 pub src: String,
315
316 pub alt: String,
318
319 #[serde(default, skip_serializing_if = "Option::is_none")]
321 pub title: Option<String>,
322
323 #[serde(default, skip_serializing_if = "Option::is_none")]
325 pub width: Option<u32>,
326
327 #[serde(default, skip_serializing_if = "Option::is_none")]
329 pub height: Option<u32>,
330}
331
332#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
334pub struct TableCellBlock {
335 #[serde(default, skip_serializing_if = "Option::is_none")]
337 pub id: Option<String>,
338
339 #[serde(default = "default_span", skip_serializing_if = "is_default_span")]
341 pub colspan: u32,
342
343 #[serde(default = "default_span", skip_serializing_if = "is_default_span")]
345 pub rowspan: u32,
346
347 #[serde(default, skip_serializing_if = "Option::is_none")]
349 pub align: Option<CellAlign>,
350
351 pub children: Vec<Text>,
353
354 #[serde(default, skip_serializing_if = "BlockAttributes::is_empty")]
356 pub attributes: BlockAttributes,
357}
358
359fn default_span() -> u32 {
360 1
361}
362
363#[allow(clippy::trivially_copy_pass_by_ref)] fn is_default_span(span: &u32) -> bool {
365 *span == 1
366}
367
368#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
370#[serde(rename_all = "lowercase")]
371pub enum CellAlign {
372 Left,
374 Center,
376 Right,
378}
379
380#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
382pub struct MathBlock {
383 #[serde(default, skip_serializing_if = "Option::is_none")]
385 pub id: Option<String>,
386
387 pub display: bool,
389
390 pub format: MathFormat,
392
393 pub value: String,
395}
396
397#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
399#[serde(rename_all = "lowercase")]
400pub enum MathFormat {
401 Latex,
403 Mathml,
405}
406
407#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
413#[serde(rename_all = "kebab-case")]
414pub enum WritingMode {
415 #[default]
418 HorizontalTb,
419
420 VerticalRl,
423
424 VerticalLr,
427
428 SidewaysRl,
430
431 SidewaysLr,
433}
434
435#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
437#[serde(rename_all = "camelCase")]
438pub struct MeasurementBlock {
439 #[serde(default, skip_serializing_if = "Option::is_none")]
441 pub id: Option<String>,
442
443 pub value: f64,
445
446 #[serde(default, skip_serializing_if = "Option::is_none")]
448 pub uncertainty: Option<f64>,
449
450 #[serde(default, skip_serializing_if = "Option::is_none")]
452 pub uncertainty_notation: Option<UncertaintyNotation>,
453
454 #[serde(default, skip_serializing_if = "Option::is_none")]
456 pub exponent: Option<i32>,
457
458 pub display: String,
460
461 #[serde(default, skip_serializing_if = "Option::is_none")]
463 pub unit: Option<String>,
464}
465
466impl MeasurementBlock {
467 #[must_use]
469 pub fn new(value: f64, display: impl Into<String>) -> Self {
470 Self {
471 id: None,
472 value,
473 uncertainty: None,
474 uncertainty_notation: None,
475 exponent: None,
476 display: display.into(),
477 unit: None,
478 }
479 }
480
481 #[must_use]
483 pub fn with_unit(mut self, unit: impl Into<String>) -> Self {
484 self.unit = Some(unit.into());
485 self
486 }
487
488 #[must_use]
490 pub fn with_uncertainty(mut self, uncertainty: f64, notation: UncertaintyNotation) -> Self {
491 self.uncertainty = Some(uncertainty);
492 self.uncertainty_notation = Some(notation);
493 self
494 }
495
496 #[must_use]
498 pub fn with_exponent(mut self, exponent: i32) -> Self {
499 self.exponent = Some(exponent);
500 self
501 }
502}
503
504#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
506#[serde(rename_all = "lowercase")]
507pub enum UncertaintyNotation {
508 Parenthetical,
510 Plusminus,
512 Range,
514 Percent,
516}
517
518#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
520#[serde(rename_all = "camelCase")]
521pub struct SignatureBlock {
522 #[serde(default, skip_serializing_if = "Option::is_none")]
524 pub id: Option<String>,
525
526 pub signature_type: BlockSignatureType,
528
529 #[serde(default, skip_serializing_if = "Option::is_none")]
531 pub signer: Option<SignerDetails>,
532
533 #[serde(default, skip_serializing_if = "Option::is_none")]
535 pub timestamp: Option<String>,
536
537 #[serde(default, skip_serializing_if = "Option::is_none")]
539 pub purpose: Option<SignaturePurpose>,
540
541 #[serde(default, skip_serializing_if = "Option::is_none")]
543 pub digital_signature_ref: Option<String>,
544}
545
546impl SignatureBlock {
547 #[must_use]
549 pub fn new(signature_type: BlockSignatureType) -> Self {
550 Self {
551 id: None,
552 signature_type,
553 signer: None,
554 timestamp: None,
555 purpose: None,
556 digital_signature_ref: None,
557 }
558 }
559
560 #[must_use]
562 pub fn with_signer(mut self, signer: SignerDetails) -> Self {
563 self.signer = Some(signer);
564 self
565 }
566
567 #[must_use]
569 pub fn with_purpose(mut self, purpose: SignaturePurpose) -> Self {
570 self.purpose = Some(purpose);
571 self
572 }
573}
574
575#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
577#[serde(rename_all = "lowercase")]
578pub enum BlockSignatureType {
579 Handwritten,
581 Digital,
583 Electronic,
585 Stamp,
587}
588
589#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
591#[serde(rename_all = "lowercase")]
592pub enum SignaturePurpose {
593 Certification,
595 Approval,
597 Witness,
599 Acknowledgment,
601 Authorship,
603}
604
605#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
607pub struct SignerDetails {
608 pub name: String,
610
611 #[serde(default, skip_serializing_if = "Option::is_none")]
613 pub title: Option<String>,
614
615 #[serde(default, skip_serializing_if = "Option::is_none")]
617 pub organization: Option<String>,
618
619 #[serde(default, skip_serializing_if = "Option::is_none")]
621 pub email: Option<String>,
622
623 #[serde(default, skip_serializing_if = "Option::is_none")]
625 pub id: Option<String>,
626}
627
628impl SignerDetails {
629 #[must_use]
631 pub fn new(name: impl Into<String>) -> Self {
632 Self {
633 name: name.into(),
634 title: None,
635 organization: None,
636 email: None,
637 id: None,
638 }
639 }
640
641 #[must_use]
643 pub fn with_title(mut self, title: impl Into<String>) -> Self {
644 self.title = Some(title.into());
645 self
646 }
647
648 #[must_use]
650 pub fn with_organization(mut self, organization: impl Into<String>) -> Self {
651 self.organization = Some(organization.into());
652 self
653 }
654}
655
656#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
658pub struct SvgBlock {
659 #[serde(default, skip_serializing_if = "Option::is_none")]
661 pub id: Option<String>,
662
663 #[serde(default, skip_serializing_if = "Option::is_none")]
665 pub src: Option<String>,
666
667 #[serde(default, skip_serializing_if = "Option::is_none")]
669 pub content: Option<String>,
670
671 #[serde(default, skip_serializing_if = "Option::is_none")]
673 pub width: Option<u32>,
674
675 #[serde(default, skip_serializing_if = "Option::is_none")]
677 pub height: Option<u32>,
678
679 #[serde(default, skip_serializing_if = "Option::is_none")]
681 pub alt: Option<String>,
682}
683
684impl SvgBlock {
685 #[must_use]
687 pub fn from_src(src: impl Into<String>) -> Self {
688 Self {
689 id: None,
690 src: Some(src.into()),
691 content: None,
692 width: None,
693 height: None,
694 alt: None,
695 }
696 }
697
698 #[must_use]
700 pub fn from_content(content: impl Into<String>) -> Self {
701 Self {
702 id: None,
703 src: None,
704 content: Some(content.into()),
705 width: None,
706 height: None,
707 alt: None,
708 }
709 }
710
711 #[must_use]
713 pub fn with_alt(mut self, alt: impl Into<String>) -> Self {
714 self.alt = Some(alt.into());
715 self
716 }
717
718 #[must_use]
720 pub fn with_dimensions(mut self, width: u32, height: u32) -> Self {
721 self.width = Some(width);
722 self.height = Some(height);
723 self
724 }
725}
726
727#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
729#[serde(rename_all = "camelCase")]
730pub struct BarcodeBlock {
731 #[serde(default, skip_serializing_if = "Option::is_none")]
733 pub id: Option<String>,
734
735 pub format: BarcodeFormat,
737
738 pub data: String,
740
741 #[serde(default, skip_serializing_if = "Option::is_none")]
743 pub error_correction: Option<ErrorCorrectionLevel>,
744
745 #[serde(default, skip_serializing_if = "Option::is_none")]
747 pub size: Option<BarcodeSize>,
748
749 #[serde(default, skip_serializing_if = "Option::is_none")]
751 pub quiet_zone: Option<String>,
752
753 pub alt: String,
755}
756
757impl BarcodeBlock {
758 #[must_use]
760 pub fn new(format: BarcodeFormat, data: impl Into<String>, alt: impl Into<String>) -> Self {
761 Self {
762 id: None,
763 format,
764 data: data.into(),
765 error_correction: None,
766 size: None,
767 quiet_zone: None,
768 alt: alt.into(),
769 }
770 }
771
772 #[must_use]
774 pub fn with_error_correction(mut self, level: ErrorCorrectionLevel) -> Self {
775 self.error_correction = Some(level);
776 self
777 }
778
779 #[must_use]
781 pub fn with_size(mut self, width: String, height: String) -> Self {
782 self.size = Some(BarcodeSize { width, height });
783 self
784 }
785}
786
787#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
789#[serde(rename_all = "lowercase")]
790pub enum BarcodeFormat {
791 Qr,
793 DataMatrix,
795 Code128,
797 Code39,
799 Ean13,
801 Ean8,
803 UpcA,
805 Pdf417,
807}
808
809#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
811pub enum ErrorCorrectionLevel {
812 L,
814 M,
816 Q,
818 H,
820}
821
822#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
824pub struct BarcodeSize {
825 pub width: String,
827 pub height: String,
829}
830
831#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
833pub struct FigureBlock {
834 #[serde(default, skip_serializing_if = "Option::is_none")]
836 pub id: Option<String>,
837
838 #[serde(default, skip_serializing_if = "Option::is_none")]
840 pub numbering: Option<FigureNumbering>,
841
842 #[serde(default, skip_serializing_if = "Option::is_none")]
844 pub subfigures: Option<Vec<Subfigure>>,
845
846 pub children: Vec<Block>,
848
849 #[serde(default, skip_serializing_if = "BlockAttributes::is_empty")]
851 pub attributes: BlockAttributes,
852}
853
854impl FigureBlock {
855 #[must_use]
857 pub fn new(children: Vec<Block>) -> Self {
858 Self {
859 id: None,
860 numbering: None,
861 subfigures: None,
862 children,
863 attributes: BlockAttributes::default(),
864 }
865 }
866}
867
868#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
870#[serde(rename_all = "camelCase")]
871pub enum FigureNumbering {
872 Auto,
874 #[serde(rename = "none")]
876 Unnumbered,
877 #[serde(untagged)]
879 Number(u32),
880}
881
882#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
884pub struct Subfigure {
885 #[serde(default, skip_serializing_if = "Option::is_none")]
887 pub id: Option<String>,
888
889 #[serde(default, skip_serializing_if = "Option::is_none")]
891 pub label: Option<String>,
892
893 pub children: Vec<Block>,
895}
896
897#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
899#[serde(rename_all = "camelCase")]
900pub struct CodeToken {
901 pub token_type: String,
903
904 pub value: String,
906
907 #[serde(default, skip_serializing_if = "Option::is_none")]
909 pub scope: Option<String>,
910}
911
912#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
914pub struct FigCaptionBlock {
915 #[serde(default, skip_serializing_if = "Option::is_none")]
917 pub id: Option<String>,
918
919 pub children: Vec<Text>,
921
922 #[serde(default, skip_serializing_if = "BlockAttributes::is_empty")]
924 pub attributes: BlockAttributes,
925}
926
927impl FigCaptionBlock {
928 #[must_use]
930 pub fn new(children: Vec<Text>) -> Self {
931 Self {
932 id: None,
933 children,
934 attributes: BlockAttributes::default(),
935 }
936 }
937}
938
939#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
941#[serde(rename_all = "camelCase")]
942pub struct AdmonitionBlock {
943 #[serde(default, skip_serializing_if = "Option::is_none")]
945 pub id: Option<String>,
946
947 pub variant: AdmonitionVariant,
949
950 #[serde(default, skip_serializing_if = "Option::is_none")]
952 pub title: Option<String>,
953
954 pub children: Vec<Block>,
956
957 #[serde(default, skip_serializing_if = "BlockAttributes::is_empty")]
959 pub attributes: BlockAttributes,
960}
961
962impl AdmonitionBlock {
963 #[must_use]
965 pub fn new(variant: AdmonitionVariant, children: Vec<Block>) -> Self {
966 Self {
967 id: None,
968 variant,
969 title: None,
970 children,
971 attributes: BlockAttributes::default(),
972 }
973 }
974
975 #[must_use]
977 pub fn with_title(mut self, title: impl Into<String>) -> Self {
978 self.title = Some(title.into());
979 self
980 }
981
982 #[must_use]
984 pub fn with_id(mut self, id: impl Into<String>) -> Self {
985 self.id = Some(id.into());
986 self
987 }
988}
989
990#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, strum::Display)]
992#[serde(rename_all = "lowercase")]
993pub enum AdmonitionVariant {
994 Note,
996 Tip,
998 Info,
1000 Warning,
1002 Caution,
1004 Danger,
1006 Important,
1008 Example,
1010}
1011
1012#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1014pub struct DefinitionListBlock {
1015 #[serde(default, skip_serializing_if = "Option::is_none")]
1017 pub id: Option<String>,
1018
1019 pub children: Vec<Block>,
1021
1022 #[serde(default, skip_serializing_if = "BlockAttributes::is_empty")]
1024 pub attributes: BlockAttributes,
1025}
1026
1027impl DefinitionListBlock {
1028 #[must_use]
1030 pub fn new(children: Vec<Block>) -> Self {
1031 Self {
1032 id: None,
1033 children,
1034 attributes: BlockAttributes::default(),
1035 }
1036 }
1037}
1038
1039fn serialize_block_as_map<S: serde::Serializer>(
1041 type_str: &str,
1042 value: &serde_json::Value,
1043 serializer: S,
1044) -> Result<S::Ok, S::Error> {
1045 use serde::ser::Error;
1046 let obj = value
1047 .as_object()
1048 .ok_or_else(|| S::Error::custom("expected object"))?;
1049 let mut map = serializer.serialize_map(Some(1 + obj.len()))?;
1050 map.serialize_entry("type", type_str)?;
1051 for (k, v) in obj {
1052 map.serialize_entry(k, v)?;
1053 }
1054 map.end()
1055}
1056
1057#[derive(Serialize)]
1060struct InlineParagraph<'a> {
1061 #[serde(skip_serializing_if = "Option::is_none")]
1062 id: &'a Option<String>,
1063 children: &'a Vec<Text>,
1064 #[serde(skip_serializing_if = "BlockAttributes::is_empty")]
1065 attributes: &'a BlockAttributes,
1066}
1067
1068#[derive(Serialize)]
1069struct InlineHeading<'a> {
1070 #[serde(skip_serializing_if = "Option::is_none")]
1071 id: &'a Option<String>,
1072 level: u8,
1073 children: &'a Vec<Text>,
1074 #[serde(skip_serializing_if = "BlockAttributes::is_empty")]
1075 attributes: &'a BlockAttributes,
1076}
1077
1078#[derive(Serialize)]
1079struct InlineList<'a> {
1080 #[serde(skip_serializing_if = "Option::is_none")]
1081 id: &'a Option<String>,
1082 ordered: bool,
1083 #[serde(skip_serializing_if = "Option::is_none")]
1084 start: &'a Option<u32>,
1085 children: &'a Vec<Block>,
1086 #[serde(skip_serializing_if = "BlockAttributes::is_empty")]
1087 attributes: &'a BlockAttributes,
1088}
1089
1090#[derive(Serialize)]
1091#[serde(rename_all = "camelCase")]
1092struct InlineListItem<'a> {
1093 #[serde(skip_serializing_if = "Option::is_none")]
1094 id: &'a Option<String>,
1095 #[serde(skip_serializing_if = "Option::is_none")]
1096 checked: &'a Option<bool>,
1097 children: &'a Vec<Block>,
1098 #[serde(skip_serializing_if = "BlockAttributes::is_empty")]
1099 attributes: &'a BlockAttributes,
1100}
1101
1102#[derive(Serialize)]
1103struct InlineContainer<'a> {
1104 #[serde(skip_serializing_if = "Option::is_none")]
1105 id: &'a Option<String>,
1106 children: &'a Vec<Block>,
1107 #[serde(skip_serializing_if = "BlockAttributes::is_empty")]
1108 attributes: &'a BlockAttributes,
1109}
1110
1111#[derive(Serialize)]
1112#[serde(rename_all = "camelCase")]
1113struct InlineCodeBlock<'a> {
1114 #[serde(skip_serializing_if = "Option::is_none")]
1115 id: &'a Option<String>,
1116 #[serde(skip_serializing_if = "Option::is_none")]
1117 language: &'a Option<String>,
1118 #[serde(skip_serializing_if = "Option::is_none")]
1119 highlighting: &'a Option<String>,
1120 #[serde(skip_serializing_if = "Option::is_none")]
1121 tokens: &'a Option<Vec<CodeToken>>,
1122 children: &'a Vec<Text>,
1123 #[serde(skip_serializing_if = "BlockAttributes::is_empty")]
1124 attributes: &'a BlockAttributes,
1125}
1126
1127#[derive(Serialize)]
1128struct InlineTableRow<'a> {
1129 #[serde(skip_serializing_if = "Option::is_none")]
1130 id: &'a Option<String>,
1131 #[serde(skip_serializing_if = "std::ops::Not::not")]
1132 header: bool,
1133 children: &'a Vec<Block>,
1134 #[serde(skip_serializing_if = "BlockAttributes::is_empty")]
1135 attributes: &'a BlockAttributes,
1136}
1137
1138#[derive(Serialize)]
1139struct InlineTextContainer<'a> {
1140 #[serde(skip_serializing_if = "Option::is_none")]
1141 id: &'a Option<String>,
1142 children: &'a Vec<Text>,
1143 #[serde(skip_serializing_if = "BlockAttributes::is_empty")]
1144 attributes: &'a BlockAttributes,
1145}
1146
1147#[derive(Serialize)]
1148struct InlineIdOnly<'a> {
1149 #[serde(skip_serializing_if = "Option::is_none")]
1150 id: &'a Option<String>,
1151}
1152
1153impl Serialize for Block {
1154 #[allow(clippy::too_many_lines)] fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1156 use serde::ser::Error;
1157
1158 match self {
1159 Self::Paragraph {
1160 id,
1161 children,
1162 attributes,
1163 } => {
1164 let inner = InlineParagraph {
1165 id,
1166 children,
1167 attributes,
1168 };
1169 let val = serde_json::to_value(&inner).map_err(S::Error::custom)?;
1170 serialize_block_as_map("paragraph", &val, serializer)
1171 }
1172 Self::Heading {
1173 id,
1174 level,
1175 children,
1176 attributes,
1177 } => {
1178 let inner = InlineHeading {
1179 id,
1180 level: *level,
1181 children,
1182 attributes,
1183 };
1184 let val = serde_json::to_value(&inner).map_err(S::Error::custom)?;
1185 serialize_block_as_map("heading", &val, serializer)
1186 }
1187 Self::List {
1188 id,
1189 ordered,
1190 start,
1191 children,
1192 attributes,
1193 } => {
1194 let inner = InlineList {
1195 id,
1196 ordered: *ordered,
1197 start,
1198 children,
1199 attributes,
1200 };
1201 let val = serde_json::to_value(&inner).map_err(S::Error::custom)?;
1202 serialize_block_as_map("list", &val, serializer)
1203 }
1204 Self::ListItem {
1205 id,
1206 checked,
1207 children,
1208 attributes,
1209 } => {
1210 let inner = InlineListItem {
1211 id,
1212 checked,
1213 children,
1214 attributes,
1215 };
1216 let val = serde_json::to_value(&inner).map_err(S::Error::custom)?;
1217 serialize_block_as_map("listItem", &val, serializer)
1218 }
1219 Self::Blockquote {
1220 id,
1221 children,
1222 attributes,
1223 } => {
1224 let inner = InlineContainer {
1225 id,
1226 children,
1227 attributes,
1228 };
1229 let val = serde_json::to_value(&inner).map_err(S::Error::custom)?;
1230 serialize_block_as_map("blockquote", &val, serializer)
1231 }
1232 Self::CodeBlock {
1233 id,
1234 language,
1235 highlighting,
1236 tokens,
1237 children,
1238 attributes,
1239 } => {
1240 let inner = InlineCodeBlock {
1241 id,
1242 language,
1243 highlighting,
1244 tokens,
1245 children,
1246 attributes,
1247 };
1248 let val = serde_json::to_value(&inner).map_err(S::Error::custom)?;
1249 serialize_block_as_map("codeBlock", &val, serializer)
1250 }
1251 Self::HorizontalRule { id } => {
1252 let inner = InlineIdOnly { id };
1253 let val = serde_json::to_value(&inner).map_err(S::Error::custom)?;
1254 serialize_block_as_map("horizontalRule", &val, serializer)
1255 }
1256 Self::Image(img) => {
1257 let val = serde_json::to_value(img).map_err(S::Error::custom)?;
1258 serialize_block_as_map("image", &val, serializer)
1259 }
1260 Self::Table {
1261 id,
1262 children,
1263 attributes,
1264 } => {
1265 let inner = InlineContainer {
1266 id,
1267 children,
1268 attributes,
1269 };
1270 let val = serde_json::to_value(&inner).map_err(S::Error::custom)?;
1271 serialize_block_as_map("table", &val, serializer)
1272 }
1273 Self::TableRow {
1274 id,
1275 header,
1276 children,
1277 attributes,
1278 } => {
1279 let inner = InlineTableRow {
1280 id,
1281 header: *header,
1282 children,
1283 attributes,
1284 };
1285 let val = serde_json::to_value(&inner).map_err(S::Error::custom)?;
1286 serialize_block_as_map("tableRow", &val, serializer)
1287 }
1288 Self::TableCell(cell) => {
1289 let val = serde_json::to_value(cell).map_err(S::Error::custom)?;
1290 serialize_block_as_map("tableCell", &val, serializer)
1291 }
1292 Self::Math(math) => {
1293 let val = serde_json::to_value(math).map_err(S::Error::custom)?;
1294 serialize_block_as_map("math", &val, serializer)
1295 }
1296 Self::Break { id } => {
1297 let inner = InlineIdOnly { id };
1298 let val = serde_json::to_value(&inner).map_err(S::Error::custom)?;
1299 serialize_block_as_map("break", &val, serializer)
1300 }
1301 Self::DefinitionList(dl) => {
1302 let val = serde_json::to_value(dl).map_err(S::Error::custom)?;
1303 serialize_block_as_map("definitionList", &val, serializer)
1304 }
1305 Self::DefinitionItem {
1306 id,
1307 children,
1308 attributes,
1309 } => {
1310 let inner = InlineContainer {
1311 id,
1312 children,
1313 attributes,
1314 };
1315 let val = serde_json::to_value(&inner).map_err(S::Error::custom)?;
1316 serialize_block_as_map("definitionItem", &val, serializer)
1317 }
1318 Self::DefinitionTerm {
1319 id,
1320 children,
1321 attributes,
1322 } => {
1323 let inner = InlineTextContainer {
1324 id,
1325 children,
1326 attributes,
1327 };
1328 let val = serde_json::to_value(&inner).map_err(S::Error::custom)?;
1329 serialize_block_as_map("definitionTerm", &val, serializer)
1330 }
1331 Self::DefinitionDescription {
1332 id,
1333 children,
1334 attributes,
1335 } => {
1336 let inner = InlineContainer {
1337 id,
1338 children,
1339 attributes,
1340 };
1341 let val = serde_json::to_value(&inner).map_err(S::Error::custom)?;
1342 serialize_block_as_map("definitionDescription", &val, serializer)
1343 }
1344 Self::Measurement(m) => {
1345 let val = serde_json::to_value(m).map_err(S::Error::custom)?;
1346 serialize_block_as_map("measurement", &val, serializer)
1347 }
1348 Self::Signature(sig) => {
1349 let val = serde_json::to_value(sig).map_err(S::Error::custom)?;
1350 serialize_block_as_map("signature", &val, serializer)
1351 }
1352 Self::Svg(svg) => {
1353 let val = serde_json::to_value(svg).map_err(S::Error::custom)?;
1354 serialize_block_as_map("svg", &val, serializer)
1355 }
1356 Self::Barcode(bc) => {
1357 let val = serde_json::to_value(bc).map_err(S::Error::custom)?;
1358 serialize_block_as_map("barcode", &val, serializer)
1359 }
1360 Self::Figure(fig) => {
1361 let val = serde_json::to_value(fig).map_err(S::Error::custom)?;
1362 serialize_block_as_map("figure", &val, serializer)
1363 }
1364 Self::FigCaption(fc) => {
1365 let val = serde_json::to_value(fc).map_err(S::Error::custom)?;
1366 serialize_block_as_map("figcaption", &val, serializer)
1367 }
1368 Self::Admonition(adm) => {
1369 let val = serde_json::to_value(adm).map_err(S::Error::custom)?;
1370 serialize_block_as_map("admonition", &val, serializer)
1371 }
1372 Self::Extension(ext) => {
1373 let type_str = ext.full_type();
1375 let attr_count = ext.attributes.as_object().map_or(0, serde_json::Map::len);
1376 let mut count = 1; if ext.id.is_some() {
1378 count += 1;
1379 }
1380 if !ext.children.is_empty() {
1381 count += 1;
1382 }
1383 if ext.fallback.is_some() {
1384 count += 1;
1385 }
1386 count += attr_count;
1387
1388 let mut map = serializer.serialize_map(Some(count))?;
1389 map.serialize_entry("type", &type_str)?;
1390 if let Some(id) = &ext.id {
1391 map.serialize_entry("id", id)?;
1392 }
1393 if !ext.children.is_empty() {
1394 map.serialize_entry("children", &ext.children)?;
1395 }
1396 if let Some(fallback) = &ext.fallback {
1397 map.serialize_entry("fallback", fallback)?;
1398 }
1399 if let Some(obj) = ext.attributes.as_object() {
1400 for (k, v) in obj {
1401 map.serialize_entry(k, v)?;
1402 }
1403 }
1404 map.end()
1405 }
1406 }
1407 }
1408}
1409
1410impl<'de> Deserialize<'de> for Block {
1411 #[allow(clippy::too_many_lines)] fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1413 let mut value = serde_json::Value::deserialize(deserializer)?;
1415
1416 let obj = value
1417 .as_object_mut()
1418 .ok_or_else(|| de::Error::custom("block must be an object"))?;
1419
1420 let type_str = obj
1421 .get("type")
1422 .and_then(serde_json::Value::as_str)
1423 .ok_or_else(|| de::Error::missing_field("type"))?
1424 .to_string();
1425
1426 obj.remove("type");
1428
1429 match type_str.as_str() {
1430 "paragraph" => {
1431 #[derive(Deserialize)]
1432 struct Inner {
1433 #[serde(default)]
1434 id: Option<String>,
1435 #[serde(default)]
1436 children: Vec<Text>,
1437 #[serde(default)]
1438 attributes: BlockAttributes,
1439 }
1440 let inner: Inner = serde_json::from_value(value).map_err(de::Error::custom)?;
1441 Ok(Block::Paragraph {
1442 id: inner.id,
1443 children: inner.children,
1444 attributes: inner.attributes,
1445 })
1446 }
1447 "heading" => {
1448 #[derive(Deserialize)]
1449 struct Inner {
1450 #[serde(default)]
1451 id: Option<String>,
1452 level: u8,
1453 #[serde(default)]
1454 children: Vec<Text>,
1455 #[serde(default)]
1456 attributes: BlockAttributes,
1457 }
1458 let inner: Inner = serde_json::from_value(value).map_err(de::Error::custom)?;
1459 Ok(Block::Heading {
1460 id: inner.id,
1461 level: inner.level,
1462 children: inner.children,
1463 attributes: inner.attributes,
1464 })
1465 }
1466 "list" => {
1467 #[derive(Deserialize)]
1468 struct Inner {
1469 #[serde(default)]
1470 id: Option<String>,
1471 #[serde(default)]
1472 ordered: bool,
1473 #[serde(default)]
1474 start: Option<u32>,
1475 #[serde(default)]
1476 children: Vec<Block>,
1477 #[serde(default)]
1478 attributes: BlockAttributes,
1479 }
1480 let inner: Inner = serde_json::from_value(value).map_err(de::Error::custom)?;
1481 Ok(Block::List {
1482 id: inner.id,
1483 ordered: inner.ordered,
1484 start: inner.start,
1485 children: inner.children,
1486 attributes: inner.attributes,
1487 })
1488 }
1489 "listItem" => {
1490 #[derive(Deserialize)]
1491 struct Inner {
1492 #[serde(default)]
1493 id: Option<String>,
1494 #[serde(default)]
1495 checked: Option<bool>,
1496 #[serde(default)]
1497 children: Vec<Block>,
1498 #[serde(default)]
1499 attributes: BlockAttributes,
1500 }
1501 let inner: Inner = serde_json::from_value(value).map_err(de::Error::custom)?;
1502 Ok(Block::ListItem {
1503 id: inner.id,
1504 checked: inner.checked,
1505 children: inner.children,
1506 attributes: inner.attributes,
1507 })
1508 }
1509 "blockquote" => {
1510 #[derive(Deserialize)]
1511 struct Inner {
1512 #[serde(default)]
1513 id: Option<String>,
1514 #[serde(default)]
1515 children: Vec<Block>,
1516 #[serde(default)]
1517 attributes: BlockAttributes,
1518 }
1519 let inner: Inner = serde_json::from_value(value).map_err(de::Error::custom)?;
1520 Ok(Block::Blockquote {
1521 id: inner.id,
1522 children: inner.children,
1523 attributes: inner.attributes,
1524 })
1525 }
1526 "codeBlock" => {
1527 #[derive(Deserialize)]
1528 struct Inner {
1529 #[serde(default)]
1530 id: Option<String>,
1531 #[serde(default)]
1532 language: Option<String>,
1533 #[serde(default)]
1534 highlighting: Option<String>,
1535 #[serde(default)]
1536 tokens: Option<Vec<CodeToken>>,
1537 #[serde(default)]
1538 children: Vec<Text>,
1539 #[serde(default)]
1540 attributes: BlockAttributes,
1541 }
1542 let inner: Inner = serde_json::from_value(value).map_err(de::Error::custom)?;
1543 Ok(Block::CodeBlock {
1544 id: inner.id,
1545 language: inner.language,
1546 highlighting: inner.highlighting,
1547 tokens: inner.tokens,
1548 children: inner.children,
1549 attributes: inner.attributes,
1550 })
1551 }
1552 "horizontalRule" => {
1553 #[derive(Deserialize)]
1554 struct Inner {
1555 #[serde(default)]
1556 id: Option<String>,
1557 }
1558 let inner: Inner = serde_json::from_value(value).map_err(de::Error::custom)?;
1559 Ok(Block::HorizontalRule { id: inner.id })
1560 }
1561 "image" => {
1562 let img: ImageBlock = serde_json::from_value(value).map_err(de::Error::custom)?;
1563 Ok(Block::Image(img))
1564 }
1565 "table" => {
1566 #[derive(Deserialize)]
1567 struct Inner {
1568 #[serde(default)]
1569 id: Option<String>,
1570 #[serde(default)]
1571 children: Vec<Block>,
1572 #[serde(default)]
1573 attributes: BlockAttributes,
1574 }
1575 let inner: Inner = serde_json::from_value(value).map_err(de::Error::custom)?;
1576 Ok(Block::Table {
1577 id: inner.id,
1578 children: inner.children,
1579 attributes: inner.attributes,
1580 })
1581 }
1582 "tableRow" => {
1583 #[derive(Deserialize)]
1584 struct Inner {
1585 #[serde(default)]
1586 id: Option<String>,
1587 #[serde(default)]
1588 header: bool,
1589 #[serde(default)]
1590 children: Vec<Block>,
1591 #[serde(default)]
1592 attributes: BlockAttributes,
1593 }
1594 let inner: Inner = serde_json::from_value(value).map_err(de::Error::custom)?;
1595 Ok(Block::TableRow {
1596 id: inner.id,
1597 header: inner.header,
1598 children: inner.children,
1599 attributes: inner.attributes,
1600 })
1601 }
1602 "tableCell" => {
1603 let cell: TableCellBlock =
1604 serde_json::from_value(value).map_err(de::Error::custom)?;
1605 Ok(Block::TableCell(cell))
1606 }
1607 "math" => {
1608 let math: MathBlock = serde_json::from_value(value).map_err(de::Error::custom)?;
1609 Ok(Block::Math(math))
1610 }
1611 "break" => {
1612 #[derive(Deserialize)]
1613 struct Inner {
1614 #[serde(default)]
1615 id: Option<String>,
1616 }
1617 let inner: Inner = serde_json::from_value(value).map_err(de::Error::custom)?;
1618 Ok(Block::Break { id: inner.id })
1619 }
1620 "definitionList" => {
1621 let dl: DefinitionListBlock =
1622 serde_json::from_value(value).map_err(de::Error::custom)?;
1623 Ok(Block::DefinitionList(dl))
1624 }
1625 "definitionItem" => {
1626 #[derive(Deserialize)]
1627 struct Inner {
1628 #[serde(default)]
1629 id: Option<String>,
1630 #[serde(default)]
1631 children: Vec<Block>,
1632 #[serde(default)]
1633 attributes: BlockAttributes,
1634 }
1635 let inner: Inner = serde_json::from_value(value).map_err(de::Error::custom)?;
1636 Ok(Block::DefinitionItem {
1637 id: inner.id,
1638 children: inner.children,
1639 attributes: inner.attributes,
1640 })
1641 }
1642 "definitionTerm" => {
1643 #[derive(Deserialize)]
1644 struct Inner {
1645 #[serde(default)]
1646 id: Option<String>,
1647 #[serde(default)]
1648 children: Vec<Text>,
1649 #[serde(default)]
1650 attributes: BlockAttributes,
1651 }
1652 let inner: Inner = serde_json::from_value(value).map_err(de::Error::custom)?;
1653 Ok(Block::DefinitionTerm {
1654 id: inner.id,
1655 children: inner.children,
1656 attributes: inner.attributes,
1657 })
1658 }
1659 "definitionDescription" => {
1660 #[derive(Deserialize)]
1661 struct Inner {
1662 #[serde(default)]
1663 id: Option<String>,
1664 #[serde(default)]
1665 children: Vec<Block>,
1666 #[serde(default)]
1667 attributes: BlockAttributes,
1668 }
1669 let inner: Inner = serde_json::from_value(value).map_err(de::Error::custom)?;
1670 Ok(Block::DefinitionDescription {
1671 id: inner.id,
1672 children: inner.children,
1673 attributes: inner.attributes,
1674 })
1675 }
1676 "measurement" => {
1677 let m: MeasurementBlock =
1678 serde_json::from_value(value).map_err(de::Error::custom)?;
1679 Ok(Block::Measurement(m))
1680 }
1681 "signature" => {
1682 let sig: SignatureBlock =
1683 serde_json::from_value(value).map_err(de::Error::custom)?;
1684 Ok(Block::Signature(sig))
1685 }
1686 "svg" => {
1687 let svg: SvgBlock = serde_json::from_value(value).map_err(de::Error::custom)?;
1688 Ok(Block::Svg(svg))
1689 }
1690 "barcode" => {
1691 let bc: BarcodeBlock = serde_json::from_value(value).map_err(de::Error::custom)?;
1692 Ok(Block::Barcode(bc))
1693 }
1694 "figure" => {
1695 let fig: FigureBlock = serde_json::from_value(value).map_err(de::Error::custom)?;
1696 Ok(Block::Figure(fig))
1697 }
1698 "figcaption" | "figCaption" => {
1699 let fc: FigCaptionBlock =
1701 serde_json::from_value(value).map_err(de::Error::custom)?;
1702 Ok(Block::FigCaption(fc))
1703 }
1704 "admonition" => {
1705 let adm: AdmonitionBlock =
1706 serde_json::from_value(value).map_err(de::Error::custom)?;
1707 Ok(Block::Admonition(adm))
1708 }
1709
1710 "extension" => {
1712 let ext: ExtensionBlock =
1713 serde_json::from_value(value).map_err(de::Error::custom)?;
1714 Ok(Block::Extension(ext))
1715 }
1716
1717 other if other.contains(':') => {
1719 let (namespace, block_type) = other.split_once(':').unwrap();
1720 let obj = value
1721 .as_object()
1722 .ok_or_else(|| de::Error::custom("expected object"))?;
1723
1724 let id = obj
1725 .get("id")
1726 .and_then(serde_json::Value::as_str)
1727 .map(ToString::to_string);
1728 let children: Vec<Block> = obj
1729 .get("children")
1730 .map(|v| serde_json::from_value(v.clone()))
1731 .transpose()
1732 .map_err(de::Error::custom)?
1733 .unwrap_or_default();
1734 let fallback: Option<Box<Block>> = obj
1735 .get("fallback")
1736 .map(|v| serde_json::from_value(v.clone()))
1737 .transpose()
1738 .map_err(de::Error::custom)?;
1739
1740 let reserved = ["id", "children", "fallback"];
1742 let mut attrs = serde_json::Map::new();
1743 for (k, v) in obj {
1744 if !reserved.contains(&k.as_str()) {
1745 attrs.insert(k.clone(), v.clone());
1746 }
1747 }
1748 let attributes = if attrs.is_empty() {
1749 serde_json::Value::Null
1750 } else {
1751 serde_json::Value::Object(attrs)
1752 };
1753
1754 Ok(Block::Extension(ExtensionBlock {
1755 namespace: namespace.to_string(),
1756 block_type: block_type.to_string(),
1757 id,
1758 attributes,
1759 children,
1760 fallback,
1761 }))
1762 }
1763
1764 unknown => Err(de::Error::custom(format!("unknown block type: {unknown}"))),
1765 }
1766 }
1767}
1768
1769impl Block {
1771 #[must_use]
1773 pub fn paragraph(children: Vec<Text>) -> Self {
1774 Self::Paragraph {
1775 id: None,
1776 children,
1777 attributes: BlockAttributes::default(),
1778 }
1779 }
1780
1781 #[must_use]
1783 pub fn heading(level: u8, children: Vec<Text>) -> Self {
1784 Self::Heading {
1785 id: None,
1786 level: level.clamp(1, 6),
1787 children,
1788 attributes: BlockAttributes::default(),
1789 }
1790 }
1791
1792 #[must_use]
1794 pub fn unordered_list(items: Vec<Block>) -> Self {
1795 Self::List {
1796 id: None,
1797 ordered: false,
1798 start: None,
1799 children: items,
1800 attributes: BlockAttributes::default(),
1801 }
1802 }
1803
1804 #[must_use]
1806 pub fn ordered_list(items: Vec<Block>) -> Self {
1807 Self::List {
1808 id: None,
1809 ordered: true,
1810 start: None,
1811 children: items,
1812 attributes: BlockAttributes::default(),
1813 }
1814 }
1815
1816 #[must_use]
1818 pub fn list_item(children: Vec<Block>) -> Self {
1819 Self::ListItem {
1820 id: None,
1821 checked: None,
1822 children,
1823 attributes: BlockAttributes::default(),
1824 }
1825 }
1826
1827 #[must_use]
1829 pub fn checkbox(checked: bool, children: Vec<Block>) -> Self {
1830 Self::ListItem {
1831 id: None,
1832 checked: Some(checked),
1833 children,
1834 attributes: BlockAttributes::default(),
1835 }
1836 }
1837
1838 #[must_use]
1840 pub fn blockquote(children: Vec<Block>) -> Self {
1841 Self::Blockquote {
1842 id: None,
1843 children,
1844 attributes: BlockAttributes::default(),
1845 }
1846 }
1847
1848 #[must_use]
1850 pub fn code_block(code: impl Into<String>, language: Option<String>) -> Self {
1851 Self::CodeBlock {
1852 id: None,
1853 language,
1854 highlighting: None,
1855 tokens: None,
1856 children: vec![Text::plain(code)],
1857 attributes: BlockAttributes::default(),
1858 }
1859 }
1860
1861 #[must_use]
1863 pub fn horizontal_rule() -> Self {
1864 Self::HorizontalRule { id: None }
1865 }
1866
1867 #[must_use]
1869 pub fn image(src: impl Into<String>, alt: impl Into<String>) -> Self {
1870 Self::Image(ImageBlock {
1871 id: None,
1872 src: src.into(),
1873 alt: alt.into(),
1874 title: None,
1875 width: None,
1876 height: None,
1877 })
1878 }
1879
1880 #[must_use]
1882 pub fn table(rows: Vec<Block>) -> Self {
1883 Self::Table {
1884 id: None,
1885 children: rows,
1886 attributes: BlockAttributes::default(),
1887 }
1888 }
1889
1890 #[must_use]
1892 pub fn table_row(cells: Vec<Block>, header: bool) -> Self {
1893 Self::TableRow {
1894 id: None,
1895 header,
1896 children: cells,
1897 attributes: BlockAttributes::default(),
1898 }
1899 }
1900
1901 #[must_use]
1903 pub fn table_cell(children: Vec<Text>) -> Self {
1904 Self::TableCell(TableCellBlock {
1905 id: None,
1906 colspan: 1,
1907 rowspan: 1,
1908 align: None,
1909 children,
1910 attributes: BlockAttributes::default(),
1911 })
1912 }
1913
1914 #[must_use]
1916 pub fn math(value: impl Into<String>, format: MathFormat, display: bool) -> Self {
1917 Self::Math(MathBlock {
1918 id: None,
1919 display,
1920 format,
1921 value: value.into(),
1922 })
1923 }
1924
1925 #[must_use]
1927 pub fn line_break() -> Self {
1928 Self::Break { id: None }
1929 }
1930
1931 #[must_use]
1933 pub fn definition_list(items: Vec<Block>) -> Self {
1934 Self::DefinitionList(DefinitionListBlock::new(items))
1935 }
1936
1937 #[must_use]
1939 pub fn definition_item(children: Vec<Block>) -> Self {
1940 Self::DefinitionItem {
1941 id: None,
1942 children,
1943 attributes: BlockAttributes::default(),
1944 }
1945 }
1946
1947 #[must_use]
1949 pub fn definition_term(children: Vec<Text>) -> Self {
1950 Self::DefinitionTerm {
1951 id: None,
1952 children,
1953 attributes: BlockAttributes::default(),
1954 }
1955 }
1956
1957 #[must_use]
1959 pub fn definition_description(children: Vec<Block>) -> Self {
1960 Self::DefinitionDescription {
1961 id: None,
1962 children,
1963 attributes: BlockAttributes::default(),
1964 }
1965 }
1966
1967 #[must_use]
1969 pub fn measurement(value: f64, display: impl Into<String>) -> Self {
1970 Self::Measurement(MeasurementBlock::new(value, display))
1971 }
1972
1973 #[must_use]
1975 pub fn signature(signature_type: BlockSignatureType) -> Self {
1976 Self::Signature(SignatureBlock::new(signature_type))
1977 }
1978
1979 #[must_use]
1981 pub fn svg_from_src(src: impl Into<String>) -> Self {
1982 Self::Svg(SvgBlock::from_src(src))
1983 }
1984
1985 #[must_use]
1987 pub fn svg_from_content(content: impl Into<String>) -> Self {
1988 Self::Svg(SvgBlock::from_content(content))
1989 }
1990
1991 #[must_use]
1993 pub fn barcode(format: BarcodeFormat, data: impl Into<String>, alt: impl Into<String>) -> Self {
1994 Self::Barcode(BarcodeBlock::new(format, data, alt))
1995 }
1996
1997 #[must_use]
1999 pub fn figure(children: Vec<Block>) -> Self {
2000 Self::Figure(FigureBlock::new(children))
2001 }
2002
2003 #[must_use]
2005 pub fn figcaption(children: Vec<Text>) -> Self {
2006 Self::FigCaption(FigCaptionBlock::new(children))
2007 }
2008
2009 #[must_use]
2011 pub fn admonition(variant: AdmonitionVariant, children: Vec<Block>) -> Self {
2012 Self::Admonition(AdmonitionBlock::new(variant, children))
2013 }
2014
2015 #[must_use]
2020 pub fn block_type(&self) -> Cow<'_, str> {
2021 match self {
2022 Self::Paragraph { .. } => Cow::Borrowed("paragraph"),
2023 Self::Heading { .. } => Cow::Borrowed("heading"),
2024 Self::List { .. } => Cow::Borrowed("list"),
2025 Self::ListItem { .. } => Cow::Borrowed("listItem"),
2026 Self::Blockquote { .. } => Cow::Borrowed("blockquote"),
2027 Self::CodeBlock { .. } => Cow::Borrowed("codeBlock"),
2028 Self::HorizontalRule { .. } => Cow::Borrowed("horizontalRule"),
2029 Self::Image(_) => Cow::Borrowed("image"),
2030 Self::Table { .. } => Cow::Borrowed("table"),
2031 Self::TableRow { .. } => Cow::Borrowed("tableRow"),
2032 Self::TableCell(_) => Cow::Borrowed("tableCell"),
2033 Self::Math(_) => Cow::Borrowed("math"),
2034 Self::Break { .. } => Cow::Borrowed("break"),
2035 Self::DefinitionList(_) => Cow::Borrowed("definitionList"),
2036 Self::DefinitionItem { .. } => Cow::Borrowed("definitionItem"),
2037 Self::DefinitionTerm { .. } => Cow::Borrowed("definitionTerm"),
2038 Self::DefinitionDescription { .. } => Cow::Borrowed("definitionDescription"),
2039 Self::Measurement(_) => Cow::Borrowed("measurement"),
2040 Self::Signature(_) => Cow::Borrowed("signature"),
2041 Self::Svg(_) => Cow::Borrowed("svg"),
2042 Self::Barcode(_) => Cow::Borrowed("barcode"),
2043 Self::Figure(_) => Cow::Borrowed("figure"),
2044 Self::FigCaption(_) => Cow::Borrowed("figcaption"),
2045 Self::Admonition(_) => Cow::Borrowed("admonition"),
2046 Self::Extension(ext) => Cow::Owned(ext.full_type()),
2047 }
2048 }
2049
2050 #[must_use]
2052 pub fn id(&self) -> Option<&str> {
2053 match self {
2054 Self::Paragraph { id, .. }
2055 | Self::Heading { id, .. }
2056 | Self::List { id, .. }
2057 | Self::ListItem { id, .. }
2058 | Self::Blockquote { id, .. }
2059 | Self::CodeBlock { id, .. }
2060 | Self::HorizontalRule { id }
2061 | Self::Table { id, .. }
2062 | Self::TableRow { id, .. }
2063 | Self::Break { id }
2064 | Self::DefinitionItem { id, .. }
2065 | Self::DefinitionTerm { id, .. }
2066 | Self::DefinitionDescription { id, .. } => id.as_deref(),
2067 Self::Image(img) => img.id.as_deref(),
2068 Self::TableCell(cell) => cell.id.as_deref(),
2069 Self::Math(math) => math.id.as_deref(),
2070 Self::DefinitionList(dl) => dl.id.as_deref(),
2071 Self::Measurement(m) => m.id.as_deref(),
2072 Self::Signature(sig) => sig.id.as_deref(),
2073 Self::Svg(svg) => svg.id.as_deref(),
2074 Self::Barcode(bc) => bc.id.as_deref(),
2075 Self::Figure(fig) => fig.id.as_deref(),
2076 Self::FigCaption(fc) => fc.id.as_deref(),
2077 Self::Admonition(adm) => adm.id.as_deref(),
2078 Self::Extension(ext) => ext.id.as_deref(),
2079 }
2080 }
2081
2082 #[must_use]
2084 pub fn extension(namespace: impl Into<String>, block_type: impl Into<String>) -> Self {
2085 Self::Extension(ExtensionBlock::new(namespace, block_type))
2086 }
2087
2088 #[must_use]
2090 pub fn is_extension(&self) -> bool {
2091 matches!(self, Self::Extension(_))
2092 }
2093
2094 #[must_use]
2096 pub fn as_extension(&self) -> Option<&ExtensionBlock> {
2097 match self {
2098 Self::Extension(ext) => Some(ext),
2099 _ => None,
2100 }
2101 }
2102}
2103
2104#[cfg(test)]
2105mod tests {
2106 use super::*;
2107
2108 #[test]
2109 fn test_content_new() {
2110 let content = Content::new(vec![Block::paragraph(vec![Text::plain("Hello")])]);
2111 assert_eq!(content.version, "0.1");
2112 assert_eq!(content.len(), 1);
2113 assert!(!content.is_empty());
2114 }
2115
2116 #[test]
2117 fn test_content_empty() {
2118 let content = Content::empty();
2119 assert!(content.is_empty());
2120 assert_eq!(content.len(), 0);
2121 }
2122
2123 #[test]
2124 fn test_paragraph() {
2125 let block = Block::paragraph(vec![Text::plain("Hello")]);
2126 assert_eq!(block.block_type(), "paragraph");
2127 assert!(block.id().is_none());
2128 }
2129
2130 #[test]
2131 fn test_heading() {
2132 let block = Block::heading(1, vec![Text::plain("Title")]);
2133 if let Block::Heading { level, .. } = &block {
2134 assert_eq!(*level, 1);
2135 } else {
2136 panic!("Expected Heading");
2137 }
2138
2139 let block = Block::heading(10, vec![Text::plain("Title")]);
2141 if let Block::Heading { level, .. } = &block {
2142 assert_eq!(*level, 6);
2143 }
2144 }
2145
2146 #[test]
2147 fn test_list() {
2148 let items = vec![
2149 Block::list_item(vec![Block::paragraph(vec![Text::plain("Item 1")])]),
2150 Block::list_item(vec![Block::paragraph(vec![Text::plain("Item 2")])]),
2151 ];
2152 let list = Block::unordered_list(items);
2153 assert_eq!(list.block_type(), "list");
2154 }
2155
2156 #[test]
2157 fn test_code_block() {
2158 let block = Block::code_block("fn main() {}", Some("rust".to_string()));
2159 if let Block::CodeBlock {
2160 language, children, ..
2161 } = &block
2162 {
2163 assert_eq!(language.as_deref(), Some("rust"));
2164 assert_eq!(children[0].value, "fn main() {}");
2165 } else {
2166 panic!("Expected CodeBlock");
2167 }
2168 }
2169
2170 #[test]
2171 fn test_image() {
2172 let block = Block::image("assets/photo.png", "A photo");
2173 if let Block::Image(img) = &block {
2174 assert_eq!(img.src, "assets/photo.png");
2175 assert_eq!(img.alt, "A photo");
2176 } else {
2177 panic!("Expected Image");
2178 }
2179 }
2180
2181 #[test]
2182 fn test_math() {
2183 let block = Block::math("E = mc^2", MathFormat::Latex, true);
2184 if let Block::Math(math) = &block {
2185 assert_eq!(math.value, "E = mc^2");
2186 assert_eq!(math.format, MathFormat::Latex);
2187 assert!(math.display);
2188 } else {
2189 panic!("Expected Math");
2190 }
2191 }
2192
2193 #[test]
2194 fn test_block_serialization() {
2195 let block = Block::paragraph(vec![Text::plain("Test")]);
2196 let json = serde_json::to_string(&block).unwrap();
2197 assert!(json.contains("\"type\":\"paragraph\""));
2198 }
2199
2200 #[test]
2201 fn test_content_serialization() {
2202 let content = Content::new(vec![
2203 Block::heading(1, vec![Text::plain("Title")]),
2204 Block::paragraph(vec![Text::plain("Body")]),
2205 ]);
2206 let json = serde_json::to_string_pretty(&content).unwrap();
2207 assert!(json.contains("\"version\": \"0.1\""));
2208 assert!(json.contains("\"type\": \"heading\""));
2209 assert!(json.contains("\"type\": \"paragraph\""));
2210 }
2211
2212 #[test]
2213 fn test_block_deserialization() {
2214 let json = r#"{
2215 "type": "heading",
2216 "level": 2,
2217 "children": [{"value": "Section"}]
2218 }"#;
2219 let block: Block = serde_json::from_str(json).unwrap();
2220 if let Block::Heading {
2221 level, children, ..
2222 } = block
2223 {
2224 assert_eq!(level, 2);
2225 assert_eq!(children[0].value, "Section");
2226 } else {
2227 panic!("Expected Heading");
2228 }
2229 }
2230
2231 #[test]
2232 fn test_table_serialization() {
2233 let table = Block::table(vec![Block::table_row(
2234 vec![Block::table_cell(vec![Text::plain("Header")])],
2235 true,
2236 )]);
2237 let json = serde_json::to_string(&table).unwrap();
2238 assert!(json.contains("\"type\":\"table\""));
2239 assert!(json.contains("\"type\":\"tableRow\""));
2240 assert!(json.contains("\"header\":true"));
2241 }
2242
2243 #[test]
2244 fn test_extension_block() {
2245 let ext = Block::extension("forms", "textInput");
2246 assert!(ext.is_extension());
2247 assert_eq!(ext.block_type(), "forms:textInput");
2248
2249 if let Block::Extension(inner) = &ext {
2250 assert_eq!(inner.namespace, "forms");
2251 assert_eq!(inner.block_type, "textInput");
2252 assert_eq!(inner.full_type(), "forms:textInput");
2253 } else {
2254 panic!("Expected Extension");
2255 }
2256 }
2257
2258 #[test]
2259 fn test_extension_as_extension() {
2260 let ext = Block::extension("semantic", "citation");
2261 let inner = ext.as_extension().expect("should be extension");
2262 assert_eq!(inner.namespace, "semantic");
2263
2264 let para = Block::paragraph(vec![Text::plain("Not extension")]);
2265 assert!(para.as_extension().is_none());
2266 }
2267
2268 #[test]
2269 fn test_extension_with_fallback() {
2270 let fallback = Block::paragraph(vec![Text::plain("[Form field]")]);
2271 let ext = ExtensionBlock::new("forms", "textInput")
2272 .with_id("name-field")
2273 .with_fallback(fallback);
2274
2275 assert_eq!(ext.id, Some("name-field".to_string()));
2276 assert!(ext.fallback_content().is_some());
2277 }
2278
2279 #[test]
2282 fn test_definition_list() {
2283 let dl = Block::definition_list(vec![Block::definition_item(vec![
2284 Block::definition_term(vec![Text::plain("Term")]),
2285 Block::definition_description(vec![Block::paragraph(vec![Text::plain("Description")])]),
2286 ])]);
2287 assert_eq!(dl.block_type(), "definitionList");
2288 }
2289
2290 #[test]
2291 fn test_measurement() {
2292 let m = Block::measurement(9.81, "9.81 m/s²");
2293 assert_eq!(m.block_type(), "measurement");
2294 if let Block::Measurement(meas) = &m {
2295 assert!((meas.value - 9.81).abs() < 0.001);
2296 assert_eq!(meas.display, "9.81 m/s²");
2297 } else {
2298 panic!("Expected Measurement");
2299 }
2300 }
2301
2302 #[test]
2303 fn test_measurement_with_uncertainty() {
2304 let m = MeasurementBlock::new(1.234, "1.234(5) m")
2305 .with_unit("m")
2306 .with_uncertainty(0.005, UncertaintyNotation::Parenthetical);
2307 assert_eq!(m.unit, Some("m".to_string()));
2308 assert!(m.uncertainty.is_some());
2309 assert_eq!(
2310 m.uncertainty_notation,
2311 Some(UncertaintyNotation::Parenthetical)
2312 );
2313 }
2314
2315 #[test]
2316 fn test_signature() {
2317 let sig = Block::signature(BlockSignatureType::Digital);
2318 assert_eq!(sig.block_type(), "signature");
2319 if let Block::Signature(s) = &sig {
2320 assert_eq!(s.signature_type, BlockSignatureType::Digital);
2321 } else {
2322 panic!("Expected Signature");
2323 }
2324 }
2325
2326 #[test]
2327 fn test_signature_with_signer() {
2328 let signer = SignerDetails::new("John Doe")
2329 .with_title("CEO")
2330 .with_organization("Acme Corp");
2331 let sig = SignatureBlock::new(BlockSignatureType::Handwritten)
2332 .with_signer(signer)
2333 .with_purpose(SignaturePurpose::Approval);
2334 assert!(sig.signer.is_some());
2335 assert_eq!(sig.purpose, Some(SignaturePurpose::Approval));
2336 }
2337
2338 #[test]
2339 fn test_svg_from_src() {
2340 let svg = Block::svg_from_src("diagram.svg");
2341 assert_eq!(svg.block_type(), "svg");
2342 if let Block::Svg(s) = &svg {
2343 assert_eq!(s.src, Some("diagram.svg".to_string()));
2344 assert!(s.content.is_none());
2345 } else {
2346 panic!("Expected Svg");
2347 }
2348 }
2349
2350 #[test]
2351 fn test_svg_from_content() {
2352 let svg = Block::svg_from_content("<svg>...</svg>");
2353 if let Block::Svg(s) = &svg {
2354 assert!(s.src.is_none());
2355 assert_eq!(s.content, Some("<svg>...</svg>".to_string()));
2356 } else {
2357 panic!("Expected Svg");
2358 }
2359 }
2360
2361 #[test]
2362 fn test_barcode() {
2363 let bc = Block::barcode(
2364 BarcodeFormat::Qr,
2365 "https://example.com",
2366 "Link to example.com",
2367 );
2368 assert_eq!(bc.block_type(), "barcode");
2369 if let Block::Barcode(b) = &bc {
2370 assert_eq!(b.format, BarcodeFormat::Qr);
2371 assert_eq!(b.data, "https://example.com");
2372 assert_eq!(b.alt, "Link to example.com");
2373 } else {
2374 panic!("Expected Barcode");
2375 }
2376 }
2377
2378 #[test]
2379 fn test_barcode_with_options() {
2380 let bc = BarcodeBlock::new(BarcodeFormat::Qr, "data", "Meaningful alt text")
2381 .with_error_correction(ErrorCorrectionLevel::H)
2382 .with_size("2in".to_string(), "2in".to_string());
2383 assert_eq!(bc.error_correction, Some(ErrorCorrectionLevel::H));
2384 assert!(bc.size.is_some());
2385 }
2386
2387 #[test]
2388 fn test_figure() {
2389 let fig = Block::figure(vec![
2390 Block::image("photo.png", "A photo"),
2391 Block::figcaption(vec![Text::plain("Figure 1: A photo")]),
2392 ]);
2393 assert_eq!(fig.block_type(), "figure");
2394 }
2395
2396 #[test]
2397 fn test_figcaption() {
2398 let fc = Block::figcaption(vec![Text::plain("Caption text")]);
2399 assert_eq!(fc.block_type(), "figcaption");
2400 }
2401
2402 #[test]
2403 fn test_writing_mode_serialization() {
2404 let attrs = BlockAttributes {
2405 dir: None,
2406 lang: None,
2407 writing_mode: Some(WritingMode::VerticalRl),
2408 };
2409 let json = serde_json::to_string(&attrs).unwrap();
2410 assert!(json.contains("\"writingMode\":\"vertical-rl\""));
2411 }
2412
2413 #[test]
2414 fn test_writing_mode_deserialization() {
2415 let json = r#"{"writingMode":"vertical-lr"}"#;
2416 let attrs: BlockAttributes = serde_json::from_str(json).unwrap();
2417 assert_eq!(attrs.writing_mode, Some(WritingMode::VerticalLr));
2418 }
2419
2420 #[test]
2421 fn test_measurement_serialization() {
2422 let m = MeasurementBlock::new(299_792_458.0, "299,792,458 m/s")
2423 .with_unit("m/s")
2424 .with_exponent(8);
2425 let json = serde_json::to_string(&m).unwrap();
2426 assert!(json.contains("\"value\":299792458")); assert!(json.contains("\"unit\":\"m/s\""));
2428 assert!(json.contains("\"exponent\":8"));
2429 }
2430
2431 #[test]
2432 fn test_barcode_format_serialization() {
2433 let bc = BarcodeBlock::new(BarcodeFormat::DataMatrix, "ABC123", "Product code ABC123");
2434 let json = serde_json::to_string(&bc).unwrap();
2435 assert!(json.contains("\"format\":\"datamatrix\""));
2437 }
2438
2439 #[test]
2440 fn test_signature_type_serialization() {
2441 let sig = SignatureBlock::new(BlockSignatureType::Electronic);
2442 let json = serde_json::to_string(&sig).unwrap();
2443 assert!(json.contains("\"signatureType\":\"electronic\""));
2444 }
2445
2446 #[test]
2447 fn test_new_block_types_deserialization() {
2448 let json = r#"{"type":"definitionList","children":[]}"#;
2450 let block: Block = serde_json::from_str(json).unwrap();
2451 assert_eq!(block.block_type(), "definitionList");
2452
2453 let json = r#"{"type":"measurement","value":3.14159,"display":"π"}"#;
2455 let block: Block = serde_json::from_str(json).unwrap();
2456 assert_eq!(block.block_type(), "measurement");
2457
2458 let json = r#"{"type":"svg","src":"diagram.svg"}"#;
2460 let block: Block = serde_json::from_str(json).unwrap();
2461 assert_eq!(block.block_type(), "svg");
2462
2463 let json = r#"{"type":"barcode","format":"qr","data":"test","alt":"Test QR code"}"#;
2465 let block: Block = serde_json::from_str(json).unwrap();
2466 assert_eq!(block.block_type(), "barcode");
2467
2468 let json = r#"{"type":"figure","children":[]}"#;
2470 let block: Block = serde_json::from_str(json).unwrap();
2471 assert_eq!(block.block_type(), "figure");
2472 }
2473
2474 #[test]
2477 fn test_admonition() {
2478 let adm = Block::admonition(
2479 AdmonitionVariant::Warning,
2480 vec![Block::paragraph(vec![Text::plain("Be careful!")])],
2481 );
2482 assert_eq!(adm.block_type(), "admonition");
2483 if let Block::Admonition(a) = &adm {
2484 assert_eq!(a.variant, AdmonitionVariant::Warning);
2485 assert_eq!(a.children.len(), 1);
2486 } else {
2487 panic!("Expected Admonition");
2488 }
2489 }
2490
2491 #[test]
2492 fn test_admonition_with_title() {
2493 let adm = AdmonitionBlock::new(
2494 AdmonitionVariant::Note,
2495 vec![Block::paragraph(vec![Text::plain("Important info")])],
2496 )
2497 .with_title("Please Note")
2498 .with_id("note-1");
2499
2500 assert_eq!(adm.title, Some("Please Note".to_string()));
2501 assert_eq!(adm.id, Some("note-1".to_string()));
2502 }
2503
2504 #[test]
2505 fn test_admonition_serialization() {
2506 let adm = Block::admonition(
2507 AdmonitionVariant::Tip,
2508 vec![Block::paragraph(vec![Text::plain("Pro tip!")])],
2509 );
2510 let json = serde_json::to_string(&adm).unwrap();
2511 assert!(json.contains("\"type\":\"admonition\""));
2512 assert!(json.contains("\"variant\":\"tip\""));
2513 }
2514
2515 #[test]
2516 fn test_admonition_deserialization() {
2517 let json = r#"{
2518 "type": "admonition",
2519 "variant": "danger",
2520 "title": "Warning!",
2521 "children": [
2522 {"type": "paragraph", "children": [{"value": "Do not proceed!"}]}
2523 ]
2524 }"#;
2525 let block: Block = serde_json::from_str(json).unwrap();
2526 assert_eq!(block.block_type(), "admonition");
2527 if let Block::Admonition(adm) = block {
2528 assert_eq!(adm.variant, AdmonitionVariant::Danger);
2529 assert_eq!(adm.title, Some("Warning!".to_string()));
2530 assert_eq!(adm.children.len(), 1);
2531 } else {
2532 panic!("Expected Admonition");
2533 }
2534 }
2535
2536 #[test]
2537 fn test_admonition_variant_display() {
2538 assert_eq!(AdmonitionVariant::Note.to_string(), "Note");
2539 assert_eq!(AdmonitionVariant::Warning.to_string(), "Warning");
2540 assert_eq!(AdmonitionVariant::Important.to_string(), "Important");
2541 }
2542
2543 #[test]
2544 fn test_all_admonition_variants() {
2545 let variants = [
2546 (AdmonitionVariant::Note, "note"),
2547 (AdmonitionVariant::Tip, "tip"),
2548 (AdmonitionVariant::Info, "info"),
2549 (AdmonitionVariant::Warning, "warning"),
2550 (AdmonitionVariant::Caution, "caution"),
2551 (AdmonitionVariant::Danger, "danger"),
2552 (AdmonitionVariant::Important, "important"),
2553 (AdmonitionVariant::Example, "example"),
2554 ];
2555
2556 for (variant, expected_name) in variants {
2557 let adm = AdmonitionBlock::new(variant, vec![]);
2558 let json = serde_json::to_string(&adm).unwrap();
2559 assert!(
2560 json.contains(&format!("\"variant\":\"{expected_name}\"")),
2561 "Variant {variant:?} should serialize as {expected_name}",
2562 );
2563 }
2564 }
2565}
2566
2567#[cfg(test)]
2568mod proptests {
2569 use super::*;
2570 use proptest::prelude::*;
2571
2572 fn arb_text_content() -> impl Strategy<Value = String> {
2574 "[a-zA-Z0-9 .,!?]{0,100}".prop_map(|s| s)
2575 }
2576
2577 fn arb_optional_string() -> impl Strategy<Value = Option<String>> {
2579 prop_oneof![Just(None), "[a-zA-Z0-9_-]{1,20}".prop_map(Some),]
2580 }
2581
2582 fn arb_heading_level() -> impl Strategy<Value = u8> {
2584 1u8..=6u8
2585 }
2586
2587 fn arb_paragraph() -> impl Strategy<Value = Block> {
2589 (arb_optional_string(), arb_text_content()).prop_map(|(id, text)| {
2590 let mut block = Block::paragraph(vec![Text::plain(text)]);
2591 if let Block::Paragraph {
2592 id: ref mut block_id,
2593 ..
2594 } = block
2595 {
2596 *block_id = id;
2597 }
2598 block
2599 })
2600 }
2601
2602 fn arb_heading() -> impl Strategy<Value = Block> {
2604 (
2605 arb_optional_string(),
2606 arb_heading_level(),
2607 arb_text_content(),
2608 )
2609 .prop_map(|(id, level, text)| {
2610 let mut block = Block::heading(level, vec![Text::plain(text)]);
2611 if let Block::Heading {
2612 id: ref mut block_id,
2613 ..
2614 } = block
2615 {
2616 *block_id = id;
2617 }
2618 block
2619 })
2620 }
2621
2622 fn arb_code_block() -> impl Strategy<Value = Block> {
2624 (
2625 arb_optional_string(),
2626 arb_text_content(),
2627 arb_optional_string(),
2628 )
2629 .prop_map(|(id, code, language)| {
2630 let mut block = Block::code_block(code, language);
2631 if let Block::CodeBlock {
2632 id: ref mut block_id,
2633 ..
2634 } = block
2635 {
2636 *block_id = id;
2637 }
2638 block
2639 })
2640 }
2641
2642 fn arb_math_block() -> impl Strategy<Value = Block> {
2644 (
2645 arb_optional_string(),
2646 arb_text_content(),
2647 prop_oneof![Just(MathFormat::Latex), Just(MathFormat::Mathml)],
2648 any::<bool>(),
2649 )
2650 .prop_map(|(id, value, format, display)| {
2651 let mut block = Block::math(value, format, display);
2652 if let Block::Math(ref mut math) = block {
2653 math.id = id;
2654 }
2655 block
2656 })
2657 }
2658
2659 proptest! {
2660 #[test]
2662 fn paragraph_json_roundtrip(para in arb_paragraph()) {
2663 let json = serde_json::to_string(¶).unwrap();
2664 let parsed: Block = serde_json::from_str(&json).unwrap();
2665 prop_assert_eq!(para, parsed);
2666 }
2667
2668 #[test]
2670 fn heading_json_roundtrip(heading in arb_heading()) {
2671 let json = serde_json::to_string(&heading).unwrap();
2672 let parsed: Block = serde_json::from_str(&json).unwrap();
2673 prop_assert_eq!(heading, parsed);
2674 }
2675
2676 #[test]
2678 fn code_block_json_roundtrip(code in arb_code_block()) {
2679 let json = serde_json::to_string(&code).unwrap();
2680 let parsed: Block = serde_json::from_str(&json).unwrap();
2681 prop_assert_eq!(code, parsed);
2682 }
2683
2684 #[test]
2686 fn math_block_json_roundtrip(math in arb_math_block()) {
2687 let json = serde_json::to_string(&math).unwrap();
2688 let parsed: Block = serde_json::from_str(&json).unwrap();
2689 prop_assert_eq!(math, parsed);
2690 }
2691
2692 #[test]
2694 fn heading_level_clamped(level in any::<u8>()) {
2695 let block = Block::heading(level, vec![Text::plain("Test")]);
2696 if let Block::Heading { level: actual, .. } = block {
2697 prop_assert!((1..=6).contains(&actual));
2698 } else {
2699 prop_assert!(false, "Expected Heading block");
2700 }
2701 }
2702
2703 #[test]
2705 fn content_version_is_spec_version(blocks in prop::collection::vec(arb_paragraph(), 0..5)) {
2706 let blocks_len = blocks.len();
2707 let content = Content::new(blocks);
2708 prop_assert_eq!(&content.version, crate::SPEC_VERSION);
2709 prop_assert_eq!(content.len(), blocks_len);
2710 }
2711
2712 #[test]
2714 fn paragraph_block_type(para in arb_paragraph()) {
2715 prop_assert_eq!(para.block_type(), "paragraph");
2716 }
2717
2718 #[test]
2720 fn heading_block_type(heading in arb_heading()) {
2721 prop_assert_eq!(heading.block_type(), "heading");
2722 }
2723 }
2724
2725 #[test]
2726 fn code_block_highlighting_and_tokens_roundtrip() {
2727 let json = serde_json::json!({
2728 "type": "codeBlock",
2729 "value": "let x = 1;",
2730 "language": "rust",
2731 "children": [],
2732 "highlighting": "monokai",
2733 "tokens": [
2734 { "tokenType": "keyword", "value": "let" },
2735 { "tokenType": "identifier", "value": "x", "scope": "variable" }
2736 ]
2737 });
2738 let block: Block = serde_json::from_value(json).unwrap();
2739 if let Block::CodeBlock {
2740 highlighting,
2741 tokens,
2742 ..
2743 } = &block
2744 {
2745 assert_eq!(highlighting.as_deref(), Some("monokai"));
2746 let toks = tokens.as_ref().unwrap();
2747 assert_eq!(toks.len(), 2);
2748 assert_eq!(toks[0].token_type, "keyword");
2749 assert_eq!(toks[1].scope.as_deref(), Some("variable"));
2750 } else {
2751 panic!("Expected CodeBlock");
2752 }
2753 let serialized = serde_json::to_value(&block).unwrap();
2755 let parsed: Block = serde_json::from_value(serialized).unwrap();
2756 assert_eq!(block, parsed);
2757 }
2758
2759 #[test]
2760 fn code_block_without_new_fields_defaults_to_none() {
2761 let json = serde_json::json!({
2762 "type": "codeBlock",
2763 "value": "print('hello')",
2764 "language": "python",
2765 "children": []
2766 });
2767 let block: Block = serde_json::from_value(json).unwrap();
2768 if let Block::CodeBlock {
2769 highlighting,
2770 tokens,
2771 ..
2772 } = &block
2773 {
2774 assert!(highlighting.is_none());
2775 assert!(tokens.is_none());
2776 } else {
2777 panic!("Expected CodeBlock");
2778 }
2779 }
2780
2781 #[test]
2782 fn figure_numbering_serialization() {
2783 let json = serde_json::to_value(FigureNumbering::Auto).unwrap();
2785 assert_eq!(json, serde_json::json!("auto"));
2786
2787 let json = serde_json::to_value(FigureNumbering::Unnumbered).unwrap();
2789 assert_eq!(json, serde_json::json!("none"));
2790
2791 let json = serde_json::to_value(FigureNumbering::Number(3)).unwrap();
2793 assert_eq!(json, serde_json::json!(3));
2794
2795 let auto: FigureNumbering = serde_json::from_str("\"auto\"").unwrap();
2797 assert_eq!(auto, FigureNumbering::Auto);
2798 let unnumbered: FigureNumbering = serde_json::from_str("\"none\"").unwrap();
2799 assert_eq!(unnumbered, FigureNumbering::Unnumbered);
2800 let num: FigureNumbering = serde_json::from_str("3").unwrap();
2801 assert_eq!(num, FigureNumbering::Number(3));
2802 }
2803
2804 #[test]
2805 fn figure_with_numbering_and_subfigures_roundtrip() {
2806 let json = serde_json::json!({
2807 "type": "figure",
2808 "children": [
2809 { "type": "image", "src": "fig1.png", "alt": "Figure 1" }
2810 ],
2811 "numbering": "auto",
2812 "subfigures": [
2813 {
2814 "id": "sub-a",
2815 "label": "(a)",
2816 "children": [
2817 { "type": "paragraph", "children": [{ "type": "text", "value": "Sub A" }] }
2818 ]
2819 }
2820 ]
2821 });
2822 let block: Block = serde_json::from_value(json).unwrap();
2823 if let Block::Figure(fig) = &block {
2824 assert_eq!(fig.numbering, Some(FigureNumbering::Auto));
2825 let subs = fig.subfigures.as_ref().unwrap();
2826 assert_eq!(subs.len(), 1);
2827 assert_eq!(subs[0].id.as_deref(), Some("sub-a"));
2828 assert_eq!(subs[0].label.as_deref(), Some("(a)"));
2829 } else {
2830 panic!("Expected Figure block");
2831 }
2832 let serialized = serde_json::to_value(&block).unwrap();
2833 let parsed: Block = serde_json::from_value(serialized).unwrap();
2834 assert_eq!(block, parsed);
2835 }
2836
2837 #[test]
2838 fn figure_without_new_fields_defaults_to_none() {
2839 let json = serde_json::json!({
2840 "type": "figure",
2841 "children": []
2842 });
2843 let block: Block = serde_json::from_value(json).unwrap();
2844 if let Block::Figure(fig) = &block {
2845 assert!(fig.numbering.is_none());
2846 assert!(fig.subfigures.is_none());
2847 } else {
2848 panic!("Expected Figure block");
2849 }
2850 }
2851}