salvo_oapi/
naming.rs

1use std::any::TypeId;
2use std::collections::BTreeMap;
3use std::sync::LazyLock;
4
5use parking_lot::{RwLock, RwLockReadGuard};
6use regex::Regex;
7
8/// NameRule is used to specify the rule of naming.
9#[derive(Default, Debug, Clone, Copy)]
10pub enum NameRule {
11    /// Auto generate name by namer.
12    #[default]
13    Auto,
14    /// Force to use the given name.
15    Force(&'static str),
16}
17
18static GLOBAL_NAMER: LazyLock<RwLock<Box<dyn Namer>>> =
19    LazyLock::new(|| RwLock::new(Box::new(FlexNamer::new())));
20static NAME_TYPES: LazyLock<RwLock<BTreeMap<String, (TypeId, &'static str)>>> =
21    LazyLock::new(Default::default);
22
23/// Set global namer.
24///
25/// Set global namer, all the types will be named by this namer. You should call this method before
26/// at before you generate OpenAPI schema.
27///
28/// # Example
29///
30/// ```rust
31/// # use salvo_oapi::extract::*;
32/// # use salvo_core::prelude::*;
33/// # #[tokio::main]
34/// # async fn main() {
35/// salvo_oapi::naming::set_namer(
36///     salvo_oapi::naming::FlexNamer::new()
37///         .short_mode(true)
38///         .generic_delimiter('_', '_'),
39/// );
40/// # }
41/// ```
42pub fn set_namer(namer: impl Namer) {
43    *GLOBAL_NAMER.write() = Box::new(namer);
44    NAME_TYPES.write().clear();
45}
46
47#[doc(hidden)]
48pub fn namer() -> RwLockReadGuard<'static, Box<dyn Namer>> {
49    GLOBAL_NAMER.read()
50}
51
52/// Get type info by name.
53pub fn type_info_by_name(name: &str) -> Option<(TypeId, &'static str)> {
54    NAME_TYPES.read().get(name).cloned()
55}
56
57/// Set type info by name.
58pub fn set_name_type_info(
59    name: String,
60    type_id: TypeId,
61    type_name: &'static str,
62) -> Option<(TypeId, &'static str)> {
63    NAME_TYPES.write().insert(name, (type_id, type_name))
64}
65
66/// Assign name to type and returns the name.
67///
68/// If the type is already named, return the existing name.
69pub fn assign_name<T: 'static>(rule: NameRule) -> String {
70    let type_id = TypeId::of::<T>();
71    let type_name = std::any::type_name::<T>();
72    for (name, (exist_id, _)) in NAME_TYPES.read().iter() {
73        if *exist_id == type_id {
74            return name.clone();
75        }
76    }
77    namer().assign_name(type_id, type_name, rule)
78}
79
80/// Get the name of the type. Panic if the name is not exist.
81pub fn get_name<T: 'static>() -> String {
82    let type_id = TypeId::of::<T>();
83    for (name, (exist_id, _)) in NAME_TYPES.read().iter() {
84        if *exist_id == type_id {
85            return name.clone();
86        }
87    }
88    panic!(
89        "Type not found in the name registry: {:?}",
90        std::any::type_name::<T>()
91    );
92}
93
94fn type_generic_part(type_name: &str) -> String {
95    let re = Regex::new(r"^[^<]+").expect("Invalid regex");
96    let result = re.replace_all(type_name, "");
97    result.to_string()
98}
99/// Namer is used to assign names to types.
100pub trait Namer: Sync + Send + 'static {
101    /// Assign name to type.
102    fn assign_name(&self, type_id: TypeId, type_name: &'static str, rule: NameRule) -> String;
103}
104
105/// A namer that generates wordy names.
106#[derive(Default, Clone, Debug)]
107pub struct FlexNamer {
108    short_mode: bool,
109    generic_delimiter: Option<(String, String)>,
110}
111impl FlexNamer {
112    /// Create a new FlexNamer.
113    #[must_use]
114    pub fn new() -> Self {
115        Default::default()
116    }
117
118    /// Set the short mode.
119    #[must_use]
120    pub fn short_mode(mut self, short_mode: bool) -> Self {
121        self.short_mode = short_mode;
122        self
123    }
124
125    /// Set the delimiter for generic types.
126    #[must_use]
127    pub fn generic_delimiter(mut self, open: impl Into<String>, close: impl Into<String>) -> Self {
128        self.generic_delimiter = Some((open.into(), close.into()));
129        self
130    }
131}
132impl Namer for FlexNamer {
133    fn assign_name(&self, type_id: TypeId, type_name: &'static str, rule: NameRule) -> String {
134        let name = match rule {
135            NameRule::Auto => {
136                let mut base = if self.short_mode {
137                    let re = Regex::new(r"([^<>]*::)+").expect("Invalid regex");
138                    re.replace_all(type_name, "").to_string()
139                } else {
140                    type_name.replace("::", ".")
141                };
142                if let Some((open, close)) = &self.generic_delimiter {
143                    base = base.replace('<', open).replace('>', close);
144                }
145                let mut name = base.clone();
146                let mut count = 1;
147                while let Some(exist_id) = type_info_by_name(&name).map(|t| t.0) {
148                    if exist_id != type_id {
149                        count += 1;
150                        name = format!("{base}{count}");
151                    } else {
152                        break;
153                    }
154                }
155                name
156            }
157            NameRule::Force(force_name) => {
158                let mut base = if self.short_mode {
159                    let re = Regex::new(r"([^<>]*::)+").expect("Invalid regex");
160                    re.replace_all(type_name, "").to_string()
161                } else {
162                    format! {"{}{}", force_name, type_generic_part(type_name).replace("::", ".")}
163                };
164                if let Some((open, close)) = &self.generic_delimiter {
165                    base = base.replace('<', open).replace('>', close);
166                }
167                let mut name = base.clone();
168                let mut count = 1;
169                while let Some((exist_id, exist_name)) = type_info_by_name(&name) {
170                    if exist_id != type_id {
171                        count += 1;
172                        tracing::error!("Duplicate name for types: {}, {}", exist_name, type_name);
173                        name = format!("{base}{count}");
174                    } else {
175                        break;
176                    }
177                }
178                name
179            }
180        };
181        set_name_type_info(name.clone(), type_id, type_name);
182        name
183    }
184}
185
186mod tests {
187    #[test]
188    fn test_name() {
189        use super::*;
190
191        struct MyString;
192        mod nest {
193            pub(crate) struct MyString;
194        }
195
196        let name = assign_name::<String>(NameRule::Auto);
197        assert_eq!(name, "alloc.string.String");
198        let name = assign_name::<Vec<String>>(NameRule::Auto);
199        assert_eq!(name, "alloc.vec.Vec<alloc.string.String>");
200
201        let name = assign_name::<MyString>(NameRule::Auto);
202        assert_eq!(name, "salvo_oapi.naming.tests.test_name.MyString");
203        let name = assign_name::<nest::MyString>(NameRule::Auto);
204        assert_eq!(name, "salvo_oapi.naming.tests.test_name.nest.MyString");
205
206        // let namer = FlexNamer::new().generic_delimiter('_', '_');
207        // set_namer(namer);
208
209        // let name = assign_name::<String>(NameRule::Auto);
210        // assert_eq!(name, "alloc.string.String");
211        // let name = assign_name::<Vec<String>>(NameRule::Auto);
212        // assert_eq!(name, "alloc.vec.Vec_alloc.string.String_");
213
214        // let namer = FlexNamer::new().short_mode(true).generic_delimiter('_', '_');
215        // set_namer(namer);
216
217        // let name = assign_name::<String>(NameRule::Auto);
218        // assert_eq!(name, "String");
219        // let name = assign_name::<Vec<String>>(NameRule::Auto);
220        // assert_eq!(name, "Vec_String_");
221
222        // let namer = FlexNamer::new().short_mode(true).generic_delimiter('_', '_');
223        // set_namer(namer);
224
225        // struct MyString;
226        // mod nest {
227        //     pub(crate) struct MyString;
228        // }
229
230        // let name = assign_name::<MyString>(NameRule::Auto);
231        // assert_eq!(name, "MyString");
232        // let name = assign_name::<nest::MyString>(NameRule::Auto);
233        // assert_eq!(name, "MyString2");
234    }
235}