Skip to main content

canic_host/icp/response/
mod.rs

1//! Module: icp::response
2//!
3//! Responsibility: decode the canonical ICP CLI JSON response envelope.
4//! Does not own: command execution, endpoint DTOs, or operator rendering.
5//! Boundary: unwraps top-level `response_bytes` and decodes typed Candid values.
6
7#[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///
24/// IcpJsonResponseError
25///
26/// Typed failure while decoding one ICP CLI JSON response envelope.
27///
28
29#[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
47/// Decode a plain Candid value from the canonical ICP CLI JSON envelope.
48pub 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
56/// Decode a `Result<T, canic_core::dto::error::Error>` from the canonical envelope.
57pub 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}