Routing (WebPath)
Modular & Introspective Routing System (WebPath)
To build a scalable, maintainable, and automatable Web application, we designed a declarative routing system based on the WebPath struct, instead of directly using Axum's Router in a scattered way.
The core idea: first describe the entire routing structure as a data tree, then "compile" that tree into a real Axum Router.
Core Advantages
- Modularity: Each business module can independently define its own route tree, then merge them.
- Introspection: At startup, we can traverse the entire route tree to get all API info (path, method, name) for automation tasks like updating the API permissions database.
- Hierarchy:
.nest()clearly expresses route hierarchy, keeping code structure aligned with URL structure. - Clean Code: Avoids registering all routes in one giant file.
Core Concept: WebPath Struct
WebPath is the basic building block of our routing system. Think of it as a file system directory or file.
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct WebPath {
pub final_path: String, // Compiled URL path, e.g., "/sys/user/list"
pub webmethod: WebPathType, // HTTP method type
pub apiname: Option<String>, // Readable API name for automation
#[serde(skip)]
pub method_router: Option<MethodRouter>, // Actual Axum MethodRouter
sub_paths: HashMap<String, WebPath>, // Child paths (branches of the route tree)
}How to Define Routes
1. .route() - Define an API Endpoint
fn user_api_routes() -> WebPath {
WebPath::new()
.route("/list", WebPathType::Get, Some("Get User List"), get(sys_user_handler::list))
.route("/add", WebPathType::Post, Some("Add User"), post(sys_user_handler::add))
}2. .nest() - Nest Routes (Create Groups)
fn user_module_router() -> WebPath {
WebPath::new().nest("/user", user_api_routes())
}
pub fn router_sys() -> WebPath {
WebPath::new().nest("/sys", user_module_router())
}3. .merge() - Merge Different Module Routes
let mut webpath = WebPath::new();
webpath = webpath.merge(sys_controll::router_sys());
webpath = webpath.merge(wechat::router_wechat());Route Compilation & Introspection
WebApi::routers() is the "main engine" that converts the WebPath tree into an Axum Router while performing introspection:
pub fn routers() -> Router {
let mut webpath = WebPath::new();
webpath = webpath.merge(sys_controll::router_sys());
webpath = webpath.merge(wechat::router_wechat());
// ... more modules
let expand_path = webpath.final_to_path(); // Flatten the tree
let final_paths = expand_path.get_last_level_paths();
// Auto-update API permissions in database
let invfun = InvokeFunctionMsg {
job_id: None,
callfun: "updateapi".to_owned(),
parmets: serde_json::to_string(&final_paths).unwrap(),
};
tokio::spawn(async move {
let _ = InvokeFunctionWorker::execute_async(invfun).await;
});
// Build Axum Router
let mut router = Router::new();
for p in final_paths {
if let Some(method_router) = p.method_router.clone() {
router = router.route(&p.final_path, method_router);
}
}
Router::new().merge(router)
}Two Entry Points
1. WebApi::routers() - Protected Routes
All routes requiring authentication. The introspection and auto-sync happen here.
2. WebApi::white_routers() - Public Whitelist Routes
For public APIs like login, registration, callbacks. No authentication middleware, no introspection overhead.
pub fn white_routers() -> Router {
Router::new().merge(sys_controll::white_sys())
}Developer Best Practices
Security Warning
- Default Protected: New APIs should default to protected routes (
router_sys()). Only use whitelist when the API should be accessible without login. - Separation of Responsibilities:
sys_controll::router_sys()for authenticated routes;sys_controll::white_sys()for public routes. - Least Privilege: Keep whitelist entries minimal. Sensitive or data-modifying operations must not be in the whitelist.