1use std::any::TypeId;
2use std::collections::{BTreeSet, HashMap};
3use std::sync::{LazyLock, RwLock};
4
5use regex::Regex;
6use salvo_core::Router;
7
8use crate::{SecurityRequirement, path::PathItemType};
9
10#[derive(Debug, Default)]
11pub(crate) struct NormNode {
12 pub(crate) handler_type_id: Option<TypeId>,
14 pub(crate) handler_type_name: Option<&'static str>,
15 pub(crate) method: Option<PathItemType>,
16 pub(crate) path: Option<String>,
17 pub(crate) children: Vec<NormNode>,
18 pub(crate) metadata: Metadata,
19}
20
21impl NormNode {
22 pub(crate) fn new(router: &Router, inherted_metadata: Metadata) -> Self {
23 let mut node = NormNode {
24 metadata: inherted_metadata,
26 ..NormNode::default()
27 };
28 let registry = METADATA_REGISTRY
29 .read()
30 .expect("failed to lock METADATA_REGISTRY for read");
31 if let Some(metadata) = registry.get(&router.id) {
32 node.metadata.tags.extend(metadata.tags.iter().cloned());
33 node.metadata
34 .securities
35 .extend(metadata.securities.iter().cloned());
36 }
37
38 let regex = Regex::new(r#"<([^/:>]+)(:[^>]*)?>"#).expect("invalid regex");
39 for filter in router.filters() {
40 let info = format!("{filter:?}");
41 if info.starts_with("path:") {
42 let path = info
43 .split_once(':')
44 .expect("split once by ':' should not be get `None`")
45 .1;
46 node.path = Some(regex.replace_all(path, "{$1}").to_string());
47 } else if info.starts_with("method:") {
48 match info
49 .split_once(':')
50 .expect("split once by ':' should not be get `None`.")
51 .1
52 {
53 "GET" => node.method = Some(PathItemType::Get),
54 "POST" => node.method = Some(PathItemType::Post),
55 "PUT" => node.method = Some(PathItemType::Put),
56 "DELETE" => node.method = Some(PathItemType::Delete),
57 "HEAD" => node.method = Some(PathItemType::Head),
58 "OPTIONS" => node.method = Some(PathItemType::Options),
59 "CONNECT" => node.method = Some(PathItemType::Connect),
60 "TRACE" => node.method = Some(PathItemType::Trace),
61 "PATCH" => node.method = Some(PathItemType::Patch),
62 _ => {}
63 }
64 }
65 }
66 node.handler_type_id = router.goal.as_ref().map(|h| h.type_id());
67 node.handler_type_name = router.goal.as_ref().map(|h| h.type_name());
68 let routers = router.routers();
69 if !routers.is_empty() {
70 for router in routers {
71 node.children
72 .push(NormNode::new(router, node.metadata.clone()));
73 }
74 }
75 node
76 }
77}
78
79type MetadataMap = RwLock<HashMap<usize, Metadata>>;
81static METADATA_REGISTRY: LazyLock<MetadataMap> = LazyLock::new(MetadataMap::default);
82
83pub trait RouterExt {
85 fn oapi_security(self, security: SecurityRequirement) -> Self;
89
90 fn oapi_securities<I>(self, security: I) -> Self
94 where
95 I: IntoIterator<Item = SecurityRequirement>;
96
97 fn oapi_tag(self, tag: impl Into<String>) -> Self;
101
102 fn oapi_tags<I, V>(self, tags: I) -> Self
106 where
107 I: IntoIterator<Item = V>,
108 V: Into<String>;
109}
110
111impl RouterExt for Router {
112 fn oapi_security(self, security: SecurityRequirement) -> Self {
113 let mut guard = METADATA_REGISTRY
114 .write()
115 .expect("failed to lock METADATA_REGISTRY for write");
116 let metadata = guard.entry(self.id).or_default();
117 metadata.securities.push(security);
118 self
119 }
120 fn oapi_securities<I>(self, iter: I) -> Self
121 where
122 I: IntoIterator<Item = SecurityRequirement>,
123 {
124 let mut guard = METADATA_REGISTRY
125 .write()
126 .expect("failed to lock METADATA_REGISTRY for write");
127 let metadata = guard.entry(self.id).or_default();
128 metadata.securities.extend(iter);
129 self
130 }
131 fn oapi_tag(self, tag: impl Into<String>) -> Self {
132 let mut guard = METADATA_REGISTRY
133 .write()
134 .expect("failed to lock METADATA_REGISTRY for write");
135 let metadata = guard.entry(self.id).or_default();
136 metadata.tags.insert(tag.into());
137 self
138 }
139 fn oapi_tags<I, V>(self, iter: I) -> Self
140 where
141 I: IntoIterator<Item = V>,
142 V: Into<String>,
143 {
144 let mut guard = METADATA_REGISTRY
145 .write()
146 .expect("failed to lock METADATA_REGISTRY for write");
147 let metadata = guard.entry(self.id).or_default();
148 metadata.tags.extend(iter.into_iter().map(Into::into));
149 self
150 }
151}
152
153#[non_exhaustive]
154#[derive(Default, Clone, Debug)]
155pub(crate) struct Metadata {
156 pub(crate) tags: BTreeSet<String>,
157 pub(crate) securities: Vec<SecurityRequirement>,
158}