Skip to main content

sz_rust_cli/
stubs.rs

1//! 代码生成模板 — 对齐 PHP `think\console\command\make\stubs\*.stub`
2//!
3//! ## PHP 对齐
4//!
5//! PHP `Make::buildClass()` 读取 stub 文件并替换占位符:
6//!
7//! - `{%className%}`:类名(如 `User`)
8//! - `{%namespace%}`:命名空间(如 `app\model` 或 `app\controller`)
9//! - `{%actionSuffix%}`:方法后缀(PHP 默认空字符串)
10//! - `{%app_namespace%}`:应用命名空间(PHP 默认 `app`)
11//!
12//! Rust 端使用常量字符串存储模板,通过 `str::replace` 替换占位符。
13//!
14//! ## 模板列表
15//!
16//! | 常量 | 对齐 PHP stub | 用途 |
17//! |------|--------------|------|
18//! | `MODEL_STUB` | `model.stub` | `make:model` 生成 Model 骨架 |
19//! | `CONTROLLER_STUB` | `controller.stub` | `make:controller` 生成 7 方法 Controller |
20//! | `CONTROLLER_API_STUB` | `controller.api.stub` | `make:controller --api` 生成 5 方法 API Controller |
21//! | `CONTROLLER_PLAIN_STUB` | `controller.plain.stub` | `make:controller --plain` 生成空 Controller |
22//! | `MIGRATION_UP_STUB` | — | `make:migration` 生成 up SQL |
23//! | `MIGRATION_DOWN_STUB` | — | `make:migration` 生成 down SQL |
24//! | `SEED_STUB` | `make:seeder` | `make:seeder` 生成填充 SQL |
25//! | `VALIDATE_STUB` | `make:validate` | `make:validate` 生成验证器骨架 |
26//! | `EVENT_STUB` | `make:event` | `make:event` 生成事件类骨架 |
27//! | `LISTENER_STUB` | `make:listener` | `make:listener` 生成监听器骨架 |
28//! | `COMMAND_STUB` | `make:command` | `make:command` 生成自定义命令骨架 |
29//! | `SERVICE_STUB` | `make:service` | `make:service` 生成服务类骨架 |
30
31/// Model 模板(对齐 PHP `model.stub`)
32///
33/// PHP 原文:
34/// ```php
35/// <?php
36/// namespace {%namespace%};
37///
38/// use think\Model;
39///
40/// /**
41///  * @mixin \think\Model
42///  */
43/// class {%className%} extends Model
44/// {
45///     //
46/// }
47/// ```
48pub const MODEL_STUB: &str = r#"//! {%namespace%}::{%className%}
49//!
50//! 由 `sz-rust make:model` 生成。
51
52use sz_rust_core::orm::Model;
53
54/// {%className%} 模型
55///
56/// 对齐 PHP `class {%className%} extends Model`
57pub struct {%className%};
58
59impl Model for {%className%} {
60    fn table_name() -> &'static str {
61        "{%table_name%}"
62    }
63}
64"#;
65
66/// Controller 模板(对齐 PHP `controller.stub`)
67///
68/// PHP 原文包含 7 个 RESTful 方法:index / create / save / read / edit / update / delete
69pub const CONTROLLER_STUB: &str = r#"//! {%namespace%}::{%className%} 控制器
70//!
71//! 由 `sz-rust make:controller` 生成,对齐 PHP `controller.stub`(7 个 RESTful 方法)。
72
73use sz_rust_core::controller::SzController;
74use sz_rust_core::request::Request;
75use sz_rust_core::response::Response;
76
77/// {%className%} 控制器
78pub struct {%className%};
79
80impl {%className%} {
81    /// 列表(GET /{%route%})
82    pub async fn index(_req: Request) -> Response {
83        Response::success("index")
84    }
85
86    /// 新建表单(GET /{%route%}/create)
87    pub async fn create(_req: Request) -> Response {
88        Response::success("create")
89    }
90
91    /// 保存(POST /{%route%})
92    pub async fn save(_req: Request) -> Response {
93        Response::success("save")
94    }
95
96    /// 详情(GET /{%route%}/{id})
97    pub async fn read(_req: Request) -> Response {
98        Response::success("read")
99    }
100
101    /// 编辑表单(GET /{%route%}/{id}/edit)
102    pub async fn edit(_req: Request) -> Response {
103        Response::success("edit")
104    }
105
106    /// 更新(PUT /{%route%}/{id})
107    pub async fn update(_req: Request) -> Response {
108        Response::success("update")
109    }
110
111    /// 删除(DELETE /{%route%}/{id})
112    pub async fn delete(_req: Request) -> Response {
113        Response::success("delete")
114    }
115}
116"#;
117
118/// API Controller 模板(对齐 PHP `controller.api.stub`)
119///
120/// PHP 原文包含 5 个方法(无 create/edit):index / save / read / update / delete
121pub const CONTROLLER_API_STUB: &str = r#"//! {%namespace%}::{%className%} API 控制器
122//!
123//! 由 `sz-rust make:controller --api` 生成,对齐 PHP `controller.api.stub`(5 个方法)。
124
125use sz_rust_core::request::Request;
126use sz_rust_core::response::Response;
127
128/// {%className%} API 控制器
129pub struct {%className%};
130
131impl {%className%} {
132    /// 列表(GET /{%route%})
133    pub async fn index(_req: Request) -> Response {
134        Response::success("index")
135    }
136
137    /// 保存(POST /{%route%})
138    pub async fn save(_req: Request) -> Response {
139        Response::success("save")
140    }
141
142    /// 详情(GET /{%route%}/{id})
143    pub async fn read(_req: Request) -> Response {
144        Response::success("read")
145    }
146
147    /// 更新(PUT /{%route%}/{id})
148    pub async fn update(_req: Request) -> Response {
149        Response::success("update")
150    }
151
152    /// 删除(DELETE /{%route%}/{id})
153    pub async fn delete(_req: Request) -> Response {
154        Response::success("delete")
155    }
156}
157"#;
158
159/// Plain Controller 模板(对齐 PHP `controller.plain.stub`)
160///
161/// PHP 原文为空类
162pub const CONTROLLER_PLAIN_STUB: &str = r#"//! {%namespace%}::{%className%} 控制器
163//!
164//! 由 `sz-rust make:controller --plain` 生成,对齐 PHP `controller.plain.stub`(空类)。
165
166/// {%className%} 控制器(空骨架)
167pub struct {%className%};
168"#;
169
170/// 迁移 up SQL 模板(对齐 Phinx 风格)
171pub const MIGRATION_UP_STUB: &str = r#"-- Migration: {%name%}
172-- Direction: UP
173-- Created: {%timestamp%}
174
175-- 在此编写 up SQL
176-- CREATE TABLE IF NOT EXISTS {%table_name%} (
177--     id BIGINT PRIMARY KEY AUTO_INCREMENT,
178--     created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
179--     updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
180-- );
181"#;
182
183/// 迁移 down SQL 模板(对齐 Phinx 风格)
184pub const MIGRATION_DOWN_STUB: &str = r#"-- Migration: {%name%}
185-- Direction: DOWN
186-- Created: {%timestamp%}
187
188-- 在此编写 down SQL
189-- DROP TABLE IF EXISTS {%table_name%};
190"#;
191
192/// 填充 SQL 模板(对齐 PHP `make:seeder`)
193///
194/// 生成 `.sql` 文件骨架,供 `db:seed` 命令加载执行。
195pub const SEED_STUB: &str = r#"-- Seed: {%name%}
196-- Created: {%timestamp%}
197
198-- 在此编写数据填充 SQL
199-- INSERT INTO users (name, email) VALUES ('admin', 'admin@example.com');
200"#;
201
202/// 验证器模板(对齐 PHP `make:validate`)
203///
204/// 生成 `<Name>.rs` 验证器骨架,包含 `Validate` 结构体初始化与常见规则示例。
205pub const VALIDATE_STUB: &str = r#"//! {%namespace%}::{%className%} 验证器
206//!
207//! 由 `sz-rust make:validate` 生成,对齐 PHP `make:validate`。
208//! 业务实现 `rules()` 返回验证规则,调用 `validate(&data)` 执行验证。
209
210use sz_rust_core::validate::Validate;
211
212/// {%className%} 验证器
213///
214/// 对齐 PHP `class {%className%} extends \think\Validate`。
215pub struct {%className%}Validate;
216
217impl {%className%}Validate {
218    /// 创建验证器实例(含规则定义)
219    ///
220    /// 对齐 PHP `protected $rule = [...]`。
221    pub fn new() -> Validate {
222        Validate::new()
223            .rule("name", "require|max:50")
224            .rule("age", "require|integer|gt:0")
225            // 在此添加更多验证规则
226    }
227}
228
229impl Default for {%className%}Validate {
230    fn default() -> Self {
231        Self
232    }
233}
234"#;
235
236/// Event 模板(对齐 PHP `make:event`)
237///
238/// PHP 原文生成继承 `think\Event` 的事件类,Rust 生成包含事件名与负载数据的结构体。
239pub const EVENT_STUB: &str = r#"//! {%namespace%}::{%className%} 事件
240//!
241//! 由 `sz-rust make:event` 生成,对齐 PHP `make:event`。
242//! 通过 `EventDispatcher::trigger({%className%}::name(), &payload)` 触发。
243
244use serde_json::Value;
245
246/// {%className%} 事件
247///
248/// 对齐 PHP `class {%className%} extends \think\Event`。
249pub struct {%className%};
250
251impl {%className%} {
252    /// 事件名称(用于监听器注册与触发)
253    pub fn name() -> &'static str {
254        "{%event_name%}"
255    }
256
257    /// 构造事件负载
258    ///
259    /// 在此组装事件需要传递给监听器的数据。
260    pub fn payload(&self) -> Value {
261        Value::Null
262    }
263}
264"#;
265
266/// Listener 模板(对齐 PHP `make:listener`)
267///
268/// PHP 原文生成含 `handle($params)` 方法的监听器类,Rust 生成实现 `Listener` trait 的结构体。
269pub const LISTENER_STUB: &str = r#"//! {%namespace%}::{%className%} 监听器
270//!
271//! 由 `sz-rust make:listener` 生成,对齐 PHP `make:listener`。
272//! 通过 `EventDispatcher::listen("{%event_name%}", Arc::new({%className%}))` 注册。
273
274use serde_json::Value;
275use sz_rust_core::event::{EventError, Listener};
276
277/// {%className%} 监听器
278///
279/// 对齐 PHP `class {%className%} { public function handle($params) {} }`。
280pub struct {%className%};
281
282impl Listener for {%className%} {
283    /// 处理事件(对齐 PHP `Listener::handle($params)`)
284    ///
285    /// # 参数
286    ///
287    /// - `params`:事件负载
288    ///
289    /// # 返回
290    ///
291    /// - `Ok(Value::Null)`:继续执行后续监听器
292    /// - `Ok(其他值)`:在 `once=true` 模式下停止后续监听器
293    /// - `Err(_)`:停止后续监听器执行
294    fn handle(&self, _params: &Value) -> Result<Value, EventError> {
295        // 在此实现监听逻辑
296        Ok(Value::Null)
297    }
298}
299"#;
300
301/// Command 模板(对齐 PHP `make:command`)
302///
303/// PHP 原文生成继承 `think\console\Command` 的命令类,Rust 生成实现 `Command` trait 的结构体。
304pub const COMMAND_STUB: &str = r#"//! {%namespace%}::{%className%} 命令
305//!
306//! 由 `sz-rust make:command` 生成,对齐 PHP `make:command`。
307//! 通过 `Console::register(Box::new({%className%}))` 注册后可被 `sz-rust {%command_name%}` 调用。
308
309use sz_rust_cli::console::{Command, CommandSignature};
310use sz_rust_cli::error::CliError;
311
312/// {%className%} 命令
313///
314/// 对齐 PHP `class {%className%} extends \think\console\Command`。
315pub struct {%className%};
316
317impl Command for {%className%} {
318    /// 命令签名(对齐 PHP `Command::configure()`)
319    fn signature(&self) -> CommandSignature {
320        CommandSignature::new(
321            "{%command_name%}",
322            "{%className%} 自定义命令",
323        )
324        .usage("sz-rust {%command_name%} [options]")
325    }
326
327    /// 执行命令(对齐 PHP `Command::execute(Input $input, Output $output)`)
328    fn execute(&self, _args: &[String]) -> Result<i32, CliError> {
329        println!("{%className%} executed");
330        Ok(0)
331    }
332}
333"#;
334
335/// Service 模板(对齐 PHP `make:service`)
336///
337/// PHP 原文生成含 `__construct` 与业务方法的服务类,Rust 生成可注入容器的服务结构体。
338pub const SERVICE_STUB: &str = r#"//! {%namespace%}::{%className%} 服务
339//!
340//! 由 `sz-rust make:service` 生成,对齐 PHP `make:service`。
341//! 通过 `Container::singleton::<{%className%}>()` 注册后可在控制器中自动注入。
342
343/// {%className%} 服务
344///
345/// 对齐 PHP `class {%className%} { public function __construct() {} }`。
346pub struct {%className%};
347
348impl {%className%} {
349    /// 创建服务实例
350    pub fn new() -> Self {
351        Self
352    }
353
354    /// 业务方法示例
355    ///
356    /// 在此实现具体业务逻辑。
357    pub fn execute(&self) -> Result<(), String> {
358        // 在此实现业务逻辑
359        Ok(())
360    }
361}
362
363impl Default for {%className%} {
364    fn default() -> Self {
365        Self::new()
366    }
367}
368"#;
369
370/// 中间件模板(对齐 PHP `make:middleware`)
371///
372/// 生成基于 `SzMiddleware` trait 的中间件骨架。
373pub const MIDDLEWARE_STUB: &str = r#"//! {%namespace%}::{%className%} 中间件
374//!
375//! 由 `sz-rust make:middleware` 生成。
376//! 在中间件链配置中通过 `MiddlewareChain::add(MiddlewareKind::{%className%})` 注册。
377
378use sz_rust_core::middleware::SzMiddleware;
379use axum::{body::Body, http::Request, response::Response};
380
381/// {%className%} 中间件
382pub struct {%className%};
383
384#[sz_rust_core::middleware]
385impl SzMiddleware for {%className%} {
386    async fn handle(
387        &self,
388        req: Request<Body>,
389        next: impl FnOnce(Request<Body>) -> std::pin::Pin<Box<dyn std::future::Future<Output = Response> + Send>>
390    ) -> Response {
391        // 前置处理
392        // tracing::info!("请求进入 {%className%}: {}", req.uri().path());
393
394        let response = next(req).await;
395
396        // 后置处理
397        // tracing::info!("响应离开 {%className%}: {}", response.status());
398
399        response
400    }
401}
402"#;
403
404/// 替换模板占位符
405///
406/// 对齐 PHP `Make::buildClass()` 中的 `str_replace` 调用。
407///
408/// # 参数
409///
410/// - `template`:模板字符串
411/// - `replacements`:占位符 → 替换值(如 `{%className%}` → `User`)
412pub fn render_template(template: &str, replacements: &[(&str, &str)]) -> String {
413    let mut result = template.to_string();
414    for (placeholder, value) in replacements {
415        result = result.replace(placeholder, value);
416    }
417    result
418}
419
420#[cfg(test)]
421mod tests {
422    use super::*;
423
424    #[test]
425    fn test_model_stub_contains_class_placeholder() {
426        assert!(MODEL_STUB.contains("{%className%}"));
427        assert!(MODEL_STUB.contains("{%namespace%}"));
428        assert!(MODEL_STUB.contains("{%table_name%}"));
429    }
430
431    #[test]
432    fn test_controller_stub_has_seven_methods() {
433        assert!(CONTROLLER_STUB.contains("index"));
434        assert!(CONTROLLER_STUB.contains("create"));
435        assert!(CONTROLLER_STUB.contains("save"));
436        assert!(CONTROLLER_STUB.contains("read"));
437        assert!(CONTROLLER_STUB.contains("edit"));
438        assert!(CONTROLLER_STUB.contains("update"));
439        assert!(CONTROLLER_STUB.contains("delete"));
440    }
441
442    #[test]
443    fn test_controller_api_stub_has_five_methods() {
444        assert!(CONTROLLER_API_STUB.contains("index"));
445        assert!(CONTROLLER_API_STUB.contains("save"));
446        assert!(CONTROLLER_API_STUB.contains("read"));
447        assert!(CONTROLLER_API_STUB.contains("update"));
448        assert!(CONTROLLER_API_STUB.contains("delete"));
449        // API 模板不应包含 create/edit
450        assert!(!CONTROLLER_API_STUB.contains("pub async fn create"));
451        assert!(!CONTROLLER_API_STUB.contains("pub async fn edit"));
452    }
453
454    #[test]
455    fn test_controller_plain_stub_is_empty_class() {
456        assert!(CONTROLLER_PLAIN_STUB.contains("struct {%className%}"));
457    }
458
459    #[test]
460    fn test_migration_stubs_contain_placeholders() {
461        assert!(MIGRATION_UP_STUB.contains("{%name%}"));
462        assert!(MIGRATION_UP_STUB.contains("{%timestamp%}"));
463        assert!(MIGRATION_DOWN_STUB.contains("{%name%}"));
464        assert!(MIGRATION_DOWN_STUB.contains("{%timestamp%}"));
465    }
466
467    #[test]
468    fn test_seed_stub_contains_placeholders() {
469        assert!(SEED_STUB.contains("{%name%}"));
470        assert!(SEED_STUB.contains("{%timestamp%}"));
471    }
472
473    #[test]
474    fn test_seed_stub_render() {
475        let result = render_template(
476            SEED_STUB,
477            &[
478                ("{%name%}", "001_users"),
479                ("{%timestamp%}", "2026-07-31 00:00:00 UTC"),
480            ],
481        );
482        assert!(result.contains("001_users"));
483        assert!(result.contains("2026-07-31"));
484        // 渲染后不应残留占位符
485        assert!(!result.contains("{%"));
486    }
487
488    #[test]
489    fn test_validate_stub_contains_placeholders() {
490        assert!(VALIDATE_STUB.contains("{%className%}"));
491        assert!(VALIDATE_STUB.contains("{%namespace%}"));
492    }
493
494    #[test]
495    fn test_validate_stub_render() {
496        let result = render_template(
497            VALIDATE_STUB,
498            &[
499                ("{%className%}", "User"),
500                ("{%namespace%}", "app::validate"),
501            ],
502        );
503        assert!(result.contains("UserValidate"));
504        assert!(result.contains("app::validate"));
505        assert!(result.contains("use sz_rust_core::validate::Validate"));
506        // 渲染后不应残留占位符
507        assert!(!result.contains("{%"));
508    }
509
510    #[test]
511    fn test_render_template_basic() {
512        let result = render_template("Hello {%name%}!", &[("{%name%}", "World")]);
513        assert_eq!(result, "Hello World!");
514    }
515
516    #[test]
517    fn test_render_template_multiple_placeholders() {
518        let result = render_template(
519            "{%className%} in {%namespace%}",
520            &[("{%className%}", "User"), ("{%namespace%}", "app::model")],
521        );
522        assert_eq!(result, "User in app::model");
523    }
524
525    #[test]
526    fn test_render_template_no_placeholders() {
527        let result = render_template("no placeholders", &[]);
528        assert_eq!(result, "no placeholders");
529    }
530
531    #[test]
532    fn test_render_template_repeated_placeholders() {
533        let result = render_template("{%x%} and {%x%}", &[("{%x%}", "A")]);
534        assert_eq!(result, "A and A");
535    }
536
537    // ---------- 新增 make 命令模板测试 ----------
538
539    #[test]
540    fn test_event_stub_contains_placeholders() {
541        assert!(EVENT_STUB.contains("{%className%}"));
542        assert!(EVENT_STUB.contains("{%namespace%}"));
543        assert!(EVENT_STUB.contains("{%event_name%}"));
544    }
545
546    #[test]
547    fn test_event_stub_render() {
548        let result = render_template(
549            EVENT_STUB,
550            &[
551                ("{%className%}", "UserLogin"),
552                ("{%namespace%}", "app::event"),
553                ("{%event_name%}", "UserLogin"),
554            ],
555        );
556        assert!(result.contains("UserLogin"));
557        assert!(result.contains("app::event"));
558        assert!(result.contains("use serde_json::Value"));
559        // 渲染后不应残留占位符
560        assert!(!result.contains("{%"));
561    }
562
563    #[test]
564    fn test_listener_stub_contains_placeholders() {
565        assert!(LISTENER_STUB.contains("{%className%}"));
566        assert!(LISTENER_STUB.contains("{%namespace%}"));
567        assert!(LISTENER_STUB.contains("{%event_name%}"));
568    }
569
570    #[test]
571    fn test_listener_stub_render() {
572        let result = render_template(
573            LISTENER_STUB,
574            &[
575                ("{%className%}", "SendWelcomeEmail"),
576                ("{%namespace%}", "app::listener"),
577                ("{%event_name%}", "UserLogin"),
578            ],
579        );
580        assert!(result.contains("SendWelcomeEmail"));
581        assert!(result.contains("app::listener"));
582        assert!(result.contains("use sz_rust_core::event::{EventError, Listener}"));
583        assert!(result.contains("impl Listener for SendWelcomeEmail"));
584        // 渲染后不应残留占位符
585        assert!(!result.contains("{%"));
586    }
587
588    #[test]
589    fn test_command_stub_contains_placeholders() {
590        assert!(COMMAND_STUB.contains("{%className%}"));
591        assert!(COMMAND_STUB.contains("{%namespace%}"));
592        assert!(COMMAND_STUB.contains("{%command_name%}"));
593    }
594
595    #[test]
596    fn test_command_stub_render() {
597        let result = render_template(
598            COMMAND_STUB,
599            &[
600                ("{%className%}", "SyncData"),
601                ("{%namespace%}", "app::command"),
602                ("{%command_name%}", "sync:data"),
603            ],
604        );
605        assert!(result.contains("SyncData"));
606        assert!(result.contains("app::command"));
607        assert!(result.contains("sync:data"));
608        assert!(result.contains("use sz_rust_cli::console::{Command, CommandSignature}"));
609        assert!(result.contains("impl Command for SyncData"));
610        // 渲染后不应残留占位符
611        assert!(!result.contains("{%"));
612    }
613
614    #[test]
615    fn test_service_stub_contains_placeholders() {
616        assert!(SERVICE_STUB.contains("{%className%}"));
617        assert!(SERVICE_STUB.contains("{%namespace%}"));
618    }
619
620    #[test]
621    fn test_service_stub_render() {
622        let result = render_template(
623            SERVICE_STUB,
624            &[
625                ("{%className%}", "UserService"),
626                ("{%namespace%}", "app::service"),
627            ],
628        );
629        assert!(result.contains("UserService"));
630        assert!(result.contains("app::service"));
631        assert!(result.contains("impl Default for UserService"));
632        // 渲染后不应残留占位符
633        assert!(!result.contains("{%"));
634    }
635}