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}