salvo_oapi/openapi/
tag.rs1use std::cmp::Ordering;
5
6use serde::{Deserialize, Serialize};
7
8use super::external_docs::ExternalDocs;
9use crate::PropMap;
10
11#[non_exhaustive]
17#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq)]
18#[serde(rename_all = "camelCase")]
19pub struct Tag {
20 pub name: String,
22
23 #[serde(skip_serializing_if = "Option::is_none")]
28 pub summary: Option<String>,
29
30 #[serde(skip_serializing_if = "Option::is_none")]
32 pub description: Option<String>,
33
34 #[serde(skip_serializing_if = "Option::is_none")]
36 pub external_docs: Option<ExternalDocs>,
37
38 #[serde(skip_serializing_if = "Option::is_none")]
43 pub parent: Option<String>,
44
45 #[serde(skip_serializing_if = "Option::is_none")]
50 pub kind: Option<String>,
51
52 #[serde(skip_serializing_if = "PropMap::is_empty", flatten)]
54 pub extensions: PropMap<String, serde_json::Value>,
55}
56impl Ord for Tag {
57 fn cmp(&self, other: &Self) -> Ordering {
58 self.name.cmp(&other.name)
59 }
60}
61impl PartialOrd for Tag {
62 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
63 Some(self.cmp(other))
64 }
65}
66impl From<String> for Tag {
67 fn from(name: String) -> Self {
68 Self::new(name)
69 }
70}
71impl From<&String> for Tag {
72 fn from(name: &String) -> Self {
73 Self::new(name)
74 }
75}
76impl<'a> From<&'a str> for Tag {
77 fn from(name: &'a str) -> Self {
78 Self::new(name.to_owned())
79 }
80}
81
82impl Tag {
83 #[must_use]
85 pub fn new(name: impl Into<String>) -> Self {
86 Self {
87 name: name.into(),
88 ..Default::default()
89 }
90 }
91 #[must_use]
93 pub fn name(mut self, name: impl Into<String>) -> Self {
94 self.name = name.into();
95 self
96 }
97
98 #[must_use]
100 pub fn summary(mut self, summary: impl Into<String>) -> Self {
101 self.summary = Some(summary.into());
102 self
103 }
104
105 #[must_use]
107 pub fn description(mut self, description: impl Into<String>) -> Self {
108 self.description = Some(description.into());
109 self
110 }
111
112 #[must_use]
114 pub fn parent(mut self, parent: impl Into<String>) -> Self {
115 self.parent = Some(parent.into());
116 self
117 }
118
119 #[must_use]
121 pub fn kind(mut self, kind: impl Into<String>) -> Self {
122 self.kind = Some(kind.into());
123 self
124 }
125
126 #[must_use]
128 pub fn external_docs(mut self, external_docs: ExternalDocs) -> Self {
129 self.external_docs = Some(external_docs);
130 self
131 }
132
133 #[must_use]
135 pub fn add_extension<K: Into<String>>(mut self, key: K, value: serde_json::Value) -> Self {
136 self.extensions.insert(key.into(), value);
137 self
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::{ExternalDocs, Tag};
144
145 #[test]
146 fn tag_new() {
147 let tag = Tag::new("tag name");
148 assert_eq!(tag.name, "tag name");
149 assert!(tag.description.is_none());
150 assert!(tag.external_docs.is_none());
151 assert!(tag.extensions.is_empty());
152
153 let tag = tag.name("new tag name");
154 assert_eq!(tag.name, "new tag name");
155
156 let tag = tag.description("description");
157 assert!(tag.description.is_some());
158
159 let tag = tag.external_docs(ExternalDocs::new(""));
160 assert!(tag.external_docs.is_some());
161 }
162
163 #[test]
164 fn tag_openapi_3_2_fields_round_trip() {
165 let tag = Tag::new("partner")
166 .summary("Partner")
167 .description("Operations available to the partners network")
168 .parent("external")
169 .kind("audience");
170
171 let value = serde_json::to_value(&tag).expect("serialize");
172 assert_eq!(
173 value,
174 serde_json::json!({
175 "name": "partner",
176 "summary": "Partner",
177 "description": "Operations available to the partners network",
178 "parent": "external",
179 "kind": "audience"
180 })
181 );
182
183 let parsed: Tag = serde_json::from_value(value).expect("deserialize");
184 assert_eq!(parsed, tag);
185 }
186
187 #[test]
188 fn tag_3_1_output_is_unchanged() {
189 let tag = Tag::new("pets").description("pet operations");
190 assert_eq!(
191 serde_json::to_value(&tag).expect("serialize"),
192 serde_json::json!({ "name": "pets", "description": "pet operations" })
193 );
194 }
195
196 #[test]
197 fn from_string() {
198 let name = "tag name".to_owned();
199 let tag = Tag::from(name);
200 assert_eq!(tag.name, "tag name".to_owned());
201 }
202
203 #[test]
204 fn from_string_ref() {
205 let name = "tag name".to_owned();
206 let tag = Tag::from(&name);
207 assert_eq!(tag.name, "tag name".to_owned());
208 }
209
210 #[test]
211 fn from_str() {
212 let name = "tag name";
213 let tag = Tag::from(name);
214 assert_eq!(tag.name, "tag name");
215 }
216
217 #[test]
218 fn cmp() {
219 let tag1 = Tag::new("a");
220 let tag2 = Tag::new("b");
221
222 assert!(tag1 < tag2);
223 }
224}