Files
pezkuwi-subxt/cli/src/commands/explore/runtime_apis/mod.rs
T
pezkuwichain b8ee6a084f Rebrand subxt to pezkuwi-subxt with pezsp_runtime support
- Renamed all crate names from subxt-* to pezkuwi-subxt-*
- Updated codegen to use pezsp_runtime, pezsp_core, pezframe_support instead of sp_runtime, sp_core, frame_support
- Replaced all internal references from subxt_* to pezkuwi_subxt_*
- Added local path dependencies to Pezkuwi SDK crates
- Updated workspace configuration for edition 2024
2025-12-19 16:00:14 +03:00

205 lines
6.5 KiB
Rust

use crate::utils::{
FileOrUrl, Indent, SyntaxHighlight, create_client, fields_composite_example,
fields_description, first_paragraph_of_docs, parse_string_into_scale_value,
};
use color_eyre::{
eyre::{bail, eyre},
owo_colors::OwoColorize,
};
use indoc::{formatdoc, writedoc};
use scale_typegen_description::type_description;
use scale_value::Value;
use subxt::{
Metadata,
ext::{scale_decode::DecodeAsType, scale_encode::EncodeAsType},
};
use pezkuwi_subxt_metadata::RuntimeApiMetadata;
/// Runs for a specified runtime API trait.
/// Cases to consider:
/// ```text
/// method is:
/// None => Show pallet docs + available methods
/// Some (invalid) => Show Error + available methods
/// Some (valid) => Show method docs + output type description
/// execute is:
/// false => Show input type description + Example Value
/// true => validate (trailing args + build node connection)
/// validation is:
/// Err => Show Error
/// Ok => Make a runtime api call with the provided args.
/// response is:
/// Err => Show Error
/// Ok => Show the result
/// ```
pub async fn run<'a>(
method: Option<String>,
execute: bool,
trailing_args: Vec<String>,
runtime_api_metadata: RuntimeApiMetadata<'a>,
metadata: &'a Metadata,
file_or_url: FileOrUrl,
output: &mut impl std::io::Write,
) -> color_eyre::Result<()> {
let api_name = runtime_api_metadata.name();
let usage = || {
let methods = methods_to_string(&runtime_api_metadata);
formatdoc! {"
Usage:
subxt explore api {api_name} <METHOD>
explore a specific runtime api method
{methods}
"}
};
// If method is None: Show pallet docs + available methods
let Some(method_name) = method else {
let doc_string = first_paragraph_of_docs(runtime_api_metadata.docs()).indent(4);
if !doc_string.is_empty() {
writedoc! {output, "
Description:
{doc_string}
"}?;
}
writeln!(output, "{}", usage())?;
return Ok(());
};
// If method is invalid: Show Error + available methods
let Some(method) = runtime_api_metadata
.methods()
.find(|e| e.name().eq_ignore_ascii_case(&method_name))
else {
return Err(eyre!(
"\"{method_name}\" method not found for \"{method_name}\" runtime api!\n\n{}",
usage()
));
};
// redeclare to not use the wrong capitalization of the input from here on:
let method_name = method.name();
// Method is valid. Show method docs + output type description
let doc_string = first_paragraph_of_docs(method.docs()).indent(4);
if !doc_string.is_empty() {
writedoc! {output, "
Description:
{doc_string}
"}?;
}
let input_value_placeholder = "<INPUT_VALUE>".blue();
// Output type description
let input_values = || {
if method.inputs().len() == 0 {
return format!("The method does not require an {input_value_placeholder}");
}
let fields: Vec<(Option<&str>, u32)> =
method.inputs().map(|f| (Some(&*f.name), f.id)).collect();
let fields_description =
fields_description(&fields, method.name(), metadata.types()).indent(4);
let fields_example =
fields_composite_example(method.inputs().map(|e| e.id), metadata.types())
.indent(4)
.highlight();
formatdoc! {"
The method expects an {input_value_placeholder} with this shape:
{fields_description}
For example you could provide this {input_value_placeholder}:
{fields_example}"}
};
let execute_usage = || {
let output = type_description(method.output_ty(), metadata.types(), true)
.expect("No Type Description")
.indent(4)
.highlight();
let input = input_values();
formatdoc! {"
Usage:
subxt explore api {api_name} {method_name} --execute {input_value_placeholder}
make a runtime api request
The Output of this method has the following shape:
{output}
{input}"}
};
writeln!(output, "{}", execute_usage())?;
if !execute {
return Ok(());
}
if trailing_args.len() != method.inputs().len() {
bail!(
"The number of trailing arguments you provided after the `execute` flag does not match the expected number of inputs!\n{}",
execute_usage()
);
}
// encode each provided input as bytes of the correct type:
let args_data: Vec<Value> = method
.inputs()
.zip(trailing_args.iter())
.map(|(ty, arg)| {
let value = parse_string_into_scale_value(arg)?;
let value_str = value.indent(4);
// convert to bytes:
writedoc! {output, "
You submitted the following {input_value_placeholder}:
{value_str}
"}?;
// encode, then decode. This ensures that the scale value is of the correct shape for the param:
let bytes = value.encode_as_type(ty.id, metadata.types())?;
let value = Value::decode_as_type(&mut &bytes[..], ty.id, metadata.types())?;
Ok(value)
})
.collect::<color_eyre::Result<Vec<Value>>>()?;
let method_call =
subxt::dynamic::runtime_api_call::<_, Value>(api_name, method.name(), args_data);
let client = create_client(&file_or_url).await?;
let output_value = client
.runtime_api()
.at_latest()
.await?
.call(method_call)
.await?;
let output_value = output_value.to_string().highlight();
writedoc! {output, "
Returned value:
{output_value}
"}?;
Ok(())
}
fn methods_to_string(runtime_api_metadata: &RuntimeApiMetadata<'_>) -> String {
let api_name = runtime_api_metadata.name();
if runtime_api_metadata.methods().len() == 0 {
return format!("No <METHOD>'s available for the \"{api_name}\" runtime api.");
}
let mut output = format!("Available <METHOD>'s available for the \"{api_name}\" runtime api:");
let mut strings: Vec<_> = runtime_api_metadata.methods().map(|e| e.name()).collect();
strings.sort();
for variant in strings {
output.push_str("\n ");
output.push_str(variant);
}
output
}