canic_core/api/ic/
http.rs

1use crate::{PublicError, dto::http as http_dto, ops::ic::http as http_ops};
2use candid::{CandidType, Nat};
3use serde::{Deserialize, Serialize, de::DeserializeOwned};
4
5///
6/// Http Api
7///
8/// Stable HTTP API for canic users.
9/// Enforces metrics, limits, and IC-safe defaults.
10///
11
12///
13/// ApiHttpRequest
14///
15
16#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
17pub struct ApiHttpRequest {
18    pub url: String,
19    pub max_response_bytes: Option<u64>,
20    pub method: ApiHttpMethod,
21    pub headers: Vec<ApiHttpHeader>,
22    pub body: Option<Vec<u8>>,
23    pub is_replicated: Option<bool>,
24}
25
26///
27/// ApiHttpResponse
28///
29
30#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
31pub struct ApiHttpResponse {
32    pub status: Nat,
33    pub headers: Vec<ApiHttpHeader>,
34    #[serde(with = "serde_bytes")]
35    pub body: Vec<u8>,
36}
37
38///
39/// ApiHttpMethod
40///
41
42#[derive(CandidType, Clone, Copy, Debug, Deserialize, Serialize)]
43pub enum ApiHttpMethod {
44    #[serde(rename = "get")]
45    GET,
46    #[serde(rename = "post")]
47    POST,
48    #[serde(rename = "head")]
49    HEAD,
50}
51
52///
53/// ApiHttpHeader
54///
55
56#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
57pub struct ApiHttpHeader {
58    pub name: String,
59    pub value: String,
60}
61
62/// Perform a GET request and deserialize a JSON response.
63/// Returns an error on non-2xx status codes or JSON decode failures.
64pub async fn get<T: DeserializeOwned>(
65    url: &str,
66    headers: &[(&str, &str)],
67) -> Result<T, PublicError> {
68    http_ops::get(url, headers).await.map_err(PublicError::from)
69}
70
71/// Same as `get`, with an explicit metrics label.
72/// Returns an error on non-2xx status codes or JSON decode failures.
73pub async fn get_with_label<T: DeserializeOwned>(
74    url: &str,
75    headers: &[(&str, &str)],
76    label: &str,
77) -> Result<T, PublicError> {
78    http_ops::get_with_label(url, headers, Some(label))
79        .await
80        .map_err(PublicError::from)
81}
82
83/// Perform a raw HTTP request with metrics, returning the response verbatim.
84pub async fn get_raw(args: ApiHttpRequest) -> Result<ApiHttpResponse, PublicError> {
85    http_ops::get_raw(args.into())
86        .await
87        .map(ApiHttpResponse::from)
88        .map_err(PublicError::from)
89}
90
91impl From<ApiHttpRequest> for http_dto::HttpRequestArgs {
92    fn from(req: ApiHttpRequest) -> Self {
93        Self {
94            url: req.url,
95            max_response_bytes: req.max_response_bytes,
96            method: req.method.into(),
97            headers: req.headers.into_iter().map(Into::into).collect(),
98            body: req.body,
99            is_replicated: req.is_replicated,
100        }
101    }
102}
103
104impl From<http_dto::HttpRequestResult> for ApiHttpResponse {
105    fn from(res: http_dto::HttpRequestResult) -> Self {
106        Self {
107            status: res.status,
108            headers: res.headers.into_iter().map(Into::into).collect(),
109            body: res.body,
110        }
111    }
112}
113
114impl From<ApiHttpMethod> for http_dto::HttpMethod {
115    fn from(method: ApiHttpMethod) -> Self {
116        match method {
117            ApiHttpMethod::GET => Self::GET,
118            ApiHttpMethod::POST => Self::POST,
119            ApiHttpMethod::HEAD => Self::HEAD,
120        }
121    }
122}
123
124impl From<ApiHttpHeader> for http_dto::HttpHeader {
125    fn from(header: ApiHttpHeader) -> Self {
126        Self {
127            name: header.name,
128            value: header.value,
129        }
130    }
131}
132
133impl From<http_dto::HttpHeader> for ApiHttpHeader {
134    fn from(header: http_dto::HttpHeader) -> Self {
135        Self {
136            name: header.name,
137            value: header.value,
138        }
139    }
140}