Skip to main content

quanttide_devops/contract/
scope.rs

1use serde::de::{Deserializer, MapAccess, Visitor};
2use serde::{Deserialize, Serialize};
3use std::fmt;
4
5use super::platform::Registry;
6use super::stage::StageRelease;
7
8// ── Scopes(上下文维度)───────────────────────────────────────────────
9
10/// 作用域(上下文维度)。
11///
12/// 通过 scope 为不同组件挂载不同的 Stage、Platform、Source 组合。
13#[derive(Debug, Clone, Serialize)]
14#[serde(rename_all = "snake_case")]
15pub struct Scope {
16    pub name: String,
17    pub dir: String,
18    #[serde(default)]
19    pub language: Language,
20    #[serde(default)]
21    pub framework: String,
22    #[serde(default)]
23    pub build_tool: BuildTool,
24    #[serde(default)]
25    pub registry: Registry,
26    #[serde(default)]
27    pub release: StageRelease,
28    #[serde(default)]
29    pub test_threshold: Option<f64>,
30    #[serde(default)]
31    pub ci_workflow: Option<String>,
32}
33
34/// 编程语言。
35#[derive(Debug, Clone, PartialEq, Serialize)]
36#[serde(rename_all = "snake_case")]
37pub enum Language {
38    Rust,
39    Python,
40    Go,
41    Dart,
42    #[serde(rename = "typescript")]
43    TypeScript,
44    Unknown(String),
45}
46
47impl Default for Language {
48    fn default() -> Self {
49        Self::Unknown("auto".into())
50    }
51}
52
53impl Language {
54    /// 返回语言的显示名称。
55    pub fn as_str(&self) -> &str {
56        match self {
57            Self::Rust => "rust",
58            Self::Python => "python",
59            Self::Go => "go",
60            Self::Dart => "dart",
61            Self::TypeScript => "typescript",
62            Self::Unknown(s) => s,
63        }
64    }
65}
66
67/// 构建工具。
68#[derive(Debug, Clone, PartialEq, Serialize)]
69#[serde(rename_all = "snake_case")]
70pub enum BuildTool {
71    Cargo,
72    Uv,
73    Go,
74    Flutter,
75    Npm,
76    Unknown(String),
77}
78
79impl Default for BuildTool {
80    fn default() -> Self {
81        Self::Unknown("auto".into())
82    }
83}
84
85impl BuildTool {
86    /// 返回构建工具的显示名称。
87    pub fn as_str(&self) -> &str {
88        match self {
89            Self::Cargo => "cargo",
90            Self::Uv => "uv",
91            Self::Go => "go",
92            Self::Flutter => "flutter",
93            Self::Npm => "npm",
94            Self::Unknown(s) => s,
95        }
96    }
97}
98
99// ── 自定义反序列化(Language / BuildTool)────────────────────────────
100
101impl<'de> Deserialize<'de> for Language {
102    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
103    where
104        D: Deserializer<'de>,
105    {
106        let s = String::deserialize(deserializer)?;
107        Ok(match s.as_str() {
108            "rust" => Language::Rust,
109            "python" => Language::Python,
110            "go" => Language::Go,
111            "dart" => Language::Dart,
112            "typescript" | "ts" | "node" => Language::TypeScript,
113            other => Language::Unknown(other.to_string()),
114        })
115    }
116}
117
118impl<'de> Deserialize<'de> for BuildTool {
119    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
120    where
121        D: Deserializer<'de>,
122    {
123        let s = String::deserialize(deserializer)?;
124        Ok(match s.as_str() {
125            "cargo" => BuildTool::Cargo,
126            "uv" | "poetry" | "pdm" => BuildTool::Uv,
127            "go" => BuildTool::Go,
128            "flutter" => BuildTool::Flutter,
129            "npm" | "pnpm" | "yarn" | "bun" => BuildTool::Npm,
130            other => BuildTool::Unknown(other.to_string()),
131        })
132    }
133}
134
135// ── 自定义反序列化(scopes: map → Vec<Scope>)────────────────────────
136
137/// YAML 中的 scope 原始配置(map 格式的中间表示)。
138#[derive(Debug, Deserialize)]
139#[serde(rename_all = "snake_case")]
140struct ScopeConfig {
141    dir: String,
142    #[serde(default)]
143    language: Option<Language>,
144    #[serde(default)]
145    framework: Option<String>,
146    #[serde(default)]
147    build_tool: Option<BuildTool>,
148    #[serde(default)]
149    registry: Option<Registry>,
150    #[serde(default)]
151    release: Option<StageRelease>,
152    #[serde(default)]
153    test_threshold: Option<f64>,
154    #[serde(default)]
155    ci_workflow: Option<String>,
156}
157
158pub fn deserialize_scopes<'de, D>(deserializer: D) -> Result<Vec<Scope>, D::Error>
159where
160    D: Deserializer<'de>,
161{
162    /// 访问器:将 `{ name: { dir, ... } }` 转为 `[Scope, ...]`。
163    struct ScopesVisitor;
164
165    impl<'de> Visitor<'de> for ScopesVisitor {
166        type Value = Vec<Scope>;
167
168        fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
169            f.write_str("作用域映射")
170        }
171
172        fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
173        where
174            M: MapAccess<'de>,
175        {
176            let mut scopes = Vec::new();
177            while let Some((name, config)) = access.next_entry::<String, ScopeConfig>()? {
178                scopes.push(Scope {
179                    name,
180                    dir: config.dir,
181                    language: config.language.unwrap_or(Language::Unknown("auto".into())),
182                    framework: config.framework.unwrap_or_default(),
183                    build_tool: config
184                        .build_tool
185                        .unwrap_or(BuildTool::Unknown("auto".into())),
186                    registry: config.registry.unwrap_or(Registry::None),
187                    release: config.release.unwrap_or_default(),
188                    test_threshold: config.test_threshold,
189                    ci_workflow: config.ci_workflow,
190                });
191            }
192            Ok(scopes)
193        }
194    }
195
196    deserializer.deserialize_map(ScopesVisitor)
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202
203    #[test]
204    fn test_language_as_str() {
205        assert_eq!(Language::Rust.as_str(), "rust");
206        assert_eq!(Language::Python.as_str(), "python");
207        assert_eq!(Language::Go.as_str(), "go");
208        assert_eq!(Language::Dart.as_str(), "dart");
209        assert_eq!(Language::TypeScript.as_str(), "typescript");
210        assert_eq!(Language::Unknown("zig".into()).as_str(), "zig");
211    }
212
213    #[test]
214    fn test_build_tool_as_str() {
215        assert_eq!(BuildTool::Cargo.as_str(), "cargo");
216        assert_eq!(BuildTool::Uv.as_str(), "uv");
217        assert_eq!(BuildTool::Go.as_str(), "go");
218        assert_eq!(BuildTool::Flutter.as_str(), "flutter");
219        assert_eq!(BuildTool::Npm.as_str(), "npm");
220        assert_eq!(BuildTool::Unknown("make".into()).as_str(), "make");
221    }
222
223    #[test]
224    fn test_language_deserialize() {
225        let lang: Language = serde_yaml::from_str("rust").unwrap();
226        assert_eq!(lang, Language::Rust);
227        let lang: Language = serde_yaml::from_str("zig").unwrap();
228        assert_eq!(lang, Language::Unknown("zig".into()));
229    }
230
231    #[test]
232    fn test_build_tool_deserialize() {
233        let tool: BuildTool = serde_yaml::from_str("cargo").unwrap();
234        assert_eq!(tool, BuildTool::Cargo);
235        let tool: BuildTool = serde_yaml::from_str("make").unwrap();
236        assert_eq!(tool, BuildTool::Unknown("make".into()));
237    }
238
239    #[test]
240    fn test_language_default() {
241        assert_eq!(Language::default(), Language::Unknown("auto".into()));
242    }
243
244    #[test]
245    fn test_build_tool_default() {
246        assert_eq!(BuildTool::default(), BuildTool::Unknown("auto".into()));
247    }
248}