#![warn(missing_docs)]
#![deny(clippy::panic)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::expect_used)]
#![deny(clippy::unimplemented)]
#[cfg(test)]
mod tests;
use anyhow::{anyhow, Context};
use candid::types::value::IDLValue;
use candid_parser::{
parse_idl_args,
types::{IDLProg, IDLType, IDLTypes},
IDLArgs,
};
use clap::Parser;
use idl2json::{
idl2json, idl2json_with_weak_names, idl_args2json_with_weak_names, polyfill, BytesFormat,
Idl2JsonOptions,
};
use std::{path::PathBuf, str::FromStr};
pub fn main(args: &Args, idl_str: &str) -> anyhow::Result<String> {
let idl_args: IDLArgs = parse_idl_args(idl_str).with_context(|| anyhow!("Malformed input"))?;
let idl2json_options = {
let progs: anyhow::Result<Vec<IDLProg>> = args
.did
.iter()
.map(|did| {
let did_as_str = std::fs::read_to_string(did)
.with_context(|| anyhow!("Could not read did file '{}'.", did.display()))?;
IDLProg::from_str(&did_as_str)
.with_context(|| anyhow!("Failed to parse did file '{}'", did.display()))
})
.collect();
let progs = progs?;
Idl2JsonOptions {
prog: progs,
bytes_as: args.bytes_as,
compact: args.compact,
..Idl2JsonOptions::default()
}
};
if args.init {
let idl_types = polyfill::idl_prog::get_init_arg_type(
idl2json_options
.prog
.first()
.context("Please specify which .did file to use.")?,
)
.context("Failed to get the service argument from the did file.")?;
serde_json::to_string(&idl_args2json_with_weak_names(
&idl_args,
&idl_types,
&idl2json_options,
))
.context("Failed to serialize to json")
} else if let Some(idl_type) = &args.typ {
if idl_type.trim().starts_with('(') {
let idl_types = IDLTypes::from_str(idl_type).context("Failed to parse type")?;
serde_json::to_string(&idl_args2json_with_weak_names(
&idl_args,
&idl_types,
&idl2json_options,
))
.context("Failed to serialize to json")
} else {
let idl_type = IDLType::from_str(idl_type).context("Failed to parse type")?;
convert_all(&idl_args, &Some(idl_type), &idl2json_options)
}
} else {
convert_all(&idl_args, &None, &idl2json_options)
}
}
fn convert_one(
idl_value: &IDLValue,
idl_type: &Option<IDLType>,
idl2json_options: &Idl2JsonOptions,
) -> anyhow::Result<String> {
let json_value = if let Some(idl_type) = idl_type {
idl2json_with_weak_names(idl_value, idl_type, idl2json_options)
} else {
idl2json(idl_value, idl2json_options)
};
(if idl2json_options.compact {
serde_json::to_string
} else {
serde_json::to_string_pretty
})(&json_value)
.with_context(|| anyhow!("Cannot print to stderr"))
}
fn convert_all(
idl_args: &IDLArgs,
idl_type: &Option<IDLType>,
idl2json_options: &Idl2JsonOptions,
) -> anyhow::Result<String> {
let json_structures: anyhow::Result<Vec<String>> = idl_args
.args
.iter()
.map(|idl_value| convert_one(idl_value, idl_type, idl2json_options))
.collect();
Ok(json_structures?.join("\n"))
}
#[derive(Parser, Debug, Default)]
#[clap(name("idl2json"), version = concat!(env!("CARGO_PKG_VERSION"), "\ncandid ", env!("CARGO_CANDID_VERSION")))]
pub struct Args {
#[clap(short, long)]
did: Vec<PathBuf>,
#[clap(short, long)]
typ: Option<String>,
#[clap(short, long, requires("did"))]
init: bool,
#[clap(short, long, arg_enum)]
bytes_as: Option<BytesFormat>,
#[clap(short, long)]
compact: bool,
}