Skip to content

Commit d0ba1d0

Browse files
committed
better debugging and fix auth bugs
1 parent dba6057 commit d0ba1d0

7 files changed

Lines changed: 570 additions & 327 deletions

File tree

src/api.rs

Lines changed: 49 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -71,29 +71,6 @@ impl ApiClient {
7171
}
7272
}
7373

74-
fn debug_headers(&self) -> Vec<(&str, String)> {
75-
let masked = if self.api_key.len() > 4 {
76-
format!("Bearer ...{}", &self.api_key[self.api_key.len()-4..])
77-
} else {
78-
"Bearer ***".to_string()
79-
};
80-
let mut headers = vec![("Authorization", masked)];
81-
if let Some(ref ws) = self.workspace_id {
82-
headers.push(("X-Workspace-Id", ws.clone()));
83-
}
84-
if let Some(ref sid) = self.sandbox_id {
85-
// Send both headers during the session→sandbox migration window.
86-
headers.push(("X-Session-Id", sid.clone()));
87-
headers.push(("X-Sandbox-Id", sid.clone()));
88-
}
89-
headers
90-
}
91-
92-
fn log_request(&self, method: &str, url: &str, body: Option<&serde_json::Value>) {
93-
let headers = self.debug_headers();
94-
let header_refs: Vec<(&str, &str)> = headers.iter().map(|(k, v)| (*k, v.as_str())).collect();
95-
util::debug_request(method, url, &header_refs, body);
96-
}
9774

9875
/// Prints an error for a non-2xx response and exits. On 4xx, first re-probes
9976
/// the API key: if it's actually invalid, a clear re-auth hint is shown
@@ -122,29 +99,25 @@ impl ApiClient {
12299
req
123100
}
124101

125-
/// GET request with query parameters, returns parsed response.
126-
/// Parameters with `None` values are omitted.
127-
pub fn get_with_params<T: DeserializeOwned>(&self, path: &str, params: &[(&str, Option<String>)]) -> T {
128-
let filtered: Vec<(&str, &String)> = params.iter()
129-
.filter_map(|(k, v)| v.as_ref().map(|val| (*k, val)))
130-
.collect();
131-
let url = format!("{}{path}", self.api_url);
132-
self.log_request("GET", &url, None);
133-
134-
let resp = match self.build_request(reqwest::Method::GET, &url).query(&filtered).send() {
135-
Ok(r) => r,
102+
/// Send via `util::send_debug` and unwrap connection errors with the
103+
/// CLI's standard "error connecting" exit. All public HTTP methods
104+
/// route through here so debug logging is uniform.
105+
fn send(
106+
&self,
107+
builder: reqwest::blocking::RequestBuilder,
108+
body_for_log: Option<&serde_json::Value>,
109+
) -> (reqwest::StatusCode, String) {
110+
match util::send_debug(&self.client, builder, body_for_log) {
111+
Ok(pair) => pair,
136112
Err(e) => {
137113
eprintln!("error connecting to API: {e}");
138114
std::process::exit(1);
139115
}
140-
};
141-
142-
let (status, body) = util::debug_response(resp);
143-
if !status.is_success() {
144-
self.fail_response(status, body);
145116
}
117+
}
146118

147-
match serde_json::from_str(&body) {
119+
fn parse_json<T: DeserializeOwned>(body: &str) -> T {
120+
match serde_json::from_str(body) {
148121
Ok(v) => v,
149122
Err(e) => {
150123
eprintln!("error parsing response: {e}");
@@ -153,159 +126,83 @@ impl ApiClient {
153126
}
154127
}
155128

156-
/// GET request, returns parsed response.
157-
pub fn get<T: DeserializeOwned>(&self, path: &str) -> T {
129+
/// GET request with query parameters, returns parsed response.
130+
/// Parameters with `None` values are omitted.
131+
pub fn get_with_params<T: DeserializeOwned>(&self, path: &str, params: &[(&str, Option<String>)]) -> T {
132+
let filtered: Vec<(&str, &String)> = params.iter()
133+
.filter_map(|(k, v)| v.as_ref().map(|val| (*k, val)))
134+
.collect();
158135
let url = format!("{}{path}", self.api_url);
159-
self.log_request("GET", &url, None);
160-
161-
let resp = match self.build_request(reqwest::Method::GET, &url).send() {
162-
Ok(r) => r,
163-
Err(e) => {
164-
eprintln!("error connecting to API: {e}");
165-
std::process::exit(1);
166-
}
167-
};
168-
169-
let (status, body) = util::debug_response(resp);
136+
let req = self.build_request(reqwest::Method::GET, &url).query(&filtered);
137+
let (status, body) = self.send(req, None);
170138
if !status.is_success() {
171139
self.fail_response(status, body);
172140
}
141+
Self::parse_json(&body)
142+
}
173143

174-
match serde_json::from_str(&body) {
175-
Ok(v) => v,
176-
Err(e) => {
177-
eprintln!("error parsing response: {e}");
178-
std::process::exit(1);
179-
}
144+
/// GET request, returns parsed response.
145+
pub fn get<T: DeserializeOwned>(&self, path: &str) -> T {
146+
let url = format!("{}{path}", self.api_url);
147+
let req = self.build_request(reqwest::Method::GET, &url);
148+
let (status, body) = self.send(req, None);
149+
if !status.is_success() {
150+
self.fail_response(status, body);
180151
}
152+
Self::parse_json(&body)
181153
}
182154

183155
/// GET request; returns `None` on HTTP 404. Other status codes use the same handling as
184156
/// [`Self::get`]. Used when probing many paths where a missing resource is normal.
185157
pub fn get_none_if_not_found<T: DeserializeOwned>(&self, path: &str) -> Option<T> {
186158
let url = format!("{}{path}", self.api_url);
187-
self.log_request("GET", &url, None);
188-
189-
let resp = match self.build_request(reqwest::Method::GET, &url).send() {
190-
Ok(r) => r,
191-
Err(e) => {
192-
eprintln!("error connecting to API: {e}");
193-
std::process::exit(1);
194-
}
195-
};
196-
197-
let (status, body) = util::debug_response(resp);
159+
let req = self.build_request(reqwest::Method::GET, &url);
160+
let (status, body) = self.send(req, None);
198161
if status == reqwest::StatusCode::NOT_FOUND {
199162
return None;
200163
}
201164
if !status.is_success() {
202165
self.fail_response(status, body);
203166
}
204-
205-
match serde_json::from_str(&body) {
206-
Ok(v) => Some(v),
207-
Err(e) => {
208-
eprintln!("error parsing response: {e}");
209-
std::process::exit(1);
210-
}
211-
}
167+
Some(Self::parse_json(&body))
212168
}
213169

214170
/// POST request with JSON body, returns parsed response.
215171
pub fn post<T: DeserializeOwned>(&self, path: &str, body: &serde_json::Value) -> T {
216172
let url = format!("{}{path}", self.api_url);
217-
self.log_request("POST", &url, Some(body));
218-
219-
let resp = match self.build_request(reqwest::Method::POST, &url)
220-
.json(body)
221-
.send()
222-
{
223-
Ok(r) => r,
224-
Err(e) => {
225-
eprintln!("error connecting to API: {e}");
226-
std::process::exit(1);
227-
}
228-
};
229-
230-
let (status, resp_body) = util::debug_response(resp);
173+
let req = self.build_request(reqwest::Method::POST, &url).json(body);
174+
let (status, resp_body) = self.send(req, Some(body));
231175
if !status.is_success() {
232176
self.fail_response(status, resp_body);
233177
}
234-
235-
match serde_json::from_str(&resp_body) {
236-
Ok(v) => v,
237-
Err(e) => {
238-
eprintln!("error parsing response: {e}");
239-
std::process::exit(1);
240-
}
241-
}
178+
Self::parse_json(&resp_body)
242179
}
243180

244181
/// GET request, exits only on connection error, returns raw (status, body).
245182
/// Use for best-effort endpoints (e.g. health checks) where the caller wants
246183
/// to handle non-2xx responses gracefully instead of aborting.
247184
pub fn get_raw(&self, path: &str) -> (reqwest::StatusCode, String) {
248185
let url = format!("{}{path}", self.api_url);
249-
self.log_request("GET", &url, None);
250-
251-
let resp = match self.build_request(reqwest::Method::GET, &url).send() {
252-
Ok(r) => r,
253-
Err(e) => {
254-
eprintln!("error connecting to API: {e}");
255-
std::process::exit(1);
256-
}
257-
};
258-
259-
util::debug_response(resp)
186+
let req = self.build_request(reqwest::Method::GET, &url);
187+
self.send(req, None)
260188
}
261189

262190
/// POST request with JSON body, exits on error, returns raw (status, body).
263191
pub fn post_raw(&self, path: &str, body: &serde_json::Value) -> (reqwest::StatusCode, String) {
264192
let url = format!("{}{path}", self.api_url);
265-
self.log_request("POST", &url, Some(body));
266-
267-
let resp = match self.build_request(reqwest::Method::POST, &url)
268-
.json(body)
269-
.send()
270-
{
271-
Ok(r) => r,
272-
Err(e) => {
273-
eprintln!("error connecting to API: {e}");
274-
std::process::exit(1);
275-
}
276-
};
277-
278-
util::debug_response(resp)
193+
let req = self.build_request(reqwest::Method::POST, &url).json(body);
194+
self.send(req, Some(body))
279195
}
280196

281197
/// PATCH request with JSON body, returns parsed response.
282198
pub fn patch<T: DeserializeOwned>(&self, path: &str, body: &serde_json::Value) -> T {
283199
let url = format!("{}{path}", self.api_url);
284-
self.log_request("PATCH", &url, Some(body));
285-
286-
let resp = match self.build_request(reqwest::Method::PATCH, &url)
287-
.json(body)
288-
.send()
289-
{
290-
Ok(r) => r,
291-
Err(e) => {
292-
eprintln!("error connecting to API: {e}");
293-
std::process::exit(1);
294-
}
295-
};
296-
297-
let (status, resp_body) = util::debug_response(resp);
200+
let req = self.build_request(reqwest::Method::PATCH, &url).json(body);
201+
let (status, resp_body) = self.send(req, Some(body));
298202
if !status.is_success() {
299203
self.fail_response(status, resp_body);
300204
}
301-
302-
match serde_json::from_str(&resp_body) {
303-
Ok(v) => v,
304-
Err(e) => {
305-
eprintln!("error parsing response: {e}");
306-
std::process::exit(1);
307-
}
308-
}
205+
Self::parse_json(&resp_body)
309206
}
310207

311208
/// POST with a custom request body (for file uploads). Returns raw status and body.
@@ -317,24 +214,16 @@ impl ApiClient {
317214
content_length: Option<u64>,
318215
) -> (reqwest::StatusCode, String) {
319216
let url = format!("{}{path}", self.api_url);
320-
self.log_request("POST", &url, None);
321-
322217
let mut req = self.build_request(reqwest::Method::POST, &url)
323218
.header("Content-Type", content_type);
324-
325219
if let Some(len) = content_length {
326220
req = req.header("Content-Length", len);
327221
}
328-
329-
let resp = match req.body(reqwest::blocking::Body::new(reader)).send() {
330-
Ok(r) => r,
331-
Err(e) => {
332-
eprintln!("error connecting to API: {e}");
333-
std::process::exit(1);
334-
}
335-
};
336-
337-
util::debug_response(resp)
222+
let req = req.body(reqwest::blocking::Body::new(reader));
223+
// Body is an opaque stream — nothing meaningful to print under
224+
// --debug, so pass `None`. Headers (including the masked
225+
// Authorization) still log.
226+
self.send(req, None)
338227
}
339228

340229
}

0 commit comments

Comments
 (0)