oo_bindgen/model/
doc.rs

1//! Documentation
2//!
3//! Documentation is split in two; the `Doc` contains the mandatory brief description and optional details paragraph.
4//! The `DocString` represents a single paragraph of text. It can parse references and create hyperlinks in the
5//! generated doc. All the references are checked when creating the library and an error will be returned if a
6//! reference cannot be resolved.
7//!
8//! When a `Doc` is needed, the user can provide a string and it will automatically be parsed and interpreted as
9//! just a brief description. If additional details are needed, then the user may use the `doc()` function to
10//! build a more complex documentation. The `doc()` functions takes the brief string as a parameter, then can be
11//! chained with `details()` to add details paragraph or with `warning()` to add a warning paragraph.
12//!
13//! For parameters, only a `DocString` is accepted, because some generators do not support paragraphs for parameters.
14//!
15//! ### References
16//!
17//! In any `DocString`, you can put references that will print a hyperlink in the generated doc. You simply put
18//! the reference between curly braces and the `DocString` parser will resolve them. All the names used must match
19//! exactly what is specified __in the schema__, each generator takes care of the renaming the type for the target
20//! language.
21//!
22//! Here are all the type of links available:
23//! - `{param:MyParam}`: references the parameter `MyParam` of a method. __This is only valid within a method documentation__.
24//!   Not all documentation generator supports hyperlinking this, so `code font` is used instead.
25//! - `{class:MyClass}`: references the class `MyClass`.
26//! - `{class:MyClass.foo()}`: references the method `foo()` of `MyClass`. Can be a static, non-static, or async method.
27//!   No need to put parameters.
28//! - `{class:MyClass.[constructor]}`: references `MyClass`'s constructor.
29//! - `{class:MyClass.[destructor]}`: references `MyClass`'s destructor (the `Dispose()` method in C#, the `close()` method in Java).
30//! - `{struct:MyStruct}`: references the structure `MyStruct`.
31//! - `{struct:MyStruct.foo}`: references the `foo` element inside `MyStruct`.
32//! - `{struct:MyStruct.foo()}`: references the `foo()` method of `MyStruct`. Can be a static or non-static method. No need to put parameters.
33//! - `{enum:MyEnum}`: references the enum `MyEnum`.
34//! - `{enum:MyEnum.foo}`: references the `foo` variant of `MyEnum`.
35//! - `{interface:MyInterface}`: references the interface `MyInterface`.
36//! - `{interface:MyInterface.foo()}`: references the `foo()` callback of `MyInterface`. This cannot reference the `on_destroy` callback.
37//!
38//! There other miscellaneous tag that can be used:
39//! - `{null}`: prints `NULL` in C, or `null` in C# and Java.
40//! - `{iterator}`: prints `iterator` in C, or `collection` in C# and Java.
41
42use 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
136/// Used in builders
137pub(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        // Add remaining string
285        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    /// Reference to an argument
326    /// Can only be used within the context of a function or callback function
327    Argument(String),
328    /// Reference a class
329    Class(String),
330    /// Reference a class method
331    ///
332    /// First string is the class name, second is the method's name
333    ClassMethod(String, String),
334    /// Reference to the class constructor
335    ClassConstructor(String),
336    /// Reference to the class destructor
337    ClassDestructor(String),
338    /// Reference a struct
339    Struct(String),
340    /// Reference a field within a struct
341    ///
342    /// First string is the struct name, second is the field name
343    StructField(String, String),
344    /// Reference an enum
345    Enum(String),
346    /// Reference an enum variant
347    ///
348    /// First string is the enum name, second is the enum variant name
349    EnumVariant(String, String),
350    /// Reference an interface
351    Interface(String),
352    /// Reference a method of a interface
353    ///
354    /// First string is the interface name, second is the method's name
355    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/// Validated doc reference
510#[non_exhaustive]
511#[derive(Debug, Clone)]
512pub enum Validated {
513    /// Reference to an argument
514    /// can only be used in docs for functions or callback methods
515    Argument(Name),
516    /// Reference a class
517    Class(ClassDeclarationHandle),
518    /// Reference a class method
519    ClassMethod(
520        Handle<Class<Unvalidated>>,
521        Name,
522        Handle<Function<Unvalidated>>,
523    ),
524    /// Reference to the class constructor
525    ClassConstructor(Handle<Class<Unvalidated>>, ClassConstructor<Unvalidated>),
526    /// Reference to the class destructor
527    ClassDestructor(Handle<Class<Unvalidated>>, ClassDestructor<Unvalidated>),
528    /// Reference a struct
529    Struct(StructType<Unvalidated>),
530    /// Reference a field within a struct
531    ///
532    /// Second parameter is the field name inside that struct
533    StructField(StructType<Unvalidated>, Name),
534    /// Reference an enum
535    Enum(Handle<Enum<Unvalidated>>),
536    /// Reference an enum variant
537    EnumVariant(Handle<Enum<Unvalidated>>, Name),
538    /// Reference an interface
539    Interface(Handle<Interface<Unvalidated>>),
540    /// Reference a method of a interface
541    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}