use napi::bindgen_prelude::Buffer;
use napi_derive::napi;
use std::collections::HashMap;
use std::ptr;
#[napi]
pub struct EggfetchResponse {
status: u32,
url: String,
headers: Vec<(String, String)>,
body: Vec<u8>,
}
impl EggfetchResponse {
pub(crate) fn from_raw(resp: *mut eggfetch_ffi::ResponseHandle) -> Self {
unsafe {
let status = u32::from(eggfetch_ffi::eggfetch_response_status(resp));
let url_ptr = eggfetch_ffi::eggfetch_response_url(resp);
let url = if url_ptr.is_null() {
String::new()
} else {
std::ffi::CStr::from_ptr(url_ptr)
.to_string_lossy()
.into_owned()
};
if !url_ptr.is_null() {
eggfetch_ffi::eggfetch_string_free(url_ptr);
}
let header_count = eggfetch_ffi::eggfetch_response_header_count(resp);
let mut headers = Vec::with_capacity(header_count);
for i in 0..header_count {
let mut name: *mut std::os::raw::c_char = ptr::null_mut();
let mut value: *mut std::os::raw::c_char = ptr::null_mut();
let rc =
eggfetch_ffi::eggfetch_response_header(resp, i, &raw mut name, &raw mut value);
if rc == 0 {
let n = if name.is_null() {
String::new()
} else {
std::ffi::CStr::from_ptr(name)
.to_string_lossy()
.into_owned()
};
let v = if value.is_null() {
String::new()
} else {
std::ffi::CStr::from_ptr(value)
.to_string_lossy()
.into_owned()
};
if !name.is_null() {
eggfetch_ffi::eggfetch_string_free(name);
}
if !value.is_null() {
eggfetch_ffi::eggfetch_string_free(value);
}
headers.push((n, v));
}
}
let mut body_ptr = ptr::null_mut();
let mut body_len = 0;
let body =
if eggfetch_ffi::eggfetch_response_body(resp, &raw mut body_ptr, &raw mut body_len)
== 0
&& body_len > 0
&& !body_ptr.is_null()
{
let body = std::slice::from_raw_parts(body_ptr, body_len).to_vec();
eggfetch_ffi::eggfetch_body_free(body_ptr, body_len);
body
} else {
Vec::new()
};
eggfetch_ffi::eggfetch_response_free(resp);
Self {
status,
url,
headers,
body,
}
}
}
}
#[napi]
impl EggfetchResponse {
#[napi(getter)]
pub fn status(&self) -> u32 {
self.status
}
#[napi(getter)]
pub fn url(&self) -> String {
self.url.clone()
}
#[napi(getter)]
pub fn text(&self) -> String {
String::from_utf8_lossy(&self.body).into_owned()
}
#[napi(getter)]
pub fn bytes(&self) -> Buffer {
Buffer::from(self.body.clone())
}
#[napi(getter)]
pub fn json(&self) -> napi::Result<serde_json::Value> {
serde_json::from_slice(&self.body)
.map_err(|e| napi::Error::from_reason(format!("JSON parse error: {e}")))
}
#[napi(getter)]
pub fn headers(&self) -> HashMap<String, String> {
let mut map: HashMap<String, String> = HashMap::with_capacity(self.headers.len());
for (name, value) in &self.headers {
match map.entry(name.clone()) {
std::collections::hash_map::Entry::Occupied(mut existing) => {
let joined = existing.get_mut();
joined.push_str(", ");
joined.push_str(value);
}
std::collections::hash_map::Entry::Vacant(slot) => {
slot.insert(value.clone());
}
}
}
map
}
#[allow(clippy::needless_pass_by_value)]
#[napi]
pub fn get_all(&self, name: String) -> Vec<String> {
self.headers
.iter()
.filter(|(header_name, _)| header_name.eq_ignore_ascii_case(&name))
.map(|(_, value)| value.clone())
.collect()
}
#[napi(getter)]
pub fn ok(&self) -> bool {
(200..300).contains(&self.status)
}
}