codex_feedback/
feedback_diagnostics.rs1use std::collections::HashMap;
2
3pub const FEEDBACK_DIAGNOSTICS_ATTACHMENT_FILENAME: &str = "codex-connectivity-diagnostics.txt";
4const PROXY_ENV_VARS: &[&str] = &[
5 "HTTP_PROXY",
6 "http_proxy",
7 "HTTPS_PROXY",
8 "https_proxy",
9 "ALL_PROXY",
10 "all_proxy",
11];
12
13#[derive(Debug, Clone, Default, PartialEq, Eq)]
14pub struct FeedbackDiagnostics {
15 diagnostics: Vec<FeedbackDiagnostic>,
16}
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct FeedbackDiagnostic {
20 pub headline: String,
21 pub details: Vec<String>,
22}
23
24impl FeedbackDiagnostics {
25 pub fn new(diagnostics: Vec<FeedbackDiagnostic>) -> Self {
26 Self { diagnostics }
27 }
28
29 pub fn collect_from_env() -> Self {
30 Self::collect_from_pairs(std::env::vars())
31 }
32
33 fn collect_from_pairs<I, K, V>(pairs: I) -> Self
34 where
35 I: IntoIterator<Item = (K, V)>,
36 K: Into<String>,
37 V: Into<String>,
38 {
39 let env = pairs
40 .into_iter()
41 .map(|(key, value)| (key.into(), value.into()))
42 .collect::<HashMap<_, _>>();
43 let mut diagnostics = Vec::new();
44
45 let proxy_details = PROXY_ENV_VARS
46 .iter()
47 .filter_map(|key| {
48 let value = env.get(*key)?;
49 Some(format!("{key} = {value}"))
50 })
51 .collect::<Vec<_>>();
52 if !proxy_details.is_empty() {
53 diagnostics.push(FeedbackDiagnostic {
54 headline: "Proxy environment variables are set and may affect connectivity."
55 .to_string(),
56 details: proxy_details,
57 });
58 }
59
60 Self { diagnostics }
61 }
62
63 pub fn is_empty(&self) -> bool {
64 self.diagnostics.is_empty()
65 }
66
67 pub fn diagnostics(&self) -> &[FeedbackDiagnostic] {
68 &self.diagnostics
69 }
70
71 pub fn attachment_text(&self) -> Option<String> {
72 if self.diagnostics.is_empty() {
73 return None;
74 }
75
76 let mut lines = vec!["Connectivity diagnostics".to_string(), String::new()];
77 for diagnostic in &self.diagnostics {
78 lines.push(format!("- {}", diagnostic.headline));
79 lines.extend(
80 diagnostic
81 .details
82 .iter()
83 .map(|detail| format!(" - {detail}")),
84 );
85 }
86
87 Some(lines.join("\n"))
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use pretty_assertions::assert_eq;
94
95 use super::FeedbackDiagnostic;
96 use super::FeedbackDiagnostics;
97
98 #[test]
99 fn collect_from_pairs_reports_raw_values_and_attachment() {
100 let diagnostics = FeedbackDiagnostics::collect_from_pairs([
101 (
102 "HTTPS_PROXY",
103 "https://user:password@secure-proxy.example.com:443?secret=1",
104 ),
105 ("http_proxy", "proxy.example.com:8080"),
106 ("all_proxy", "socks5h://all-proxy.example.com:1080"),
107 ]);
108
109 assert_eq!(
110 diagnostics,
111 FeedbackDiagnostics {
112 diagnostics: vec![FeedbackDiagnostic {
113 headline: "Proxy environment variables are set and may affect connectivity."
114 .to_string(),
115 details: vec![
116 "http_proxy = proxy.example.com:8080".to_string(),
117 "HTTPS_PROXY = https://user:password@secure-proxy.example.com:443?secret=1"
118 .to_string(),
119 "all_proxy = socks5h://all-proxy.example.com:1080".to_string(),
120 ],
121 },],
122 }
123 );
124
125 assert_eq!(
126 diagnostics.attachment_text(),
127 Some(
128 r#"Connectivity diagnostics
129
130- Proxy environment variables are set and may affect connectivity.
131 - http_proxy = proxy.example.com:8080
132 - HTTPS_PROXY = https://user:password@secure-proxy.example.com:443?secret=1
133 - all_proxy = socks5h://all-proxy.example.com:1080"#
134 .to_string()
135 )
136 );
137 }
138
139 #[test]
140 fn collect_from_pairs_ignores_absent_values() {
141 let diagnostics = FeedbackDiagnostics::collect_from_pairs(Vec::<(String, String)>::new());
142 assert_eq!(diagnostics, FeedbackDiagnostics::default());
143 assert_eq!(diagnostics.attachment_text(), None);
144 }
145
146 #[test]
147 fn collect_from_pairs_preserves_whitespace_and_empty_values() {
148 let diagnostics =
149 FeedbackDiagnostics::collect_from_pairs([("HTTP_PROXY", " proxy with spaces ")]);
150
151 assert_eq!(
152 diagnostics,
153 FeedbackDiagnostics {
154 diagnostics: vec![FeedbackDiagnostic {
155 headline: "Proxy environment variables are set and may affect connectivity."
156 .to_string(),
157 details: vec!["HTTP_PROXY = proxy with spaces ".to_string()],
158 },],
159 }
160 );
161 }
162
163 #[test]
164 fn collect_from_pairs_reports_values_verbatim() {
165 let proxy_value = "not a valid proxy";
166 let diagnostics = FeedbackDiagnostics::collect_from_pairs([("HTTP_PROXY", proxy_value)]);
167
168 assert_eq!(
169 diagnostics,
170 FeedbackDiagnostics {
171 diagnostics: vec![FeedbackDiagnostic {
172 headline: "Proxy environment variables are set and may affect connectivity."
173 .to_string(),
174 details: vec!["HTTP_PROXY = not a valid proxy".to_string()],
175 },],
176 }
177 );
178 }
179}