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