quanttide_devops/contract/
scope.rs1use serde::de::{Deserializer, MapAccess, Visitor};
4use serde::{Deserialize, Serialize};
5use std::fmt;
6
7use super::platform::Registry;
8use super::stage::StageRelease;
9
10#[derive(Debug, Clone, Serialize)]
16#[serde(rename_all = "snake_case")]
17pub struct Scope {
18 pub name: String,
19 pub dir: String,
20 #[serde(default)]
21 pub language: Language,
22 #[serde(default)]
23 pub framework: String,
24 #[serde(default)]
25 pub build_tool: BuildTool,
26 #[serde(default)]
27 pub registry: Registry,
28 #[serde(default)]
29 pub release: StageRelease,
30 #[serde(default)]
31 pub test_threshold: Option<f64>,
32 #[serde(default)]
33 pub ci_workflow: Option<String>,
34}
35
36#[derive(Debug, Clone, PartialEq, Serialize)]
38#[serde(rename_all = "snake_case")]
39pub enum Language {
40 Rust,
41 Python,
42 Go,
43 Dart,
44 #[serde(rename = "typescript")]
45 TypeScript,
46 Unknown(String),
47}
48
49impl Default for Language {
50 fn default() -> Self {
51 Self::Unknown("auto".into())
52 }
53}
54
55impl Language {
56 pub fn as_str(&self) -> &str {
58 match self {
59 Self::Rust => "rust",
60 Self::Python => "python",
61 Self::Go => "go",
62 Self::Dart => "dart",
63 Self::TypeScript => "typescript",
64 Self::Unknown(s) => s,
65 }
66 }
67
68 pub fn default_build_tool(&self) -> BuildTool {
80 match self {
81 Self::Rust => BuildTool::Cargo,
82 Self::Python => BuildTool::Uv,
83 Self::Go => BuildTool::Go,
84 Self::Dart => BuildTool::Flutter,
85 Self::TypeScript => BuildTool::Npm,
86 Self::Unknown(_) => BuildTool::Unknown("auto".into()),
87 }
88 }
89
90 pub fn default_registry(&self) -> Registry {
101 match self {
102 Self::Rust => Registry::Crates,
103 Self::Python => Registry::PyPI,
104 Self::Go => Registry::GitHubReleases,
105 Self::Dart => Registry::PubDev,
106 Self::TypeScript => Registry::Npm,
107 Self::Unknown(_) => Registry::None,
108 }
109 }
110}
111
112#[derive(Debug, Clone, PartialEq, Serialize)]
114#[serde(rename_all = "snake_case")]
115pub enum BuildTool {
116 Cargo,
117 Uv,
118 Go,
119 Flutter,
120 Npm,
121 Unknown(String),
122}
123
124impl Default for BuildTool {
125 fn default() -> Self {
126 Self::Unknown("auto".into())
127 }
128}
129
130impl BuildTool {
131 pub fn as_str(&self) -> &str {
133 match self {
134 Self::Cargo => "cargo",
135 Self::Uv => "uv",
136 Self::Go => "go",
137 Self::Flutter => "flutter",
138 Self::Npm => "npm",
139 Self::Unknown(s) => s,
140 }
141 }
142}
143
144impl<'de> Deserialize<'de> for Language {
147 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
148 where
149 D: Deserializer<'de>,
150 {
151 let s = String::deserialize(deserializer)?;
152 Ok(match s.as_str() {
153 "rust" => Language::Rust,
154 "python" => Language::Python,
155 "go" => Language::Go,
156 "dart" => Language::Dart,
157 "typescript" | "ts" | "node" => Language::TypeScript,
158 other => Language::Unknown(other.to_string()),
159 })
160 }
161}
162
163impl<'de> Deserialize<'de> for BuildTool {
164 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
165 where
166 D: Deserializer<'de>,
167 {
168 let s = String::deserialize(deserializer)?;
169 Ok(match s.as_str() {
170 "cargo" => BuildTool::Cargo,
171 "uv" | "poetry" | "pdm" => BuildTool::Uv,
172 "go" => BuildTool::Go,
173 "flutter" => BuildTool::Flutter,
174 "npm" | "pnpm" | "yarn" | "bun" => BuildTool::Npm,
175 other => BuildTool::Unknown(other.to_string()),
176 })
177 }
178}
179
180#[derive(Debug, Deserialize)]
184#[serde(rename_all = "snake_case")]
185struct ScopeConfig {
186 dir: String,
187 #[serde(default)]
188 language: Option<Language>,
189 #[serde(default)]
190 framework: Option<String>,
191 #[serde(default)]
192 build_tool: Option<BuildTool>,
193 #[serde(default)]
194 registry: Option<Registry>,
195 #[serde(default)]
196 release: Option<StageRelease>,
197 #[serde(default)]
198 test_threshold: Option<f64>,
199 #[serde(default)]
200 ci_workflow: Option<String>,
201}
202
203pub fn deserialize_scopes<'de, D>(deserializer: D) -> Result<Vec<Scope>, D::Error>
204where
205 D: Deserializer<'de>,
206{
207 struct ScopesVisitor;
209
210 impl<'de> Visitor<'de> for ScopesVisitor {
211 type Value = Vec<Scope>;
212
213 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
214 f.write_str("作用域映射")
215 }
216
217 fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
218 where
219 M: MapAccess<'de>,
220 {
221 let mut scopes = Vec::new();
222 while let Some((name, config)) = access.next_entry::<String, ScopeConfig>()? {
223 scopes.push(Scope {
224 name,
225 dir: config.dir,
226 language: config.language.unwrap_or(Language::Unknown("auto".into())),
227 framework: config.framework.unwrap_or_default(),
228 build_tool: config
229 .build_tool
230 .unwrap_or(BuildTool::Unknown("auto".into())),
231 registry: config.registry.unwrap_or(Registry::None),
232 release: config.release.unwrap_or_default(),
233 test_threshold: config.test_threshold,
234 ci_workflow: config.ci_workflow,
235 });
236 }
237 Ok(scopes)
238 }
239 }
240
241 deserializer.deserialize_map(ScopesVisitor)
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn test_language_as_str() {
250 assert_eq!(Language::Rust.as_str(), "rust");
251 assert_eq!(Language::Python.as_str(), "python");
252 assert_eq!(Language::Go.as_str(), "go");
253 assert_eq!(Language::Dart.as_str(), "dart");
254 assert_eq!(Language::TypeScript.as_str(), "typescript");
255 assert_eq!(Language::Unknown("zig".into()).as_str(), "zig");
256 }
257
258 #[test]
259 fn test_build_tool_as_str() {
260 assert_eq!(BuildTool::Cargo.as_str(), "cargo");
261 assert_eq!(BuildTool::Uv.as_str(), "uv");
262 assert_eq!(BuildTool::Go.as_str(), "go");
263 assert_eq!(BuildTool::Flutter.as_str(), "flutter");
264 assert_eq!(BuildTool::Npm.as_str(), "npm");
265 assert_eq!(BuildTool::Unknown("make".into()).as_str(), "make");
266 }
267
268 #[test]
269 fn test_language_deserialize() {
270 let lang: Language = serde_yaml::from_str("rust").unwrap();
271 assert_eq!(lang, Language::Rust);
272 let lang: Language = serde_yaml::from_str("zig").unwrap();
273 assert_eq!(lang, Language::Unknown("zig".into()));
274 }
275
276 #[test]
277 fn test_build_tool_deserialize() {
278 let tool: BuildTool = serde_yaml::from_str("cargo").unwrap();
279 assert_eq!(tool, BuildTool::Cargo);
280 let tool: BuildTool = serde_yaml::from_str("make").unwrap();
281 assert_eq!(tool, BuildTool::Unknown("make".into()));
282 }
283
284 #[test]
285 fn test_language_default() {
286 assert_eq!(Language::default(), Language::Unknown("auto".into()));
287 }
288
289 #[test]
290 fn test_build_tool_default() {
291 assert_eq!(BuildTool::default(), BuildTool::Unknown("auto".into()));
292 }
293
294 #[test]
295 fn test_language_default_build_tool() {
296 assert_eq!(Language::Rust.default_build_tool(), BuildTool::Cargo);
297 assert_eq!(Language::Python.default_build_tool(), BuildTool::Uv);
298 assert_eq!(Language::Go.default_build_tool(), BuildTool::Go);
299 assert_eq!(Language::Dart.default_build_tool(), BuildTool::Flutter);
300 assert_eq!(Language::TypeScript.default_build_tool(), BuildTool::Npm);
301 assert!(matches!(
302 Language::Unknown("x".into()).default_build_tool(),
303 BuildTool::Unknown(_)
304 ));
305 }
306
307 #[test]
308 fn test_language_default_registry() {
309 assert_eq!(Language::Rust.default_registry(), Registry::Crates);
310 assert_eq!(Language::Python.default_registry(), Registry::PyPI);
311 assert_eq!(Language::Go.default_registry(), Registry::GitHubReleases);
312 assert_eq!(Language::Dart.default_registry(), Registry::PubDev);
313 assert_eq!(Language::TypeScript.default_registry(), Registry::Npm);
314 assert_eq!(
315 Language::Unknown("x".into()).default_registry(),
316 Registry::None
317 );
318 }
319}