1use std::str::FromStr;
4
5use borderless_id_types::{AgentId, TxIdentifier};
6use http::header::CONTENT_TYPE;
7use queries::Pagination;
8use serde::de::DeserializeOwned;
9use serde::Serialize;
10
11use crate::__private::send_http_rq;
12use crate::common::{Description, Metadata};
13use crate::contracts::Info;
14use crate::events::{CallAction, Sink};
15use crate::warn;
16
17pub use http::{HeaderName, HeaderValue, Method, Request, Response, StatusCode, Version};
18
19pub trait IntoBodyAndContentType {
23 fn into_parts(self) -> anyhow::Result<Option<(Vec<u8>, &'static str)>>;
24}
25
26pub struct Json<T>(pub T);
30
31impl<T: Serialize> IntoBodyAndContentType for Json<T> {
32 fn into_parts(self) -> anyhow::Result<Option<(Vec<u8>, &'static str)>> {
33 let body = serde_json::to_vec(&self.0)?;
34 Ok(Some((body, "application/json")))
35 }
36}
37
38pub struct Text<T>(pub T);
42
43impl<T: ToString> IntoBodyAndContentType for Text<T> {
44 fn into_parts(self) -> anyhow::Result<Option<(Vec<u8>, &'static str)>> {
45 let body = self.0.to_string().into_bytes();
46 Ok(Some((body, "text/plain")))
47 }
48}
49
50impl IntoBodyAndContentType for String {
51 fn into_parts(self) -> anyhow::Result<Option<(Vec<u8>, &'static str)>> {
52 let body = self.into_bytes();
53 Ok(Some((body, "text/plain")))
54 }
55}
56
57impl IntoBodyAndContentType for &str {
58 fn into_parts(self) -> anyhow::Result<Option<(Vec<u8>, &'static str)>> {
59 let body = self.to_string().into_bytes();
60 Ok(Some((body, "text/plain")))
61 }
62}
63
64pub struct Empty;
70
71impl IntoBodyAndContentType for Empty {
72 fn into_parts(self) -> anyhow::Result<Option<(Vec<u8>, &'static str)>> {
73 Ok(None)
74 }
75}
76
77impl IntoBodyAndContentType for () {
78 fn into_parts(self) -> anyhow::Result<Option<(Vec<u8>, &'static str)>> {
79 Ok(None)
80 }
81}
82
83pub struct Binary<T>(pub T);
87
88impl<T: Into<Vec<u8>>> IntoBodyAndContentType for Binary<T> {
89 fn into_parts(self) -> anyhow::Result<Option<(Vec<u8>, &'static str)>> {
90 let body = self.0.into();
91 Ok(Some((body, "application/octet-stream")))
92 }
93}
94
95impl IntoBodyAndContentType for Vec<u8> {
96 fn into_parts(self) -> anyhow::Result<Option<(Vec<u8>, &'static str)>> {
97 Ok(Some((self, "application/octet-stream")))
98 }
99}
100
101pub fn get(url: impl AsRef<str>) -> anyhow::Result<Response<Vec<u8>>> {
103 let request = Request::builder()
104 .method(Method::GET)
105 .uri(url.as_ref())
106 .body(())?;
107 send_request(request)
108}
109
110pub fn post_json<T: Serialize, U: AsRef<str>>(
112 data: T,
113 url: U,
114) -> anyhow::Result<Response<Vec<u8>>> {
115 let request = Request::builder()
116 .method(Method::POST)
117 .uri(url.as_ref())
118 .body(Json(data))?;
119 send_request(request)
120}
121
122pub fn send_request<T>(request: Request<T>) -> anyhow::Result<Response<Vec<u8>>>
124where
125 T: IntoBodyAndContentType,
126{
127 let (mut parts, body) = request.into_parts();
128
129 let body_bytes = match body.into_parts()? {
131 Some((bytes, content_type)) => {
132 parts
133 .headers
134 .insert(CONTENT_TYPE, HeaderValue::from_static(content_type));
135 bytes
136 }
137 None => Vec::new(),
138 };
139
140 let mut head = format!("{} {} {:?}\r\n", parts.method, parts.uri, parts.version);
142 for (name, value) in parts.headers.iter() {
143 head.push_str(&format!("{}: {}\r\n", name, value.to_str().unwrap()));
144 }
145 head.push_str("\r\n"); let (rs_head, rs_body) = send_http_rq(head, body_bytes).map_err(anyhow::Error::msg)?;
149 let rs = build_response_from_parts(&rs_head, rs_body)?;
150 Ok(rs)
151}
152
153pub fn as_json<T>(response: Response<Vec<u8>>) -> anyhow::Result<T>
157where
158 T: DeserializeOwned,
159{
160 if !response
161 .headers()
162 .get(CONTENT_TYPE)
163 .and_then(|h| h.to_str().ok())
164 .map(|h| h.starts_with("application/json"))
165 .unwrap_or_default()
166 {
167 warn!("as_json: Response does not have content-type 'application/json'");
168 }
169 Ok(serde_json::from_slice(response.body())?)
170}
171
172pub fn as_text(response: Response<Vec<u8>>) -> anyhow::Result<String> {
176 if !response
177 .headers()
178 .get(CONTENT_TYPE)
179 .and_then(|h| h.to_str().ok())
180 .map(|h| h.starts_with("text/plain"))
181 .unwrap_or_default()
182 {
183 warn!("as_text: Response does not have content-type 'text/plain'");
184 }
185 Ok(String::from_utf8(response.into_body())?)
186}
187
188fn build_response_from_parts(head: &str, body: Vec<u8>) -> anyhow::Result<Response<Vec<u8>>> {
190 let mut lines = head.lines();
191
192 let status_line = lines
194 .next()
195 .ok_or_else(|| anyhow::anyhow!("Empty response head"))?;
196 let mut status_parts = status_line.splitn(3, ' ');
197
198 let version_str = status_parts
199 .next()
200 .ok_or_else(|| anyhow::anyhow!("Missing HTTP version"))?;
201 let status_code_str = status_parts
202 .next()
203 .ok_or_else(|| anyhow::anyhow!("Missing status code"))?;
204 let _reason_phrase = status_parts.next().unwrap_or(""); let version = match version_str {
207 "HTTP/1.0" => Version::HTTP_10,
208 "HTTP/1.1" => Version::HTTP_11,
209 "HTTP/2.0" | "HTTP/2" => Version::HTTP_2,
210 _ => return Err(anyhow::anyhow!("Unsupported HTTP version: {}", version_str)),
211 };
212
213 let status_code = StatusCode::from_bytes(status_code_str.as_bytes())?;
214
215 let mut response = Response::builder().status(status_code).version(version);
217
218 let headers = response.headers_mut().unwrap();
219
220 for line in lines {
222 if line.trim().is_empty() {
223 continue; }
225 if let Some((name, value)) = line.split_once(':') {
226 let header_name = HeaderName::from_str(name.trim())?;
227 let header_value = HeaderValue::from_str(value.trim())?;
228 headers.insert(header_name, header_value);
229 } else {
230 return Err(anyhow::anyhow!("Malformed header line: {}", line));
231 }
232 }
233 Ok(response.body(body)?)
235}
236
237#[derive(Serialize)]
248pub struct PaginatedElements<T>
249where
250 T: Serialize,
251{
252 pub elements: Vec<T>,
253 pub total_elements: usize,
254 #[serde(flatten)]
255 pub pagination: Pagination,
256}
257
258#[derive(Debug, Clone, Serialize)]
260pub struct TxAction {
261 pub tx_id: TxIdentifier,
263 pub action: CallAction,
265 pub commited: u64,
266}
267
268#[derive(Debug, Clone, Serialize)]
272pub struct ContractInfo {
273 pub info: Option<Info>,
274 pub desc: Option<Description>,
275 pub meta: Option<Metadata>,
276}
277
278#[derive(Debug, Clone, Serialize)]
282pub struct AgentInfo {
283 pub agent_id: AgentId,
284 pub sinks: Vec<Sink>,
285 pub desc: Option<Description>,
286 pub meta: Option<Metadata>,
287}
288
289pub mod queries {
290 use std::{
291 collections::{HashMap, HashSet},
292 fmt::Display,
293 str::FromStr,
294 };
295
296 use borderless_id_types::{AgentId, ContractId};
297 use serde::Serialize;
298
299 pub struct Query {
300 items: HashMap<String, String>,
307 other: HashSet<String>,
309 }
310
311 impl Query {
312 pub fn parse<S: AsRef<str>>(query_str: S) -> Query {
314 let mut items = HashMap::new();
315 let mut other = HashSet::new();
316 for encoded in query_str.as_ref().split('&') {
318 let key_value = encoded; if key_value.contains(['<', '>']) {
325 other.insert(key_value.replace('+', " "));
327 } else {
328 let mut iter = key_value.splitn(2, '=');
330 let key = iter.next().unwrap_or_default();
331 let value = iter.next().unwrap_or_default();
332 if !value.is_empty() && !key.is_empty() {
334 match key {
335 "page" | "per_page" | "sort" | "order" | "action" => {
337 items.insert(key.to_string(), value.to_string());
338 }
339 "contract_id" | "contract-id" => {
341 if let Ok(id) = ContractId::parse_str(value) {
342 other.insert(format!("contract_id={}", id));
343 }
344 }
345 "agent_id" | "agent-id" | "process_id" | "process-id" => {
346 if let Ok(id) = AgentId::parse_str(value) {
347 other.insert(format!("agent_id={}", id));
348 }
349 }
350 _ => {
352 other.insert(format!("{}={}", key, value.replace('+', " "),));
353 }
354 }
355 }
356 }
357 }
358 Query { items, other }
359 }
360
361 pub fn pagination(&self) -> Option<Pagination> {
363 let page_item = self.items.get("page")?;
364 let per_page_item = self.items.get("per_page")?;
365 let page = usize::from_str(page_item).ok()?;
366 let per_page = usize::from_str(per_page_item).ok()?;
367 Some(Pagination { page, per_page })
368 }
369
370 pub fn sorting(&self) -> Option<Sorting> {
372 let sort_by = self.items.get("sort")?.clone();
373 let order_item = match self.items.get("order") {
374 Some(item) => item,
375 None => {
376 return Some(Sorting {
377 sort_by,
378 order: Order::Ascending,
379 })
380 }
381 };
382 let order = match order_item.to_ascii_lowercase().as_ref() {
383 "ascending" | "asc" => Order::Ascending,
384 "descending" | "desc" => Order::Descending,
385 _ => return None,
387 };
388 Some(Sorting { sort_by, order })
389 }
390
391 pub fn action_query(&self) -> Option<String> {
393 let _action = self.items.get("action")?;
394 todo!("re-implement this for new action system")
395 }
396
397 pub fn contains_other(&self) -> bool {
398 !self.other.is_empty()
399 }
400
401 pub fn other(&self) -> impl Iterator<Item = &str> {
402 self.other.iter().map(|s| s.as_str())
403 }
404 }
405
406 #[derive(Debug, PartialEq, Eq)]
407 pub enum Order {
408 Ascending,
409 Descending,
410 }
411
412 impl AsRef<str> for Order {
413 fn as_ref(&self) -> &str {
414 match self {
415 Order::Ascending => "ASC",
416 Order::Descending => "DESC",
417 }
418 }
419 }
420
421 impl Display for Order {
422 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
423 write!(f, "{}", self.as_ref())
424 }
425 }
426
427 #[derive(Debug, PartialEq, Eq)]
428 pub struct Sorting {
429 pub sort_by: String,
430 pub order: Order,
431 }
432
433 #[derive(Serialize, Clone)]
439 pub struct Pagination {
440 pub page: usize,
441 pub per_page: usize,
442 }
443
444 impl Default for Pagination {
445 fn default() -> Self {
446 Self {
447 page: 1,
448 per_page: 100,
449 }
450 }
451 }
452
453 impl Pagination {
454 pub fn from_query(query: Option<&str>) -> Option<Pagination> {
462 let query = query?;
463 let mut page_str: Option<&str> = None;
464 let mut per_page_str: Option<&str> = None;
465 for piece in query.split('&') {
466 if piece.starts_with("page=") {
467 page_str = Some(piece);
468 } else if piece.starts_with("per_page=") || piece.starts_with("per-page") {
469 per_page_str = Some(piece);
470 }
471 if page_str.is_some() && per_page_str.is_some() {
472 break;
473 }
474 }
475 #[allow(clippy::field_reassign_with_default)]
478 match (page_str, per_page_str) {
479 (Some(page_str), Some(per_page_str)) => {
480 let page_num: &str = page_str.split('=').nth(1)?;
481 let per_page_num: &str = per_page_str.split('=').nth(1)?;
482 let page = usize::from_str(page_num).ok()?;
483 let per_page = usize::from_str(per_page_num).ok()?;
484 Some(Pagination { page, per_page })
485 }
486 (Some(page_str), None) => {
487 let page_num: &str = page_str.split('=').nth(1)?;
488 let page = usize::from_str(page_num).ok()?;
489 let mut pagination = Pagination::default();
490 pagination.page = page;
491 Some(pagination)
492 }
493 (None, Some(per_page_str)) => {
494 let per_page_num: &str = per_page_str.split('=').nth(1)?;
495 let per_page = usize::from_str(per_page_num).ok()?;
496 let mut pagination = Pagination::default();
497 pagination.per_page = per_page;
498 Some(pagination)
499 }
500 _ => None,
501 }
502 }
503
504 pub fn to_range(&self) -> std::ops::Range<usize> {
509 self.clone().into()
510 }
511 }
512
513 impl From<Pagination> for std::ops::Range<usize> {
514 fn from(value: Pagination) -> Self {
515 let start = value.page.saturating_sub(1) * value.per_page;
516 let end = value.page * value.per_page;
517 Self { start, end }
518 }
519 }
520
521 #[cfg(test)]
522 mod query_tests {
523 use super::*;
524
525 #[test]
526 fn pagination() {
527 let queries = [
528 "page=10&per_page=2312", "per_page=2312&page=10", "something_else=null&page=10&per_page=2312", "page=10&something_else=null&per_page=2312", ];
533 for query in queries {
534 let pagination = Pagination::from_query(Some(query));
535 assert!(pagination.is_some());
536 let pagination = pagination.unwrap();
537 assert_eq!(pagination.page, 10);
538 assert_eq!(pagination.per_page, 2312);
539 let pagination = Query::parse(query).pagination();
541 assert!(pagination.is_some());
542 let pagination = pagination.unwrap();
543 assert_eq!(pagination.page, 10);
544 assert_eq!(pagination.per_page, 2312);
545 }
546 assert!(Pagination::from_query(None).is_none());
547 let bad_queries = [
548 "page=10&per_page=", "per_page=2312&page=", "page=id&perpage=2312", "something_else=null", ];
553 for query in bad_queries {
554 let pagination = Pagination::from_query(Some(query));
555 assert!(
556 pagination.is_none(),
557 "This query should not work: {}",
558 query
559 );
560 let pagination = Query::parse(query).pagination();
562 assert!(
563 pagination.is_none(),
564 "This query should not work: {}",
565 query
566 );
567 }
568 }
569
570 #[test]
571 fn keyvalue() {
572 let good_queries = [
573 "key=id&value=2312", "value=2312&key=id", "something_else=null&key=id&value=2312", "key=id&something_else=null&value=2312", ];
578 for query in good_queries {
579 let key_value = Query::parse(query);
580 assert!(key_value.contains_other());
581 assert!(key_value.other().any(|s| s == "key=id"));
582 assert!(key_value.other().any(|s| s == "value=2312"));
583 }
584 let bad_queries = [
585 "key=&value=", "value=&key=", "ky=id&vaue=2312", "something_else=null", ];
590 for query in bad_queries {
591 let key_value = Query::parse(query);
592 assert!(!key_value.other().any(|s| s == "key=id"));
593 assert!(!key_value.other().any(|s| s == "value=2312"));
594 }
595 }
596
597 #[test]
598 fn sorting() {
599 let good_queries_asc = [
600 "sort=sensor_id&order=asc",
601 "sort=sensor_id&order=ascending",
602 "order=ascending&sort=sensor_id",
603 "order=asc&sort=sensor_id",
604 "sort=sensor_id&order=aSc",
605 "sort=sensor_id&order=ASCending",
606 "order=Ascending&sort=sensor_id",
607 "order=Asc&sort=sensor_id",
608 "sort=sensor_id", "sort=sensor_id&order", ];
611 for query in good_queries_asc {
612 let sorting = Query::parse(query).sorting();
613 assert!(sorting.is_some());
614 let sorting = sorting.unwrap();
615 assert_eq!(
616 sorting,
617 Sorting {
618 sort_by: "sensor_id".to_string(),
619 order: Order::Ascending
620 }
621 );
622 }
623 let good_queries_desc = [
624 "sort=sensor_id&order=desc",
625 "sort=sensor_id&order=descending",
626 "order=descending&sort=sensor_id",
627 "order=desc&sort=sensor_id",
628 "sort=sensor_id&order=dESc",
629 "sort=sensor_id&order=Descending",
630 "order=Descending&sort=sensor_id",
631 "order=Desc&sort=sensor_id",
632 ];
633 for query in good_queries_desc {
634 let sorting = Query::parse(query).sorting();
635 assert!(sorting.is_some());
636 let sorting = sorting.unwrap();
637 assert_eq!(
638 sorting,
639 Sorting {
640 sort_by: "sensor_id".to_string(),
641 order: Order::Descending
642 }
643 );
644 }
645 let bad_queries = [
646 "sort=sensor_id&order=something", "sort=&order=descending", "order=ascending", ];
650 for query in bad_queries {
651 let sorting = Query::parse(query).sorting();
652 assert!(sorting.is_none(), "This query should not work: {}", query);
653 }
654 }
655
656 #[test]
657 fn parse_contract_id() {
658 let contract_ids = [
659 "contract-id=c073e869-4ae1-892c-aba7-2ad8318d5c12",
660 "contract_id=c073e869-4ae1-892c-aba7-2ad8318d5c12",
661 ];
662 for id in contract_ids {
663 let query = Query::parse(id);
664 assert!(query.contains_other(), "Query should contain contract-id");
665 let cid = query.other().next().unwrap();
666 assert_eq!(cid, "contract_id=c073e869-4ae1-892c-aba7-2ad8318d5c12");
667 }
668 let agent_ids = [
669 "process_id=a073e869-4ae1-892c-aba7-2ad8318d5c12",
670 "process-id=a073e869-4ae1-892c-aba7-2ad8318d5c12",
671 "agent-id=a073e869-4ae1-892c-aba7-2ad8318d5c12",
672 "agent_id=a073e869-4ae1-892c-aba7-2ad8318d5c12",
673 ];
674 for id in agent_ids {
675 let query = Query::parse(id);
676 assert!(query.contains_other(), "Query should contain contract-id");
677 let cid = query.other().next().unwrap();
678 assert_eq!(cid, "agent_id=a073e869-4ae1-892c-aba7-2ad8318d5c12");
679 }
680 }
681 }
682}
683
684#[cfg(test)]
685mod tests {
686 use super::*;
687
688 #[test]
689 fn test_valid_response() {
690 let head = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nX-Test: 123\r\n\r\n";
691 let body = b"Hello, world!".to_vec();
692
693 let response = build_response_from_parts(head, body.clone()).unwrap();
694
695 assert_eq!(response.status(), StatusCode::OK);
696 assert_eq!(response.version(), Version::HTTP_11);
697 assert_eq!(
698 response.headers().get("Content-Type").unwrap(),
699 "text/plain"
700 );
701 assert_eq!(response.headers().get("X-Test").unwrap(), "123");
702 assert_eq!(response.body(), &body);
703 }
704
705 #[test]
706 fn test_valid_http_2_response() {
707 let head = "HTTP/2 204 No Content\r\nX-Empty: yes\r\n\r\n";
708 let body = Vec::new();
709
710 let response = build_response_from_parts(head, body.clone()).unwrap();
711
712 assert_eq!(response.status(), StatusCode::NO_CONTENT);
713 assert_eq!(response.version(), Version::HTTP_2);
714 assert_eq!(response.headers().get("X-Empty").unwrap(), "yes");
715 assert_eq!(response.body(), &body);
716 }
717
718 #[test]
719 fn test_missing_status_line_parts() {
720 let head = "HTTP/1.1\r\nContent-Type: text/plain\r\n\r\n";
721 let body = b"Oops".to_vec();
722
723 let result = build_response_from_parts(head, body);
724 assert!(result.is_err());
725 }
726
727 #[test]
728 fn test_invalid_http_version() {
729 let head = "HTTP/3.0 200 OK\r\nContent-Type: text/plain\r\n\r\n";
730 let body = b"Invalid".to_vec();
731
732 let result = build_response_from_parts(head, body);
733 assert!(result.is_err());
734 }
735
736 #[test]
737 fn test_invalid_status_code() {
738 let head = "HTTP/1.1 abc OK\r\nContent-Type: text/plain\r\n\r\n";
739 let body = b"Invalid".to_vec();
740
741 let result = build_response_from_parts(head, body);
742 assert!(result.is_err());
743 }
744
745 #[test]
746 fn test_malformed_header() {
747 let head = "HTTP/1.1 200 OK\r\nBad-Header-Without-Colon\r\n\r\n";
748 let body = b"BadHeader".to_vec();
749
750 let result = build_response_from_parts(head, body);
751 assert!(result.is_err());
752 }
753
754 #[test]
755 fn test_empty_head() {
756 let head = "";
757 let body = b"Empty".to_vec();
758
759 let result = build_response_from_parts(head, body);
760 assert!(result.is_err());
761 }
762}