Skip to main content

salvo_oapi/openapi/
xml.rs

1//! Implements [OpenAPI Xml Object][xml_object] types.
2//!
3//! [xml_object]: https://spec.openapis.org/oas/latest.html#xml-object
4use std::borrow::Cow;
5
6use serde::{Deserialize, Serialize};
7
8/// Implements [OpenAPI Xml Object][xml_object].
9///
10/// Can be used to modify xml output format of specific [OpenAPI Schema Object][schema_object] which
11/// are implemented in [`schema`][schema] module.
12///
13/// [xml_object]: https://spec.openapis.org/oas/latest.html#xml-object
14/// [schema_object]: https://spec.openapis.org/oas/latest.html#schema-object
15/// [schema]: ../schema/index.html
16#[non_exhaustive]
17#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq)]
18#[serde(rename_all = "camelCase")]
19pub struct Xml {
20    /// The kind of XML node the schema corresponds to. Added in OpenAPI 3.2.
21    ///
22    /// When set, [`Xml::attribute`] and [`Xml::wrapped`] must not be used — `nodeType` is the
23    /// replacement for both.
24    ///
25    /// See <https://spec.openapis.org/oas/v3.2.0.html#xml-object>.
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub node_type: Option<XmlNodeType>,
28
29    /// Used to replace the name of attribute or type used in schema property.
30    /// When used with [`Xml::wrapped`] attribute the name will be used as a wrapper name
31    /// for wrapped array instead of the item or type name.
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub name: Option<Cow<'static, str>>,
34
35    /// Valid uri definition of namespace used in xml.
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub namespace: Option<Cow<'static, str>>,
38
39    /// Prefix for xml element [`Xml::name`].
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub prefix: Option<Cow<'static, str>>,
42
43    /// Flag deciding will this attribute translate to element attribute instead of xml element.
44    ///
45    /// Deprecated in OpenAPI 3.2 in favour of [`XmlNodeType::Attribute`].
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub attribute: Option<bool>,
48
49    /// Flag only usable with array definition. If set to true the output xml will wrap the array
50    /// of items `<pets><pet></pet></pets>` instead of unwrapped `<pet></pet>`.
51    ///
52    /// Deprecated in OpenAPI 3.2 in favour of [`XmlNodeType::Element`] on the array schema.
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub wrapped: Option<bool>,
55}
56
57/// The kind of XML [DOM node](https://dom.spec.whatwg.org/#interface-node) a schema describes.
58///
59/// Used by the OpenAPI 3.2 [`Xml::node_type`] field.
60///
61/// See <https://spec.openapis.org/oas/v3.2.0.html#xml-node-types>.
62#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
63#[serde(rename_all = "lowercase")]
64pub enum XmlNodeType {
65    /// The schema represents an element and describes its contents.
66    Element,
67    /// The schema represents an attribute and describes its value.
68    Attribute,
69    /// The schema represents a text node (parsed character data).
70    Text,
71    /// The schema represents a CDATA section.
72    Cdata,
73    /// The schema does not correspond to any node; nodes for its subschemas are placed
74    /// directly under the parent schema's node.
75    None,
76}
77
78impl Xml {
79    /// Construct a new [`Xml`] object.
80    #[must_use]
81    pub fn new() -> Self {
82        Self {
83            ..Default::default()
84        }
85    }
86}
87
88impl Xml {
89    /// Set [`Xml::node_type`]. Requires OpenAPI 3.2.
90    ///
91    /// Builder style chainable consuming add node type method.
92    #[must_use]
93    pub fn node_type(mut self, node_type: XmlNodeType) -> Self {
94        self.node_type = Some(node_type);
95        self
96    }
97
98    /// Add [`Xml::name`] to xml object.
99    ///
100    /// Builder style chainable consuming add name method.
101    #[must_use]
102    pub fn name<S: Into<Cow<'static, str>>>(mut self, name: S) -> Self {
103        self.name = Some(name.into());
104        self
105    }
106
107    /// Add [`Xml::namespace`] to xml object.
108    ///
109    /// Builder style chainable consuming add namespace method.
110    #[must_use]
111    pub fn namespace<S: Into<Cow<'static, str>>>(mut self, namespace: S) -> Self {
112        self.namespace = Some(namespace.into());
113        self
114    }
115
116    /// Add [`Xml::prefix`] to xml object.
117    ///
118    /// Builder style chainable consuming add prefix method.
119    #[must_use]
120    pub fn prefix<S: Into<Cow<'static, str>>>(mut self, prefix: S) -> Self {
121        self.prefix = Some(prefix.into());
122        self
123    }
124
125    /// Mark [`Xml`] object as attribute. See [`Xml::attribute`]
126    ///
127    /// Builder style chainable consuming add attribute method.
128    #[must_use]
129    pub fn attribute(mut self, attribute: bool) -> Self {
130        self.attribute = Some(attribute);
131        self
132    }
133
134    /// Mark [`Xml`] object wrapped. See [`Xml::wrapped`]
135    ///
136    /// Builder style chainable consuming add wrapped method.
137    #[must_use]
138    pub fn wrapped(mut self, wrapped: bool) -> Self {
139        self.wrapped = Some(wrapped);
140        self
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::{Xml, XmlNodeType};
147
148    #[test]
149    fn xml_new() {
150        let mut xml = Xml::new();
151
152        assert!(xml.name.is_none());
153        assert!(xml.namespace.is_none());
154        assert!(xml.prefix.is_none());
155        assert!(xml.attribute.is_none());
156        assert!(xml.wrapped.is_none());
157
158        xml = xml.name("name");
159        assert!(xml.name.is_some());
160
161        xml = xml.namespace("namespace");
162        assert!(xml.namespace.is_some());
163
164        xml = xml.prefix("prefix");
165        assert!(xml.prefix.is_some());
166
167        xml = xml.attribute(true);
168        assert!(xml.attribute.is_some());
169
170        xml = xml.wrapped(true);
171        assert!(xml.wrapped.is_some());
172    }
173
174    #[test]
175    fn xml_node_type_round_trips() {
176        for (node_type, rendered) in [
177            (XmlNodeType::Element, "element"),
178            (XmlNodeType::Attribute, "attribute"),
179            (XmlNodeType::Text, "text"),
180            (XmlNodeType::Cdata, "cdata"),
181            (XmlNodeType::None, "none"),
182        ] {
183            let xml = Xml::new().node_type(node_type);
184            let value = serde_json::to_value(&xml).expect("serialize");
185            assert_eq!(value, serde_json::json!({ "nodeType": rendered }));
186            let parsed: Xml = serde_json::from_value(value).expect("deserialize");
187            assert_eq!(parsed.node_type, Some(node_type));
188        }
189    }
190
191    #[test]
192    fn xml_without_node_type_is_unchanged() {
193        let xml = Xml::new().name("pet").wrapped(true);
194        assert_eq!(
195            serde_json::to_value(&xml).expect("serialize"),
196            serde_json::json!({ "name": "pet", "wrapped": true })
197        );
198    }
199}