salvo_oapi/openapi/
tag.rs

1//! Implements [OpenAPI Tag Object][tag] types.
2//!
3//! [tag]: https://spec.openapis.org/oas/latest.html#tag-object
4use std::cmp::Ordering;
5
6use serde::{Deserialize, Serialize};
7
8use super::external_docs::ExternalDocs;
9use crate::PropMap;
10
11/// Implements [OpenAPI Tag Object][tag].
12///
13/// Tag can be used to provide additional metadata for tags used by path operations.
14///
15/// [tag]: https://spec.openapis.org/oas/latest.html#tag-object
16#[non_exhaustive]
17#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq)]
18#[serde(rename_all = "camelCase")]
19pub struct Tag {
20    /// Name of the tag. Should match to tag of **operation**.
21    pub name: String,
22
23    /// Additional description for the tag shown in the document.
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub description: Option<String>,
26
27    /// Additional external documentation for the tag.
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub external_docs: Option<ExternalDocs>,
30
31    /// Optional extensions "x-something"
32    #[serde(skip_serializing_if = "PropMap::is_empty", flatten)]
33    pub extensions: PropMap<String, serde_json::Value>,
34}
35impl Ord for Tag {
36    fn cmp(&self, other: &Self) -> Ordering {
37        self.name.cmp(&other.name)
38    }
39}
40impl PartialOrd for Tag {
41    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
42        Some(self.cmp(other))
43    }
44}
45impl From<String> for Tag {
46    fn from(name: String) -> Self {
47        Self::new(name)
48    }
49}
50impl From<&String> for Tag {
51    fn from(name: &String) -> Self {
52        Self::new(name)
53    }
54}
55impl<'a> From<&'a str> for Tag {
56    fn from(name: &'a str) -> Self {
57        Self::new(name.to_owned())
58    }
59}
60
61impl Tag {
62    /// Construct a new [`Tag`] with given name.
63    pub fn new(name: impl Into<String>) -> Self {
64        Self {
65            name: name.into(),
66            ..Default::default()
67        }
68    }
69    /// Add name of the tag.
70    pub fn name(mut self, name: impl Into<String>) -> Self {
71        self.name = name.into();
72        self
73    }
74
75    /// Add additional description for the tag.
76    pub fn description(mut self, description: impl Into<String>) -> Self {
77        self.description = Some(description.into());
78        self
79    }
80
81    /// Add additional external documentation for the tag.
82    pub fn external_docs(mut self, external_docs: ExternalDocs) -> Self {
83        self.external_docs = Some(external_docs);
84        self
85    }
86
87    /// Add openapi extension (`x-something`) for [`Tag`].
88    pub fn add_extension<K: Into<String>>(mut self, key: K, value: serde_json::Value) -> Self {
89        self.extensions.insert(key.into(), value);
90        self
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::ExternalDocs;
97    use super::Tag;
98
99    #[test]
100    fn tag_new() {
101        let tag = Tag::new("tag name");
102        assert_eq!(tag.name, "tag name");
103        assert!(tag.description.is_none());
104        assert!(tag.external_docs.is_none());
105        assert!(tag.extensions.is_empty());
106
107        let tag = tag.name("new tag name");
108        assert_eq!(tag.name, "new tag name");
109
110        let tag = tag.description("description");
111        assert!(tag.description.is_some());
112
113        let tag = tag.external_docs(ExternalDocs::new(""));
114        assert!(tag.external_docs.is_some());
115    }
116
117    #[test]
118    fn from_string() {
119        let name = "tag name".to_string();
120        let tag = Tag::from(name);
121        assert_eq!(tag.name, "tag name".to_string());
122    }
123
124    #[test]
125    fn from_string_ref() {
126        let name = "tag name".to_string();
127        let tag = Tag::from(&name);
128        assert_eq!(tag.name, "tag name".to_string());
129    }
130
131    #[test]
132    fn from_str() {
133        let name = "tag name";
134        let tag = Tag::from(name);
135        assert_eq!(tag.name, "tag name");
136    }
137
138    #[test]
139    fn cmp() {
140        let tag1 = Tag::new("a");
141        let tag2 = Tag::new("b");
142
143        assert!(tag1 < tag2);
144    }
145}