#![allow(clippy::needless_doctest_main)]
#![doc = include_str!("../README.md")]
use candid::Principal;
use candid_parser::bindings::rust::{
Config as BindgenConfig, ExternalConfig, emit_bindgen, output_handlebar,
};
use candid_parser::configs::Configs;
use candid_parser::pretty_check_file;
use std::fs;
use std::io::Write;
use std::path::PathBuf;
use std::str::FromStr;
#[derive(Debug)]
pub struct Config {
canister_name: String,
candid_path: PathBuf,
mode: Mode,
type_selector_config_path: Option<PathBuf>, }
#[derive(Debug)]
enum Mode {
TypesOnly,
StaticCallee { canister_id: Principal },
DynamicCallee { env_var_name: String },
}
impl Config {
pub fn new<N, P>(canister_name: N, candid_path: P) -> Self
where
N: Into<String>,
P: Into<PathBuf>,
{
Self {
canister_name: canister_name.into(),
candid_path: candid_path.into(),
mode: Mode::TypesOnly,
type_selector_config_path: None,
}
}
pub fn static_callee<S>(&mut self, canister_id: S) -> &mut Self
where
S: Into<Principal>,
{
if !matches!(self.mode, Mode::TypesOnly) {
panic!("The bindgen mode has already been set.");
}
self.mode = Mode::StaticCallee {
canister_id: canister_id.into(),
};
self
}
pub fn dynamic_callee<S>(&mut self, env_var_name: S) -> &mut Self
where
S: Into<String>,
{
if !matches!(self.mode, Mode::TypesOnly) {
panic!("The bindgen mode has already been set.");
}
self.mode = Mode::DynamicCallee {
env_var_name: env_var_name.into(),
};
self
}
pub fn set_type_selector_config<P>(&mut self, path: P) -> &mut Self
where
P: Into<PathBuf>,
{
self.type_selector_config_path = Some(path.into());
self
}
pub fn generate(&self) {
let type_selector_configs_str = match &self.type_selector_config_path {
Some(p) => {
println!("cargo:rerun-if-changed={}", p.display());
fs::read_to_string(p).unwrap_or_else(|e| {
panic!(
"failed to read the type selector config file ({}): {}",
p.display(),
e
)
})
}
None => "".to_string(),
};
let type_selector_configs = Configs::from_str(&type_selector_configs_str)
.unwrap_or_else(|e| panic!("failed to parse the type selector config: {}", e));
let rust_bindgen_config = BindgenConfig::new(type_selector_configs);
println!("cargo:rerun-if-changed={}", self.candid_path.display());
let (env, actor, prog) = pretty_check_file(&self.candid_path).unwrap_or_else(|e| {
panic!(
"failed to parse candid file ({}): {}",
self.candid_path.display(),
e
)
});
let (output, _unused) = emit_bindgen(&rust_bindgen_config, &env, &actor, &prog);
let mut external = ExternalConfig::default();
let content = match &self.mode {
Mode::StaticCallee { canister_id } => {
let template = include_str!("templates/static_callee.hbs");
external
.0
.insert("canister_id".to_string(), canister_id.to_string());
output_handlebar(output, external, template)
}
Mode::DynamicCallee { env_var_name } => {
let template = include_str!("templates/dynamic_callee.hbs");
external
.0
.insert("env_var_name".to_string(), env_var_name.to_string());
output_handlebar(output, external, template)
}
Mode::TypesOnly => {
let template = include_str!("templates/types_only.hbs");
output_handlebar(output, external, template)
}
};
let out_dir_str = std::env::var("OUT_DIR")
.expect("OUT_DIR should always be set when execute the build.rs script");
let out_dir = PathBuf::from(out_dir_str);
let generated_path = out_dir.join(format!("{}.rs", self.canister_name));
let mut file = fs::File::create(&generated_path).unwrap_or_else(|e| {
panic!(
"failed to create the output file ({}): {}",
generated_path.display(),
e
)
});
writeln!(file, "{content}").unwrap_or_else(|e| {
panic!(
"failed to write to the output file ({}): {}",
generated_path.display(),
e
)
});
}
}