eggfetch_node/
response.rs1use napi::bindgen_prelude::Buffer;
4use napi_derive::napi;
5use std::collections::HashMap;
6use std::ptr;
7
8#[napi]
10pub struct EggfetchResponse {
11 status: u32,
12 url: String,
13 headers: Vec<(String, String)>,
17 body: Vec<u8>,
18}
19
20impl EggfetchResponse {
21 pub(crate) fn from_raw(resp: *mut eggfetch_ffi::ResponseHandle) -> Self {
23 unsafe {
24 let status = u32::from(eggfetch_ffi::eggfetch_response_status(resp));
25 let url_ptr = eggfetch_ffi::eggfetch_response_url(resp);
26 let url = if url_ptr.is_null() {
27 String::new()
28 } else {
29 std::ffi::CStr::from_ptr(url_ptr)
30 .to_string_lossy()
31 .into_owned()
32 };
33 if !url_ptr.is_null() {
34 eggfetch_ffi::eggfetch_string_free(url_ptr);
35 }
36
37 let header_count = eggfetch_ffi::eggfetch_response_header_count(resp);
38 let mut headers = Vec::with_capacity(header_count);
39 for i in 0..header_count {
40 let mut name: *mut std::os::raw::c_char = ptr::null_mut();
41 let mut value: *mut std::os::raw::c_char = ptr::null_mut();
42 let rc =
43 eggfetch_ffi::eggfetch_response_header(resp, i, &raw mut name, &raw mut value);
44 if rc == 0 {
45 let n = if name.is_null() {
46 String::new()
47 } else {
48 std::ffi::CStr::from_ptr(name)
49 .to_string_lossy()
50 .into_owned()
51 };
52 let v = if value.is_null() {
53 String::new()
54 } else {
55 std::ffi::CStr::from_ptr(value)
56 .to_string_lossy()
57 .into_owned()
58 };
59 if !name.is_null() {
60 eggfetch_ffi::eggfetch_string_free(name);
61 }
62 if !value.is_null() {
63 eggfetch_ffi::eggfetch_string_free(value);
64 }
65 headers.push((n, v));
66 }
67 }
68
69 let mut body_ptr = ptr::null_mut();
70 let mut body_len = 0;
71 let body =
72 if eggfetch_ffi::eggfetch_response_body(resp, &raw mut body_ptr, &raw mut body_len)
73 == 0
74 && body_len > 0
75 && !body_ptr.is_null()
76 {
77 let body = std::slice::from_raw_parts(body_ptr, body_len).to_vec();
78 eggfetch_ffi::eggfetch_body_free(body_ptr, body_len);
79 body
80 } else {
81 Vec::new()
82 };
83
84 eggfetch_ffi::eggfetch_response_free(resp);
85
86 Self {
87 status,
88 url,
89 headers,
90 body,
91 }
92 }
93 }
94}
95
96#[napi]
97impl EggfetchResponse {
98 #[napi(getter)]
100 pub fn status(&self) -> u32 {
101 self.status
102 }
103
104 #[napi(getter)]
106 pub fn url(&self) -> String {
107 self.url.clone()
108 }
109
110 #[napi(getter)]
112 pub fn text(&self) -> String {
113 String::from_utf8_lossy(&self.body).into_owned()
114 }
115
116 #[napi(getter)]
118 pub fn bytes(&self) -> Buffer {
119 Buffer::from(self.body.clone())
120 }
121
122 #[napi(getter)]
128 pub fn json(&self) -> napi::Result<serde_json::Value> {
129 serde_json::from_slice(&self.body)
130 .map_err(|e| napi::Error::from_reason(format!("JSON parse error: {e}")))
131 }
132
133 #[napi(getter)]
139 pub fn headers(&self) -> HashMap<String, String> {
140 let mut map: HashMap<String, String> = HashMap::with_capacity(self.headers.len());
141 for (name, value) in &self.headers {
142 match map.entry(name.clone()) {
143 std::collections::hash_map::Entry::Occupied(mut existing) => {
144 let joined = existing.get_mut();
145 joined.push_str(", ");
146 joined.push_str(value);
147 }
148 std::collections::hash_map::Entry::Vacant(slot) => {
149 slot.insert(value.clone());
150 }
151 }
152 }
153 map
154 }
155
156 #[allow(clippy::needless_pass_by_value)]
163 #[napi]
164 pub fn get_all(&self, name: String) -> Vec<String> {
165 self.headers
166 .iter()
167 .filter(|(header_name, _)| header_name.eq_ignore_ascii_case(&name))
168 .map(|(_, value)| value.clone())
169 .collect()
170 }
171
172 #[napi(getter)]
174 pub fn ok(&self) -> bool {
175 (200..300).contains(&self.status)
176 }
177}