changepacks_core/
config.rs1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
9#[serde(rename_all = "camelCase")]
10pub struct Config {
11 #[serde(default)]
13 pub ignore: Vec<String>,
14
15 #[serde(default = "default_base_branch")]
17 pub base_branch: String,
18
19 #[serde(default)]
21 pub latest_package: Option<String>,
22
23 #[serde(default)]
25 pub publish: HashMap<String, String>,
26
27 #[serde(default)]
31 pub update_on: HashMap<String, Vec<String>>,
32}
33
34fn default_base_branch() -> String {
35 "main".to_string()
36}
37
38impl Default for Config {
39 fn default() -> Self {
40 Self {
41 ignore: Vec::new(),
42 base_branch: default_base_branch(),
43 latest_package: None,
44 publish: HashMap::new(),
45 update_on: HashMap::new(),
46 }
47 }
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53
54 #[test]
55 fn test_config_default() {
56 let config = Config::default();
57 assert!(config.ignore.is_empty());
58 assert_eq!(config.base_branch, "main");
59 assert!(config.latest_package.is_none());
60 assert!(config.publish.is_empty());
61 assert!(config.update_on.is_empty());
62 }
63
64 #[test]
65 fn test_config_deserialize_full() {
66 let json = r#"{
67 "ignore": ["examples/**", "docs/**"],
68 "baseBranch": "develop",
69 "latestPackage": "crates/core/Cargo.toml",
70 "publish": {
71 "node": "npm publish --access public",
72 "rust": "cargo publish"
73 },
74 "updateOn": {
75 "crates/core/Cargo.toml": ["bridge/node/package.json", "bridge/python/pyproject.toml"]
76 }
77 }"#;
78 let config: Config = serde_json::from_str(json).unwrap();
79 assert_eq!(config.ignore, vec!["examples/**", "docs/**"]);
80 assert_eq!(config.base_branch, "develop");
81 assert_eq!(
82 config.latest_package.as_deref(),
83 Some("crates/core/Cargo.toml")
84 );
85 assert_eq!(config.publish.len(), 2);
86 assert_eq!(
87 config.publish.get("node").unwrap(),
88 "npm publish --access public"
89 );
90 assert_eq!(config.publish.get("rust").unwrap(), "cargo publish");
91 assert_eq!(config.update_on.len(), 1);
92 let update_targets = config.update_on.get("crates/core/Cargo.toml").unwrap();
93 assert_eq!(update_targets.len(), 2);
94 assert!(update_targets.contains(&"bridge/node/package.json".to_string()));
95 assert!(update_targets.contains(&"bridge/python/pyproject.toml".to_string()));
96 }
97
98 #[test]
99 fn test_config_deserialize_partial() {
100 let json = r#"{ "baseBranch": "release" }"#;
101 let config: Config = serde_json::from_str(json).unwrap();
102 assert!(config.ignore.is_empty());
103 assert_eq!(config.base_branch, "release");
104 assert!(config.latest_package.is_none());
105 assert!(config.publish.is_empty());
106 assert!(config.update_on.is_empty());
107 }
108
109 #[test]
110 fn test_config_deserialize_empty_object() {
111 let json = r#"{}"#;
112 let config: Config = serde_json::from_str(json).unwrap();
113 assert_eq!(config.base_branch, "main");
114 assert!(config.ignore.is_empty());
115 assert!(config.latest_package.is_none());
116 assert!(config.publish.is_empty());
117 assert!(config.update_on.is_empty());
118 }
119
120 #[test]
121 fn test_config_ignore_patterns() {
122 let json = r#"{ "ignore": ["**/*", "!crates/changepacks/Cargo.toml", "!bridge/**"] }"#;
123 let config: Config = serde_json::from_str(json).unwrap();
124 assert_eq!(config.ignore.len(), 3);
125 assert_eq!(config.ignore[0], "**/*");
126 assert_eq!(config.ignore[1], "!crates/changepacks/Cargo.toml");
127 assert_eq!(config.ignore[2], "!bridge/**");
128 }
129
130 #[test]
131 fn test_config_publish_map() {
132 let json = r#"{
133 "publish": {
134 "node": "npm publish",
135 "python": "uv publish",
136 "rust": "cargo publish",
137 "dart": "dart pub publish",
138 "bridge/node/package.json": "npm publish --access public"
139 }
140 }"#;
141 let config: Config = serde_json::from_str(json).unwrap();
142 assert_eq!(config.publish.len(), 5);
143 assert_eq!(config.publish.get("node").unwrap(), "npm publish");
144 assert_eq!(config.publish.get("python").unwrap(), "uv publish");
145 assert_eq!(config.publish.get("rust").unwrap(), "cargo publish");
146 assert_eq!(config.publish.get("dart").unwrap(), "dart pub publish");
147 assert_eq!(
148 config.publish.get("bridge/node/package.json").unwrap(),
149 "npm publish --access public"
150 );
151 }
152
153 #[test]
154 fn test_config_update_on_map() {
155 let json = r#"{
156 "updateOn": {
157 "crates/changepacks/Cargo.toml": ["bridge/node/package.json"],
158 "crates/core/Cargo.toml": ["bridge/python/pyproject.toml", "bridge/node/package.json"]
159 }
160 }"#;
161 let config: Config = serde_json::from_str(json).unwrap();
162 assert_eq!(config.update_on.len(), 2);
163
164 let changepacks_targets = config
165 .update_on
166 .get("crates/changepacks/Cargo.toml")
167 .unwrap();
168 assert_eq!(changepacks_targets.len(), 1);
169 assert_eq!(changepacks_targets[0], "bridge/node/package.json");
170
171 let core_targets = config.update_on.get("crates/core/Cargo.toml").unwrap();
172 assert_eq!(core_targets.len(), 2);
173 }
174
175 #[test]
176 fn test_config_serialize_roundtrip() {
177 let mut config = Config {
178 ignore: vec!["test/**".to_string()],
179 base_branch: "develop".to_string(),
180 latest_package: Some("Cargo.toml".to_string()),
181 ..Config::default()
182 };
183 config
184 .publish
185 .insert("rust".to_string(), "cargo publish".to_string());
186 config.update_on.insert(
187 "Cargo.toml".to_string(),
188 vec!["bridge/package.json".to_string()],
189 );
190
191 let json = serde_json::to_string(&config).unwrap();
192 let deserialized: Config = serde_json::from_str(&json).unwrap();
193 assert_eq!(config, deserialized);
194 }
195}