1pub 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
69pub 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
121pub 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
162pub 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
173pub 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
186pub 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
195pub 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
205pub 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
239pub 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
269pub 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
304pub 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
338pub 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
373pub 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
407pub 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 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 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 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 #[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 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 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 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 assert!(!result.contains("{%"));
637 }
638}