Skip to main content

lexicon_spec/
ecosystem.rs

1//! Ecosystem manifest types for multi-repo governance.
2
3use serde::{Deserialize, Serialize};
4
5/// Manifest describing a multi-repo ecosystem.
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
7pub struct EcosystemManifest {
8    /// Human-readable name for this ecosystem.
9    pub name: String,
10
11    /// Repositories that belong to this ecosystem.
12    #[serde(default)]
13    pub repos: Vec<RepoEntry>,
14
15    /// Contracts shared across all repos in the ecosystem.
16    #[serde(default)]
17    pub shared_contracts: Vec<String>,
18
19    /// Governance rules that apply ecosystem-wide.
20    #[serde(default)]
21    pub governance_rules: Vec<String>,
22}
23
24/// A repository entry within the ecosystem.
25#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
26pub struct RepoEntry {
27    /// Repository name.
28    pub name: String,
29
30    /// Path (relative or absolute) to the repository root.
31    pub path: String,
32
33    /// Role of this repository in the ecosystem.
34    pub role: RepoRole,
35
36    /// Human-readable description.
37    #[serde(default)]
38    pub description: String,
39}
40
41/// The role a repository plays in the ecosystem.
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
43#[serde(rename_all = "lowercase")]
44pub enum RepoRole {
45    /// Core platform repository.
46    Platform,
47    /// Microservice or standalone service.
48    Service,
49    /// Shared library consumed by other repos.
50    Library,
51    /// Developer tooling, CLI, or build tools.
52    Tool,
53    /// Infrastructure-as-code, deployment, CI/CD.
54    Infrastructure,
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60
61    #[test]
62    fn test_ecosystem_manifest_serde_roundtrip() {
63        let manifest = EcosystemManifest {
64            name: "acme-platform".into(),
65            repos: vec![
66                RepoEntry {
67                    name: "acme-core".into(),
68                    path: "../acme-core".into(),
69                    role: RepoRole::Platform,
70                    description: "Core platform services".into(),
71                },
72                RepoEntry {
73                    name: "acme-auth".into(),
74                    path: "../acme-auth".into(),
75                    role: RepoRole::Service,
76                    description: "Authentication service".into(),
77                },
78            ],
79            shared_contracts: vec!["api-versioning".into()],
80            governance_rules: vec!["all repos must pass conformance checks".into()],
81        };
82
83        let toml_str = toml::to_string_pretty(&manifest).unwrap();
84        let back: EcosystemManifest = toml::from_str(&toml_str).unwrap();
85        assert_eq!(manifest, back);
86    }
87
88    #[test]
89    fn test_ecosystem_manifest_defaults() {
90        let toml_str = r#"
91            name = "my-ecosystem"
92        "#;
93        let manifest: EcosystemManifest = toml::from_str(toml_str).unwrap();
94        assert_eq!(manifest.name, "my-ecosystem");
95        assert!(manifest.repos.is_empty());
96        assert!(manifest.shared_contracts.is_empty());
97        assert!(manifest.governance_rules.is_empty());
98    }
99
100    #[test]
101    fn test_repo_role_serde() {
102        for role in [
103            RepoRole::Platform,
104            RepoRole::Service,
105            RepoRole::Library,
106            RepoRole::Tool,
107            RepoRole::Infrastructure,
108        ] {
109            let json = serde_json::to_string(&role).unwrap();
110            let back: RepoRole = serde_json::from_str(&json).unwrap();
111            assert_eq!(role, back);
112        }
113    }
114
115    #[test]
116    fn test_repo_entry_toml_roundtrip() {
117        let entry = RepoEntry {
118            name: "my-tool".into(),
119            path: "/opt/tools/my-tool".into(),
120            role: RepoRole::Tool,
121            description: "Internal developer tool".into(),
122        };
123
124        let toml_str = toml::to_string_pretty(&entry).unwrap();
125        let back: RepoEntry = toml::from_str(&toml_str).unwrap();
126        assert_eq!(entry, back);
127    }
128}