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