Skip to main content

sz_rust_cli/
skeleton.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2024-2026 SZ-Rust Team
3//
4//! 插件骨架元数据与产物结构定义
5//!
6//! 对应 design.md 第 2.3.2 节,定义模板元数据规范与生成产物数据结构。
7
8use serde::{Deserialize, Serialize};
9
10/// 模板元数据(对应每个模板目录下的 `template.json`)
11///
12/// 声明模板的名称、版本、描述与所需变量列表。
13/// `required_variables` 用于在渲染前校验上下文是否提供全部必需变量。
14#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
15pub struct TemplateMeta {
16    /// 模板名称(如 `"crud"`、`"master-slave"`)
17    pub name: String,
18    /// 模板版本(语义化版本,如 `"1.0.0"`)
19    pub version: String,
20    /// 模板描述
21    pub description: String,
22    /// 渲染所需变量名列表
23    pub required_variables: Vec<String>,
24}
25
26impl TemplateMeta {
27    /// 创建新的模板元数据
28    pub fn new(
29        name: impl Into<String>,
30        version: impl Into<String>,
31        description: impl Into<String>,
32        required_variables: Vec<String>,
33    ) -> Self {
34        Self {
35            name: name.into(),
36            version: version.into(),
37            description: description.into(),
38            required_variables,
39        }
40    }
41
42    /// 校验上下文是否包含全部必需变量
43    ///
44    /// 返回缺失变量列表(空表示全部满足)
45    pub fn missing_variables(&self, provided: &[&str]) -> Vec<String> {
46        self.required_variables
47            .iter()
48            .filter(|req| !provided.iter().any(|p| p == req))
49            .cloned()
50            .collect()
51    }
52}
53
54/// 生成产物中的源代码文件
55#[derive(Debug, Clone)]
56pub struct SourceFile {
57    /// 相对路径(如 `src/model.rs`)
58    pub path: String,
59    /// 文件内容
60    pub content: String,
61}
62
63/// 生成产物中的迁移文件
64#[derive(Debug, Clone)]
65pub struct MigrationFile {
66    /// 迁移文件名(如 `20260811_create_users.sql`)
67    pub name: String,
68    /// SQL 内容
69    pub content: String,
70}
71
72/// 插件骨架生成产物
73#[derive(Debug, Clone)]
74pub struct PluginSkeleton {
75    /// 插件名称
76    pub plugin_name: String,
77    /// 模板类型
78    pub template_type: String,
79    /// 源代码文件列表
80    pub source_files: Vec<SourceFile>,
81    /// 迁移文件列表
82    pub migrations: Vec<MigrationFile>,
83    /// manifest.json 内容
84    pub manifest: String,
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn test_template_meta_new() {
93        let meta = TemplateMeta::new(
94            "crud",
95            "1.0.0",
96            "CRUD plugin template",
97            vec!["plugin_name".to_string(), "table_name".to_string()],
98        );
99        assert_eq!(meta.name, "crud");
100        assert_eq!(meta.version, "1.0.0");
101        assert_eq!(meta.required_variables.len(), 2);
102    }
103
104    #[test]
105    fn test_template_meta_serialize_deserialize() {
106        let meta = TemplateMeta::new(
107            "crud",
108            "1.0.0",
109            "CRUD plugin template",
110            vec!["plugin_name".to_string()],
111        );
112        let json = serde_json::to_string(&meta).expect("serialize failed");
113        let deserialized: TemplateMeta = serde_json::from_str(&json).expect("deserialize failed");
114        assert_eq!(meta, deserialized);
115    }
116
117    #[test]
118    fn test_missing_variables_all_provided() {
119        let meta = TemplateMeta::new(
120            "crud",
121            "1.0.0",
122            "",
123            vec!["plugin_name".to_string(), "table_name".to_string()],
124        );
125        let provided = vec!["plugin_name", "table_name"];
126        let missing = meta.missing_variables(&provided);
127        assert!(missing.is_empty());
128    }
129
130    #[test]
131    fn test_missing_variables_some_missing() {
132        let meta = TemplateMeta::new(
133            "crud",
134            "1.0.0",
135            "",
136            vec![
137                "plugin_name".to_string(),
138                "table_name".to_string(),
139                "fields".to_string(),
140            ],
141        );
142        let provided = vec!["plugin_name"];
143        let missing = meta.missing_variables(&provided);
144        assert_eq!(missing.len(), 2);
145        assert!(missing.contains(&"table_name".to_string()));
146        assert!(missing.contains(&"fields".to_string()));
147    }
148}