polyc_runtime/
update_copy.rs1use crate::compat::Compatibility;
24
25pub const APPLY_NOW: &str = "Apply now";
29
30#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct UpdateCopy {
35 pub headline: String,
38 pub detail: String,
41 pub action: Option<&'static str>,
45}
46
47#[must_use]
54pub fn update_copy(class: &Compatibility, version: &str) -> UpdateCopy {
55 match class {
56 Compatibility::Hot => UpdateCopy {
57 headline: format!("Update {version} is ready to apply"),
58 detail: "It reaches new conversations right away, with no restart.".to_owned(),
59 action: Some(APPLY_NOW),
60 },
61 Compatibility::Warm => UpdateCopy {
62 headline: format!("Update {version} is ready to apply"),
63 detail: "Applying it restarts the service; conversations already underway finish \
64 first."
65 .to_owned(),
66 action: Some(APPLY_NOW),
67 },
68 Compatibility::Cold => UpdateCopy {
69 headline: format!("Update {version} needs a coordinated deploy"),
70 detail: "It changes how the parts talk to each other, so the whole deployment moves \
71 together. Reach out to whoever runs your deployment to schedule it."
72 .to_owned(),
73 action: None,
74 },
75 Compatibility::Incompatible(reason) => UpdateCopy {
76 headline: format!("Update {version} can't be applied here"),
77 detail: format!(
78 "This build was made for a different setup: {reason}. Reach out to whoever runs \
79 your deployment."
80 ),
81 action: None,
82 },
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 #![allow(clippy::pedantic, clippy::nursery, missing_docs)]
89
90 use super::*;
91 use crate::compat::Incompatibility;
92
93 fn all_classes() -> Vec<Compatibility> {
95 vec![
96 Compatibility::Hot,
97 Compatibility::Warm,
98 Compatibility::Cold,
99 Compatibility::Incompatible(Incompatibility::Wire),
100 Compatibility::Incompatible(Incompatibility::EventLog),
101 Compatibility::Incompatible(Incompatibility::Crd),
102 ]
103 }
104
105 #[test]
106 fn hot_and_warm_carry_the_apply_verb() {
107 assert_eq!(
108 update_copy(&Compatibility::Hot, "0.2.0").action,
109 Some(APPLY_NOW)
110 );
111 assert_eq!(
112 update_copy(&Compatibility::Warm, "0.2.0").action,
113 Some(APPLY_NOW)
114 );
115 }
116
117 #[test]
118 fn cold_and_incompatible_carry_no_apply_verb() {
119 assert_eq!(update_copy(&Compatibility::Cold, "0.2.0").action, None);
120 assert_eq!(
121 update_copy(&Compatibility::Incompatible(Incompatibility::Wire), "0.2.0").action,
122 None,
123 );
124 }
125
126 #[test]
127 fn each_class_speaks_plainly_and_distinctly() {
128 assert!(
130 update_copy(&Compatibility::Hot, "0.2.0")
131 .detail
132 .contains("no restart"),
133 "hot copy must say it needs no restart"
134 );
135 assert!(
136 update_copy(&Compatibility::Warm, "0.2.0")
137 .detail
138 .contains("restarts the service"),
139 "warm copy must say it restarts the service"
140 );
141 let cold = update_copy(&Compatibility::Cold, "0.2.0");
142 assert!(
143 cold.detail.contains("coordinated deploy")
144 || cold.headline.contains("coordinated deploy"),
145 "cold copy must route to a coordinated deploy"
146 );
147 }
148
149 #[test]
150 fn the_version_rides_every_headline() {
151 for class in all_classes() {
152 assert!(
153 update_copy(&class, "1.4.2").headline.contains("1.4.2"),
154 "the version must appear in the headline for {class:?}"
155 );
156 }
157 }
158
159 #[test]
160 fn no_banned_jargon_and_no_apologising_anywhere() {
161 const BANNED: &[&str] = &[
164 "operator",
165 "trifecta",
166 "rule-of-two",
167 "context budget",
168 "sub-agent",
169 "state-changing action",
170 "lethal-trifecta",
171 "please",
172 "sorry",
173 "unfortunately",
174 ];
175 for class in all_classes() {
176 let copy = update_copy(&class, "0.2.0");
177 let surface = format!(
178 "{} {} {}",
179 copy.headline,
180 copy.detail,
181 copy.action.unwrap_or_default()
182 )
183 .to_lowercase();
184 for banned in BANNED {
185 assert!(
186 !surface.contains(banned),
187 "class {class:?} copy contains banned term {banned:?}: {surface}"
188 );
189 }
190 }
191 }
192
193 #[test]
194 fn incompatible_names_the_mismatched_axis() {
195 let copy = update_copy(
196 &Compatibility::Incompatible(Incompatibility::EventLog),
197 "0.2.0",
198 );
199 assert!(
200 copy.detail.contains("event-log schema"),
201 "an incompatible update names the axis that differs: {}",
202 copy.detail
203 );
204 }
205}