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
25/// Model 模板(对齐 PHP `model.stub`)
26///
27/// PHP 原文:
28/// ```php
29/// <?php
30/// namespace {%namespace%};
31///
32/// use think\Model;
33///
34/// /**
35///  * @mixin \think\Model
36///  */
37/// class {%className%} extends Model
38/// {
39///     //
40/// }
41/// ```
42pub const MODEL_STUB: &str = r#"//! {%namespace%}::{%className%}
43//!
44//! 由 `sz-rust make:model` 生成。
45
46use sz_orm_core::model::Model;
47
48/// {%className%} 模型
49///
50/// 对齐 PHP `class {%className%} extends Model`
51pub struct {%className%};
52
53impl Model for {%className%} {
54    fn table_name() -> &'static str {
55        "{%table_name%}"
56    }
57}
58"#;
59
60/// Controller 模板(对齐 PHP `controller.stub`)
61///
62/// PHP 原文包含 7 个 RESTful 方法:index / create / save / read / edit / update / delete
63pub const CONTROLLER_STUB: &str = r#"//! {%namespace%}::{%className%} 控制器
64//!
65//! 由 `sz-rust make:controller` 生成,对齐 PHP `controller.stub`(7 个 RESTful 方法)。
66
67use sz_rust_core::controller::SzController;
68use sz_rust_core::request::Request;
69use sz_rust_core::response::Response;
70
71/// {%className%} 控制器
72pub struct {%className%};
73
74impl {%className%} {
75    /// 列表(GET /{%route%})
76    pub async fn index(_req: Request) -> Response {
77        Response::success("index")
78    }
79
80    /// 新建表单(GET /{%route%}/create)
81    pub async fn create(_req: Request) -> Response {
82        Response::success("create")
83    }
84
85    /// 保存(POST /{%route%})
86    pub async fn save(_req: Request) -> Response {
87        Response::success("save")
88    }
89
90    /// 详情(GET /{%route%}/{id})
91    pub async fn read(_req: Request) -> Response {
92        Response::success("read")
93    }
94
95    /// 编辑表单(GET /{%route%}/{id}/edit)
96    pub async fn edit(_req: Request) -> Response {
97        Response::success("edit")
98    }
99
100    /// 更新(PUT /{%route%}/{id})
101    pub async fn update(_req: Request) -> Response {
102        Response::success("update")
103    }
104
105    /// 删除(DELETE /{%route%}/{id})
106    pub async fn delete(_req: Request) -> Response {
107        Response::success("delete")
108    }
109}
110"#;
111
112/// API Controller 模板(对齐 PHP `controller.api.stub`)
113///
114/// PHP 原文包含 5 个方法(无 create/edit):index / save / read / update / delete
115pub const CONTROLLER_API_STUB: &str = r#"//! {%namespace%}::{%className%} API 控制器
116//!
117//! 由 `sz-rust make:controller --api` 生成,对齐 PHP `controller.api.stub`(5 个方法)。
118
119use sz_rust_core::request::Request;
120use sz_rust_core::response::Response;
121
122/// {%className%} API 控制器
123pub struct {%className%};
124
125impl {%className%} {
126    /// 列表(GET /{%route%})
127    pub async fn index(_req: Request) -> Response {
128        Response::success("index")
129    }
130
131    /// 保存(POST /{%route%})
132    pub async fn save(_req: Request) -> Response {
133        Response::success("save")
134    }
135
136    /// 详情(GET /{%route%}/{id})
137    pub async fn read(_req: Request) -> Response {
138        Response::success("read")
139    }
140
141    /// 更新(PUT /{%route%}/{id})
142    pub async fn update(_req: Request) -> Response {
143        Response::success("update")
144    }
145
146    /// 删除(DELETE /{%route%}/{id})
147    pub async fn delete(_req: Request) -> Response {
148        Response::success("delete")
149    }
150}
151"#;
152
153/// Plain Controller 模板(对齐 PHP `controller.plain.stub`)
154///
155/// PHP 原文为空类
156pub const CONTROLLER_PLAIN_STUB: &str = r#"//! {%namespace%}::{%className%} 控制器
157//!
158//! 由 `sz-rust make:controller --plain` 生成,对齐 PHP `controller.plain.stub`(空类)。
159
160/// {%className%} 控制器(空骨架)
161pub struct {%className%};
162"#;
163
164/// 迁移 up SQL 模板(对齐 Phinx 风格)
165pub const MIGRATION_UP_STUB: &str = r#"-- Migration: {%name%}
166-- Direction: UP
167-- Created: {%timestamp%}
168
169-- 在此编写 up SQL
170-- CREATE TABLE IF NOT EXISTS {%table_name%} (
171--     id BIGINT PRIMARY KEY AUTO_INCREMENT,
172--     created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
173--     updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
174-- );
175"#;
176
177/// 迁移 down SQL 模板(对齐 Phinx 风格)
178pub const MIGRATION_DOWN_STUB: &str = r#"-- Migration: {%name%}
179-- Direction: DOWN
180-- Created: {%timestamp%}
181
182-- 在此编写 down SQL
183-- DROP TABLE IF EXISTS {%table_name%};
184"#;
185
186/// 替换模板占位符
187///
188/// 对齐 PHP `Make::buildClass()` 中的 `str_replace` 调用。
189///
190/// # 参数
191///
192/// - `template`:模板字符串
193/// - `replacements`:占位符 → 替换值(如 `{%className%}` → `User`)
194pub fn render_template(template: &str, replacements: &[(&str, &str)]) -> String {
195    let mut result = template.to_string();
196    for (placeholder, value) in replacements {
197        result = result.replace(placeholder, value);
198    }
199    result
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    #[test]
207    fn test_model_stub_contains_class_placeholder() {
208        assert!(MODEL_STUB.contains("{%className%}"));
209        assert!(MODEL_STUB.contains("{%namespace%}"));
210        assert!(MODEL_STUB.contains("{%table_name%}"));
211    }
212
213    #[test]
214    fn test_controller_stub_has_seven_methods() {
215        assert!(CONTROLLER_STUB.contains("index"));
216        assert!(CONTROLLER_STUB.contains("create"));
217        assert!(CONTROLLER_STUB.contains("save"));
218        assert!(CONTROLLER_STUB.contains("read"));
219        assert!(CONTROLLER_STUB.contains("edit"));
220        assert!(CONTROLLER_STUB.contains("update"));
221        assert!(CONTROLLER_STUB.contains("delete"));
222    }
223
224    #[test]
225    fn test_controller_api_stub_has_five_methods() {
226        assert!(CONTROLLER_API_STUB.contains("index"));
227        assert!(CONTROLLER_API_STUB.contains("save"));
228        assert!(CONTROLLER_API_STUB.contains("read"));
229        assert!(CONTROLLER_API_STUB.contains("update"));
230        assert!(CONTROLLER_API_STUB.contains("delete"));
231        // API 模板不应包含 create/edit
232        assert!(!CONTROLLER_API_STUB.contains("pub async fn create"));
233        assert!(!CONTROLLER_API_STUB.contains("pub async fn edit"));
234    }
235
236    #[test]
237    fn test_controller_plain_stub_is_empty_class() {
238        assert!(CONTROLLER_PLAIN_STUB.contains("struct {%className%}"));
239    }
240
241    #[test]
242    fn test_migration_stubs_contain_placeholders() {
243        assert!(MIGRATION_UP_STUB.contains("{%name%}"));
244        assert!(MIGRATION_UP_STUB.contains("{%timestamp%}"));
245        assert!(MIGRATION_DOWN_STUB.contains("{%name%}"));
246        assert!(MIGRATION_DOWN_STUB.contains("{%timestamp%}"));
247    }
248
249    #[test]
250    fn test_render_template_basic() {
251        let result = render_template("Hello {%name%}!", &[("{%name%}", "World")]);
252        assert_eq!(result, "Hello World!");
253    }
254
255    #[test]
256    fn test_render_template_multiple_placeholders() {
257        let result = render_template(
258            "{%className%} in {%namespace%}",
259            &[("{%className%}", "User"), ("{%namespace%}", "app::model")],
260        );
261        assert_eq!(result, "User in app::model");
262    }
263
264    #[test]
265    fn test_render_template_no_placeholders() {
266        let result = render_template("no placeholders", &[]);
267        assert_eq!(result, "no placeholders");
268    }
269
270    #[test]
271    fn test_render_template_repeated_placeholders() {
272        let result = render_template("{%x%} and {%x%}", &[("{%x%}", "A")]);
273        assert_eq!(result, "A and A");
274    }
275}