1use serde::{Deserialize, Serialize};
4
5use super::Text;
6use crate::extensions::ExtensionBlock;
7
8#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
13pub struct Content {
14 pub version: String,
16
17 pub blocks: Vec<Block>,
19}
20
21impl Content {
22 #[must_use]
24 pub fn new(blocks: Vec<Block>) -> Self {
25 Self {
26 version: crate::SPEC_VERSION.to_string(),
27 blocks,
28 }
29 }
30
31 #[must_use]
33 pub fn empty() -> Self {
34 Self::new(Vec::new())
35 }
36
37 #[must_use]
39 pub fn is_empty(&self) -> bool {
40 self.blocks.is_empty()
41 }
42
43 #[must_use]
45 pub fn len(&self) -> usize {
46 self.blocks.len()
47 }
48}
49
50impl Default for Content {
51 fn default() -> Self {
52 Self::empty()
53 }
54}
55
56#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
58#[serde(rename_all = "camelCase")]
59pub struct BlockAttributes {
60 #[serde(default, skip_serializing_if = "Option::is_none")]
62 pub dir: Option<String>,
63
64 #[serde(default, skip_serializing_if = "Option::is_none")]
66 pub lang: Option<String>,
67
68 #[serde(default, skip_serializing_if = "Option::is_none")]
70 pub writing_mode: Option<WritingMode>,
71}
72
73impl BlockAttributes {
74 #[must_use]
76 pub fn is_empty(&self) -> bool {
77 self.dir.is_none() && self.lang.is_none() && self.writing_mode.is_none()
78 }
79}
80
81#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
86#[serde(tag = "type", rename_all = "camelCase")]
87pub enum Block {
88 Paragraph {
90 #[serde(default, skip_serializing_if = "Option::is_none")]
92 id: Option<String>,
93
94 children: Vec<Text>,
96
97 #[serde(default, skip_serializing_if = "BlockAttributes::is_empty")]
99 attributes: BlockAttributes,
100 },
101
102 Heading {
104 #[serde(default, skip_serializing_if = "Option::is_none")]
106 id: Option<String>,
107
108 level: u8,
110
111 children: Vec<Text>,
113
114 #[serde(default, skip_serializing_if = "BlockAttributes::is_empty")]
116 attributes: BlockAttributes,
117 },
118
119 List {
121 #[serde(default, skip_serializing_if = "Option::is_none")]
123 id: Option<String>,
124
125 ordered: bool,
127
128 #[serde(default, skip_serializing_if = "Option::is_none")]
130 start: Option<u32>,
131
132 children: Vec<Block>,
134
135 #[serde(default, skip_serializing_if = "BlockAttributes::is_empty")]
137 attributes: BlockAttributes,
138 },
139
140 ListItem {
142 #[serde(default, skip_serializing_if = "Option::is_none")]
144 id: Option<String>,
145
146 #[serde(default, skip_serializing_if = "Option::is_none")]
148 checked: Option<bool>,
149
150 children: Vec<Block>,
152
153 #[serde(default, skip_serializing_if = "BlockAttributes::is_empty")]
155 attributes: BlockAttributes,
156 },
157
158 Blockquote {
160 #[serde(default, skip_serializing_if = "Option::is_none")]
162 id: Option<String>,
163
164 children: Vec<Block>,
166
167 #[serde(default, skip_serializing_if = "BlockAttributes::is_empty")]
169 attributes: BlockAttributes,
170 },
171
172 CodeBlock {
174 #[serde(default, skip_serializing_if = "Option::is_none")]
176 id: Option<String>,
177
178 #[serde(default, skip_serializing_if = "Option::is_none")]
180 language: Option<String>,
181
182 #[serde(default, skip_serializing_if = "Option::is_none")]
184 highlighting: Option<String>,
185
186 #[serde(default, skip_serializing_if = "Option::is_none")]
188 tokens: Option<Vec<CodeToken>>,
189
190 children: Vec<Text>,
192
193 #[serde(default, skip_serializing_if = "BlockAttributes::is_empty")]
195 attributes: BlockAttributes,
196 },
197
198 HorizontalRule {
200 #[serde(default, skip_serializing_if = "Option::is_none")]
202 id: Option<String>,
203 },
204
205 Image(ImageBlock),
207
208 Table {
210 #[serde(default, skip_serializing_if = "Option::is_none")]
212 id: Option<String>,
213
214 children: Vec<Block>,
216
217 #[serde(default, skip_serializing_if = "BlockAttributes::is_empty")]
219 attributes: BlockAttributes,
220 },
221
222 TableRow {
224 #[serde(default, skip_serializing_if = "Option::is_none")]
226 id: Option<String>,
227
228 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
230 header: bool,
231
232 children: Vec<Block>,
234
235 #[serde(default, skip_serializing_if = "BlockAttributes::is_empty")]
237 attributes: BlockAttributes,
238 },
239
240 TableCell(TableCellBlock),
242
243 Math(MathBlock),
245
246 Break {
248 #[serde(default, skip_serializing_if = "Option::is_none")]
250 id: Option<String>,
251 },
252
253 DefinitionList(DefinitionListBlock),
255
256 DefinitionItem {
258 #[serde(default, skip_serializing_if = "Option::is_none")]
260 id: Option<String>,
261
262 children: Vec<Block>,
264
265 #[serde(default, skip_serializing_if = "BlockAttributes::is_empty")]
267 attributes: BlockAttributes,
268 },
269
270 DefinitionTerm {
272 #[serde(default, skip_serializing_if = "Option::is_none")]
274 id: Option<String>,
275
276 children: Vec<Text>,
278
279 #[serde(default, skip_serializing_if = "BlockAttributes::is_empty")]
281 attributes: BlockAttributes,
282 },
283
284 DefinitionDescription {
286 #[serde(default, skip_serializing_if = "Option::is_none")]
288 id: Option<String>,
289
290 children: Vec<Block>,
292
293 #[serde(default, skip_serializing_if = "BlockAttributes::is_empty")]
295 attributes: BlockAttributes,
296 },
297
298 Measurement(MeasurementBlock),
300
301 Signature(SignatureBlock),
303
304 Svg(SvgBlock),
306
307 Barcode(BarcodeBlock),
309
310 Figure(FigureBlock),
312
313 FigCaption(FigCaptionBlock),
315
316 Admonition(AdmonitionBlock),
318
319 Extension(ExtensionBlock),
325}
326
327#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
329pub struct ImageBlock {
330 #[serde(default, skip_serializing_if = "Option::is_none")]
332 pub id: Option<String>,
333
334 pub src: String,
336
337 pub alt: String,
339
340 #[serde(default, skip_serializing_if = "Option::is_none")]
342 pub title: Option<String>,
343
344 #[serde(default, skip_serializing_if = "Option::is_none")]
346 pub width: Option<u32>,
347
348 #[serde(default, skip_serializing_if = "Option::is_none")]
350 pub height: Option<u32>,
351}
352
353#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
355pub struct TableCellBlock {
356 #[serde(default, skip_serializing_if = "Option::is_none")]
358 pub id: Option<String>,
359
360 #[serde(default = "default_span", skip_serializing_if = "is_default_span")]
362 pub colspan: u32,
363
364 #[serde(default = "default_span", skip_serializing_if = "is_default_span")]
366 pub rowspan: u32,
367
368 #[serde(default, skip_serializing_if = "Option::is_none")]
370 pub align: Option<CellAlign>,
371
372 pub children: Vec<Text>,
374
375 #[serde(default, skip_serializing_if = "BlockAttributes::is_empty")]
377 pub attributes: BlockAttributes,
378}
379
380fn default_span() -> u32 {
381 1
382}
383
384#[allow(clippy::trivially_copy_pass_by_ref)] fn is_default_span(span: &u32) -> bool {
386 *span == 1
387}
388
389#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
391#[serde(rename_all = "lowercase")]
392pub enum CellAlign {
393 Left,
395 Center,
397 Right,
399}
400
401#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
403pub struct MathBlock {
404 #[serde(default, skip_serializing_if = "Option::is_none")]
406 pub id: Option<String>,
407
408 pub display: bool,
410
411 pub format: MathFormat,
413
414 pub value: String,
416}
417
418#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
420#[serde(rename_all = "lowercase")]
421pub enum MathFormat {
422 Latex,
424 Mathml,
426}
427
428#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
434#[serde(rename_all = "kebab-case")]
435pub enum WritingMode {
436 #[default]
439 HorizontalTb,
440
441 VerticalRl,
444
445 VerticalLr,
448
449 SidewaysRl,
451
452 SidewaysLr,
454}
455
456#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
458#[serde(rename_all = "camelCase")]
459pub struct MeasurementBlock {
460 #[serde(default, skip_serializing_if = "Option::is_none")]
462 pub id: Option<String>,
463
464 pub value: f64,
466
467 #[serde(default, skip_serializing_if = "Option::is_none")]
469 pub uncertainty: Option<f64>,
470
471 #[serde(default, skip_serializing_if = "Option::is_none")]
473 pub uncertainty_notation: Option<UncertaintyNotation>,
474
475 #[serde(default, skip_serializing_if = "Option::is_none")]
477 pub exponent: Option<i32>,
478
479 pub display: String,
481
482 #[serde(default, skip_serializing_if = "Option::is_none")]
484 pub unit: Option<String>,
485}
486
487impl MeasurementBlock {
488 #[must_use]
490 pub fn new(value: f64, display: impl Into<String>) -> Self {
491 Self {
492 id: None,
493 value,
494 uncertainty: None,
495 uncertainty_notation: None,
496 exponent: None,
497 display: display.into(),
498 unit: None,
499 }
500 }
501
502 #[must_use]
504 pub fn with_unit(mut self, unit: impl Into<String>) -> Self {
505 self.unit = Some(unit.into());
506 self
507 }
508
509 #[must_use]
511 pub fn with_uncertainty(mut self, uncertainty: f64, notation: UncertaintyNotation) -> Self {
512 self.uncertainty = Some(uncertainty);
513 self.uncertainty_notation = Some(notation);
514 self
515 }
516
517 #[must_use]
519 pub fn with_exponent(mut self, exponent: i32) -> Self {
520 self.exponent = Some(exponent);
521 self
522 }
523}
524
525#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
527#[serde(rename_all = "lowercase")]
528pub enum UncertaintyNotation {
529 Parenthetical,
531 Plusminus,
533 Range,
535 Percent,
537}
538
539#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
541#[serde(rename_all = "camelCase")]
542pub struct SignatureBlock {
543 #[serde(default, skip_serializing_if = "Option::is_none")]
545 pub id: Option<String>,
546
547 pub signature_type: BlockSignatureType,
549
550 #[serde(default, skip_serializing_if = "Option::is_none")]
552 pub signer: Option<SignerDetails>,
553
554 #[serde(default, skip_serializing_if = "Option::is_none")]
556 pub timestamp: Option<String>,
557
558 #[serde(default, skip_serializing_if = "Option::is_none")]
560 pub purpose: Option<SignaturePurpose>,
561
562 #[serde(default, skip_serializing_if = "Option::is_none")]
564 pub digital_signature_ref: Option<String>,
565}
566
567impl SignatureBlock {
568 #[must_use]
570 pub fn new(signature_type: BlockSignatureType) -> Self {
571 Self {
572 id: None,
573 signature_type,
574 signer: None,
575 timestamp: None,
576 purpose: None,
577 digital_signature_ref: None,
578 }
579 }
580
581 #[must_use]
583 pub fn with_signer(mut self, signer: SignerDetails) -> Self {
584 self.signer = Some(signer);
585 self
586 }
587
588 #[must_use]
590 pub fn with_purpose(mut self, purpose: SignaturePurpose) -> Self {
591 self.purpose = Some(purpose);
592 self
593 }
594}
595
596#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
598#[serde(rename_all = "lowercase")]
599pub enum BlockSignatureType {
600 Handwritten,
602 Digital,
604 Electronic,
606 Stamp,
608}
609
610#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
612#[serde(rename_all = "lowercase")]
613pub enum SignaturePurpose {
614 Certification,
616 Approval,
618 Witness,
620 Acknowledgment,
622 Authorship,
624}
625
626#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
628pub struct SignerDetails {
629 pub name: String,
631
632 #[serde(default, skip_serializing_if = "Option::is_none")]
634 pub title: Option<String>,
635
636 #[serde(default, skip_serializing_if = "Option::is_none")]
638 pub organization: Option<String>,
639
640 #[serde(default, skip_serializing_if = "Option::is_none")]
642 pub email: Option<String>,
643
644 #[serde(default, skip_serializing_if = "Option::is_none")]
646 pub id: Option<String>,
647}
648
649impl SignerDetails {
650 #[must_use]
652 pub fn new(name: impl Into<String>) -> Self {
653 Self {
654 name: name.into(),
655 title: None,
656 organization: None,
657 email: None,
658 id: None,
659 }
660 }
661
662 #[must_use]
664 pub fn with_title(mut self, title: impl Into<String>) -> Self {
665 self.title = Some(title.into());
666 self
667 }
668
669 #[must_use]
671 pub fn with_organization(mut self, organization: impl Into<String>) -> Self {
672 self.organization = Some(organization.into());
673 self
674 }
675}
676
677#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
679pub struct SvgBlock {
680 #[serde(default, skip_serializing_if = "Option::is_none")]
682 pub id: Option<String>,
683
684 #[serde(default, skip_serializing_if = "Option::is_none")]
686 pub src: Option<String>,
687
688 #[serde(default, skip_serializing_if = "Option::is_none")]
690 pub content: Option<String>,
691
692 #[serde(default, skip_serializing_if = "Option::is_none")]
694 pub width: Option<u32>,
695
696 #[serde(default, skip_serializing_if = "Option::is_none")]
698 pub height: Option<u32>,
699
700 #[serde(default, skip_serializing_if = "Option::is_none")]
702 pub alt: Option<String>,
703}
704
705impl SvgBlock {
706 #[must_use]
708 pub fn from_src(src: impl Into<String>) -> Self {
709 Self {
710 id: None,
711 src: Some(src.into()),
712 content: None,
713 width: None,
714 height: None,
715 alt: None,
716 }
717 }
718
719 #[must_use]
721 pub fn from_content(content: impl Into<String>) -> Self {
722 Self {
723 id: None,
724 src: None,
725 content: Some(content.into()),
726 width: None,
727 height: None,
728 alt: None,
729 }
730 }
731
732 #[must_use]
734 pub fn with_alt(mut self, alt: impl Into<String>) -> Self {
735 self.alt = Some(alt.into());
736 self
737 }
738
739 #[must_use]
741 pub fn with_dimensions(mut self, width: u32, height: u32) -> Self {
742 self.width = Some(width);
743 self.height = Some(height);
744 self
745 }
746}
747
748#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
750#[serde(rename_all = "camelCase")]
751pub struct BarcodeBlock {
752 #[serde(default, skip_serializing_if = "Option::is_none")]
754 pub id: Option<String>,
755
756 pub format: BarcodeFormat,
758
759 pub data: String,
761
762 #[serde(default, skip_serializing_if = "Option::is_none")]
764 pub error_correction: Option<ErrorCorrectionLevel>,
765
766 #[serde(default, skip_serializing_if = "Option::is_none")]
768 pub size: Option<BarcodeSize>,
769
770 #[serde(default, skip_serializing_if = "Option::is_none")]
772 pub quiet_zone: Option<String>,
773
774 pub alt: String,
776}
777
778impl BarcodeBlock {
779 #[must_use]
781 pub fn new(format: BarcodeFormat, data: impl Into<String>, alt: impl Into<String>) -> Self {
782 Self {
783 id: None,
784 format,
785 data: data.into(),
786 error_correction: None,
787 size: None,
788 quiet_zone: None,
789 alt: alt.into(),
790 }
791 }
792
793 #[must_use]
795 pub fn with_error_correction(mut self, level: ErrorCorrectionLevel) -> Self {
796 self.error_correction = Some(level);
797 self
798 }
799
800 #[must_use]
802 pub fn with_size(mut self, width: String, height: String) -> Self {
803 self.size = Some(BarcodeSize { width, height });
804 self
805 }
806}
807
808#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
810#[serde(rename_all = "lowercase")]
811pub enum BarcodeFormat {
812 Qr,
814 DataMatrix,
816 Code128,
818 Code39,
820 Ean13,
822 Ean8,
824 UpcA,
826 Pdf417,
828}
829
830#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
832pub enum ErrorCorrectionLevel {
833 L,
835 M,
837 Q,
839 H,
841}
842
843#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
845pub struct BarcodeSize {
846 pub width: String,
848 pub height: String,
850}
851
852#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
854pub struct FigureBlock {
855 #[serde(default, skip_serializing_if = "Option::is_none")]
857 pub id: Option<String>,
858
859 #[serde(default, skip_serializing_if = "Option::is_none")]
861 pub numbering: Option<FigureNumbering>,
862
863 #[serde(default, skip_serializing_if = "Option::is_none")]
865 pub subfigures: Option<Vec<Subfigure>>,
866
867 pub children: Vec<Block>,
869
870 #[serde(default, skip_serializing_if = "BlockAttributes::is_empty")]
872 pub attributes: BlockAttributes,
873}
874
875impl FigureBlock {
876 #[must_use]
878 pub fn new(children: Vec<Block>) -> Self {
879 Self {
880 id: None,
881 numbering: None,
882 subfigures: None,
883 children,
884 attributes: BlockAttributes::default(),
885 }
886 }
887}
888
889#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
891#[serde(rename_all = "camelCase")]
892pub enum FigureNumbering {
893 Auto,
895 #[serde(rename = "none")]
897 Unnumbered,
898 #[serde(untagged)]
900 Number(u32),
901}
902
903#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
905pub struct Subfigure {
906 #[serde(default, skip_serializing_if = "Option::is_none")]
908 pub id: Option<String>,
909
910 #[serde(default, skip_serializing_if = "Option::is_none")]
912 pub label: Option<String>,
913
914 pub children: Vec<Block>,
916}
917
918#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
920#[serde(rename_all = "camelCase")]
921pub struct CodeToken {
922 pub token_type: String,
924
925 pub value: String,
927
928 #[serde(default, skip_serializing_if = "Option::is_none")]
930 pub scope: Option<String>,
931}
932
933#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
935pub struct FigCaptionBlock {
936 #[serde(default, skip_serializing_if = "Option::is_none")]
938 pub id: Option<String>,
939
940 pub children: Vec<Text>,
942
943 #[serde(default, skip_serializing_if = "BlockAttributes::is_empty")]
945 pub attributes: BlockAttributes,
946}
947
948impl FigCaptionBlock {
949 #[must_use]
951 pub fn new(children: Vec<Text>) -> Self {
952 Self {
953 id: None,
954 children,
955 attributes: BlockAttributes::default(),
956 }
957 }
958}
959
960#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
962#[serde(rename_all = "camelCase")]
963pub struct AdmonitionBlock {
964 #[serde(default, skip_serializing_if = "Option::is_none")]
966 pub id: Option<String>,
967
968 pub variant: AdmonitionVariant,
970
971 #[serde(default, skip_serializing_if = "Option::is_none")]
973 pub title: Option<String>,
974
975 pub children: Vec<Block>,
977
978 #[serde(default, skip_serializing_if = "BlockAttributes::is_empty")]
980 pub attributes: BlockAttributes,
981}
982
983impl AdmonitionBlock {
984 #[must_use]
986 pub fn new(variant: AdmonitionVariant, children: Vec<Block>) -> Self {
987 Self {
988 id: None,
989 variant,
990 title: None,
991 children,
992 attributes: BlockAttributes::default(),
993 }
994 }
995
996 #[must_use]
998 pub fn with_title(mut self, title: impl Into<String>) -> Self {
999 self.title = Some(title.into());
1000 self
1001 }
1002
1003 #[must_use]
1005 pub fn with_id(mut self, id: impl Into<String>) -> Self {
1006 self.id = Some(id.into());
1007 self
1008 }
1009}
1010
1011#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, strum::Display)]
1013#[serde(rename_all = "lowercase")]
1014pub enum AdmonitionVariant {
1015 Note,
1017 Tip,
1019 Info,
1021 Warning,
1023 Caution,
1025 Danger,
1027 Important,
1029 Example,
1031}
1032
1033#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1035pub struct DefinitionListBlock {
1036 #[serde(default, skip_serializing_if = "Option::is_none")]
1038 pub id: Option<String>,
1039
1040 pub children: Vec<Block>,
1042
1043 #[serde(default, skip_serializing_if = "BlockAttributes::is_empty")]
1045 pub attributes: BlockAttributes,
1046}
1047
1048impl DefinitionListBlock {
1049 #[must_use]
1051 pub fn new(children: Vec<Block>) -> Self {
1052 Self {
1053 id: None,
1054 children,
1055 attributes: BlockAttributes::default(),
1056 }
1057 }
1058}
1059
1060impl Block {
1062 #[must_use]
1064 pub fn paragraph(children: Vec<Text>) -> Self {
1065 Self::Paragraph {
1066 id: None,
1067 children,
1068 attributes: BlockAttributes::default(),
1069 }
1070 }
1071
1072 #[must_use]
1074 pub fn heading(level: u8, children: Vec<Text>) -> Self {
1075 Self::Heading {
1076 id: None,
1077 level: level.clamp(1, 6),
1078 children,
1079 attributes: BlockAttributes::default(),
1080 }
1081 }
1082
1083 #[must_use]
1085 pub fn unordered_list(items: Vec<Block>) -> Self {
1086 Self::List {
1087 id: None,
1088 ordered: false,
1089 start: None,
1090 children: items,
1091 attributes: BlockAttributes::default(),
1092 }
1093 }
1094
1095 #[must_use]
1097 pub fn ordered_list(items: Vec<Block>) -> Self {
1098 Self::List {
1099 id: None,
1100 ordered: true,
1101 start: None,
1102 children: items,
1103 attributes: BlockAttributes::default(),
1104 }
1105 }
1106
1107 #[must_use]
1109 pub fn list_item(children: Vec<Block>) -> Self {
1110 Self::ListItem {
1111 id: None,
1112 checked: None,
1113 children,
1114 attributes: BlockAttributes::default(),
1115 }
1116 }
1117
1118 #[must_use]
1120 pub fn checkbox(checked: bool, children: Vec<Block>) -> Self {
1121 Self::ListItem {
1122 id: None,
1123 checked: Some(checked),
1124 children,
1125 attributes: BlockAttributes::default(),
1126 }
1127 }
1128
1129 #[must_use]
1131 pub fn blockquote(children: Vec<Block>) -> Self {
1132 Self::Blockquote {
1133 id: None,
1134 children,
1135 attributes: BlockAttributes::default(),
1136 }
1137 }
1138
1139 #[must_use]
1141 pub fn code_block(code: impl Into<String>, language: Option<String>) -> Self {
1142 Self::CodeBlock {
1143 id: None,
1144 language,
1145 highlighting: None,
1146 tokens: None,
1147 children: vec![Text::plain(code)],
1148 attributes: BlockAttributes::default(),
1149 }
1150 }
1151
1152 #[must_use]
1154 pub fn horizontal_rule() -> Self {
1155 Self::HorizontalRule { id: None }
1156 }
1157
1158 #[must_use]
1160 pub fn image(src: impl Into<String>, alt: impl Into<String>) -> Self {
1161 Self::Image(ImageBlock {
1162 id: None,
1163 src: src.into(),
1164 alt: alt.into(),
1165 title: None,
1166 width: None,
1167 height: None,
1168 })
1169 }
1170
1171 #[must_use]
1173 pub fn table(rows: Vec<Block>) -> Self {
1174 Self::Table {
1175 id: None,
1176 children: rows,
1177 attributes: BlockAttributes::default(),
1178 }
1179 }
1180
1181 #[must_use]
1183 pub fn table_row(cells: Vec<Block>, header: bool) -> Self {
1184 Self::TableRow {
1185 id: None,
1186 header,
1187 children: cells,
1188 attributes: BlockAttributes::default(),
1189 }
1190 }
1191
1192 #[must_use]
1194 pub fn table_cell(children: Vec<Text>) -> Self {
1195 Self::TableCell(TableCellBlock {
1196 id: None,
1197 colspan: 1,
1198 rowspan: 1,
1199 align: None,
1200 children,
1201 attributes: BlockAttributes::default(),
1202 })
1203 }
1204
1205 #[must_use]
1207 pub fn math(value: impl Into<String>, format: MathFormat, display: bool) -> Self {
1208 Self::Math(MathBlock {
1209 id: None,
1210 display,
1211 format,
1212 value: value.into(),
1213 })
1214 }
1215
1216 #[must_use]
1218 pub fn line_break() -> Self {
1219 Self::Break { id: None }
1220 }
1221
1222 #[must_use]
1224 pub fn definition_list(items: Vec<Block>) -> Self {
1225 Self::DefinitionList(DefinitionListBlock::new(items))
1226 }
1227
1228 #[must_use]
1230 pub fn definition_item(children: Vec<Block>) -> Self {
1231 Self::DefinitionItem {
1232 id: None,
1233 children,
1234 attributes: BlockAttributes::default(),
1235 }
1236 }
1237
1238 #[must_use]
1240 pub fn definition_term(children: Vec<Text>) -> Self {
1241 Self::DefinitionTerm {
1242 id: None,
1243 children,
1244 attributes: BlockAttributes::default(),
1245 }
1246 }
1247
1248 #[must_use]
1250 pub fn definition_description(children: Vec<Block>) -> Self {
1251 Self::DefinitionDescription {
1252 id: None,
1253 children,
1254 attributes: BlockAttributes::default(),
1255 }
1256 }
1257
1258 #[must_use]
1260 pub fn measurement(value: f64, display: impl Into<String>) -> Self {
1261 Self::Measurement(MeasurementBlock::new(value, display))
1262 }
1263
1264 #[must_use]
1266 pub fn signature(signature_type: BlockSignatureType) -> Self {
1267 Self::Signature(SignatureBlock::new(signature_type))
1268 }
1269
1270 #[must_use]
1272 pub fn svg_from_src(src: impl Into<String>) -> Self {
1273 Self::Svg(SvgBlock::from_src(src))
1274 }
1275
1276 #[must_use]
1278 pub fn svg_from_content(content: impl Into<String>) -> Self {
1279 Self::Svg(SvgBlock::from_content(content))
1280 }
1281
1282 #[must_use]
1284 pub fn barcode(format: BarcodeFormat, data: impl Into<String>, alt: impl Into<String>) -> Self {
1285 Self::Barcode(BarcodeBlock::new(format, data, alt))
1286 }
1287
1288 #[must_use]
1290 pub fn figure(children: Vec<Block>) -> Self {
1291 Self::Figure(FigureBlock::new(children))
1292 }
1293
1294 #[must_use]
1296 pub fn figcaption(children: Vec<Text>) -> Self {
1297 Self::FigCaption(FigCaptionBlock::new(children))
1298 }
1299
1300 #[must_use]
1302 pub fn admonition(variant: AdmonitionVariant, children: Vec<Block>) -> Self {
1303 Self::Admonition(AdmonitionBlock::new(variant, children))
1304 }
1305
1306 #[must_use]
1311 pub fn block_type(&self) -> &'static str {
1312 match self {
1313 Self::Paragraph { .. } => "paragraph",
1314 Self::Heading { .. } => "heading",
1315 Self::List { .. } => "list",
1316 Self::ListItem { .. } => "listItem",
1317 Self::Blockquote { .. } => "blockquote",
1318 Self::CodeBlock { .. } => "codeBlock",
1319 Self::HorizontalRule { .. } => "horizontalRule",
1320 Self::Image(_) => "image",
1321 Self::Table { .. } => "table",
1322 Self::TableRow { .. } => "tableRow",
1323 Self::TableCell(_) => "tableCell",
1324 Self::Math(_) => "math",
1325 Self::Break { .. } => "break",
1326 Self::DefinitionList(_) => "definitionList",
1327 Self::DefinitionItem { .. } => "definitionItem",
1328 Self::DefinitionTerm { .. } => "definitionTerm",
1329 Self::DefinitionDescription { .. } => "definitionDescription",
1330 Self::Measurement(_) => "measurement",
1331 Self::Signature(_) => "signature",
1332 Self::Svg(_) => "svg",
1333 Self::Barcode(_) => "barcode",
1334 Self::Figure(_) => "figure",
1335 Self::FigCaption(_) => "figCaption",
1336 Self::Admonition(_) => "admonition",
1337 Self::Extension(_) => "extension",
1338 }
1339 }
1340
1341 #[must_use]
1343 pub fn id(&self) -> Option<&str> {
1344 match self {
1345 Self::Paragraph { id, .. }
1346 | Self::Heading { id, .. }
1347 | Self::List { id, .. }
1348 | Self::ListItem { id, .. }
1349 | Self::Blockquote { id, .. }
1350 | Self::CodeBlock { id, .. }
1351 | Self::HorizontalRule { id }
1352 | Self::Table { id, .. }
1353 | Self::TableRow { id, .. }
1354 | Self::Break { id }
1355 | Self::DefinitionItem { id, .. }
1356 | Self::DefinitionTerm { id, .. }
1357 | Self::DefinitionDescription { id, .. } => id.as_deref(),
1358 Self::Image(img) => img.id.as_deref(),
1359 Self::TableCell(cell) => cell.id.as_deref(),
1360 Self::Math(math) => math.id.as_deref(),
1361 Self::DefinitionList(dl) => dl.id.as_deref(),
1362 Self::Measurement(m) => m.id.as_deref(),
1363 Self::Signature(sig) => sig.id.as_deref(),
1364 Self::Svg(svg) => svg.id.as_deref(),
1365 Self::Barcode(bc) => bc.id.as_deref(),
1366 Self::Figure(fig) => fig.id.as_deref(),
1367 Self::FigCaption(fc) => fc.id.as_deref(),
1368 Self::Admonition(adm) => adm.id.as_deref(),
1369 Self::Extension(ext) => ext.id.as_deref(),
1370 }
1371 }
1372
1373 #[must_use]
1375 pub fn extension(namespace: impl Into<String>, block_type: impl Into<String>) -> Self {
1376 Self::Extension(ExtensionBlock::new(namespace, block_type))
1377 }
1378
1379 #[must_use]
1381 pub fn is_extension(&self) -> bool {
1382 matches!(self, Self::Extension(_))
1383 }
1384
1385 #[must_use]
1387 pub fn as_extension(&self) -> Option<&ExtensionBlock> {
1388 match self {
1389 Self::Extension(ext) => Some(ext),
1390 _ => None,
1391 }
1392 }
1393}
1394
1395#[cfg(test)]
1396mod tests {
1397 use super::*;
1398
1399 #[test]
1400 fn test_content_new() {
1401 let content = Content::new(vec![Block::paragraph(vec![Text::plain("Hello")])]);
1402 assert_eq!(content.version, "0.1");
1403 assert_eq!(content.len(), 1);
1404 assert!(!content.is_empty());
1405 }
1406
1407 #[test]
1408 fn test_content_empty() {
1409 let content = Content::empty();
1410 assert!(content.is_empty());
1411 assert_eq!(content.len(), 0);
1412 }
1413
1414 #[test]
1415 fn test_paragraph() {
1416 let block = Block::paragraph(vec![Text::plain("Hello")]);
1417 assert_eq!(block.block_type(), "paragraph");
1418 assert!(block.id().is_none());
1419 }
1420
1421 #[test]
1422 fn test_heading() {
1423 let block = Block::heading(1, vec![Text::plain("Title")]);
1424 if let Block::Heading { level, .. } = &block {
1425 assert_eq!(*level, 1);
1426 } else {
1427 panic!("Expected Heading");
1428 }
1429
1430 let block = Block::heading(10, vec![Text::plain("Title")]);
1432 if let Block::Heading { level, .. } = &block {
1433 assert_eq!(*level, 6);
1434 }
1435 }
1436
1437 #[test]
1438 fn test_list() {
1439 let items = vec![
1440 Block::list_item(vec![Block::paragraph(vec![Text::plain("Item 1")])]),
1441 Block::list_item(vec![Block::paragraph(vec![Text::plain("Item 2")])]),
1442 ];
1443 let list = Block::unordered_list(items);
1444 assert_eq!(list.block_type(), "list");
1445 }
1446
1447 #[test]
1448 fn test_code_block() {
1449 let block = Block::code_block("fn main() {}", Some("rust".to_string()));
1450 if let Block::CodeBlock {
1451 language, children, ..
1452 } = &block
1453 {
1454 assert_eq!(language.as_deref(), Some("rust"));
1455 assert_eq!(children[0].value, "fn main() {}");
1456 } else {
1457 panic!("Expected CodeBlock");
1458 }
1459 }
1460
1461 #[test]
1462 fn test_image() {
1463 let block = Block::image("assets/photo.png", "A photo");
1464 if let Block::Image(img) = &block {
1465 assert_eq!(img.src, "assets/photo.png");
1466 assert_eq!(img.alt, "A photo");
1467 } else {
1468 panic!("Expected Image");
1469 }
1470 }
1471
1472 #[test]
1473 fn test_math() {
1474 let block = Block::math("E = mc^2", MathFormat::Latex, true);
1475 if let Block::Math(math) = &block {
1476 assert_eq!(math.value, "E = mc^2");
1477 assert_eq!(math.format, MathFormat::Latex);
1478 assert!(math.display);
1479 } else {
1480 panic!("Expected Math");
1481 }
1482 }
1483
1484 #[test]
1485 fn test_block_serialization() {
1486 let block = Block::paragraph(vec![Text::plain("Test")]);
1487 let json = serde_json::to_string(&block).unwrap();
1488 assert!(json.contains("\"type\":\"paragraph\""));
1489 }
1490
1491 #[test]
1492 fn test_content_serialization() {
1493 let content = Content::new(vec![
1494 Block::heading(1, vec![Text::plain("Title")]),
1495 Block::paragraph(vec![Text::plain("Body")]),
1496 ]);
1497 let json = serde_json::to_string_pretty(&content).unwrap();
1498 assert!(json.contains("\"version\": \"0.1\""));
1499 assert!(json.contains("\"type\": \"heading\""));
1500 assert!(json.contains("\"type\": \"paragraph\""));
1501 }
1502
1503 #[test]
1504 fn test_block_deserialization() {
1505 let json = r#"{
1506 "type": "heading",
1507 "level": 2,
1508 "children": [{"value": "Section"}]
1509 }"#;
1510 let block: Block = serde_json::from_str(json).unwrap();
1511 if let Block::Heading {
1512 level, children, ..
1513 } = block
1514 {
1515 assert_eq!(level, 2);
1516 assert_eq!(children[0].value, "Section");
1517 } else {
1518 panic!("Expected Heading");
1519 }
1520 }
1521
1522 #[test]
1523 fn test_table_serialization() {
1524 let table = Block::table(vec![Block::table_row(
1525 vec![Block::table_cell(vec![Text::plain("Header")])],
1526 true,
1527 )]);
1528 let json = serde_json::to_string(&table).unwrap();
1529 assert!(json.contains("\"type\":\"table\""));
1530 assert!(json.contains("\"type\":\"tableRow\""));
1531 assert!(json.contains("\"header\":true"));
1532 }
1533
1534 #[test]
1535 fn test_extension_block() {
1536 let ext = Block::extension("forms", "textInput");
1537 assert!(ext.is_extension());
1538 assert_eq!(ext.block_type(), "extension");
1539
1540 if let Block::Extension(inner) = &ext {
1541 assert_eq!(inner.namespace, "forms");
1542 assert_eq!(inner.block_type, "textInput");
1543 assert_eq!(inner.full_type(), "forms:textInput");
1544 } else {
1545 panic!("Expected Extension");
1546 }
1547 }
1548
1549 #[test]
1550 fn test_extension_as_extension() {
1551 let ext = Block::extension("semantic", "citation");
1552 let inner = ext.as_extension().expect("should be extension");
1553 assert_eq!(inner.namespace, "semantic");
1554
1555 let para = Block::paragraph(vec![Text::plain("Not extension")]);
1556 assert!(para.as_extension().is_none());
1557 }
1558
1559 #[test]
1560 fn test_extension_with_fallback() {
1561 let fallback = Block::paragraph(vec![Text::plain("[Form field]")]);
1562 let ext = ExtensionBlock::new("forms", "textInput")
1563 .with_id("name-field")
1564 .with_fallback(fallback);
1565
1566 assert_eq!(ext.id, Some("name-field".to_string()));
1567 assert!(ext.fallback_content().is_some());
1568 }
1569
1570 #[test]
1573 fn test_definition_list() {
1574 let dl = Block::definition_list(vec![Block::definition_item(vec![
1575 Block::definition_term(vec![Text::plain("Term")]),
1576 Block::definition_description(vec![Block::paragraph(vec![Text::plain("Description")])]),
1577 ])]);
1578 assert_eq!(dl.block_type(), "definitionList");
1579 }
1580
1581 #[test]
1582 fn test_measurement() {
1583 let m = Block::measurement(9.81, "9.81 m/s²");
1584 assert_eq!(m.block_type(), "measurement");
1585 if let Block::Measurement(meas) = &m {
1586 assert!((meas.value - 9.81).abs() < 0.001);
1587 assert_eq!(meas.display, "9.81 m/s²");
1588 } else {
1589 panic!("Expected Measurement");
1590 }
1591 }
1592
1593 #[test]
1594 fn test_measurement_with_uncertainty() {
1595 let m = MeasurementBlock::new(1.234, "1.234(5) m")
1596 .with_unit("m")
1597 .with_uncertainty(0.005, UncertaintyNotation::Parenthetical);
1598 assert_eq!(m.unit, Some("m".to_string()));
1599 assert!(m.uncertainty.is_some());
1600 assert_eq!(
1601 m.uncertainty_notation,
1602 Some(UncertaintyNotation::Parenthetical)
1603 );
1604 }
1605
1606 #[test]
1607 fn test_signature() {
1608 let sig = Block::signature(BlockSignatureType::Digital);
1609 assert_eq!(sig.block_type(), "signature");
1610 if let Block::Signature(s) = &sig {
1611 assert_eq!(s.signature_type, BlockSignatureType::Digital);
1612 } else {
1613 panic!("Expected Signature");
1614 }
1615 }
1616
1617 #[test]
1618 fn test_signature_with_signer() {
1619 let signer = SignerDetails::new("John Doe")
1620 .with_title("CEO")
1621 .with_organization("Acme Corp");
1622 let sig = SignatureBlock::new(BlockSignatureType::Handwritten)
1623 .with_signer(signer)
1624 .with_purpose(SignaturePurpose::Approval);
1625 assert!(sig.signer.is_some());
1626 assert_eq!(sig.purpose, Some(SignaturePurpose::Approval));
1627 }
1628
1629 #[test]
1630 fn test_svg_from_src() {
1631 let svg = Block::svg_from_src("diagram.svg");
1632 assert_eq!(svg.block_type(), "svg");
1633 if let Block::Svg(s) = &svg {
1634 assert_eq!(s.src, Some("diagram.svg".to_string()));
1635 assert!(s.content.is_none());
1636 } else {
1637 panic!("Expected Svg");
1638 }
1639 }
1640
1641 #[test]
1642 fn test_svg_from_content() {
1643 let svg = Block::svg_from_content("<svg>...</svg>");
1644 if let Block::Svg(s) = &svg {
1645 assert!(s.src.is_none());
1646 assert_eq!(s.content, Some("<svg>...</svg>".to_string()));
1647 } else {
1648 panic!("Expected Svg");
1649 }
1650 }
1651
1652 #[test]
1653 fn test_barcode() {
1654 let bc = Block::barcode(
1655 BarcodeFormat::Qr,
1656 "https://example.com",
1657 "Link to example.com",
1658 );
1659 assert_eq!(bc.block_type(), "barcode");
1660 if let Block::Barcode(b) = &bc {
1661 assert_eq!(b.format, BarcodeFormat::Qr);
1662 assert_eq!(b.data, "https://example.com");
1663 assert_eq!(b.alt, "Link to example.com");
1664 } else {
1665 panic!("Expected Barcode");
1666 }
1667 }
1668
1669 #[test]
1670 fn test_barcode_with_options() {
1671 let bc = BarcodeBlock::new(BarcodeFormat::Qr, "data", "Meaningful alt text")
1672 .with_error_correction(ErrorCorrectionLevel::H)
1673 .with_size("2in".to_string(), "2in".to_string());
1674 assert_eq!(bc.error_correction, Some(ErrorCorrectionLevel::H));
1675 assert!(bc.size.is_some());
1676 }
1677
1678 #[test]
1679 fn test_figure() {
1680 let fig = Block::figure(vec![
1681 Block::image("photo.png", "A photo"),
1682 Block::figcaption(vec![Text::plain("Figure 1: A photo")]),
1683 ]);
1684 assert_eq!(fig.block_type(), "figure");
1685 }
1686
1687 #[test]
1688 fn test_figcaption() {
1689 let fc = Block::figcaption(vec![Text::plain("Caption text")]);
1690 assert_eq!(fc.block_type(), "figCaption");
1691 }
1692
1693 #[test]
1694 fn test_writing_mode_serialization() {
1695 let attrs = BlockAttributes {
1696 dir: None,
1697 lang: None,
1698 writing_mode: Some(WritingMode::VerticalRl),
1699 };
1700 let json = serde_json::to_string(&attrs).unwrap();
1701 assert!(json.contains("\"writingMode\":\"vertical-rl\""));
1702 }
1703
1704 #[test]
1705 fn test_writing_mode_deserialization() {
1706 let json = r#"{"writingMode":"vertical-lr"}"#;
1707 let attrs: BlockAttributes = serde_json::from_str(json).unwrap();
1708 assert_eq!(attrs.writing_mode, Some(WritingMode::VerticalLr));
1709 }
1710
1711 #[test]
1712 fn test_measurement_serialization() {
1713 let m = MeasurementBlock::new(299_792_458.0, "299,792,458 m/s")
1714 .with_unit("m/s")
1715 .with_exponent(8);
1716 let json = serde_json::to_string(&m).unwrap();
1717 assert!(json.contains("\"value\":299792458")); assert!(json.contains("\"unit\":\"m/s\""));
1719 assert!(json.contains("\"exponent\":8"));
1720 }
1721
1722 #[test]
1723 fn test_barcode_format_serialization() {
1724 let bc = BarcodeBlock::new(BarcodeFormat::DataMatrix, "ABC123", "Product code ABC123");
1725 let json = serde_json::to_string(&bc).unwrap();
1726 assert!(json.contains("\"format\":\"datamatrix\""));
1728 }
1729
1730 #[test]
1731 fn test_signature_type_serialization() {
1732 let sig = SignatureBlock::new(BlockSignatureType::Electronic);
1733 let json = serde_json::to_string(&sig).unwrap();
1734 assert!(json.contains("\"signatureType\":\"electronic\""));
1735 }
1736
1737 #[test]
1738 fn test_new_block_types_deserialization() {
1739 let json = r#"{"type":"definitionList","children":[]}"#;
1741 let block: Block = serde_json::from_str(json).unwrap();
1742 assert_eq!(block.block_type(), "definitionList");
1743
1744 let json = r#"{"type":"measurement","value":3.14159,"display":"π"}"#;
1746 let block: Block = serde_json::from_str(json).unwrap();
1747 assert_eq!(block.block_type(), "measurement");
1748
1749 let json = r#"{"type":"svg","src":"diagram.svg"}"#;
1751 let block: Block = serde_json::from_str(json).unwrap();
1752 assert_eq!(block.block_type(), "svg");
1753
1754 let json = r#"{"type":"barcode","format":"qr","data":"test","alt":"Test QR code"}"#;
1756 let block: Block = serde_json::from_str(json).unwrap();
1757 assert_eq!(block.block_type(), "barcode");
1758
1759 let json = r#"{"type":"figure","children":[]}"#;
1761 let block: Block = serde_json::from_str(json).unwrap();
1762 assert_eq!(block.block_type(), "figure");
1763 }
1764
1765 #[test]
1768 fn test_admonition() {
1769 let adm = Block::admonition(
1770 AdmonitionVariant::Warning,
1771 vec![Block::paragraph(vec![Text::plain("Be careful!")])],
1772 );
1773 assert_eq!(adm.block_type(), "admonition");
1774 if let Block::Admonition(a) = &adm {
1775 assert_eq!(a.variant, AdmonitionVariant::Warning);
1776 assert_eq!(a.children.len(), 1);
1777 } else {
1778 panic!("Expected Admonition");
1779 }
1780 }
1781
1782 #[test]
1783 fn test_admonition_with_title() {
1784 let adm = AdmonitionBlock::new(
1785 AdmonitionVariant::Note,
1786 vec![Block::paragraph(vec![Text::plain("Important info")])],
1787 )
1788 .with_title("Please Note")
1789 .with_id("note-1");
1790
1791 assert_eq!(adm.title, Some("Please Note".to_string()));
1792 assert_eq!(adm.id, Some("note-1".to_string()));
1793 }
1794
1795 #[test]
1796 fn test_admonition_serialization() {
1797 let adm = Block::admonition(
1798 AdmonitionVariant::Tip,
1799 vec![Block::paragraph(vec![Text::plain("Pro tip!")])],
1800 );
1801 let json = serde_json::to_string(&adm).unwrap();
1802 assert!(json.contains("\"type\":\"admonition\""));
1803 assert!(json.contains("\"variant\":\"tip\""));
1804 }
1805
1806 #[test]
1807 fn test_admonition_deserialization() {
1808 let json = r#"{
1809 "type": "admonition",
1810 "variant": "danger",
1811 "title": "Warning!",
1812 "children": [
1813 {"type": "paragraph", "children": [{"value": "Do not proceed!"}]}
1814 ]
1815 }"#;
1816 let block: Block = serde_json::from_str(json).unwrap();
1817 assert_eq!(block.block_type(), "admonition");
1818 if let Block::Admonition(adm) = block {
1819 assert_eq!(adm.variant, AdmonitionVariant::Danger);
1820 assert_eq!(adm.title, Some("Warning!".to_string()));
1821 assert_eq!(adm.children.len(), 1);
1822 } else {
1823 panic!("Expected Admonition");
1824 }
1825 }
1826
1827 #[test]
1828 fn test_admonition_variant_display() {
1829 assert_eq!(AdmonitionVariant::Note.to_string(), "Note");
1830 assert_eq!(AdmonitionVariant::Warning.to_string(), "Warning");
1831 assert_eq!(AdmonitionVariant::Important.to_string(), "Important");
1832 }
1833
1834 #[test]
1835 fn test_all_admonition_variants() {
1836 let variants = [
1837 (AdmonitionVariant::Note, "note"),
1838 (AdmonitionVariant::Tip, "tip"),
1839 (AdmonitionVariant::Info, "info"),
1840 (AdmonitionVariant::Warning, "warning"),
1841 (AdmonitionVariant::Caution, "caution"),
1842 (AdmonitionVariant::Danger, "danger"),
1843 (AdmonitionVariant::Important, "important"),
1844 (AdmonitionVariant::Example, "example"),
1845 ];
1846
1847 for (variant, expected_name) in variants {
1848 let adm = AdmonitionBlock::new(variant, vec![]);
1849 let json = serde_json::to_string(&adm).unwrap();
1850 assert!(
1851 json.contains(&format!("\"variant\":\"{expected_name}\"")),
1852 "Variant {variant:?} should serialize as {expected_name}",
1853 );
1854 }
1855 }
1856}
1857
1858#[cfg(test)]
1859mod proptests {
1860 use super::*;
1861 use proptest::prelude::*;
1862
1863 fn arb_text_content() -> impl Strategy<Value = String> {
1865 "[a-zA-Z0-9 .,!?]{0,100}".prop_map(|s| s)
1866 }
1867
1868 fn arb_optional_string() -> impl Strategy<Value = Option<String>> {
1870 prop_oneof![Just(None), "[a-zA-Z0-9_-]{1,20}".prop_map(Some),]
1871 }
1872
1873 fn arb_heading_level() -> impl Strategy<Value = u8> {
1875 1u8..=6u8
1876 }
1877
1878 fn arb_paragraph() -> impl Strategy<Value = Block> {
1880 (arb_optional_string(), arb_text_content()).prop_map(|(id, text)| {
1881 let mut block = Block::paragraph(vec![Text::plain(text)]);
1882 if let Block::Paragraph {
1883 id: ref mut block_id,
1884 ..
1885 } = block
1886 {
1887 *block_id = id;
1888 }
1889 block
1890 })
1891 }
1892
1893 fn arb_heading() -> impl Strategy<Value = Block> {
1895 (
1896 arb_optional_string(),
1897 arb_heading_level(),
1898 arb_text_content(),
1899 )
1900 .prop_map(|(id, level, text)| {
1901 let mut block = Block::heading(level, vec![Text::plain(text)]);
1902 if let Block::Heading {
1903 id: ref mut block_id,
1904 ..
1905 } = block
1906 {
1907 *block_id = id;
1908 }
1909 block
1910 })
1911 }
1912
1913 fn arb_code_block() -> impl Strategy<Value = Block> {
1915 (
1916 arb_optional_string(),
1917 arb_text_content(),
1918 arb_optional_string(),
1919 )
1920 .prop_map(|(id, code, language)| {
1921 let mut block = Block::code_block(code, language);
1922 if let Block::CodeBlock {
1923 id: ref mut block_id,
1924 ..
1925 } = block
1926 {
1927 *block_id = id;
1928 }
1929 block
1930 })
1931 }
1932
1933 fn arb_math_block() -> impl Strategy<Value = Block> {
1935 (
1936 arb_optional_string(),
1937 arb_text_content(),
1938 prop_oneof![Just(MathFormat::Latex), Just(MathFormat::Mathml)],
1939 any::<bool>(),
1940 )
1941 .prop_map(|(id, value, format, display)| {
1942 let mut block = Block::math(value, format, display);
1943 if let Block::Math(ref mut math) = block {
1944 math.id = id;
1945 }
1946 block
1947 })
1948 }
1949
1950 proptest! {
1951 #[test]
1953 fn paragraph_json_roundtrip(para in arb_paragraph()) {
1954 let json = serde_json::to_string(¶).unwrap();
1955 let parsed: Block = serde_json::from_str(&json).unwrap();
1956 prop_assert_eq!(para, parsed);
1957 }
1958
1959 #[test]
1961 fn heading_json_roundtrip(heading in arb_heading()) {
1962 let json = serde_json::to_string(&heading).unwrap();
1963 let parsed: Block = serde_json::from_str(&json).unwrap();
1964 prop_assert_eq!(heading, parsed);
1965 }
1966
1967 #[test]
1969 fn code_block_json_roundtrip(code in arb_code_block()) {
1970 let json = serde_json::to_string(&code).unwrap();
1971 let parsed: Block = serde_json::from_str(&json).unwrap();
1972 prop_assert_eq!(code, parsed);
1973 }
1974
1975 #[test]
1977 fn math_block_json_roundtrip(math in arb_math_block()) {
1978 let json = serde_json::to_string(&math).unwrap();
1979 let parsed: Block = serde_json::from_str(&json).unwrap();
1980 prop_assert_eq!(math, parsed);
1981 }
1982
1983 #[test]
1985 fn heading_level_clamped(level in any::<u8>()) {
1986 let block = Block::heading(level, vec![Text::plain("Test")]);
1987 if let Block::Heading { level: actual, .. } = block {
1988 prop_assert!((1..=6).contains(&actual));
1989 } else {
1990 prop_assert!(false, "Expected Heading block");
1991 }
1992 }
1993
1994 #[test]
1996 fn content_version_is_spec_version(blocks in prop::collection::vec(arb_paragraph(), 0..5)) {
1997 let blocks_len = blocks.len();
1998 let content = Content::new(blocks);
1999 prop_assert_eq!(&content.version, crate::SPEC_VERSION);
2000 prop_assert_eq!(content.len(), blocks_len);
2001 }
2002
2003 #[test]
2005 fn paragraph_block_type(para in arb_paragraph()) {
2006 prop_assert_eq!(para.block_type(), "paragraph");
2007 }
2008
2009 #[test]
2011 fn heading_block_type(heading in arb_heading()) {
2012 prop_assert_eq!(heading.block_type(), "heading");
2013 }
2014 }
2015
2016 #[test]
2017 fn code_block_highlighting_and_tokens_roundtrip() {
2018 let json = serde_json::json!({
2019 "type": "codeBlock",
2020 "value": "let x = 1;",
2021 "language": "rust",
2022 "children": [],
2023 "highlighting": "monokai",
2024 "tokens": [
2025 { "tokenType": "keyword", "value": "let" },
2026 { "tokenType": "identifier", "value": "x", "scope": "variable" }
2027 ]
2028 });
2029 let block: Block = serde_json::from_value(json).unwrap();
2030 if let Block::CodeBlock {
2031 highlighting,
2032 tokens,
2033 ..
2034 } = &block
2035 {
2036 assert_eq!(highlighting.as_deref(), Some("monokai"));
2037 let toks = tokens.as_ref().unwrap();
2038 assert_eq!(toks.len(), 2);
2039 assert_eq!(toks[0].token_type, "keyword");
2040 assert_eq!(toks[1].scope.as_deref(), Some("variable"));
2041 } else {
2042 panic!("Expected CodeBlock");
2043 }
2044 let serialized = serde_json::to_value(&block).unwrap();
2046 let parsed: Block = serde_json::from_value(serialized).unwrap();
2047 assert_eq!(block, parsed);
2048 }
2049
2050 #[test]
2051 fn code_block_without_new_fields_defaults_to_none() {
2052 let json = serde_json::json!({
2053 "type": "codeBlock",
2054 "value": "print('hello')",
2055 "language": "python",
2056 "children": []
2057 });
2058 let block: Block = serde_json::from_value(json).unwrap();
2059 if let Block::CodeBlock {
2060 highlighting,
2061 tokens,
2062 ..
2063 } = &block
2064 {
2065 assert!(highlighting.is_none());
2066 assert!(tokens.is_none());
2067 } else {
2068 panic!("Expected CodeBlock");
2069 }
2070 }
2071
2072 #[test]
2073 fn figure_numbering_serialization() {
2074 let json = serde_json::to_value(FigureNumbering::Auto).unwrap();
2076 assert_eq!(json, serde_json::json!("auto"));
2077
2078 let json = serde_json::to_value(FigureNumbering::Unnumbered).unwrap();
2080 assert_eq!(json, serde_json::json!("none"));
2081
2082 let json = serde_json::to_value(FigureNumbering::Number(3)).unwrap();
2084 assert_eq!(json, serde_json::json!(3));
2085
2086 let auto: FigureNumbering = serde_json::from_str("\"auto\"").unwrap();
2088 assert_eq!(auto, FigureNumbering::Auto);
2089 let unnumbered: FigureNumbering = serde_json::from_str("\"none\"").unwrap();
2090 assert_eq!(unnumbered, FigureNumbering::Unnumbered);
2091 let num: FigureNumbering = serde_json::from_str("3").unwrap();
2092 assert_eq!(num, FigureNumbering::Number(3));
2093 }
2094
2095 #[test]
2096 fn figure_with_numbering_and_subfigures_roundtrip() {
2097 let json = serde_json::json!({
2098 "type": "figure",
2099 "children": [
2100 { "type": "image", "src": "fig1.png", "alt": "Figure 1" }
2101 ],
2102 "numbering": "auto",
2103 "subfigures": [
2104 {
2105 "id": "sub-a",
2106 "label": "(a)",
2107 "children": [
2108 { "type": "paragraph", "children": [{ "type": "text", "value": "Sub A" }] }
2109 ]
2110 }
2111 ]
2112 });
2113 let block: Block = serde_json::from_value(json).unwrap();
2114 if let Block::Figure(fig) = &block {
2115 assert_eq!(fig.numbering, Some(FigureNumbering::Auto));
2116 let subs = fig.subfigures.as_ref().unwrap();
2117 assert_eq!(subs.len(), 1);
2118 assert_eq!(subs[0].id.as_deref(), Some("sub-a"));
2119 assert_eq!(subs[0].label.as_deref(), Some("(a)"));
2120 } else {
2121 panic!("Expected Figure block");
2122 }
2123 let serialized = serde_json::to_value(&block).unwrap();
2124 let parsed: Block = serde_json::from_value(serialized).unwrap();
2125 assert_eq!(block, parsed);
2126 }
2127
2128 #[test]
2129 fn figure_without_new_fields_defaults_to_none() {
2130 let json = serde_json::json!({
2131 "type": "figure",
2132 "children": []
2133 });
2134 let block: Block = serde_json::from_value(json).unwrap();
2135 if let Block::Figure(fig) = &block {
2136 assert!(fig.numbering.is_none());
2137 assert!(fig.subfigures.is_none());
2138 } else {
2139 panic!("Expected Figure block");
2140 }
2141 }
2142}