Hey! @qijing_yu
I often use the example apps and simplify them, and then change them to my use case.
I know that the most recent changes are to do with the transform function. Can you provide a tip on how I could just change that to allow this to keep working?
use candid::{CandidType, Principal};
use ic_cdk_macros::{self, query, update};
use ic_cdk::api::management_canister::http_request::{
HttpHeader, HttpMethod, TransformFunc, TransformType,
};
use serde::{Deserialize, Serialize};
use serde_json::{self, Value};
use std::collections::{HashMap};
#[derive(CandidType, Deserialize, Debug, Clone)]
pub struct CanisterHttpRequestArgs {
pub url: String,
pub max_response_bytes: Option<u64>,
pub headers: Vec<HttpHeader>,
pub body: Option<Vec<u8>>,
pub method: HttpMethod,
pub transform: Option<TransformType>,
}
#[derive(CandidType, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct CanisterHttpResponsePayload {
pub status: u128,
pub headers: Vec<HttpHeader>,
pub body: Vec<u8>,
}
// How many data points in each Coinbase API call. Maximum allowed is 300
pub const DATA_POINTS_PER_API: u64 = 200;
pub const MAX_RESPONSE_BYTES: u64 = 10 * 6 * DATA_POINTS_PER_API;
/*
A function to call IC http_request function with sample interval of REMOTE_FETCH_GRANULARITY seconds. Each API
call fetches DATA_POINTS_PER_API data points, which is equivalent of DATA_POINTS_PER_API minutes of data.
*/
#[update]
async fn get_rate() -> String {
let host = "api.coinbase.com";
let mut host_header = host.clone().to_owned();
host_header.push_str(":443");
// prepare system http_request call
let request_headers = vec![
HttpHeader {
name: "Host".to_string(),
value: host_header,
},
HttpHeader {
name: "User-Agent".to_string(),
value: "exchange_rate_canister".to_string(),
},
];
let url = format!("https://{host}/v2/prices/icp-usd/spot");
ic_cdk::api::print(url.clone());
let request = CanisterHttpRequestArgs {
url: url,
method: HttpMethod::GET,
body: None,
max_response_bytes: Some(MAX_RESPONSE_BYTES),
transform: Some(TransformType::Function(TransformFunc(candid::Func {
principal: ic_cdk::api::id(),
method: "transform".to_string(),
}))),
headers: request_headers,
};
let body = candid::utils::encode_one(&request).unwrap();
ic_cdk::api::print(format!("Making IC http_request call now."));
match ic_cdk::api::call::call_raw(
Principal::management_canister(),
"http_request",
&body[..],
2_000_000_000,
)
.await
{
Ok(result) => {
// decode the result
let decoded_result: CanisterHttpResponsePayload =
candid::utils::decode_one(&result).expect("IC http_request failed!");
let decoded_body = String::from_utf8(decoded_result.body)
.expect("Remote service response is not UTF-8 encoded.");
let fetch = decode_body_to_rates(&decoded_body);
return fetch.get("data").unwrap().get("amount").unwrap().to_string();
}
Err((r, m)) => {
let message =
format!("The http_request resulted into error. RejectionCode: {r:?}, Error: {m}");
ic_cdk::api::print(message.clone());
return message.clone().to_string();
}
}
}
fn decode_body_to_rates(body: &str) -> Value {
let json: serde_json::Value =
serde_json::from_str(&body).unwrap();
return json
}
#[query]
async fn transform(raw: CanisterHttpResponsePayload) -> CanisterHttpResponsePayload {
let mut sanitized = raw.clone();
sanitized.headers = vec![];
sanitized
}
fn main() {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_decode_body_to_rates() {
let body = "{\"data\":{\"base\":\"ICP\",\"currency\":\"USD\",\"amount\":\"5.96\"}}";
let fetched = decode_body_to_rates(body);
println!("{:?}", fetched.get("data").unwrap().get("amount").unwrap());
assert_eq!(*fetched.get("data").unwrap().get("amount").unwrap(), serde_json::json!("5.96"));
}
}