1#![deny(
19 absolute_paths_not_starting_with_crate,
22 explicit_outlives_requirements,
23 macro_use_extern_crate,
24 redundant_lifetimes,
25 anonymous_parameters,
26 bare_trait_objects,
27 missing_copy_implementations,
29 missing_debug_implementations,
30 missing_docs,
31 trivial_numeric_casts,
34 unreachable_pub,
35 unstable_features,
37 unused_lifetimes,
39 unused_macro_rules,
40 unused_extern_crates,
41 unused_import_braces,
42 unused_qualifications,
43 unused_results,
44 variant_size_differences,
45
46 warnings, clippy::all,
49 clippy::pedantic,
51 clippy::cargo,
53)]
54#![allow(
55 clippy::blanket_clippy_restriction_lints, clippy::implicit_return, clippy::module_name_repetitions, clippy::multiple_crate_versions, clippy::missing_errors_doc, clippy::missing_panics_doc, clippy::panic_in_result_fn,
63 clippy::shadow_same, clippy::shadow_reuse, clippy::exhaustive_enums,
66 clippy::exhaustive_structs,
67 clippy::indexing_slicing,
68 clippy::separated_literal_suffix, clippy::single_char_lifetime_names, )]
71#![doc = include_str!("../README.md")]
72
73use crate::model::UriInfo;
74
75pub mod ci;
77pub mod config;
79pub mod core;
81pub mod error;
83pub mod macros;
85pub mod model;
87
88#[allow(missing_docs)]
89pub trait IRouter {
90 fn app_name(&self) -> &str;
92
93 fn uri_infos(&self) -> &Vec<UriInfo>;
95}
96
97#[allow(missing_docs)]
98#[cfg(feature = "axum")]
99pub mod axum_impl {
100 use super::model::UriInfo;
101 use crate::IRouter;
102 use axum::extract::Request;
103 use axum::response::IntoResponse;
104 use axum::routing::MethodRouter;
105 use axum::Router;
106 use std::convert::Infallible;
107 use tower_service::Service;
108
109 #[derive(Debug, Clone)]
145 pub struct ShenYuRouter<S = ()> {
146 app_name: String,
147 inner: Router<S>,
148 uri_infos: Vec<UriInfo>,
149 }
150
151 impl<S> ShenYuRouter<S>
152 where
153 S: Clone + Send + Sync + 'static,
154 {
155 #[must_use]
156 pub fn new(app_name: &str) -> Self {
157 Self {
158 app_name: app_name.to_string(),
159 inner: Router::new(),
160 uri_infos: Vec::new(),
161 }
162 }
163
164 #[must_use]
165 pub fn uri_info(mut self, uri_info: UriInfo) -> Self {
166 self.uri_infos.push(uri_info);
167 self
168 }
169
170 #[must_use]
171 pub fn route(mut self, path: &str, method: &str, method_router: MethodRouter<S>) -> Self {
172 self.inner = self.inner.route(path, method_router);
173 self.uri_infos.push(UriInfo {
174 path: path.to_string(),
175 rule_name: path.to_string(),
176 service_name: None,
177 method_name: method.to_string(),
178 });
179 self
180 }
181
182 #[must_use]
183 pub fn route_service<T>(mut self, path: &str, method: &str, service: T) -> Self
184 where
185 T: Service<Request, Error = Infallible> + Clone + Send + 'static,
186 T::Response: IntoResponse,
187 T::Future: Send + 'static,
188 {
189 self.inner = self.inner.route_service(path, service);
190 self.uri_infos.push(UriInfo {
191 path: path.to_string(),
192 rule_name: path.to_string(),
193 service_name: None,
194 method_name: method.to_string(),
195 });
196 self
197 }
198
199 #[must_use]
200 #[track_caller]
201 pub fn nest(mut self, path: &str, route: ShenYuRouter<S>) -> Self {
202 self.inner = self.inner.nest(path, route.inner);
203 self.uri_infos.extend(route.uri_infos);
204 self
205 }
206
207 #[must_use]
208 #[track_caller]
209 pub fn nest_service<T>(mut self, path: &str, method: &str, service: T) -> Self
210 where
211 T: Service<Request, Error = Infallible> + Clone + Send + 'static,
212 T::Response: IntoResponse,
213 T::Future: Send + 'static,
214 {
215 self.inner = self.inner.nest_service(path, service);
216 self.uri_infos.push(UriInfo {
217 path: path.to_string(),
218 rule_name: path.to_string(),
219 service_name: None,
220 method_name: method.to_string(),
221 });
222 self
223 }
224
225 #[must_use]
226 pub fn uri_infos(&self) -> &Vec<UriInfo> {
227 &self.uri_infos
228 }
229
230 #[must_use]
231 #[track_caller]
232 pub fn merge<R>(mut self, other: ShenYuRouter<R>) -> Self
233 where
234 R: Into<Router<S>>,
235 S: Clone + Send + Sync + 'static,
236 Router<S>: From<Router<R>>,
237 {
238 self.inner = self.inner.merge(other.inner);
239 self.uri_infos.extend(other.uri_infos);
240 self
241 }
242 }
243
244 impl<S> From<ShenYuRouter<S>> for Router<S>
245 where
246 S: Clone + Send + Sync + 'static,
247 {
248 fn from(val: ShenYuRouter<S>) -> Self {
249 val.inner
250 }
251 }
252
253 impl<S> IRouter for ShenYuRouter<S> {
254 fn app_name(&self) -> &str {
255 &self.app_name
256 }
257
258 fn uri_infos(&self) -> &Vec<UriInfo> {
259 &self.uri_infos
260 }
261 }
262}
263
264#[allow(missing_docs)]
265#[cfg(feature = "actix-web")]
266pub mod actix_web_impl {
267 use super::model::UriInfo;
268 use crate::IRouter;
269
270 #[derive(Debug, Clone)]
280 pub struct ShenYuRouter {
281 app_name: String,
282 uri_infos: Vec<UriInfo>,
283 }
284
285 impl ShenYuRouter {
286 #[must_use]
287 pub fn new(app_name: &str) -> Self {
288 Self {
289 app_name: app_name.to_string(),
290 uri_infos: Vec::new(),
291 }
292 }
293
294 pub fn route(&mut self, path: &str, method: &str) {
295 self.uri_infos.push(UriInfo {
296 path: path.to_string().clone(),
297 rule_name: path.to_string().clone(),
298 service_name: None,
299 method_name: method.to_string(),
300 });
301 }
302 }
303
304 impl IRouter for ShenYuRouter {
305 fn app_name(&self) -> &str {
306 &self.app_name
307 }
308
309 fn uri_infos(&self) -> &Vec<UriInfo> {
310 &self.uri_infos
311 }
312 }
313
314 #[macro_export]
326 macro_rules! register_once {
327 ($config:expr, $router:expr, $port:literal) => {
328 use std::sync::OnceLock;
329 use $crate::IRouter;
330
331 static ONCE: OnceLock<()> = OnceLock::new();
332 ONCE.get_or_init(|| {
333 let client = {
334 let res = $crate::core::ShenyuClient::new(
335 $config,
336 $router.app_name(),
337 $router.uri_infos(),
338 $port,
339 );
340 let client = res.unwrap();
341 client
342 };
343 client.register().expect("Failed to register");
344 actix_web::rt::spawn(async move {
345 tokio::select! {
347 _ = actix_web::rt::signal::ctrl_c() => {
348 client.offline_register();
349 }
350 }
351 });
352 });
353 };
354 }
355
356 #[macro_export]
370 macro_rules! shenyu_router {
371 ($router:expr, $app:expr, $($path:expr => $method:ident($handler:expr))*) => {
372 $(
373 $router.route($path, stringify!($method));
375 $app = $app.service(actix_web::web::resource($path).route(actix_web::web::$method().to($handler)));
376 )*
377 }
378 }
379}
380
381#[cfg(test)]
382#[cfg(feature = "axum")]
383mod tests_axum {
384 use super::axum_impl::ShenYuRouter;
385 use crate::config::ShenYuConfig;
386 use crate::core::ShenyuClient;
387 use crate::IRouter;
388 use axum::routing::{get, post};
389 use serde_json::Value;
390 use std::collections::HashMap;
391
392 async fn health_handler() -> &'static str {
393 "OK"
394 }
395
396 async fn create_user_handler() -> &'static str {
397 "User created"
398 }
399
400 #[tokio::test]
401 async fn test_login() {
402 let mut hashmap = HashMap::new();
403 _ = hashmap.insert("username", "admin");
404 _ = hashmap.insert("password", "123456");
405 let params = [
406 ("userName", hashmap.get("username").copied().unwrap()),
407 ("password", hashmap.get("password").copied().unwrap()),
408 ];
409
410 let res = ureq::get("http://127.0.0.1:9095/platform/login")
412 .query_pairs(params)
413 .call()
414 .unwrap();
415 let res_data: Value = res.into_json().unwrap();
416 print!("res_data: {:?}", res_data);
417 print!("res_data:token {:?}", res_data["data"]["token"]);
418 }
419
420 #[tokio::test]
421 async fn build_client() {
422 let app = ShenYuRouter::<()>::new("shenyu_client_app")
423 .nest("/api", ShenYuRouter::new("api"))
424 .route("/health", "get", get(health_handler))
425 .route("/users", "post", post(create_user_handler));
426 let config = ShenYuConfig::from_yaml_file("config.yml").unwrap();
427 let res = ShenyuClient::new(config, app.app_name(), app.uri_infos(), 9527);
428 assert!(&res.is_ok());
429 let client = &mut res.unwrap();
430
431 client.register().unwrap();
432 client.offline_register();
433 }
434
435 #[test]
436 fn it_works() {
437 let binding = ShenYuRouter::<()>::new("shenyu_client_app");
438 let app = binding
439 .nest("/api", ShenYuRouter::new("api"))
440 .route("/health", "get", get(health_handler))
441 .route("/users", "post", post(create_user_handler));
442 let uri_infos = app.uri_infos();
443 assert_eq!(uri_infos.len(), 2);
444 assert_eq!(uri_infos[0].path, "/health");
445 assert_eq!(uri_infos[1].path, "/users");
446 }
447}
448
449#[cfg(test)]
450#[cfg(feature = "actix-web")]
451mod tests_actix_web {
452 use super::actix_web_impl::ShenYuRouter;
453 use crate::config::ShenYuConfig;
454 use crate::core::ShenyuClient;
455 use crate::IRouter;
456
457 #[tokio::test]
458 async fn build_client() {
459 let app = ShenYuRouter::new("shenyu_client_app");
460 let config = ShenYuConfig::from_yaml_file("config.yml").unwrap();
461 let res = ShenyuClient::new(config, app.app_name(), app.uri_infos(), 9527);
462 assert!(&res.is_ok());
463 let client = &mut res.unwrap();
464
465 client.register().unwrap();
466 client.offline_register();
467 }
468}