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    #[must_use]
64    pub fn new(name: impl Into<String>) -> Self {
65        Self {
66            name: name.into(),
67            ..Default::default()
68        }
69    }
70    /// Add name of the tag.
71    #[must_use]
72    pub fn name(mut self, name: impl Into<String>) -> Self {
73        self.name = name.into();
74        self
75    }
76
77    /// Add additional description for the tag.
78    #[must_use]
79    pub fn description(mut self, description: impl Into<String>) -> Self {
80        self.description = Some(description.into());
81        self
82    }
83
84    /// Add additional external documentation for the tag.
85    #[must_use]
86    pub fn external_docs(mut self, external_docs: ExternalDocs) -> Self {
87        self.external_docs = Some(external_docs);
88        self
89    }
90
91    /// Add openapi extension (`x-something`) for [`Tag`].
92    #[must_use]
93    pub fn add_extension<K: Into<String>>(mut self, key: K, value: serde_json::Value) -> Self {
94        self.extensions.insert(key.into(), value);
95        self
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::ExternalDocs;
102    use super::Tag;
103
104    #[test]
105    fn tag_new() {
106        let tag = Tag::new("tag name");
107        assert_eq!(tag.name, "tag name");
108        assert!(tag.description.is_none());
109        assert!(tag.external_docs.is_none());
110        assert!(tag.extensions.is_empty());
111
112        let tag = tag.name("new tag name");
113        assert_eq!(tag.name, "new tag name");
114
115        let tag = tag.description("description");
116        assert!(tag.description.is_some());
117
118        let tag = tag.external_docs(ExternalDocs::new(""));
119        assert!(tag.external_docs.is_some());
120    }
121
122    #[test]
123    fn from_string() {
124        let name = "tag name".to_owned();
125        let tag = Tag::from(name);
126        assert_eq!(tag.name, "tag name".to_owned());
127    }
128
129    #[test]
130    fn from_string_ref() {
131        let name = "tag name".to_owned();
132        let tag = Tag::from(&name);
133        assert_eq!(tag.name, "tag name".to_owned());
134    }
135
136    #[test]
137    fn from_str() {
138        let name = "tag name";
139        let tag = Tag::from(name);
140        assert_eq!(tag.name, "tag name");
141    }
142
143    #[test]
144    fn cmp() {
145        let tag1 = Tag::new("a");
146        let tag2 = Tag::new("b");
147
148        assert!(tag1 < tag2);
149    }
150}