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 = eggfetch_ffi::eggfetch_response_header(resp, i, &mut name, &mut value);
43 if rc == 0 {
44 let n = if name.is_null() {
45 String::new()
46 } else {
47 std::ffi::CStr::from_ptr(name)
48 .to_string_lossy()
49 .into_owned()
50 };
51 let v = if value.is_null() {
52 String::new()
53 } else {
54 std::ffi::CStr::from_ptr(value)
55 .to_string_lossy()
56 .into_owned()
57 };
58 if !name.is_null() {
59 eggfetch_ffi::eggfetch_string_free(name);
60 }
61 if !value.is_null() {
62 eggfetch_ffi::eggfetch_string_free(value);
63 }
64 headers.push((n, v));
65 }
66 }
67
68 let mut body_ptr = ptr::null_mut();
69 let mut body_len = 0;
70 let body = if eggfetch_ffi::eggfetch_response_body(resp, &mut body_ptr, &mut body_len)
71 == 0
72 && body_len > 0
73 && !body_ptr.is_null()
74 {
75 let body = std::slice::from_raw_parts(body_ptr, body_len).to_vec();
76 eggfetch_ffi::eggfetch_body_free(body_ptr, body_len);
77 body
78 } else {
79 Vec::new()
80 };
81
82 eggfetch_ffi::eggfetch_response_free(resp);
83
84 Self {
85 status,
86 url,
87 headers,
88 body,
89 }
90 }
91 }
92}
93
94#[napi]
95impl EggfetchResponse {
96 #[napi(getter)]
98 pub fn status(&self) -> u32 {
99 self.status
100 }
101
102 #[napi(getter)]
104 pub fn url(&self) -> String {
105 self.url.clone()
106 }
107
108 #[napi(getter)]
110 pub fn text(&self) -> String {
111 String::from_utf8_lossy(&self.body).into_owned()
112 }
113
114 #[napi(getter)]
116 pub fn bytes(&self) -> Buffer {
117 Buffer::from(self.body.clone())
118 }
119
120 #[napi(getter)]
126 pub fn json(&self) -> napi::Result<serde_json::Value> {
127 serde_json::from_slice(&self.body)
128 .map_err(|e| napi::Error::from_reason(format!("JSON parse error: {e}")))
129 }
130
131 #[napi(getter)]
137 pub fn headers(&self) -> HashMap<String, String> {
138 let mut map: HashMap<String, String> = HashMap::with_capacity(self.headers.len());
139 for (name, value) in &self.headers {
140 match map.entry(name.clone()) {
141 std::collections::hash_map::Entry::Occupied(mut existing) => {
142 let joined = existing.get_mut();
143 joined.push_str(", ");
144 joined.push_str(value);
145 }
146 std::collections::hash_map::Entry::Vacant(slot) => {
147 slot.insert(value.clone());
148 }
149 }
150 }
151 map
152 }
153
154 #[allow(clippy::needless_pass_by_value)]
161 #[napi]
162 pub fn get_all(&self, name: String) -> Vec<String> {
163 self.headers
164 .iter()
165 .filter(|(header_name, _)| header_name.eq_ignore_ascii_case(&name))
166 .map(|(_, value)| value.clone())
167 .collect()
168 }
169
170 #[napi(getter)]
172 pub fn ok(&self) -> bool {
173 (200..300).contains(&self.status)
174 }
175}