use std::{collections::HashMap, fs, path::Path};
use convert_case::{Case, Casing};
use dialoguer::{theme::ColorfulTheme, MultiSelect};
use serde::{Deserialize, Serialize};
use serde_json::from_str;
use crate::{
common::{find_candid, find_wasm},
ic_test_json::{CanisterSetup, IcpTestSetup},
};
#[derive(Debug, Serialize, Deserialize)]
pub struct DfxJson {
pub canisters: Option<HashMap<String, DfxCanister>>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct DfxCanister {
pub init_arg_file: Option<String>,
pub init_arg: Option<String>,
pub package: Option<String>,
pub wasm: Option<String>,
pub candid: Option<String>,
#[serde(rename = "type")]
pub canister_type: Option<String>,
pub dependencies: Option<Vec<String>>,
pub id: Option<String>,
pub specified_id: Option<String>,
}
pub fn add_canister(
canister_name: &str,
canister: &DfxCanister,
setup: &mut IcpTestSetup,
) -> Result<(), anyhow::Error> {
if canister.canister_type == Some("asset".to_string()) {
return Ok(());
}
let old_canister = setup.icp_setup.canisters.get(canister_name);
let candid = find_candid(canister_name, canister).map(|x| x.to_string_lossy().to_string());
let wasm = find_wasm(canister_name, canister, setup)?;
let generate_bindings =
wasm.is_some() && candid.is_some() && old_canister.is_none_or(|c| c.generate_bindings);
let mut canister_setup = CanisterSetup {
name: canister_name.to_string(),
init_args_rust: "".to_string(),
var_name: canister_name.to_case(Case::Snake),
service_name: format!("{canister_name}Canister").to_case(Case::Pascal),
candid_path: candid,
wasm,
specified_id: None,
init_arg_file: canister.init_arg_file.clone(),
init_arg: canister.init_arg.clone(),
generate_bindings,
};
canister_setup.specified_id = canister.specified_id.clone();
if let Some(old_canister) = old_canister {
if canister_setup.init_arg_file.is_none() {
canister_setup.init_arg_file = old_canister.init_arg_file.clone();
}
if canister_setup.init_arg.is_none() {
canister_setup.init_arg = old_canister.init_arg.clone();
}
}
let _ = setup
.icp_setup
.canisters
.insert(canister_name.to_string(), canister_setup);
Ok(())
}
fn check_dfx_json_exists(setup: &mut IcpTestSetup) -> anyhow::Result<()> {
if let Some(dfx_json) = &setup.icp_setup.dfx_json {
let dfx_json_path = Path::new(dfx_json);
if !(dfx_json_path.exists() || dfx_json_path.is_file()) {
return Err(anyhow::anyhow!("'dfx.json' not found! Make sure you are starting the ic-test at the root of your canister project."));
}
}
Ok(())
}
pub fn add_canisters(setup: &mut IcpTestSetup) -> anyhow::Result<()> {
if setup.icp_setup.skip_dfx_json {
return Ok(());
}
check_dfx_json_exists(setup)?;
let dfx_json = if let Some(dfx_json) = &setup.icp_setup.dfx_json {
dfx_json
} else {
return Ok(());
};
let dfx_json_path = Path::new(dfx_json);
let json_string = fs::read_to_string(dfx_json_path)?;
let json = from_str::<DfxJson>(&json_string)?;
if let Some(dfx_canisters) = &json.canisters {
if dfx_canisters.is_empty() {
return Err(anyhow::anyhow!(
"No canisters were found in the 'dfx.json' file!"
));
}
for (canister_name, canister) in dfx_canisters {
add_canister(canister_name, canister, setup)?;
}
if setup.ui {
let selectable_canister_names: Vec<_> = setup.icp_setup.canisters.keys().collect();
let defaults: Vec<_> = setup
.icp_setup
.canisters
.values()
.map(|x| x.generate_bindings)
.collect();
let selection = MultiSelect::with_theme(&ColorfulTheme::default())
.with_prompt("Which canister bindings do you want to generate?")
.items(&selectable_canister_names)
.defaults(&defaults)
.interact()
.unwrap();
setup.icp_setup.canisters.iter_mut().enumerate().for_each(
|(idx, (_name, canister))| {
canister.generate_bindings = selection.contains(&idx);
},
);
}
}
Ok(())
}