pink_extension/chain_extension/
http_request.rs

1use alloc::string::String;
2use alloc::vec::Vec;
3use num_enum::{IntoPrimitive, TryFromPrimitive};
4
5use super::ErrorCode;
6#[derive(scale::Encode, scale::Decode)]
7#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
8pub struct HttpRequest {
9    pub url: String,
10    pub method: String,
11    pub headers: Vec<(String, String)>,
12    pub body: Vec<u8>,
13}
14
15impl HttpRequest {
16    /// Create a new http request.
17    pub fn new(
18        url: impl Into<String>,
19        method: impl Into<String>,
20        headers: Vec<(String, String)>,
21        body: Vec<u8>,
22    ) -> Self {
23        Self {
24            url: url.into(),
25            method: method.into(),
26            headers,
27            body,
28        }
29    }
30}
31
32#[derive(scale::Encode, scale::Decode)]
33#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
34pub struct HttpResponse {
35    pub status_code: u16,
36    pub reason_phrase: String,
37    pub headers: Vec<(String, String)>,
38    pub body: Vec<u8>,
39}
40
41#[derive(scale::Encode, scale::Decode, TryFromPrimitive, IntoPrimitive, Clone, Copy, Debug)]
42#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
43#[repr(u32)]
44pub enum HttpRequestError {
45    InvalidUrl,
46    InvalidMethod,
47    InvalidHeaderName,
48    InvalidHeaderValue,
49    FailedToCreateClient,
50    Timeout,
51    NotAllowed,
52    TooManyRequests,
53    NetworkError,
54    ResponseTooLarge,
55}
56
57impl super::sealed::Sealed for HttpRequestError {}
58impl super::CodableError for HttpRequestError {
59    fn encode(&self) -> u32 {
60        let code: u32 = (*self).into();
61        code + 1
62    }
63
64    fn decode(code: u32) -> Option<Self> {
65        if code == 0 {
66            return None;
67        }
68        core::convert::TryFrom::try_from(code - 1).ok()
69    }
70}
71
72impl From<ErrorCode> for HttpRequestError {
73    fn from(value: ErrorCode) -> Self {
74        match <Self as super::CodableError>::decode(value.0) {
75            None => crate::panic!("chain extension: invalid HttpRequestError"),
76            Some(err) => err,
77        }
78    }
79}
80
81impl From<scale::Error> for HttpRequestError {
82    fn from(_value: scale::Error) -> Self {
83        crate::panic!("chain_ext: failed to decocde HttpRequestError")
84    }
85}
86
87impl HttpRequestError {
88    pub fn display(&self) -> &'static str {
89        match self {
90            Self::InvalidUrl => "Invalid URL",
91            Self::InvalidMethod => "Invalid method",
92            Self::InvalidHeaderName => "Invalid header name",
93            Self::InvalidHeaderValue => "Invalid header value",
94            Self::FailedToCreateClient => "Failed to create client",
95            Self::Timeout => "Timeout",
96            Self::NotAllowed => "Not allowed",
97            Self::TooManyRequests => "Too many requests",
98            Self::NetworkError => "Network error",
99            Self::ResponseTooLarge => "Response too large",
100        }
101    }
102}
103
104impl HttpResponse {
105    pub fn ok(body: Vec<u8>) -> Self {
106        Self {
107            status_code: 200,
108            reason_phrase: "OK".into(),
109            headers: Default::default(),
110            body,
111        }
112    }
113
114    pub fn not_found() -> Self {
115        Self {
116            status_code: 404,
117            reason_phrase: "Not Found".into(),
118            headers: Default::default(),
119            body: Default::default(),
120        }
121    }
122}
123
124#[macro_export]
125macro_rules! http_req {
126    ($method: expr, $url: expr, $data: expr, $headers: expr) => {{
127        use $crate::chain_extension::HttpRequest;
128        let headers = $headers;
129        let body = $data;
130        let request = HttpRequest {
131            url: $url.into(),
132            method: $method.into(),
133            headers,
134            body,
135        };
136        $crate::ext().http_request(request)
137    }};
138    ($method: expr, $url: expr, $data: expr) => {{
139        $crate::http_req!($method, $url, $data, Default::default())
140    }};
141}
142
143/// Make a simple HTTP GET request
144///
145/// # Arguments
146/// - url: The URL to GET
147/// - headers: The headers to send with the request
148///
149/// # Examples
150///
151/// ```ignore
152/// use pink_extension::http_get;
153/// let response = http_get!("https://example.com/");
154/// assert_eq!(response.status_code, 200);
155/// ```
156///
157/// ```ignore
158/// use pink_extension::http_get;
159/// let headers = vec![("X-Foo".into(), "Bar".into())];
160/// let response = http_get!("https://example.com/", headers);
161/// assert_eq!(response.status_code, 200);
162/// ```
163#[macro_export]
164macro_rules! http_get {
165    ($url: expr, $headers: expr) => {{
166        $crate::http_req!("GET", $url, Default::default(), $headers)
167    }};
168    ($url: expr) => {{
169        $crate::http_get!($url, Default::default())
170    }};
171}
172
173/// Make a simple HTTP POST request
174///
175/// # Arguments
176/// - url: The URL to POST
177/// - data: The payload to POST
178/// - headers: The headers to send with the request
179///
180/// # Examples
181///
182/// ```ignore
183/// use pink_extension::http_post;
184/// let response = http_post!("https://example.com/", b"Hello, world!");
185/// assert_eq!(response.status_code, 200);
186/// ```
187///
188/// ```ignore
189/// use pink_extension::http_post;
190/// let headers = vec![("X-Foo".into(), "Bar".into())];
191/// let response = http_post!("https://example.com/", b"Hello, world!", headers);
192/// assert_eq!(response.status_code, 200);
193/// ```
194#[macro_export]
195macro_rules! http_post {
196    ($url: expr, $data: expr, $headers: expr) => {{
197        $crate::http_req!("POST", $url, $data.into(), $headers)
198    }};
199    ($url: expr, $data: expr) => {{
200        $crate::http_post!($url, $data, Default::default())
201    }};
202}
203
204/// Make a simple HTTP PUT request
205///
206/// # Arguments
207/// - url: The destination URL
208/// - data: The payload to PUT
209/// - headers: The headers to send with the request
210///
211/// # Examples
212///
213/// ```ignore
214/// use pink_extension::http_put;
215/// let response = http_put!("https://example.com/", b"Hello, world!");
216/// assert_eq!(response.status_code, 200);
217/// ```
218#[macro_export]
219macro_rules! http_put {
220    ($url: expr, $data: expr, $headers: expr) => {{
221        $crate::http_req!("PUT", $url, $data.into(), $headers)
222    }};
223    ($url: expr, $data: expr) => {{
224        $crate::http_put!($url, $data, Default::default())
225    }};
226}