canic_host/icp/response/
mod.rs1#[cfg(test)]
8mod tests;
9
10use candid::CandidType;
11use canic_core::{
12 cdk::utils::hash::{DecodeHexError, decode_hex},
13 dto::error::Error as CanicError,
14};
15use serde::{Deserialize, de::DeserializeOwned};
16use thiserror::Error as ThisError;
17
18#[derive(Deserialize)]
19struct IcpJsonResponseEnvelope {
20 response_bytes: Option<String>,
21}
22
23#[derive(Debug, ThisError)]
30pub enum IcpJsonResponseError {
31 #[error("ICP response_bytes Candid was invalid: {0}")]
32 Candid(#[source] candid::Error),
33
34 #[error("ICP response_bytes was invalid hexadecimal: {0}")]
35 Hex(#[source] DecodeHexError),
36
37 #[error("ICP response was invalid JSON: {0}")]
38 Json(#[source] serde_json::Error),
39
40 #[error("ICP JSON response is missing top-level string `response_bytes`")]
41 MissingResponseBytes,
42
43 #[error("canister rejected request: {0}")]
44 Rejected(CanicError),
45}
46
47pub fn decode_json_response<T>(output: &str) -> Result<T, IcpJsonResponseError>
49where
50 T: CandidType + DeserializeOwned,
51{
52 let bytes = response_bytes(output)?;
53 candid::decode_one(&bytes).map_err(IcpJsonResponseError::Candid)
54}
55
56pub fn decode_json_result_response<T>(output: &str) -> Result<T, IcpJsonResponseError>
58where
59 T: CandidType + DeserializeOwned,
60{
61 let bytes = response_bytes(output)?;
62 let response = candid::decode_one::<Result<T, CanicError>>(&bytes)
63 .map_err(IcpJsonResponseError::Candid)?;
64 response.map_err(IcpJsonResponseError::Rejected)
65}
66
67fn response_bytes(output: &str) -> Result<Vec<u8>, IcpJsonResponseError> {
68 let envelope = serde_json::from_str::<IcpJsonResponseEnvelope>(output)
69 .map_err(IcpJsonResponseError::Json)?;
70 let response_bytes = envelope
71 .response_bytes
72 .ok_or(IcpJsonResponseError::MissingResponseBytes)?;
73 decode_hex(&response_bytes).map_err(IcpJsonResponseError::Hex)
74}