use crate::Result;
use crate::script::serde_value_to_lua_value;
use mlua::{IntoLua, Lua, Value};
use reqwest::header::HeaderMap;
use reqwest::{Response, StatusCode, header};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct WebResponse {
pub status: StatusCode,
pub url: String,
pub content: String,
pub content_type: Option<String>,
pub headers: Option<HashMap<String, Vec<String>>>,
pub error: Option<String>, pub parse: Option<bool>,
}
impl IntoLua for WebResponse {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
let table = lua.create_table()?;
let success = self.status.is_success();
let status_code = self.status.as_u16() as i64;
table.set("success", success)?;
table.set("status", status_code)?;
table.set("url", self.url)?;
let content_type_str = self.content_type.as_deref().unwrap_or_default();
let should_parse_json = self.parse.unwrap_or(false) && content_type_str.starts_with("application/json");
let content_lua_value = if should_parse_json {
let json_value: serde_json::Value = serde_json::from_str(&self.content)
.map_err(|e| crate::Error::custom(format!("Failed to parse response body as JSON: {e}")))?;
serde_value_to_lua_value(lua, json_value)
.map_err(|e| crate::Error::custom(format!("Failed to convert parsed JSON to Lua Value: {e}")))?
} else {
Value::String(lua.create_string(&self.content)?)
};
table.set("content", content_lua_value)?;
if let Some(content_type) = self.content_type {
table.set("content_type", content_type)?;
}
if let Some(headers) = self.headers {
let headers_tbl = lua.create_table()?;
for (key, values) in headers {
let value_lua = if values.len() == 1 {
Value::String(lua.create_string(&values[0])?)
} else {
let list_tbl = lua.create_table()?;
for (i, v) in values.into_iter().enumerate() {
list_tbl.set(i + 1, v)?;
}
Value::Table(list_tbl)
};
headers_tbl.set(key, value_lua)?;
}
if !headers_tbl.is_empty() {
table.set("headers", headers_tbl)?;
}
}
if let Some(error) = self.error {
table.set("error", error)?;
} else if !success {
table.set("error", format!("Not a 2xx status code ({status_code})"))?;
}
Ok(Value::Table(table))
}
}
impl WebResponse {
pub async fn from_reqwest_response(response: Response, parse_response: Option<bool>) -> Result<Self> {
let status = response.status();
let url = response.url().to_string();
let headers = response.headers().clone();
let content_type = headers
.get(header::CONTENT_TYPE)
.and_then(|h| h.to_str().ok().map(|s| s.to_owned()));
let content = response.text().await.map_err(crate::Error::Reqwest)?;
let headers_map = transform_headers(headers);
let parse = parse_response;
Ok(Self {
status,
url,
content,
content_type,
headers: Some(headers_map),
error: None,
parse,
})
}
}
fn transform_headers(headers: HeaderMap) -> HashMap<String, Vec<String>> {
headers
.into_iter()
.filter_map(|(name, value)| {
name.map(|name| (name, value))
})
.fold(HashMap::<String, Vec<String>>::new(), |mut acc, (name, value)| {
let key = name.as_str().to_lowercase();
if let Ok(value_str) = value.to_str() {
acc.entry(key).or_default().push(value_str.to_owned());
}
acc
})
}