use crate::canonical::{CanonicalError, ErrorKind};
use crate::protocol::{Method, WireRequest};
const MAX_HEAD: usize = 64 * 1024;
pub fn envelope_request(wire: &WireRequest) -> Vec<u8> {
let method = match wire.method {
Method::Post => "POST",
Method::Get => "GET",
};
let mut out = format!("{method} {} HTTP/1.1\r\n", wire.url).into_bytes();
for (name, value) in &wire.headers {
out.extend_from_slice(format!("{name}: {value}\r\n").as_bytes());
}
out.extend_from_slice(b"\r\n");
out.extend_from_slice(&wire.body);
out
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EnvelopeHead {
pub status: u16,
pub retry_after: Option<String>,
pub body_start: usize,
}
pub fn envelope_head(buf: &[u8]) -> Result<Option<EnvelopeHead>, CanonicalError> {
let Some(end) = head_end(buf) else {
return if buf.len() > MAX_HEAD {
Err(envelope_error(
"response head exceeds 64 KiB with no blank line",
))
} else {
Ok(None)
};
};
let text = String::from_utf8_lossy(&buf[..end]);
let mut lines = text.lines();
let status = lines
.next()
.and_then(|l| l.split_whitespace().nth(1))
.and_then(|t| t.parse::<u16>().ok())
.ok_or_else(|| envelope_error("response carries no HTTP status line"))?;
let retry_after = lines
.filter_map(|l| l.split_once(':'))
.find(|(name, _)| name.trim().eq_ignore_ascii_case("retry-after"))
.map(|(_, value)| value.trim().to_owned());
Ok(Some(EnvelopeHead {
status,
retry_after,
body_start: end,
}))
}
fn head_end(buf: &[u8]) -> Option<usize> {
let lf = buf.windows(2).position(|w| w == b"\n\n").map(|i| i + 2);
let crlf = buf.windows(4).position(|w| w == b"\r\n\r\n").map(|i| i + 4);
lf.into_iter().chain(crlf).min()
}
pub fn envelope_error(reason: &str) -> CanonicalError {
CanonicalError {
kind: ErrorKind::Transport,
message: format!("stdio transport: {reason}"),
provider_detail: None,
retry_after_seconds: None,
}
}