use crate::ClientError;
pub(crate) async fn parse_json_response<T: serde::de::DeserializeOwned>(
response: reqwest::Response,
) -> crate::Result<T> {
let status = response.status();
let request_id = response
.headers()
.get("x-request-id")
.and_then(|h| h.to_str().ok())
.map(String::from);
let body = response.bytes().await?;
if status.is_success() {
let parsed: T = serde_json::from_slice(&body)?;
Ok(parsed)
} else {
let body_str = String::from_utf8_lossy(&body).into_owned();
Err(ClientError::Http {
status: status.as_u16(),
body: body_str,
request_id,
})
}
}
pub(crate) fn validate_path_segment(segment: &str) -> crate::Result<()> {
if segment.is_empty() {
return Err(ClientError::Config(
"dynamic path segment must not be empty".into(),
));
}
if segment == "." || segment == ".." {
return Err(ClientError::Config(format!(
"dynamic path segment {segment:?} would traverse into a sibling endpoint"
)));
}
for c in segment.chars() {
if matches!(c, '/' | '\\' | '?' | '#') || c.is_control() {
return Err(ClientError::Config(format!(
"dynamic path segment {segment:?} contains forbidden character {c:?}"
)));
}
}
Ok(())
}
pub(crate) async fn parse_json_response_optional(response: reqwest::Response) -> crate::Result<()> {
let status = response.status();
if status.is_success() {
let _body = response.bytes().await?;
Ok(())
} else {
let request_id = response
.headers()
.get("x-request-id")
.and_then(|h| h.to_str().ok())
.map(String::from);
let body = response.bytes().await?;
let body_str = String::from_utf8_lossy(&body).into_owned();
Err(ClientError::Http {
status: status.as_u16(),
body: body_str,
request_id,
})
}
}