Skip to main content

quanttide_devops/contract/
mod.rs

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