use http::{header::AsHeaderName, HeaderMap, HeaderValue, StatusCode};
use wasi::{
http::types::{IncomingBody, IncomingResponse},
io::streams::{InputStream, StreamError},
};
use crate::tasks;
pub struct Response {
status: StatusCode,
headers: HeaderMap,
body: ResponseBody,
}
impl From<IncomingResponse> for Response {
fn from(value: IncomingResponse) -> Self {
Response {
status: StatusCode::from_u16(value.status()).expect("Invalid status code"),
headers: crate::http::fields_to_header_map(&value.headers())
.expect("Failed to convert fields to header map"),
body: ResponseBody {
inner: value.consume().expect("Response body already consumed"),
},
}
}
}
pub struct ResponseBody {
inner: IncomingBody,
}
#[derive(Debug, thiserror::Error)]
pub enum ResponseBodyError {
#[error("Failed to read response body: {0}")]
ReadError(#[from] wasi::io::streams::StreamError),
#[error("Response body already consumed")]
AlreadyConsumed,
#[error("Response body is not valid UTF-8: {0}")]
InvalidUtf8(#[from] std::string::FromUtf8Error),
#[error("Invalid JSON: {0}")]
InvalidJson(#[from] serde_json::Error),
}
impl ResponseBody {
pub fn get(&self) -> Result<InputStream, ResponseBodyError> {
self.inner
.stream()
.map_err(|_| ResponseBodyError::AlreadyConsumed)
}
}
impl Response {
pub fn status(&self) -> StatusCode {
self.status
}
pub fn headers(&self) -> &HeaderMap {
&self.headers
}
pub fn header(&self, name: impl AsHeaderName) -> Option<&HeaderValue> {
self.headers.get(name)
}
pub async fn bytes(&self) -> Result<Vec<u8>, ResponseBodyError> {
let stream = self.body.get()?;
let mut buffer = Vec::new();
loop {
tasks::wait_for(stream.subscribe()).await;
match stream.read(u64::MAX) {
Ok(data) => buffer.extend_from_slice(&data),
Err(StreamError::Closed) => break,
Err(e) => return Err(ResponseBodyError::ReadError(e)),
}
}
Ok(buffer)
}
pub async fn text(&self) -> Result<String, ResponseBodyError> {
self.bytes()
.await
.and_then(|bytes| String::from_utf8(bytes).map_err(ResponseBodyError::InvalidUtf8))
}
pub async fn json(&self) -> Result<serde_json::Value, ResponseBodyError> {
let text = self.text().await?;
serde_json::from_str(&text).map_err(ResponseBodyError::InvalidJson)
}
}