Skip to main content

quanttide_devops/contract/
mod.rs

1pub mod core;
2/// 契约模型四维架构:Stages / Platforms / Sources / Scopes。
3///
4/// 参考:<https://github.com/quanttide/quanttide-essay-of-devops/blob/main/contract/index.md>
5pub mod error;
6pub mod platform;
7pub mod scope;
8pub mod source;
9pub mod stage;
10pub mod version;
11
12pub use core::Contract;
13pub use error::ContractError;
14pub use platform::{Pipeline, Platform, Registry, SourceControl};
15pub use scope::{BuildTool, Language, Scope};
16pub use source::{Source, SourceType};
17pub use stage::{Stage, StageBuild, StageRelease, StageTest};
18pub use version::{
19    VersionState, check_version_consistency, normalize_version, validate_version, verify_version,
20};
21
22use std::path::Path;
23
24/// 从 `.quanttide/devops/contract.yaml` 加载契约。
25pub fn load(repo_path: &Path) -> Result<Contract, ContractError> {
26    let path = repo_path.join(".quanttide/devops/contract.yaml");
27    let content = std::fs::read_to_string(&path)?;
28    let mut contract: Contract = load_from_str(&content)?;
29    // Auto 类型展开为具体类型
30    if contract.sources.version.source_type == SourceType::Auto {
31        contract.sources.version.source_type = SourceType::detect(repo_path);
32    }
33    Ok(contract)
34}
35
36/// 加载契约,不存在时自动推测。
37pub fn load_or_default(repo_path: &Path) -> Contract {
38    load(repo_path).unwrap_or_else(|_| Contract::auto_detect(repo_path))
39}
40
41/// 从 YAML 字符串解析契约。
42pub fn load_from_str(s: &str) -> Result<Contract, ContractError> {
43    serde_yaml::from_str::<Contract>(s).map_err(|e| ContractError::Parse(e.to_string()))
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49
50    #[test]
51    fn test_load_from_str_valid() {
52        let yaml = r#"
53stages:
54  test:
55    threshold: 80
56
57scopes:
58  cli:
59    dir: src/cli
60"#;
61        let c = load_from_str(yaml).unwrap();
62        assert_eq!(c.scopes.len(), 1);
63        assert_eq!(c.scopes[0].name, "cli");
64        assert_eq!(c.scopes[0].dir, "src/cli");
65        assert_eq!(c.stages.test.threshold, 80.0);
66    }
67
68    #[test]
69    fn test_load_from_str_empty() {
70        let c = load_from_str("").unwrap();
71        assert!(c.scopes.is_empty());
72    }
73
74    #[test]
75    fn test_load_from_str_invalid() {
76        let err = load_from_str("invalid: [").unwrap_err();
77        assert!(err.to_string().contains("解析失败"));
78    }
79}