Skip to main content

sz_rust_core/middleware/
order.rs

1//! 中间件顺序定义 — 对齐 PHP `app/middleware.php`
2//!
3//! PHP 端 `app/middleware.php` 全局中间件顺序:
4//!
5//! ```php
6//! return [
7//!     \think\middleware\SessionInit::class,
8//!     \think\middleware\AllowCrossDomain::class,
9//! ];
10//! ```
11//!
12//! 业务层中间件(如 `app\oapc\middleware\Auth`)通过应用级 `app/<app>/middleware.php`
13//! 追加,执行顺序在全局中间件之后。
14//!
15//! ## Rust 端映射
16//!
17//! | PHP 中间件 | Rust 中间件 | 实现阶段 |
18//! |------------|-------------|---------|
19//! | `SessionInit` | `Trace`(生成 request_id,复用 sz-orm-tracing) | ✅ |
20//! | `AllowCrossDomain` | `Cors`(已实现于 `cors.rs`) | ✅ |
21//! | `app\oapc\middleware\Auth` | `Auth`(JWT 校验,复用 sz-orm-auth) | ✅ |
22//! | (PHP 端无) | `Log`(请求/响应日志) | ✅ |
23//! | (PHP 端无) | `RateLimit`(限流,复用 sz-orm-limit) | ✅ |
24//!
25//! ## 执行顺序约定
26//!
27//! Rust 端使用 `tower::ServiceBuilder`,layer 是「后注册先执行」(stack 反向)。
28//! 本模块定义的 `DEFAULT_ORDER` 表示「业务期望的执行顺序」——数组首元素最先执行。
29//! `MiddlewareChain` 内部会按需反转以适配 `ServiceBuilder::layer` 语义。
30
31use std::fmt;
32
33/// 中间件类型枚举
34///
35/// 对齐 PHP 中间件 + sz-rust 自定义中间件。每个变体对应一个具体的 Layer 实现,
36/// 由 `auth.rs` / `log.rs` / `cors.rs` / `rate_limit.rs` 等模块逐个实现。
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
38pub enum MiddlewareKind {
39    /// 追踪 span(生成 request_id,对齐 PHP `SessionInit` 的「请求初始化」语义)
40    ///
41    /// **最先执行**:包裹所有后续中间件,确保 request_id 在所有日志/追踪中可用。
42    Trace,
43    /// CORS 跨域预处理(对齐 PHP `AllowCrossDomain`)
44    ///
45    /// **第二执行**:OPTIONS 预检请求直接返回,不进入业务逻辑。
46    Cors,
47    /// 请求/响应日志(对齐 PHP `think-logger`)
48    ///
49    /// **第三执行**:在限流/鉴权之前记录所有请求(包括被拒绝的)。
50    Log,
51    /// 限流(复用 sz-orm-limit)
52    ///
53    /// **第四执行**:在鉴权之前限流,避免无效请求消耗鉴权开销。
54    RateLimit,
55    /// JWT 鉴权(对齐 PHP `app\<app>\middleware\Auth`,复用 sz-orm-auth)
56    ///
57    /// **第五执行**:通过限流后进行鉴权,未登录返回 NotLogin(-1)。
58    Auth,
59}
60
61impl MiddlewareKind {
62    /// 返回中间件的人类可读名称(用于日志和测试)
63    pub fn as_str(self) -> &'static str {
64        match self {
65            MiddlewareKind::Trace => "trace",
66            MiddlewareKind::Cors => "cors",
67            MiddlewareKind::Log => "log",
68            MiddlewareKind::RateLimit => "rate_limit",
69            MiddlewareKind::Auth => "auth",
70        }
71    }
72
73    /// 返回中间件在 PHP 端的对应物(用于文档对齐验证)
74    pub fn php_counterpart(self) -> &'static str {
75        match self {
76            MiddlewareKind::Trace => "\\think\\middleware\\SessionInit",
77            MiddlewareKind::Cors => "\\think\\middleware\\AllowCrossDomain",
78            MiddlewareKind::Log => "(none, sz-rust 自研,对齐 think-logger)",
79            MiddlewareKind::RateLimit => "(none, sz-rust 自研)",
80            MiddlewareKind::Auth => "app\\<app>\\middleware\\Auth",
81        }
82    }
83}
84
85impl fmt::Display for MiddlewareKind {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        f.write_str(self.as_str())
88    }
89}
90
91/// 默认中间件顺序(业务期望的执行顺序,数组首元素最先执行)
92///
93/// 对齐 PHP `app/middleware.php` 全局中间件 + sz-rust 业务中间件约定:
94///
95/// 1. `Trace` — 生成 request_id(对齐 PHP `SessionInit` 请求初始化语义)
96/// 2. `Cors` — 跨域预处理(对齐 PHP `AllowCrossDomain`)
97/// 3. `Log` — 请求日志(sz-rust 自研,PHP 端无全局日志中间件)
98/// 4. `RateLimit` — 限流(sz-rust 自研,PHP 端无全局限流中间件)
99/// 5. `Auth` — JWT 鉴权(对齐 PHP `app\<app>\middleware\Auth`)
100///
101/// ## 顺序设计理由
102///
103/// - `Trace` 最先:确保 request_id 在所有后续中间件的日志中可用
104/// - `Cors` 第二:OPTIONS 预检请求直接返回,不消耗后续中间件资源
105/// - `Log` 第三:记录所有请求(包括被限流/鉴权拒绝的),用于审计
106/// - `RateLimit` 第四:在鉴权之前限流,避免无效请求消耗鉴权开销
107/// - `Auth` 第五:通过限流后进行鉴权,未登录返回 NotLogin(-1)
108pub const DEFAULT_ORDER: &[MiddlewareKind] = &[
109    MiddlewareKind::Trace,
110    MiddlewareKind::Cors,
111    MiddlewareKind::Log,
112    MiddlewareKind::RateLimit,
113    MiddlewareKind::Auth,
114];
115
116/// PHP 全局中间件顺序(对齐 `app/middleware.php`)
117///
118/// PHP 端 `app/middleware.php` 返回的数组顺序:
119/// 1. `SessionInit` → Rust `Trace`
120/// 2. `AllowCrossDomain` → Rust `Cors`
121///
122/// 业务层中间件(如 `Auth`)通过应用级 `app/<app>/middleware.php` 追加,
123/// 执行顺序在全局中间件之后。
124pub const PHP_GLOBAL_ORDER: &[MiddlewareKind] = &[MiddlewareKind::Trace, MiddlewareKind::Cors];
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    // ====================================================================
131    // MiddlewareKind 枚举
132    // ====================================================================
133
134    #[test]
135    fn test_middleware_kind_as_str() {
136        assert_eq!(MiddlewareKind::Trace.as_str(), "trace");
137        assert_eq!(MiddlewareKind::Cors.as_str(), "cors");
138        assert_eq!(MiddlewareKind::Log.as_str(), "log");
139        assert_eq!(MiddlewareKind::RateLimit.as_str(), "rate_limit");
140        assert_eq!(MiddlewareKind::Auth.as_str(), "auth");
141    }
142
143    #[test]
144    fn test_middleware_kind_display() {
145        assert_eq!(MiddlewareKind::Trace.to_string(), "trace");
146        assert_eq!(MiddlewareKind::Cors.to_string(), "cors");
147        assert_eq!(MiddlewareKind::Log.to_string(), "log");
148        assert_eq!(MiddlewareKind::RateLimit.to_string(), "rate_limit");
149        assert_eq!(MiddlewareKind::Auth.to_string(), "auth");
150    }
151
152    #[test]
153    fn test_middleware_kind_php_counterpart() {
154        // 对齐 PHP 全局中间件
155        assert_eq!(
156            MiddlewareKind::Trace.php_counterpart(),
157            "\\think\\middleware\\SessionInit"
158        );
159        assert_eq!(
160            MiddlewareKind::Cors.php_counterpart(),
161            "\\think\\middleware\\AllowCrossDomain"
162        );
163        assert_eq!(
164            MiddlewareKind::Auth.php_counterpart(),
165            "app\\<app>\\middleware\\Auth"
166        );
167    }
168
169    #[test]
170    fn test_middleware_kind_eq_hash() {
171        // PartialEq + Eq + Hash 支持用作 HashMap key
172        use std::collections::HashSet;
173        let set: HashSet<MiddlewareKind> = [
174            MiddlewareKind::Trace,
175            MiddlewareKind::Cors,
176            MiddlewareKind::Trace,
177        ]
178        .into_iter()
179        .collect();
180        assert_eq!(set.len(), 2);
181        assert!(set.contains(&MiddlewareKind::Trace));
182        assert!(set.contains(&MiddlewareKind::Cors));
183        assert!(!set.contains(&MiddlewareKind::Auth));
184    }
185
186    #[test]
187    fn test_middleware_kind_clone_copy() {
188        let kind = MiddlewareKind::Cors;
189        let cloned = kind; // Copy 语义
190        assert_eq!(kind, cloned);
191    }
192
193    // ====================================================================
194    // DEFAULT_ORDER 默认顺序
195    // ====================================================================
196
197    #[test]
198    fn test_default_order_length() {
199        assert_eq!(DEFAULT_ORDER.len(), 5);
200    }
201
202    #[test]
203    fn test_default_order_trace_first() {
204        // Trace 必须最先执行(包裹所有后续中间件)
205        assert_eq!(DEFAULT_ORDER.first(), Some(&MiddlewareKind::Trace));
206    }
207
208    #[test]
209    fn test_default_order_auth_last() {
210        // Auth 必须最后执行(在限流之后,避免无效请求消耗鉴权)
211        assert_eq!(DEFAULT_ORDER.last(), Some(&MiddlewareKind::Auth));
212    }
213
214    #[test]
215    fn test_default_order_cors_before_log() {
216        // CORS 必须在 Log 之前(OPTIONS 预检直接返回,不记录日志)
217        let cors_idx = DEFAULT_ORDER
218            .iter()
219            .position(|k| *k == MiddlewareKind::Cors)
220            .expect("Cors must be in DEFAULT_ORDER");
221        let log_idx = DEFAULT_ORDER
222            .iter()
223            .position(|k| *k == MiddlewareKind::Log)
224            .expect("Log must be in DEFAULT_ORDER");
225        assert!(cors_idx < log_idx, "Cors must execute before Log");
226    }
227
228    #[test]
229    fn test_default_order_rate_limit_before_auth() {
230        // RateLimit 必须在 Auth 之前(避免无效请求消耗鉴权开销)
231        let rate_limit_idx = DEFAULT_ORDER
232            .iter()
233            .position(|k| *k == MiddlewareKind::RateLimit)
234            .expect("RateLimit must be in DEFAULT_ORDER");
235        let auth_idx = DEFAULT_ORDER
236            .iter()
237            .position(|k| *k == MiddlewareKind::Auth)
238            .expect("Auth must be in DEFAULT_ORDER");
239        assert!(
240            rate_limit_idx < auth_idx,
241            "RateLimit must execute before Auth"
242        );
243    }
244
245    #[test]
246    fn test_default_order_no_duplicates() {
247        // 默认顺序中不应有重复中间件
248        use std::collections::HashSet;
249        let set: HashSet<MiddlewareKind> = DEFAULT_ORDER.iter().copied().collect();
250        assert_eq!(
251            set.len(),
252            DEFAULT_ORDER.len(),
253            "DEFAULT_ORDER has duplicates"
254        );
255    }
256
257    #[test]
258    fn test_default_order_contains_all_kinds() {
259        // 默认顺序应包含所有中间件类型
260        for kind in [
261            MiddlewareKind::Trace,
262            MiddlewareKind::Cors,
263            MiddlewareKind::Log,
264            MiddlewareKind::RateLimit,
265            MiddlewareKind::Auth,
266        ] {
267            assert!(
268                DEFAULT_ORDER.contains(&kind),
269                "DEFAULT_ORDER missing {kind}"
270            );
271        }
272    }
273
274    // ====================================================================
275    // PHP_GLOBAL_ORDER PHP 全局顺序对齐
276    // ====================================================================
277
278    #[test]
279    fn test_php_global_order_length() {
280        // PHP app/middleware.php 返回 2 个全局中间件
281        assert_eq!(PHP_GLOBAL_ORDER.len(), 2);
282    }
283
284    #[test]
285    fn test_php_global_order_matches_php_app_middleware() {
286        // 对齐 PHP `app/middleware.php`:
287        //   \think\middleware\SessionInit::class,
288        //   \think\middleware\AllowCrossDomain::class,
289        assert_eq!(PHP_GLOBAL_ORDER[0], MiddlewareKind::Trace); // SessionInit → Trace
290        assert_eq!(PHP_GLOBAL_ORDER[1], MiddlewareKind::Cors); // AllowCrossDomain → Cors
291    }
292
293    #[test]
294    fn test_php_global_order_is_prefix_of_default() {
295        // PHP 全局中间件顺序必须是 DEFAULT_ORDER 的前缀
296        // (业务中间件 Log/RateLimit/Auth 在全局之后追加)
297        assert!(DEFAULT_ORDER.starts_with(PHP_GLOBAL_ORDER));
298    }
299}