1use std::convert::TryFrom;
43use std::fmt::Debug;
44
45use lazy_static::lazy_static;
46use regex::Regex;
47
48use crate::model::*;
49
50pub trait DocReference: Debug + Clone {}
51
52pub fn doc<D: Into<DocString<Unvalidated>>>(brief: D) -> Doc<Unvalidated> {
53 Doc {
54 brief: brief.into(),
55 details: Vec::new(),
56 }
57}
58
59pub fn text(text: &str) -> DocString<Validated> {
60 DocString {
61 elements: vec![DocStringElement::Text(text.to_string())],
62 }
63}
64
65pub fn brief(text: &str) -> Doc<Validated> {
66 Doc {
67 brief: DocString {
68 elements: vec![DocStringElement::Text(text.to_string())],
69 },
70 details: Vec::new(),
71 }
72}
73
74#[derive(Debug, Clone)]
75pub struct Doc<T>
76where
77 T: DocReference,
78{
79 pub(crate) brief: DocString<T>,
80 pub(crate) details: Vec<DocParagraph<T>>,
81}
82
83impl Doc<Validated> {
84 #[must_use]
85 pub fn warning(mut self, warning: &str) -> Self {
86 self.details.push(DocParagraph::Warning(text(warning)));
87 self
88 }
89}
90
91impl Doc<Unvalidated> {
92 pub(crate) fn validate(
93 &self,
94 symbol_name: &Name,
95 lib: &LibraryFields,
96 ) -> BindResult<Doc<Validated>> {
97 self.validate_with_args(symbol_name, lib, None)
98 }
99
100 pub(crate) fn validate_with_args(
101 &self,
102 symbol_name: &Name,
103 lib: &LibraryFields,
104 args: Option<&[Name]>,
105 ) -> BindResult<Doc<Validated>> {
106 let details: BindResult<Vec<DocParagraph<Validated>>> = self
107 .details
108 .iter()
109 .map(|x| x.validate_with_args(symbol_name, lib, args))
110 .collect();
111 Ok(Doc {
112 brief: self.brief.validate_with_args(symbol_name, lib, args)?,
113 details: details?,
114 })
115 }
116
117 #[must_use]
118 pub fn warning<D: Into<DocString<Unvalidated>>>(mut self, warning: D) -> Self {
119 self.details.push(DocParagraph::Warning(warning.into()));
120 self
121 }
122
123 #[must_use]
124 pub fn details<D: Into<DocString<Unvalidated>>>(mut self, details: D) -> Self {
125 self.details.push(DocParagraph::Details(details.into()));
126 self
127 }
128}
129
130impl<T: AsRef<str>> From<T> for Doc<Unvalidated> {
131 fn from(from: T) -> Self {
132 doc(from)
133 }
134}
135
136pub(crate) struct OptionalDoc {
138 parent_name: Name,
139 inner: Option<Doc<Unvalidated>>,
140}
141
142impl OptionalDoc {
143 pub(crate) fn new(parent_name: Name) -> Self {
144 Self {
145 parent_name,
146 inner: None,
147 }
148 }
149
150 pub(crate) fn set(&mut self, doc: Doc<Unvalidated>) -> BindResult<()> {
151 match self.inner {
152 None => {
153 self.inner = Some(doc);
154 Ok(())
155 }
156 Some(_) => Err(BindingErrorVariant::DocAlreadyDefined {
157 symbol_name: self.parent_name.clone(),
158 }
159 .into()),
160 }
161 }
162
163 pub(crate) fn extract(self) -> BindResult<Doc<Unvalidated>> {
164 match self.inner {
165 Some(doc) => Ok(doc),
166 None => Err(BindingErrorVariant::DocNotDefined {
167 symbol_name: self.parent_name.clone(),
168 }
169 .into()),
170 }
171 }
172}
173
174#[derive(Debug, Clone)]
175pub(crate) enum DocParagraph<T>
176where
177 T: DocReference,
178{
179 Details(DocString<T>),
180 Warning(DocString<T>),
181}
182
183impl DocParagraph<Unvalidated> {
184 fn validate_with_args(
185 &self,
186 symbol_name: &Name,
187 lib: &LibraryFields,
188 args: Option<&[Name]>,
189 ) -> BindResult<DocParagraph<Validated>> {
190 Ok(match self {
191 DocParagraph::Details(x) => {
192 DocParagraph::Details(x.validate_with_args(symbol_name, lib, args)?)
193 }
194 DocParagraph::Warning(x) => {
195 DocParagraph::Warning(x.validate_with_args(symbol_name, lib, args)?)
196 }
197 })
198 }
199}
200
201#[derive(Debug, Clone)]
202pub struct DocString<T>
203where
204 T: DocReference,
205{
206 elements: Vec<DocStringElement<T>>,
207}
208
209impl DocString<Unvalidated> {
210 pub(crate) fn validate(
211 &self,
212 symbol_name: &Name,
213 lib: &LibraryFields,
214 ) -> BindResult<DocString<Validated>> {
215 self.validate_with_args(symbol_name, lib, None)
216 }
217
218 pub(crate) fn validate_with_args(
219 &self,
220 symbol_name: &Name,
221 lib: &LibraryFields,
222 args: Option<&[Name]>,
223 ) -> BindResult<DocString<Validated>> {
224 let elements: BindResult<Vec<DocStringElement<Validated>>> = self
225 .elements
226 .iter()
227 .map(|x| x.validate(symbol_name, lib, args))
228 .collect();
229 Ok(DocString {
230 elements: elements?,
231 })
232 }
233}
234
235impl<T> DocString<T>
236where
237 T: DocReference,
238{
239 pub(crate) fn new() -> Self {
240 Self {
241 elements: Vec::new(),
242 }
243 }
244
245 pub(crate) fn push(&mut self, element: DocStringElement<T>) {
246 self.elements.push(element);
247 }
248
249 pub(crate) fn elements(&self) -> impl Iterator<Item = &DocStringElement<T>> {
250 self.elements.iter()
251 }
252}
253
254impl<T> Default for DocString<T>
255where
256 T: DocReference,
257{
258 fn default() -> Self {
259 DocString::new()
260 }
261}
262
263impl<U: AsRef<str>> From<U> for DocString<Unvalidated> {
264 fn from(from: U) -> DocString<Unvalidated> {
265 let mut from = from.as_ref();
266 let mut result = DocString::new();
267 while let Some(start_idx) = from.find('{') {
268 let (before_str, current_str) = from.split_at(start_idx);
269 if let Some(end_idx) = current_str.find('}') {
270 let (element_str, current_str) = current_str.split_at(end_idx + 1);
271 let element = DocStringElement::try_from(element_str)
272 .expect("Invalid docstring: ill-formatted docstring element");
273
274 if !before_str.is_empty() {
275 result.push(DocStringElement::Text(before_str.to_owned()));
276 }
277 result.push(element);
278 from = current_str;
279 } else {
280 panic!("Invalid docstring: no end bracket");
281 }
282 }
283
284 if !from.is_empty() {
286 result.push(DocStringElement::Text(from.to_owned()));
287 }
288
289 result
290 }
291}
292
293#[derive(Debug, Clone, PartialEq, Eq)]
294pub(crate) enum DocStringElement<T>
295where
296 T: DocReference,
297{
298 Text(String),
299 Null,
300 Iterator,
301 Reference(T),
302}
303
304impl DocStringElement<Unvalidated> {
305 pub(crate) fn validate(
306 &self,
307 symbol_name: &Name,
308 lib: &LibraryFields,
309 args: Option<&[Name]>,
310 ) -> BindResult<DocStringElement<Validated>> {
311 Ok(match self {
312 DocStringElement::Text(x) => DocStringElement::Text(x.clone()),
313 DocStringElement::Null => DocStringElement::Null,
314 DocStringElement::Iterator => DocStringElement::Iterator,
315 DocStringElement::Reference(x) => {
316 DocStringElement::Reference(x.validate(symbol_name, lib, args)?)
317 }
318 })
319 }
320}
321
322#[non_exhaustive]
323#[derive(Debug, Clone, PartialEq, Eq)]
324pub enum Unvalidated {
325 Argument(String),
328 Class(String),
330 ClassMethod(String, String),
334 ClassConstructor(String),
336 ClassDestructor(String),
338 Struct(String),
340 StructField(String, String),
344 Enum(String),
346 EnumVariant(String, String),
350 Interface(String),
352 InterfaceMethod(String, String),
356}
357
358impl DocReference for Unvalidated {}
359
360impl Unvalidated {
361 pub(crate) fn validate(
362 &self,
363 symbol_name: &Name,
364 lib: &LibraryFields,
365 args: Option<&[Name]>,
366 ) -> BindResult<Validated> {
367 match self {
368 Self::Argument(name) => {
369 let args = match args {
370 Some(args) => args,
371 None => {
372 return Err(BindingErrorVariant::DocInvalidArgumentContext {
373 symbol_name: symbol_name.to_string(),
374 ref_name: name.to_string(),
375 }
376 .into())
377 }
378 };
379
380 match args.iter().find(|arg| arg.as_ref() == name) {
381 Some(arg) => Ok(Validated::Argument(arg.clone())),
382 None => Err(BindingErrorVariant::DocInvalidReference {
383 symbol_name: symbol_name.to_string(),
384 ref_name: name.to_string(),
385 }
386 .into()),
387 }
388 }
389 Self::Class(name) => match lib.find_class_declaration(name) {
390 None => Err(BindingErrorVariant::DocInvalidReference {
391 symbol_name: symbol_name.to_string(),
392 ref_name: name.to_string(),
393 }
394 .into()),
395 Some(x) => Ok(Validated::Class(x.clone())),
396 },
397 Self::ClassMethod(class_name, method_name) => {
398 match lib
399 .find_class(class_name)
400 .and_then(|class| class.find_method(method_name).map(|func| (class, func)))
401 {
402 None => Err(BindingErrorVariant::DocInvalidReference {
403 symbol_name: symbol_name.to_string(),
404 ref_name: format!("{class_name}.{method_name}()"),
405 }
406 .into()),
407 Some((class, (name, function))) => {
408 Ok(Validated::ClassMethod(class.clone(), name, function))
409 }
410 }
411 }
412 Self::ClassConstructor(class_name) => {
413 match lib
414 .find_class(class_name)
415 .and_then(|x| x.constructor.clone().map(|d| (x.clone(), d)))
416 {
417 None => Err(BindingErrorVariant::DocInvalidReference {
418 symbol_name: symbol_name.to_string(),
419 ref_name: format!("{class_name}.[constructor]",),
420 }
421 .into()),
422 Some((class, constructor)) => {
423 Ok(Validated::ClassConstructor(class, constructor))
424 }
425 }
426 }
427 Self::ClassDestructor(class_name) => {
428 match lib
429 .find_class(class_name)
430 .and_then(|x| x.destructor.clone().map(|d| (x, d)))
431 {
432 None => Err(BindingErrorVariant::DocInvalidReference {
433 symbol_name: symbol_name.to_string(),
434 ref_name: format!("{class_name}.[destructor]"),
435 }
436 .into()),
437 Some((class, destructor)) => {
438 Ok(Validated::ClassDestructor(class.clone(), destructor))
439 }
440 }
441 }
442 Self::Struct(struct_name) => match lib.find_struct(struct_name) {
443 None => Err(BindingErrorVariant::DocInvalidReference {
444 symbol_name: symbol_name.to_string(),
445 ref_name: struct_name.to_string(),
446 }
447 .into()),
448 Some(x) => Ok(Validated::Struct(x.clone())),
449 },
450 Self::StructField(struct_name, field_name) => {
451 match lib
452 .find_struct(struct_name)
453 .and_then(|st| st.find_field_name(field_name).map(|n| (st, n)))
454 {
455 None => Err(BindingErrorVariant::DocInvalidReference {
456 symbol_name: symbol_name.to_string(),
457 ref_name: format!("{struct_name}.{field_name}"),
458 }
459 .into()),
460 Some((st, name)) => Ok(Validated::StructField(st.clone(), name)),
461 }
462 }
463 Self::Enum(enum_name) => match lib.find_enum(enum_name) {
464 None => Err(BindingErrorVariant::DocInvalidReference {
465 symbol_name: symbol_name.to_string(),
466 ref_name: enum_name.to_string(),
467 }
468 .into()),
469 Some(x) => Ok(Validated::Enum(x.clone())),
470 },
471 Self::EnumVariant(enum_name, variant_name) => {
472 match lib.find_enum(enum_name).and_then(|e| {
473 e.find_variant_by_name(variant_name)
474 .map(|v| (e, v.name.clone()))
475 }) {
476 None => Err(BindingErrorVariant::DocInvalidReference {
477 symbol_name: symbol_name.to_string(),
478 ref_name: format!("{enum_name}.{variant_name}"),
479 }
480 .into()),
481 Some((e, v)) => Ok(Validated::EnumVariant(e.clone(), v)),
482 }
483 }
484 Self::Interface(interface_name) => match lib.find_interface(interface_name) {
485 None => Err(BindingErrorVariant::DocInvalidReference {
486 symbol_name: symbol_name.to_string(),
487 ref_name: interface_name.to_string(),
488 }
489 .into()),
490 Some(x) => Ok(Validated::Interface(x.clone())),
491 },
492 Self::InterfaceMethod(interface_name, method_name) => {
493 match lib
494 .find_interface(interface_name)
495 .and_then(|i| i.find_callback(method_name).map(|m| (i, m)))
496 {
497 None => Err(BindingErrorVariant::DocInvalidReference {
498 symbol_name: symbol_name.to_string(),
499 ref_name: format!("{interface_name}.{method_name}()"),
500 }
501 .into()),
502 Some((i, m)) => Ok(Validated::InterfaceMethod(i.clone(), m.name.clone())),
503 }
504 }
505 }
506 }
507}
508
509#[non_exhaustive]
511#[derive(Debug, Clone)]
512pub enum Validated {
513 Argument(Name),
516 Class(ClassDeclarationHandle),
518 ClassMethod(
520 Handle<Class<Unvalidated>>,
521 Name,
522 Handle<Function<Unvalidated>>,
523 ),
524 ClassConstructor(Handle<Class<Unvalidated>>, ClassConstructor<Unvalidated>),
526 ClassDestructor(Handle<Class<Unvalidated>>, ClassDestructor<Unvalidated>),
528 Struct(StructType<Unvalidated>),
530 StructField(StructType<Unvalidated>, Name),
534 Enum(Handle<Enum<Unvalidated>>),
536 EnumVariant(Handle<Enum<Unvalidated>>, Name),
538 Interface(Handle<Interface<Unvalidated>>),
540 InterfaceMethod(Handle<Interface<Unvalidated>>, Name),
542}
543
544impl DocReference for Validated {}
545
546impl TryFrom<&str> for DocStringElement<Unvalidated> {
547 type Error = BindingError;
548
549 fn try_from(from: &str) -> Result<DocStringElement<Unvalidated>, BindingError> {
550 lazy_static! {
551 static ref RE_PARAM: Regex = Regex::new(r"\{param:([[:word:]]+)\}").unwrap();
552 static ref RE_CLASS: Regex = Regex::new(r"\{class:([[:word:]]+)\}").unwrap();
553 static ref RE_CLASS_METHOD: Regex =
554 Regex::new(r"\{class:([[:word:]]+)\.([[:word:]]+)\(\)\}").unwrap();
555 static ref RE_CLASS_CONSTRUCTOR: Regex =
556 Regex::new(r"\{class:([[:word:]]+)\.\[constructor\]\}").unwrap();
557 static ref RE_CLASS_DESTRUCTOR: Regex =
558 Regex::new(r"\{class:([[:word:]]+)\.\[destructor\]\}").unwrap();
559 static ref RE_STRUCT: Regex = Regex::new(r"\{struct:([[:word:]]+)\}").unwrap();
560 static ref RE_STRUCT_ELEMENT: Regex =
561 Regex::new(r"\{struct:([[:word:]]+)\.([[:word:]]+)\}").unwrap();
562 static ref RE_STRUCT_METHOD: Regex =
563 Regex::new(r"\{struct:([[:word:]]+)\.([[:word:]]+)\(\)\}").unwrap();
564 static ref RE_ENUM: Regex = Regex::new(r"\{enum:([[:word:]]+)\}").unwrap();
565 static ref RE_ENUM_VARIANT: Regex =
566 Regex::new(r"\{enum:([[:word:]]+)\.([[:word:]]+)\}").unwrap();
567 static ref RE_INTERFACE: Regex = Regex::new(r"\{interface:([[:word:]]+)\}").unwrap();
568 static ref RE_INTERFACE_METHOD: Regex =
569 Regex::new(r"\{interface:([[:word:]]+)\.([[:word:]]+)\(\)\}").unwrap();
570 }
571
572 fn try_get_regex(from: &str) -> Option<Unvalidated> {
573 if let Some(capture) = RE_PARAM.captures(from) {
574 return Some(Unvalidated::Argument(
575 capture.get(1).unwrap().as_str().to_owned(),
576 ));
577 }
578 if let Some(capture) = RE_CLASS.captures(from) {
579 return Some(Unvalidated::Class(
580 capture.get(1).unwrap().as_str().to_owned(),
581 ));
582 }
583 if let Some(capture) = RE_CLASS_METHOD.captures(from) {
584 return Some(Unvalidated::ClassMethod(
585 capture.get(1).unwrap().as_str().to_owned(),
586 capture.get(2).unwrap().as_str().to_owned(),
587 ));
588 }
589 if let Some(capture) = RE_CLASS_CONSTRUCTOR.captures(from) {
590 return Some(Unvalidated::ClassConstructor(
591 capture.get(1).unwrap().as_str().to_owned(),
592 ));
593 }
594 if let Some(capture) = RE_CLASS_DESTRUCTOR.captures(from) {
595 return Some(Unvalidated::ClassDestructor(
596 capture.get(1).unwrap().as_str().to_owned(),
597 ));
598 }
599 if let Some(capture) = RE_STRUCT.captures(from) {
600 return Some(Unvalidated::Struct(
601 capture.get(1).unwrap().as_str().to_owned(),
602 ));
603 }
604 if let Some(capture) = RE_STRUCT_ELEMENT.captures(from) {
605 return Some(Unvalidated::StructField(
606 capture.get(1).unwrap().as_str().to_owned(),
607 capture.get(2).unwrap().as_str().to_owned(),
608 ));
609 }
610 if let Some(capture) = RE_ENUM.captures(from) {
611 return Some(Unvalidated::Enum(
612 capture.get(1).unwrap().as_str().to_owned(),
613 ));
614 }
615 if let Some(capture) = RE_ENUM_VARIANT.captures(from) {
616 return Some(Unvalidated::EnumVariant(
617 capture.get(1).unwrap().as_str().to_owned(),
618 capture.get(2).unwrap().as_str().to_owned(),
619 ));
620 }
621 if let Some(capture) = RE_INTERFACE.captures(from) {
622 return Some(Unvalidated::Interface(
623 capture.get(1).unwrap().as_str().to_owned(),
624 ));
625 }
626 if let Some(capture) = RE_INTERFACE_METHOD.captures(from) {
627 return Some(Unvalidated::InterfaceMethod(
628 capture.get(1).unwrap().as_str().to_owned(),
629 capture.get(2).unwrap().as_str().to_owned(),
630 ));
631 }
632
633 None
634 }
635
636 if let Some(x) = try_get_regex(from) {
637 return Ok(DocStringElement::Reference(x));
638 }
639
640 if from == "{null}" {
641 return Ok(DocStringElement::Null);
642 }
643 if from == "{iterator}" {
644 return Ok(DocStringElement::Iterator);
645 }
646
647 Err(BindingErrorVariant::InvalidDocString.into())
648 }
649}
650
651#[cfg(test)]
652mod tests {
653 use std::convert::TryInto;
654
655 use super::*;
656
657 #[test]
658 fn parse_param_reference() {
659 let doc: DocString<Unvalidated> = "This is a {param:foo} test.".try_into().unwrap();
660 assert_eq!(
661 [
662 DocStringElement::Text("This is a ".to_owned()),
663 DocStringElement::Reference(Unvalidated::Argument("foo".to_owned())),
664 DocStringElement::Text(" test.".to_owned()),
665 ]
666 .as_ref(),
667 doc.elements.as_slice()
668 );
669 }
670
671 #[test]
672 fn parse_class_reference() {
673 let doc: DocString<Unvalidated> = "This is a {class:MyClass} test.".try_into().unwrap();
674 assert_eq!(
675 [
676 DocStringElement::Text("This is a ".to_owned()),
677 DocStringElement::Reference(Unvalidated::Class("MyClass".to_owned())),
678 DocStringElement::Text(" test.".to_owned()),
679 ]
680 .as_ref(),
681 doc.elements.as_slice()
682 );
683 }
684
685 #[test]
686 fn parse_class_reference_at_the_end() {
687 let doc: DocString<Unvalidated> = "This is a test {class:MyClass2}".try_into().unwrap();
688 assert_eq!(
689 [
690 DocStringElement::Text("This is a test ".to_owned()),
691 DocStringElement::Reference(Unvalidated::Class("MyClass2".to_owned())),
692 ]
693 .as_ref(),
694 doc.elements.as_slice()
695 );
696 }
697
698 #[test]
699 fn parse_class_method() {
700 let doc: DocString<Unvalidated> = "This is a {class:MyClass.do_something()} method."
701 .try_into()
702 .unwrap();
703 assert_eq!(
704 [
705 DocStringElement::Text("This is a ".to_owned()),
706 DocStringElement::Reference(Unvalidated::ClassMethod(
707 "MyClass".to_owned(),
708 "do_something".to_owned()
709 )),
710 DocStringElement::Text(" method.".to_owned()),
711 ]
712 .as_ref(),
713 doc.elements.as_slice()
714 );
715 }
716
717 #[test]
718 fn parse_struct() {
719 let doc: DocString<Unvalidated> = "This is a {struct:MyStruct} struct.".try_into().unwrap();
720 assert_eq!(
721 [
722 DocStringElement::Text("This is a ".to_owned()),
723 DocStringElement::Reference(Unvalidated::Struct("MyStruct".to_owned())),
724 DocStringElement::Text(" struct.".to_owned()),
725 ]
726 .as_ref(),
727 doc.elements.as_slice()
728 );
729 }
730
731 #[test]
732 fn parse_struct_element() {
733 let doc: DocString<Unvalidated> = "This is a {struct:MyStruct.foo} struct element."
734 .try_into()
735 .unwrap();
736 assert_eq!(
737 [
738 DocStringElement::Text("This is a ".to_owned()),
739 DocStringElement::Reference(Unvalidated::StructField(
740 "MyStruct".to_owned(),
741 "foo".to_owned()
742 )),
743 DocStringElement::Text(" struct element.".to_owned()),
744 ]
745 .as_ref(),
746 doc.elements.as_slice()
747 );
748 }
749
750 #[test]
751 fn parse_enum() {
752 let doc: DocString<Unvalidated> = "This is a {enum:MyEnum} enum.".try_into().unwrap();
753 assert_eq!(
754 [
755 DocStringElement::Text("This is a ".to_owned()),
756 DocStringElement::Reference(Unvalidated::Enum("MyEnum".to_owned())),
757 DocStringElement::Text(" enum.".to_owned()),
758 ]
759 .as_ref(),
760 doc.elements.as_slice()
761 );
762 }
763
764 #[test]
765 fn parse_enum_element() {
766 let doc: DocString<Unvalidated> = "This is a {enum:MyEnum.foo} enum variant."
767 .try_into()
768 .unwrap();
769 assert_eq!(
770 [
771 DocStringElement::Text("This is a ".to_owned()),
772 DocStringElement::Reference(Unvalidated::EnumVariant(
773 "MyEnum".to_owned(),
774 "foo".to_owned()
775 )),
776 DocStringElement::Text(" enum variant.".to_owned()),
777 ]
778 .as_ref(),
779 doc.elements.as_slice()
780 );
781 }
782
783 #[test]
784 fn parse_interface() {
785 let doc: DocString<Unvalidated> = "This is a {interface:Interface} interface."
786 .try_into()
787 .unwrap();
788 assert_eq!(
789 [
790 DocStringElement::Text("This is a ".to_owned()),
791 DocStringElement::Reference(Unvalidated::Interface("Interface".to_owned())),
792 DocStringElement::Text(" interface.".to_owned()),
793 ]
794 .as_ref(),
795 doc.elements.as_slice()
796 );
797 }
798
799 #[test]
800 fn parse_interface_method() {
801 let doc: DocString<Unvalidated> = "This is a {interface:Interface.foo()} interface method."
802 .try_into()
803 .unwrap();
804 assert_eq!(
805 [
806 DocStringElement::Text("This is a ".to_owned()),
807 DocStringElement::Reference(Unvalidated::InterfaceMethod(
808 "Interface".to_owned(),
809 "foo".to_owned()
810 )),
811 DocStringElement::Text(" interface method.".to_owned()),
812 ]
813 .as_ref(),
814 doc.elements.as_slice()
815 );
816 }
817
818 #[test]
819 fn parse_null() {
820 let doc: DocString<Unvalidated> = "This is a {null} null.".try_into().unwrap();
821 assert_eq!(
822 [
823 DocStringElement::Text("This is a ".to_owned()),
824 DocStringElement::Null,
825 DocStringElement::Text(" null.".to_owned()),
826 ]
827 .as_ref(),
828 doc.elements.as_slice()
829 );
830 }
831
832 #[test]
833 fn parse_iterator() {
834 let doc: DocString<Unvalidated> = "This is a {iterator} iterator.".try_into().unwrap();
835 assert_eq!(
836 [
837 DocStringElement::Text("This is a ".to_owned()),
838 DocStringElement::Iterator,
839 DocStringElement::Text(" iterator.".to_owned()),
840 ]
841 .as_ref(),
842 doc.elements.as_slice()
843 );
844 }
845
846 #[test]
847 fn parse_from_owned_string() {
848 doc(format!("{{null}} this is a {}", "test"));
849 }
850}