1use crate::{Error, Error::*, RhaiRes, rhai_err};
2use base64::{Engine as _, engine::general_purpose::STANDARD};
3use reqwest::{Certificate, Client, Response};
4use rhai::{Dynamic, Engine, Map};
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7use serde_json::{Value, json};
8use serde_yaml;
9use tokio::runtime::Handle;
10use tracing::*;
11
12#[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug, JsonSchema, Default)]
13pub enum ReadMethod {
14 #[default]
15 Get,
16}
17#[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug, JsonSchema, Default)]
18pub enum CreateMethod {
19 #[default]
20 Post,
21 Put,
22}
23
24#[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug, JsonSchema, Default)]
25pub enum UpdateMethod {
26 #[default]
27 Patch,
28 Put,
29 Post,
30 None,
31}
32
33#[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug, JsonSchema, Default)]
34pub enum DeleteMethod {
35 #[default]
36 Delete,
37}
38
39#[derive(Clone, Debug)]
40pub struct RestClient {
41 baseurl: String,
42 headers: Map,
43 server_ca: Option<String>,
44 client_key: Option<String>,
45 client_cert: Option<String>,
46}
47
48impl RestClient {
49 #[must_use]
50 pub fn new(base: &str) -> Self {
51 Self {
52 baseurl: base.to_string(),
53 headers: Map::new(),
54 server_ca: None,
55 client_cert: None,
56 client_key: None,
57 }
58 }
59
60 pub fn baseurl(&mut self, base: &str) -> &mut RestClient {
61 self.baseurl = base.to_string();
62 self
63 }
64
65 pub fn set_server_ca(&mut self, ca: &str) {
66 self.server_ca = Some(ca.to_string());
67 }
68
69 pub fn set_mtls(&mut self, cert: &str, key: &str) {
70 self.client_cert = Some(cert.to_string());
71 self.client_key = Some(key.to_string());
72 }
73
74 pub fn baseurl_rhai(&mut self, base: String) {
75 self.baseurl(base.as_str());
76 }
77
78 pub fn headers_reset(&mut self) -> &mut RestClient {
79 self.headers = Map::new();
80 self
81 }
82
83 pub fn headers_reset_rhai(&mut self) {
84 self.headers_reset();
85 }
86
87 pub fn add_header(&mut self, key: &str, value: &str) -> &mut RestClient {
88 self.headers
89 .insert(key.to_string().into(), value.to_string().into());
90 self
91 }
92
93 pub fn add_header_rhai(&mut self, key: String, value: String) {
94 self.add_header(key.as_str(), value.as_str());
95 }
96
97 pub fn add_header_json_content(&mut self) -> &mut RestClient {
98 if self
99 .headers
100 .clone()
101 .into_iter()
102 .any(|(c, _)| c == *"Content-Type")
103 {
104 self
105 } else {
106 self.add_header("Content-Type", "application/json; charset=utf-8")
107 }
108 }
109
110 pub fn add_header_json_accept(&mut self) -> &mut RestClient {
111 for (key, val) in self.headers.clone() {
112 debug!("RestClient.header: {:} {:}", key, val);
113 }
114 if self.headers.clone().into_iter().any(|(c, _)| c == *"Accept") {
115 self
116 } else {
117 self.add_header("Accept", "application/json")
118 }
119 }
120
121 pub fn add_header_json(&mut self) {
122 self.add_header_json_content().add_header_json_accept();
123 }
124
125 pub fn add_header_bearer(&mut self, token: &str) {
126 self.add_header("Authorization", format!("Bearer {token}").as_str());
127 }
128
129 pub fn add_header_basic(&mut self, username: &str, password: &str) {
130 let hash = STANDARD.encode(format!("{username}:{password}"));
131 self.add_header("Authorization", format!("Basic {hash}").as_str());
132 }
133
134 fn get_client(&mut self) -> std::result::Result<Client, reqwest::Error> {
135 let five_sec = std::time::Duration::from_secs(60 * 5);
136 if self.server_ca.is_none() && (self.client_cert.is_none() || self.client_key.is_none()) {
137 Client::builder()
138 .user_agent(crate::get_client_name())
139 .timeout(five_sec)
140 .build()
141 } else if self.client_cert.is_none() || self.client_key.is_none() {
142 match Certificate::from_pem(self.server_ca.clone().unwrap().as_bytes()) {
143 Ok(c) => Client::builder()
144 .user_agent(crate::get_client_name())
145 .timeout(five_sec)
146 .add_root_certificate(c)
147 .use_rustls_tls()
148 .build(),
149 Err(e) => Err(e),
150 }
151 } else {
152 let cli_cert = format!(
153 "{}\n{}",
154 self.client_key.clone().unwrap(),
155 self.client_cert.clone().unwrap()
156 );
157 match reqwest::Identity::from_pem(cli_cert.as_bytes()) {
158 Ok(identity) => {
159 if self.server_ca.is_none() {
160 Client::builder()
161 .user_agent(crate::get_client_name())
162 .timeout(five_sec)
163 .use_rustls_tls()
164 .identity(identity)
165 .build()
166 } else {
167 match Certificate::from_pem(self.server_ca.clone().unwrap().as_bytes()) {
168 Ok(c) => Client::builder()
169 .user_agent(crate::get_client_name())
170 .timeout(five_sec)
171 .add_root_certificate(c)
172 .use_rustls_tls()
173 .identity(identity)
174 .build(),
175 Err(e) => Err(e),
176 }
177 }
178 }
179 Err(e) => Err(e),
180 }
181 }
182 }
183
184 pub fn http_get(&mut self, path: &str) -> std::result::Result<Response, reqwest::Error> {
185 debug!("http_get '{}' ", format!("{}/{}", self.baseurl, path));
186 match self.get_client() {
187 Ok(client) => {
188 let mut req = client.get(format!("{}/{}", self.baseurl, path));
189 for (key, val) in self.headers.clone() {
190 req = req.header(key.to_string(), val.to_string());
191 }
192 tokio::task::block_in_place(|| Handle::current().block_on(async move { req.send().await }))
193 }
194 Err(e) => {
195 if e.is_builder() {
196 warn!("CLIENT: {e:?}");
197 }
198 Err(e)
199 }
200 }
201 }
202
203 pub fn body_get(&mut self, path: &str) -> crate::Result<String> {
204 let response = self.http_get(path).map_err(Error::ReqwestError)?;
205 if !response.status().is_success() {
206 let status = response.status();
207 let text = tokio::task::block_in_place(|| {
208 Handle::current().block_on(async move { response.text().await })
209 })
210 .map_err(Error::ReqwestError)?;
211 return Err(Error::MethodFailed(
212 "Get".to_string(),
213 status.as_u16(),
214 format!(
215 "The server returned the error: {} {} | {text}",
216 status.as_str(),
217 status.canonical_reason().unwrap_or("unknown")
218 ),
219 ));
220 }
221 let text =
222 tokio::task::block_in_place(|| Handle::current().block_on(async move { response.text().await }))
223 .map_err(Error::ReqwestError)?;
224 Ok(text)
225 }
226
227 pub fn json_get(&mut self, path: &str) -> crate::Result<Value> {
228 let text = self.body_get(path)?;
229 let json = serde_json::from_str(&text).map_err(Error::JsonError)?;
230 Ok(json)
231 }
232
233 pub fn rhai_get(&mut self, path: String) -> RhaiRes<Map> {
234 let mut ret = Map::new();
235 match self.http_get(path.as_str()) {
236 Ok(result) => {
237 ret.insert(
238 "code".to_string().into(),
239 Dynamic::from_int(result.status().as_u16().to_string().parse::<i64>().unwrap_or(0)),
240 );
241 tokio::task::block_in_place(|| {
242 tokio::runtime::Handle::current().block_on(async {
243 let headers = result
244 .headers()
245 .into_iter()
246 .map(|(key, val)| {
247 (
248 key.as_str().to_string(),
249 val.to_str().unwrap_or_default().to_string(),
250 )
251 })
252 .collect::<Vec<(String, String)>>();
253 let text = match result.text().await {
254 Ok(t) => t,
255 Err(e) => {
256 ret.insert(
257 "body".to_string().into(),
258 Dynamic::from(format!("Error reading response body: {e}")),
259 );
260 ret.insert("json".to_string().into(), Dynamic::from(json!({})));
261 ret.insert("headers".to_string().into(), Dynamic::from(headers));
262 return Err(format!("Error reading response body: {e}").into());
263 }
264 };
265 ret.insert(
266 "json".to_string().into(),
267 serde_json::from_str(&text).unwrap_or(Dynamic::from(json!({}))),
268 );
269 ret.insert("headers".to_string().into(), Dynamic::from(headers.clone()));
270 ret.insert("body".to_string().into(), Dynamic::from(text));
271 Ok(ret)
272 })
273 })
274 }
275 Err(e) => Err(format!("{e}").into()),
276 }
277 }
278
279 pub fn http_head(&mut self, path: &str) -> std::result::Result<Response, reqwest::Error> {
280 debug!("http_head '{}' ", format!("{}/{}", self.baseurl, path));
281 match self.get_client() {
282 Ok(client) => {
283 let mut req = client.head(format!("{}/{}", self.baseurl, path));
284 for (key, val) in self.headers.clone() {
285 req = req.header(key.to_string(), val.to_string());
286 }
287 tokio::task::block_in_place(|| Handle::current().block_on(async move { req.send().await }))
288 }
289 Err(e) => {
290 if e.is_builder() {
291 warn!("CLIENT: {e:?}");
292 }
293 Err(e)
294 }
295 }
296 }
297
298 pub fn header_head(&mut self, path: &str) -> crate::Result<Vec<(String, String)>> {
299 let response = self.http_get(path).map_err(Error::ReqwestError)?;
300 if !response.status().is_success() {
301 let status = response.status();
302 return Err(Error::MethodFailed(
303 "Get".to_string(),
304 status.as_u16(),
305 format!(
306 "The server returned the error: {} {}",
307 status.as_str(),
308 status.canonical_reason().unwrap_or("unknown")
309 ),
310 ));
311 }
312 Ok(response
313 .headers()
314 .into_iter()
315 .map(|(key, val)| {
316 (
317 key.as_str().to_string(),
318 val.to_str().unwrap_or_default().to_string(),
319 )
320 })
321 .collect())
322 }
323
324 pub fn rhai_head(&mut self, path: String) -> RhaiRes<Map> {
325 let mut ret = Map::new();
326 match self.http_head(path.as_str()) {
327 Ok(result) => {
328 ret.insert(
329 "code".to_string().into(),
330 Dynamic::from_int(result.status().as_u16().to_string().parse::<i64>().unwrap_or(0)),
331 );
332 let headers = result
333 .headers()
334 .into_iter()
335 .map(|(key, val)| {
336 (
337 key.as_str().to_string(),
338 val.to_str().unwrap_or_default().to_string(),
339 )
340 })
341 .collect::<Vec<(String, String)>>();
342 ret.insert("headers".to_string().into(), Dynamic::from(headers.clone()));
343 Ok(ret)
344 }
345 Err(e) => Err(format!("{e}").into()),
346 }
347 }
348
349 pub fn http_patch(&mut self, path: &str, body: &str) -> crate::Result<Response> {
350 debug!("http_patch '{}' ", format!("{}/{}", self.baseurl, path));
351 match self.get_client() {
352 Ok(client) => {
353 let mut req = client
354 .patch(format!("{}/{}", self.baseurl, path))
355 .body(body.to_string());
356 for (key, val) in self.headers.clone() {
357 req = req.header(key.to_string(), val.to_string());
358 }
359 tokio::task::block_in_place(|| Handle::current().block_on(async move { req.send().await }))
360 .map_err(Error::ReqwestError)
361 }
362 Err(e) => Err(Error::ReqwestError(e)),
363 }
364 }
365
366 pub fn body_patch(&mut self, path: &str, body: &str) -> crate::Result<String> {
367 let response = self.http_patch(path, body)?;
368 if !response.status().is_success() {
369 let status = response.status();
370 let text = tokio::task::block_in_place(|| {
371 Handle::current().block_on(async move { response.text().await })
372 })
373 .map_err(Error::ReqwestError)?;
374 return Err(Error::MethodFailed(
375 "Patch".to_string(),
376 status.as_u16(),
377 format!(
378 "The server returned the error: {} {} | {text}",
379 status.as_str(),
380 status.canonical_reason().unwrap_or("unknown")
381 ),
382 ));
383 }
384 let text =
385 tokio::task::block_in_place(|| Handle::current().block_on(async move { response.text().await }))
386 .map_err(Error::ReqwestError)?;
387 Ok(text)
388 }
389
390 pub fn json_patch(&mut self, path: &str, input: &Value) -> crate::Result<Value> {
391 let body = serde_json::to_string(input).map_err(Error::JsonError)?;
392 let text = self.body_patch(path, body.as_str())?;
393 let json = serde_json::from_str(&text).map_err(Error::JsonError)?;
394 Ok(json)
395 }
396
397 pub fn rhai_patch(&mut self, path: String, val: Dynamic) -> RhaiRes<Map> {
398 let body = if val.is_string() {
399 val.to_string()
400 } else {
401 match serde_json::to_string(&val) {
402 Ok(s) => s,
403 Err(e) => return Err(format!("Failed to serialize body: {e}").into()),
404 }
405 };
406 let mut ret = Map::new();
407 match self.http_patch(path.as_str(), &body) {
408 Ok(result) => {
409 ret.insert(
410 "code".to_string().into(),
411 Dynamic::from_int(result.status().as_u16().to_string().parse::<i64>().unwrap_or(0)),
412 );
413 tokio::task::block_in_place(|| {
414 tokio::runtime::Handle::current().block_on(async {
415 let headers = result
416 .headers()
417 .into_iter()
418 .map(|(key, val)| {
419 (
420 key.as_str().to_string(),
421 val.to_str().unwrap_or_default().to_string(),
422 )
423 })
424 .collect::<Vec<(String, String)>>();
425 let text = match result.text().await {
426 Ok(t) => t,
427 Err(e) => {
428 ret.insert(
429 "body".to_string().into(),
430 Dynamic::from(format!("Error reading response body: {e}")),
431 );
432 ret.insert("json".to_string().into(), Dynamic::from(json!({})));
433 ret.insert("headers".to_string().into(), Dynamic::from(headers));
434 return Err(format!("Error reading response body: {e}").into());
435 }
436 };
437 ret.insert(
438 "json".to_string().into(),
439 serde_json::from_str(&text).unwrap_or(Dynamic::from(json!({}))),
440 );
441 ret.insert("headers".to_string().into(), Dynamic::from(headers.clone()));
442 ret.insert("body".to_string().into(), Dynamic::from(text));
443 Ok(ret)
444 })
445 })
446 }
447 Err(e) => Err(format!("{e}").into()),
448 }
449 }
450
451 pub fn http_put(&mut self, path: &str, body: &str) -> crate::Result<Response> {
452 debug!("http_put '{}' ", format!("{}/{}", self.baseurl, path));
453 match self.get_client() {
454 Ok(client) => {
455 let mut req = client
456 .put(format!("{}/{}", self.baseurl, path))
457 .body(body.to_string());
458 for (key, val) in self.headers.clone() {
459 req = req.header(key.to_string(), val.to_string());
460 }
461 tokio::task::block_in_place(|| Handle::current().block_on(async move { req.send().await }))
462 .map_err(Error::ReqwestError)
463 }
464 Err(e) => Err(Error::ReqwestError(e)),
465 }
466 }
467
468 pub fn body_put(&mut self, path: &str, body: &str) -> crate::Result<String> {
469 let response = self.http_put(path, body)?;
470 if !response.status().is_success() {
471 let status = response.status();
472 let text = tokio::task::block_in_place(|| {
473 Handle::current().block_on(async move { response.text().await })
474 })
475 .map_err(Error::ReqwestError)?;
476 return Err(Error::MethodFailed(
477 "Put".to_string(),
478 status.as_u16(),
479 format!(
480 "The server returned the error: {} {} | {text}",
481 status.as_str(),
482 status.canonical_reason().unwrap_or("unknown")
483 ),
484 ));
485 }
486 let text =
487 tokio::task::block_in_place(|| Handle::current().block_on(async move { response.text().await }))
488 .map_err(Error::ReqwestError)?;
489 Ok(text)
490 }
491
492 pub fn json_put(&mut self, path: &str, input: &Value) -> crate::Result<Value> {
493 let body = serde_json::to_string(input).map_err(Error::JsonError)?;
494 let text = self.body_put(path, body.as_str())?;
495 let json = serde_json::from_str(&text).map_err(Error::JsonError)?;
496 Ok(json)
497 }
498
499 pub fn rhai_put(&mut self, path: String, val: Dynamic) -> RhaiRes<Map> {
500 let body = if val.is_string() {
501 val.to_string()
502 } else {
503 match serde_json::to_string(&val) {
504 Ok(s) => s,
505 Err(e) => return Err(format!("Failed to serialize body: {e}").into()),
506 }
507 };
508 let mut ret = Map::new();
509 match self.http_put(path.as_str(), &body) {
510 Ok(result) => {
511 ret.insert(
512 "code".to_string().into(),
513 Dynamic::from_int(result.status().as_u16().to_string().parse::<i64>().unwrap_or(0)),
514 );
515 tokio::task::block_in_place(|| {
516 tokio::runtime::Handle::current().block_on(async {
517 let headers = result
518 .headers()
519 .into_iter()
520 .map(|(key, val)| {
521 (
522 key.as_str().to_string(),
523 val.to_str().unwrap_or_default().to_string(),
524 )
525 })
526 .collect::<Vec<(String, String)>>();
527 let text = match result.text().await {
528 Ok(t) => t,
529 Err(e) => {
530 ret.insert(
531 "body".to_string().into(),
532 Dynamic::from(format!("Error reading response body: {e}")),
533 );
534 ret.insert("json".to_string().into(), Dynamic::from(json!({})));
535 ret.insert("headers".to_string().into(), Dynamic::from(headers));
536 return Err(format!("Error reading response body: {e}").into());
537 }
538 };
539 ret.insert(
540 "json".to_string().into(),
541 serde_json::from_str(&text).unwrap_or(Dynamic::from(json!({}))),
542 );
543 ret.insert("headers".to_string().into(), Dynamic::from(headers.clone()));
544 ret.insert("body".to_string().into(), Dynamic::from(text));
545 Ok(ret)
546 })
547 })
548 }
549 Err(e) => Err(format!("{e}").into()),
550 }
551 }
552
553 pub fn http_post(&mut self, path: &str, body: &str) -> crate::Result<Response> {
554 debug!("http_post '{}' ", format!("{}/{}", self.baseurl, path));
555 match self.get_client() {
556 Ok(client) => {
557 let mut req = client
558 .post(format!("{}/{}", self.baseurl, path))
559 .body(body.to_string());
560 for (key, val) in self.headers.clone() {
561 req = req.header(key.to_string(), val.to_string());
562 }
563 tokio::task::block_in_place(|| Handle::current().block_on(async move { req.send().await }))
564 .map_err(Error::ReqwestError)
565 }
566 Err(e) => Err(Error::ReqwestError(e)),
567 }
568 }
569
570 pub fn body_post(&mut self, path: &str, body: &str) -> crate::Result<String> {
571 let response = self.http_post(path, body)?;
572 if !response.status().is_success() {
573 let status = response.status();
574 let text = tokio::task::block_in_place(|| {
575 Handle::current().block_on(async move { response.text().await })
576 })
577 .map_err(Error::ReqwestError)?;
578 return Err(Error::MethodFailed(
579 "Post".to_string(),
580 status.as_u16(),
581 format!(
582 "The server returned the error: {} {} | {text}",
583 status.as_str(),
584 status.canonical_reason().unwrap_or("unknown")
585 ),
586 ));
587 }
588 let text =
589 tokio::task::block_in_place(|| Handle::current().block_on(async move { response.text().await }))
590 .map_err(Error::ReqwestError)?;
591 Ok(text)
592 }
593
594 pub fn json_post(&mut self, path: &str, input: &Value) -> crate::Result<Value> {
595 let body = serde_json::to_string(input).map_err(Error::JsonError)?;
596 let text = self.body_post(path, body.as_str())?;
597 let json = serde_json::from_str(&text).map_err(Error::JsonError)?;
598 Ok(json)
599 }
600
601 pub fn rhai_post(&mut self, path: String, val: Dynamic) -> RhaiRes<Map> {
602 let body = if val.is_string() {
603 val.to_string()
604 } else {
605 match serde_json::to_string(&val) {
606 Ok(s) => s,
607 Err(e) => return Err(format!("Failed to serialize body: {e}").into()),
608 }
609 };
610 let mut ret = Map::new();
611 match self.http_post(path.as_str(), &body) {
612 Ok(result) => {
613 ret.insert(
614 "code".to_string().into(),
615 Dynamic::from_int(result.status().as_u16().to_string().parse::<i64>().unwrap_or(0)),
616 );
617 tokio::task::block_in_place(|| {
618 tokio::runtime::Handle::current().block_on(async {
619 let headers = result
620 .headers()
621 .into_iter()
622 .map(|(key, val)| {
623 (
624 key.as_str().to_string(),
625 val.to_str().unwrap_or_default().to_string(),
626 )
627 })
628 .collect::<Vec<(String, String)>>();
629 let text = match result.text().await {
630 Ok(t) => t,
631 Err(e) => {
632 ret.insert(
633 "body".to_string().into(),
634 Dynamic::from(format!("Error reading response body: {e}")),
635 );
636 ret.insert("json".to_string().into(), Dynamic::from(json!({})));
637 ret.insert("headers".to_string().into(), Dynamic::from(headers));
638 return Err(format!("Error reading response body: {e}").into());
639 }
640 };
641 ret.insert(
642 "json".to_string().into(),
643 serde_json::from_str(&text).unwrap_or(Dynamic::from(json!({}))),
644 );
645 ret.insert("headers".to_string().into(), Dynamic::from(headers.clone()));
646 ret.insert("body".to_string().into(), Dynamic::from(text));
647 Ok(ret)
648 })
649 })
650 }
651 Err(e) => Err(format!("{e}").into()),
652 }
653 }
654
655 pub fn http_post_form(&mut self, path: &str, params: &[(String, String)]) -> crate::Result<Response> {
656 debug!("http_post_form '{}' ", format!("{}/{}", self.baseurl, path));
657 match self.get_client() {
658 Ok(client) => {
659 let mut req = client.post(format!("{}/{}", self.baseurl, path)).form(params);
660 for (key, val) in self.headers.clone() {
661 if key.as_str() != "Content-Type" {
662 req = req.header(key.to_string(), val.to_string());
663 }
664 }
665 tokio::task::block_in_place(|| Handle::current().block_on(async move { req.send().await }))
666 .map_err(Error::ReqwestError)
667 }
668 Err(e) => Err(Error::ReqwestError(e)),
669 }
670 }
671
672 pub fn rhai_post_form(&mut self, path: String, val: Map) -> RhaiRes<Map> {
673 let params: Vec<(String, String)> = val
674 .into_iter()
675 .map(|(k, v)| (k.to_string(), v.to_string()))
676 .collect();
677 let mut ret = Map::new();
678 match self.http_post_form(path.as_str(), ¶ms) {
679 Ok(result) => {
680 ret.insert(
681 "code".to_string().into(),
682 Dynamic::from_int(result.status().as_u16().to_string().parse::<i64>().unwrap_or(0)),
683 );
684 tokio::task::block_in_place(|| {
685 tokio::runtime::Handle::current().block_on(async {
686 let headers = result
687 .headers()
688 .into_iter()
689 .map(|(key, val)| {
690 (
691 key.as_str().to_string(),
692 val.to_str().unwrap_or_default().to_string(),
693 )
694 })
695 .collect::<Vec<(String, String)>>();
696 let text = match result.text().await {
697 Ok(t) => t,
698 Err(e) => {
699 ret.insert(
700 "body".to_string().into(),
701 Dynamic::from(format!("Error reading response body: {e}")),
702 );
703 ret.insert("json".to_string().into(), Dynamic::from(json!({})));
704 ret.insert("headers".to_string().into(), Dynamic::from(headers));
705 return Err(format!("Error reading response body: {e}").into());
706 }
707 };
708 ret.insert(
709 "json".to_string().into(),
710 serde_json::from_str(&text).unwrap_or(Dynamic::from(json!({}))),
711 );
712 ret.insert("headers".to_string().into(), Dynamic::from(headers.clone()));
713 ret.insert("body".to_string().into(), Dynamic::from(text));
714 Ok(ret)
715 })
716 })
717 }
718 Err(e) => Err(format!("{e}").into()),
719 }
720 }
721
722 pub fn http_delete(&mut self, path: &str) -> crate::Result<Response> {
723 debug!("http_delete '{}' ", format!("{}/{}", self.baseurl, path));
724 match self.get_client() {
725 Ok(client) => {
726 let mut req = client.delete(format!("{}/{}", self.baseurl, path));
727 for (key, val) in self.headers.clone() {
728 req = req.header(key.to_string(), val.to_string());
729 }
730 tokio::task::block_in_place(|| Handle::current().block_on(async move { req.send().await }))
731 .map_err(Error::ReqwestError)
732 }
733 Err(e) => Err(Error::ReqwestError(e)),
734 }
735 }
736
737 pub fn body_delete(&mut self, path: &str) -> crate::Result<String> {
738 let response = self.http_delete(path)?;
739 if !response.status().is_success() && response.status() != reqwest::StatusCode::NOT_FOUND {
740 let status = response.status();
741 let text = tokio::task::block_in_place(|| {
742 Handle::current().block_on(async move { response.text().await })
743 })
744 .map_err(Error::ReqwestError)?;
745 return Err(Error::MethodFailed(
746 "Delete".to_string(),
747 status.as_u16(),
748 format!(
749 "The server returned the error: {} {} | {text}",
750 status.as_str(),
751 status.canonical_reason().unwrap_or("unknown")
752 ),
753 ));
754 }
755 let text =
756 tokio::task::block_in_place(|| Handle::current().block_on(async move { response.text().await }))
757 .map_err(Error::ReqwestError)?;
758 Ok(text)
759 }
760
761 pub fn json_delete(&mut self, path: &str) -> crate::Result<Value> {
762 let text = self.body_delete(path)?;
763 let json =
764 serde_json::from_str(&text).or_else(|_| Ok::<serde_json::Value, Error>(json!({"body": text})))?;
765 Ok(json)
766 }
767
768 pub fn rhai_delete(&mut self, path: String) -> RhaiRes<Map> {
769 let mut ret = Map::new();
770 match self.http_delete(path.as_str()) {
771 Ok(result) => {
772 ret.insert(
773 "code".to_string().into(),
774 Dynamic::from_int(result.status().as_u16().to_string().parse::<i64>().unwrap_or(0)),
775 );
776 tokio::task::block_in_place(|| {
777 tokio::runtime::Handle::current().block_on(async {
778 let headers = result
779 .headers()
780 .into_iter()
781 .map(|(key, val)| {
782 (
783 key.as_str().to_string(),
784 val.to_str().unwrap_or_default().to_string(),
785 )
786 })
787 .collect::<Vec<(String, String)>>();
788 let text = match result.text().await {
789 Ok(t) => t,
790 Err(e) => {
791 ret.insert(
792 "body".to_string().into(),
793 Dynamic::from(format!("Error reading response body: {e}")),
794 );
795 ret.insert("json".to_string().into(), Dynamic::from(json!({})));
796 ret.insert("headers".to_string().into(), Dynamic::from(headers));
797 return Err(format!("Error reading response body: {e}").into());
798 }
799 };
800 ret.insert(
801 "json".to_string().into(),
802 serde_json::from_str(&text).unwrap_or(Dynamic::from(json!({}))),
803 );
804 ret.insert("headers".to_string().into(), Dynamic::from(headers.clone()));
805 ret.insert("body".to_string().into(), Dynamic::from(text));
806 Ok(ret)
807 })
808 })
809 }
810 Err(e) => Err(format!("{e}").into()),
811 }
812 }
813
814 pub fn obj_read(&mut self, method: ReadMethod, path: &str, key: &str) -> crate::Result<Value> {
815 let full_path = if key.is_empty() {
816 path.to_string()
817 } else {
818 format!("{path}/{key}")
819 };
820 if method == ReadMethod::Get {
821 self.json_get(&full_path)
822 } else {
823 Err(UnsupportedMethod)
824 }
825 }
826
827 pub fn obj_create(&mut self, method: CreateMethod, path: &str, input: &Value) -> crate::Result<Value> {
828 if method == CreateMethod::Post {
829 self.json_post(path, input)
830 } else if method == CreateMethod::Put {
831 self.json_put(path, input)
832 } else {
833 Err(UnsupportedMethod)
834 }
835 }
836
837 pub fn obj_update(
838 &mut self,
839 method: UpdateMethod,
840 path: &str,
841 key: &str,
842 input: &Value,
843 use_slash: bool,
844 ) -> crate::Result<Value> {
845 let full_path = if key.is_empty() {
846 path.to_string()
847 } else if use_slash {
848 format!("{path}/{key}/")
849 } else {
850 format!("{path}/{key}")
851 };
852 if method == UpdateMethod::Patch {
853 self.json_patch(&full_path, input)
854 } else if method == UpdateMethod::Put {
855 self.json_put(&full_path, input)
856 } else if method == UpdateMethod::Post {
857 self.json_post(&full_path, input)
858 } else if method == UpdateMethod::None {
859 Ok(input.clone())
860 } else {
861 Err(UnsupportedMethod)
862 }
863 }
864
865 pub fn obj_delete(&mut self, method: DeleteMethod, path: &str, key: &str) -> crate::Result<Value> {
866 let full_path = if key.is_empty() {
867 path.to_string()
868 } else {
869 format!("{path}/{key}")
870 };
871 if method == DeleteMethod::Delete {
872 self.json_delete(&full_path)
873 } else {
874 Err(UnsupportedMethod)
875 }
876 }
877
878 pub fn http_delete_with_body(&mut self, path: &str, body: &str) -> crate::Result<Response> {
879 debug!(
880 "http_delete_with_body '{}' ",
881 format!("{}/{}", self.baseurl, path)
882 );
883 match self.get_client() {
884 Ok(client) => {
885 let mut req = client
886 .delete(format!("{}/{}", self.baseurl, path))
887 .body(body.to_string());
888 for (key, val) in self.headers.clone() {
889 req = req.header(key.to_string(), val.to_string());
890 }
891 tokio::task::block_in_place(|| Handle::current().block_on(async move { req.send().await }))
892 .map_err(Error::ReqwestError)
893 }
894 Err(e) => Err(Error::ReqwestError(e)),
895 }
896 }
897
898 pub fn body_delete_with_body(&mut self, path: &str, body: &str) -> crate::Result<String> {
899 let response = self.http_delete_with_body(path, body)?;
900 if !response.status().is_success() && response.status() != reqwest::StatusCode::NOT_FOUND {
901 let status = response.status();
902 let text = tokio::task::block_in_place(|| {
903 Handle::current().block_on(async move { response.text().await })
904 })
905 .map_err(Error::ReqwestError)?;
906 return Err(Error::MethodFailed(
907 "Delete".to_string(),
908 status.as_u16(),
909 format!(
910 "The server returned the error: {} {} | {text}",
911 status.as_str(),
912 status.canonical_reason().unwrap_or("unknown")
913 ),
914 ));
915 }
916 let text =
917 tokio::task::block_in_place(|| Handle::current().block_on(async move { response.text().await }))
918 .map_err(Error::ReqwestError)?;
919 Ok(text)
920 }
921
922 pub fn json_delete_with_body(&mut self, path: &str, input: &Value) -> crate::Result<Value> {
923 let body = serde_json::to_string(input).map_err(Error::JsonError)?;
924 let text = self.body_delete_with_body(path, body.as_str())?;
925 let json =
926 serde_json::from_str(&text).or_else(|_| Ok::<serde_json::Value, Error>(json!({"body": text})))?;
927 Ok(json)
928 }
929
930 pub fn rhai_delete_with_body(&mut self, path: String, val: Dynamic) -> RhaiRes<Map> {
931 let body = if val.is_string() {
932 val.to_string()
933 } else {
934 match serde_json::to_string(&val) {
935 Ok(s) => s,
936 Err(e) => return Err(format!("Failed to serialize body: {e}").into()),
937 }
938 };
939 let mut ret = Map::new();
940 match self.http_delete_with_body(path.as_str(), &body) {
941 Ok(result) => {
942 ret.insert(
943 "code".to_string().into(),
944 Dynamic::from_int(result.status().as_u16().to_string().parse::<i64>().unwrap_or(0)),
945 );
946 tokio::task::block_in_place(|| {
947 tokio::runtime::Handle::current().block_on(async {
948 let headers = result
949 .headers()
950 .into_iter()
951 .map(|(key, val)| {
952 (
953 key.as_str().to_string(),
954 val.to_str().unwrap_or_default().to_string(),
955 )
956 })
957 .collect::<Vec<(String, String)>>();
958 let text = match result.text().await {
959 Ok(t) => t,
960 Err(e) => {
961 ret.insert(
962 "body".to_string().into(),
963 Dynamic::from(format!("Error reading response body: {e}")),
964 );
965 ret.insert("json".to_string().into(), Dynamic::from(json!({})));
966 ret.insert("headers".to_string().into(), Dynamic::from(headers));
967 return Err(format!("Error reading response body: {e}").into());
968 }
969 };
970 ret.insert(
971 "json".to_string().into(),
972 serde_json::from_str(&text).unwrap_or(Dynamic::from(json!({}))),
973 );
974 ret.insert("headers".to_string().into(), Dynamic::from(headers.clone()));
975 ret.insert("body".to_string().into(), Dynamic::from(text));
976 Ok(ret)
977 })
978 })
979 }
980 Err(e) => Err(format!("{e}").into()),
981 }
982 }
983
984 pub fn obj_delete_with_body(
985 &mut self,
986 method: DeleteMethod,
987 path: &str,
988 input: &Value,
989 ) -> crate::Result<Value> {
990 if method == DeleteMethod::Delete {
991 self.json_delete_with_body(path, input)
992 } else {
993 Err(UnsupportedMethod)
994 }
995 }
996}
997
998pub fn http_get_yaml(url: String, auth_type: String, credential: String) -> RhaiRes<Dynamic> {
999 tokio::task::block_in_place(|| {
1000 Handle::current().block_on(async move {
1001 let mut headers = reqwest::header::HeaderMap::new();
1002 match auth_type.as_str() {
1003 "bearer" => {
1004 headers.insert(
1005 reqwest::header::AUTHORIZATION,
1006 format!("Bearer {}", credential).parse().unwrap(),
1007 );
1008 }
1009 "basic" => {
1010 let encoded = STANDARD.encode(&credential);
1011 headers.insert(
1012 reqwest::header::AUTHORIZATION,
1013 format!("Basic {}", encoded).parse().unwrap(),
1014 );
1015 }
1016 _ => {}
1017 }
1018 let client = reqwest::Client::builder()
1019 .default_headers(headers)
1020 .timeout(std::time::Duration::from_secs(300))
1021 .build()
1022 .map_err(|e| Error::Other(e.to_string()))?;
1023 let response = client.get(&url).send().await.map_err(Error::ReqwestError)?;
1024 if !response.status().is_success() {
1025 return Err(Error::Other(format!(
1026 "SCAN-HTTP-001: HTTP {} for {}",
1027 response.status(),
1028 url
1029 )));
1030 }
1031 let body = response.text().await.map_err(Error::ReqwestError)?;
1032 let value: serde_yaml::Value =
1033 serde_yaml::from_str(&body).map_err(|e| Error::YamlError(e.to_string()))?;
1034 let json = serde_json::to_string(&value).map_err(Error::SerializationError)?;
1035 serde_json::from_str::<Dynamic>(&json).map_err(Error::SerializationError)
1036 })
1037 })
1038 .map_err(rhai_err)
1039}
1040
1041pub fn http_rhai_register(engine: &mut Engine) {
1042 engine
1043 .register_type_with_name::<RestClient>("RestClient")
1044 .register_fn("new_http_client", RestClient::new)
1045 .register_fn("new_client", RestClient::new)
1046 .register_fn("headers_reset", RestClient::headers_reset_rhai)
1047 .register_fn("set_baseurl", RestClient::baseurl_rhai)
1048 .register_fn("set_server_ca", RestClient::set_server_ca)
1049 .register_fn("set_mtls_cert_key", RestClient::set_mtls)
1050 .register_fn("add_header", RestClient::add_header_rhai)
1051 .register_fn("add_header_json", RestClient::add_header_json)
1052 .register_fn("add_header_bearer", RestClient::add_header_bearer)
1053 .register_fn("add_header_basic", RestClient::add_header_basic)
1054 .register_fn("head", RestClient::rhai_head)
1055 .register_fn("get", RestClient::rhai_get)
1056 .register_fn("http_get", RestClient::rhai_get)
1057 .register_fn("delete", RestClient::rhai_delete)
1058 .register_fn("http_delete", RestClient::rhai_delete)
1059 .register_fn("delete_with_body", RestClient::rhai_delete_with_body)
1060 .register_fn("http_delete_with_body", RestClient::rhai_delete_with_body)
1061 .register_fn("patch", RestClient::rhai_patch)
1062 .register_fn("http_patch", RestClient::rhai_patch)
1063 .register_fn("post", RestClient::rhai_post)
1064 .register_fn("http_post", RestClient::rhai_post)
1065 .register_fn("put", RestClient::rhai_put)
1066 .register_fn("http_put", RestClient::rhai_put)
1067 .register_fn("post_form", RestClient::rhai_post_form)
1068 .register_fn("http_post_form", RestClient::rhai_post_form)
1069 .register_fn("http_get_yaml", http_get_yaml);
1070}
1071
1072#[cfg(test)]
1073mod tests {
1074 use super::*;
1075 use wiremock::{
1076 Mock, MockServer, ResponseTemplate,
1077 matchers::{header, method, path},
1078 };
1079
1080 #[tokio::test(flavor = "multi_thread")]
1081 async fn test_http_get_yaml_ok() {
1082 let server = MockServer::start().await;
1083 Mock::given(method("GET"))
1084 .and(path("/index.yaml"))
1085 .respond_with(ResponseTemplate::new(200).set_body_string("packages:\n - name: test\n"))
1086 .mount(&server)
1087 .await;
1088
1089 let result = http_get_yaml(
1090 format!("{}/index.yaml", server.uri()),
1091 String::new(),
1092 String::new(),
1093 );
1094 assert!(result.is_ok(), "expected Ok, got {:?}", result);
1095 let d = result.unwrap();
1096 assert!(d.is_map(), "expected map Dynamic");
1097 }
1098
1099 #[tokio::test(flavor = "multi_thread")]
1100 async fn test_http_get_yaml_404() {
1101 let server = MockServer::start().await;
1102 Mock::given(method("GET"))
1103 .and(path("/missing.yaml"))
1104 .respond_with(ResponseTemplate::new(404))
1105 .mount(&server)
1106 .await;
1107
1108 let result = http_get_yaml(
1109 format!("{}/missing.yaml", server.uri()),
1110 String::new(),
1111 String::new(),
1112 );
1113 assert!(result.is_err());
1114 let err = format!("{:?}", result.unwrap_err());
1115 assert!(
1116 err.contains("SCAN-HTTP-001"),
1117 "error should contain SCAN-HTTP-001: {err}"
1118 );
1119 }
1120
1121 #[tokio::test(flavor = "multi_thread")]
1122 async fn test_http_get_yaml_bearer() {
1123 let server = MockServer::start().await;
1124 Mock::given(method("GET"))
1125 .and(path("/index.yaml"))
1126 .and(header("authorization", "Bearer token123"))
1127 .respond_with(ResponseTemplate::new(200).set_body_string("key: value\n"))
1128 .mount(&server)
1129 .await;
1130
1131 let result = http_get_yaml(
1132 format!("{}/index.yaml", server.uri()),
1133 "bearer".to_string(),
1134 "token123".to_string(),
1135 );
1136 assert!(result.is_ok(), "expected Ok, got {:?}", result);
1137 }
1138
1139 #[tokio::test(flavor = "multi_thread")]
1140 async fn test_http_get_yaml_basic() {
1141 let server = MockServer::start().await;
1142 Mock::given(method("GET"))
1143 .and(path("/index.yaml"))
1144 .and(header("authorization", "Basic dXNlcjpwYXNz"))
1145 .respond_with(ResponseTemplate::new(200).set_body_string("key: value\n"))
1146 .mount(&server)
1147 .await;
1148
1149 let result = http_get_yaml(
1150 format!("{}/index.yaml", server.uri()),
1151 "basic".to_string(),
1152 "user:pass".to_string(),
1153 );
1154 assert!(result.is_ok(), "expected Ok, got {:?}", result);
1155 }
1156}