Skip to main content

sz_rust_cli/
stubs.rs

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