contrag_core/embedders/
http_client.rs1use serde::{Deserialize, Serialize};
2use crate::error::{ContragError, Result};
3
4pub struct HttpClient {
8 max_response_bytes: u64,
9}
10
11impl HttpClient {
12 pub fn new() -> Self {
13 Self {
14 max_response_bytes: 2_000_000, }
16 }
17
18 pub async fn post(
23 &self,
24 url: String,
25 headers: Vec<(String, String)>,
26 body: Vec<u8>,
27 ) -> Result<HttpOutcallResponse> {
28 #[cfg(target_family = "wasm")]
29 {
30 use ic_cdk::api::management_canister::http_request::{
31 http_request, CanisterHttpRequestArgument, HttpMethod, HttpHeader, HttpResponse,
32 TransformContext,
33 };
34
35 let request_headers: Vec<HttpHeader> = headers
36 .into_iter()
37 .map(|(name, value)| HttpHeader { name, value })
38 .collect();
39
40 let request = CanisterHttpRequestArgument {
41 url,
42 method: HttpMethod::POST,
43 body: Some(body),
44 max_response_bytes: Some(self.max_response_bytes),
45 transform: None,
46 headers: request_headers,
47 };
48
49 let cycles = 1_000_000_000u128; match http_request(request, cycles).await {
52 Ok((response,)) => Ok(HttpOutcallResponse {
53 status: response.status.0.into(),
54 headers: response
55 .headers
56 .into_iter()
57 .map(|h| (h.name, h.value))
58 .collect(),
59 body: response.body,
60 }),
61 Err((code, msg)) => Err(ContragError::HttpOutcallError(format!(
62 "HTTP outcall failed: {:?} - {}",
63 code, msg
64 ))),
65 }
66 }
67
68 #[cfg(not(target_family = "wasm"))]
69 {
70 Err(ContragError::HttpOutcallError(
71 "HTTP outcalls only work in WASM environment".to_string(),
72 ))
73 }
74 }
75
76 pub async fn get(
78 &self,
79 url: String,
80 headers: Vec<(String, String)>,
81 ) -> Result<HttpOutcallResponse> {
82 #[cfg(target_family = "wasm")]
83 {
84 use ic_cdk::api::management_canister::http_request::{
85 http_request, CanisterHttpRequestArgument, HttpMethod, HttpHeader,
86 TransformContext,
87 };
88
89 let request_headers: Vec<HttpHeader> = headers
90 .into_iter()
91 .map(|(name, value)| HttpHeader { name, value })
92 .collect();
93
94 let request = CanisterHttpRequestArgument {
95 url,
96 method: HttpMethod::GET,
97 body: None,
98 max_response_bytes: Some(self.max_response_bytes),
99 transform: None,
100 headers: request_headers,
101 };
102
103 let cycles = 500_000_000u128; match http_request(request, cycles).await {
106 Ok((response,)) => Ok(HttpOutcallResponse {
107 status: response.status.0.into(),
108 headers: response
109 .headers
110 .into_iter()
111 .map(|h| (h.name, h.value))
112 .collect(),
113 body: response.body,
114 }),
115 Err((code, msg)) => Err(ContragError::HttpOutcallError(format!(
116 "HTTP outcall failed: {:?} - {}",
117 code, msg
118 ))),
119 }
120 }
121
122 #[cfg(not(target_family = "wasm"))]
123 {
124 Err(ContragError::HttpOutcallError(
125 "HTTP outcalls only work in WASM environment".to_string(),
126 ))
127 }
128 }
129}
130
131impl Default for HttpClient {
132 fn default() -> Self {
133 Self::new()
134 }
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct HttpOutcallResponse {
139 pub status: u16,
140 pub headers: Vec<(String, String)>,
141 pub body: Vec<u8>,
142}
143
144impl HttpOutcallResponse {
145 pub fn json<T: for<'de> Deserialize<'de>>(&self) -> Result<T> {
147 serde_json::from_slice(&self.body).map_err(|e| {
148 ContragError::SerializationError(format!("Failed to parse JSON response: {}", e))
149 })
150 }
151
152 pub fn text(&self) -> Result<String> {
154 String::from_utf8(self.body.clone()).map_err(|e| {
155 ContragError::SerializationError(format!("Failed to parse response as UTF-8: {}", e))
156 })
157 }
158}