utoipa_gen/component/schema/
xml.rs

1use proc_macro2::Ident;
2use quote::{quote, ToTokens};
3use syn::{parenthesized, parse::Parse, token::Paren, Error, LitStr, Token};
4
5use crate::parse_utils;
6
7#[derive(Default, Clone)]
8#[cfg_attr(feature = "debug", derive(Debug))]
9pub struct XmlAttr {
10    pub name: Option<String>,
11    pub namespace: Option<String>,
12    pub prefix: Option<String>,
13    pub is_attribute: bool,
14    pub is_wrapped: Option<Ident>,
15    pub wrap_name: Option<String>,
16}
17
18impl XmlAttr {
19    pub fn with_wrapped(is_wrapped: Option<Ident>, wrap_name: Option<String>) -> Self {
20        Self {
21            is_wrapped,
22            wrap_name,
23            ..Default::default()
24        }
25    }
26}
27
28impl Parse for XmlAttr {
29    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
30        const EXPECTED_ATTRIBUTE_MESSAGE: &str =
31            "unexpected attribute, expected any of: name, namespace, prefix, attribute, wrapped";
32        let mut xml = XmlAttr::default();
33
34        while !input.is_empty() {
35            let attribute = input
36                .parse::<Ident>()
37                .map_err(|error| Error::new(error.span(), EXPECTED_ATTRIBUTE_MESSAGE))?;
38            let attribute_name = &*attribute.to_string();
39
40            match attribute_name {
41                "name" => {
42                    xml.name =
43                        Some(parse_utils::parse_next(input, || input.parse::<LitStr>())?.value())
44                }
45                "namespace" => {
46                    xml.namespace =
47                        Some(parse_utils::parse_next(input, || input.parse::<LitStr>())?.value())
48                }
49                "prefix" => {
50                    xml.prefix =
51                        Some(parse_utils::parse_next(input, || input.parse::<LitStr>())?.value())
52                }
53                "attribute" => xml.is_attribute = parse_utils::parse_bool_or_true(input)?,
54                "wrapped" => {
55                    // wrapped or wrapped(name = "wrap_name")
56                    if input.peek(Paren) {
57                        let group;
58                        parenthesized!(group in input);
59
60                        let wrapped_attribute = group.parse::<Ident>().map_err(|error| {
61                            Error::new(
62                                error.span(),
63                                format!("unexpected attribute, expected: name, {error}"),
64                            )
65                        })?;
66                        if wrapped_attribute != "name" {
67                            return Err(Error::new(
68                                wrapped_attribute.span(),
69                                "unexpected wrapped attribute, expected: name",
70                            ));
71                        }
72                        group.parse::<Token![=]>()?;
73                        xml.wrap_name = Some(group.parse::<LitStr>()?.value());
74                    }
75                    xml.is_wrapped = Some(attribute);
76                }
77                _ => return Err(Error::new(attribute.span(), EXPECTED_ATTRIBUTE_MESSAGE)),
78            }
79
80            if !input.is_empty() {
81                input.parse::<Token![,]>()?;
82            }
83        }
84
85        Ok(xml)
86    }
87}
88
89impl ToTokens for XmlAttr {
90    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
91        tokens.extend(quote! {
92            utoipa::openapi::xml::XmlBuilder::new()
93        });
94
95        if let Some(ref name) = self.name {
96            tokens.extend(quote! {
97                .name(Some(#name))
98            })
99        }
100
101        if let Some(ref namespace) = self.namespace {
102            tokens.extend(quote! {
103                .namespace(Some(#namespace))
104            })
105        }
106
107        if let Some(ref prefix) = self.prefix {
108            tokens.extend(quote! {
109                .prefix(Some(#prefix))
110            })
111        }
112
113        if self.is_attribute {
114            tokens.extend(quote! {
115                .attribute(Some(true))
116            })
117        }
118
119        if self.is_wrapped.is_some() {
120            tokens.extend(quote! {
121                .wrapped(Some(true))
122            });
123
124            // if is wrapped and wrap name is defined use wrap name instead
125            if let Some(ref wrap_name) = self.wrap_name {
126                tokens.extend(quote! {
127                    .name(Some(#wrap_name))
128                })
129            }
130        }
131
132        tokens.extend(quote! { .build() })
133    }
134}