diff --git a/cli/src/commands/codegen.rs b/cli/src/commands/codegen.rs index e9adbae52b..6d05b3e545 100644 --- a/cli/src/commands/codegen.rs +++ b/cli/src/commands/codegen.rs @@ -6,7 +6,7 @@ use crate::utils::FileOrUrl; use clap::Parser as ClapParser; use color_eyre::eyre; use color_eyre::eyre::eyre; -use subxt_codegen::{DerivesRegistry, TypeSubstitutes, TypeSubstitutionError}; +use subxt_codegen::{DerivesRegistry, GenerateRuntimeApi, TypeSubstitutes, TypeSubstitutionError}; /// Generate runtime API client code from metadata. /// @@ -185,16 +185,14 @@ fn codegen( } let should_gen_docs = !no_docs; - let runtime_api = subxt_codegen::generate_runtime_api_from_bytes( - item_mod, - metadata_bytes, - derives, - type_substitutes, - crate_path, - should_gen_docs, - runtime_types_only, - ) - .map_err(|code_gen_err| eyre!("{code_gen_err}"))?; + + let runtime_api = GenerateRuntimeApi::new(item_mod, crate_path) + .derives_registry(derives) + .type_substitutes(type_substitutes) + .generate_docs(should_gen_docs) + .runtime_types_only(runtime_types_only) + .generate_from_bytes(metadata_bytes) + .map_err(|code_gen_err| eyre!("{code_gen_err}"))?; writeln!(output, "{runtime_api}")?; Ok(()) } diff --git a/codegen/src/api/mod.rs b/codegen/src/api/mod.rs index da0645b880..c814c633d2 100644 --- a/codegen/src/api/mod.rs +++ b/codegen/src/api/mod.rs @@ -28,113 +28,164 @@ use quote::{format_ident, quote}; use std::{collections::HashMap, fs, io::Read, path, string::ToString}; use syn::parse_quote; -/// Generates the API for interacting with a Substrate runtime. -/// -/// # Arguments -/// -/// * `item_mod` - The module declaration for which the API is implemented. -/// * `path` - The path to the scale encoded metadata of the runtime node. -/// * `derives` - Provide custom derives for the generated types. -/// * `type_substitutes` - Provide custom type substitutes. -/// * `crate_path` - Path to the `subxt` crate. -/// * `should_gen_docs` - True if the generated API contains the documentation from the metadata. -/// * `runtime_types_only` - Whether to limit code generation to only runtime types. -/// -/// **Note:** This is a wrapper over [RuntimeGenerator] for static metadata use-cases. -pub fn generate_runtime_api_from_path

( +/// Generate the runtime API for interacting with a substrate runtime. +pub struct GenerateRuntimeApi { item_mod: syn::ItemMod, - path: P, derives: DerivesRegistry, type_substitutes: TypeSubstitutes, crate_path: CratePath, should_gen_docs: bool, runtime_types_only: bool, -) -> Result -where - P: AsRef, -{ - let to_err = |err| CodegenError::Io(path.as_ref().to_string_lossy().into(), err); - - let mut file = fs::File::open(&path).map_err(to_err)?; - let mut bytes = Vec::new(); - file.read_to_end(&mut bytes).map_err(to_err)?; - - generate_runtime_api_from_bytes( - item_mod, - &bytes, - derives, - type_substitutes, - crate_path, - should_gen_docs, - runtime_types_only, - ) + unstable_metadata: bool, } -/// Generates the API for interacting with a substrate runtime, using metadata -/// that can be downloaded from a node at the provided URL. This function blocks -/// while retrieving the metadata. -/// -/// # Arguments -/// -/// * `item_mod` - The module declaration for which the API is implemented. -/// * `url` - HTTP/WS URL to the substrate node you'd like to pull metadata from. -/// * `derives` - Provide custom derives for the generated types. -/// * `type_substitutes` - Provide custom type substitutes. -/// * `crate_path` - Path to the `subxt` crate. -/// * `should_gen_docs` - True if the generated API contains the documentation from the metadata. -/// * `runtime_types_only` - Whether to limit code generation to only runtime types. -/// -/// **Note:** This is a wrapper over [RuntimeGenerator] for static metadata use-cases. -pub fn generate_runtime_api_from_url( +impl GenerateRuntimeApi { + /// Construct a new [`GenerateRuntimeApi`]. + pub fn new(item_mod: syn::ItemMod, crate_path: CratePath) -> Self { + GenerateRuntimeApi { + item_mod, + derives: DerivesRegistry::new(), + type_substitutes: TypeSubstitutes::new(), + crate_path, + should_gen_docs: false, + runtime_types_only: false, + unstable_metadata: false, + } + } + + /// Provide custom derives for the generated types. + /// + /// Default is no derives. + pub fn derives_registry(mut self, derives: DerivesRegistry) -> Self { + self.derives = derives; + self + } + + /// Provide custom type substitutes. + /// + /// Default is no substitutes. + pub fn type_substitutes(mut self, type_substitutes: TypeSubstitutes) -> Self { + self.type_substitutes = type_substitutes; + self + } + + /// True if the generated API contains the documentation from the metadata. + /// + /// Default: false. + pub fn generate_docs(mut self, should_gen_docs: bool) -> Self { + self.should_gen_docs = should_gen_docs; + self + } + + /// Whether to limit code generation to only runtime types. + /// + /// Default: false. + pub fn runtime_types_only(mut self, runtime_types_only: bool) -> Self { + self.runtime_types_only = runtime_types_only; + self + } + + /// Whether to fetch the unstable metadata first. + /// + /// # Note + /// + /// This takes effect only if the API is generated from URL. + /// + /// Default: false. + pub fn unstable_metadata(mut self, unstable_metadata: bool) -> Self { + self.unstable_metadata = unstable_metadata; + self + } + + /// Generate the runtime API from path. + pub fn generate_from_path

(self, path: P) -> Result + where + P: AsRef, + { + let to_err = |err| CodegenError::Io(path.as_ref().to_string_lossy().into(), err); + + let mut file = fs::File::open(&path).map_err(to_err)?; + let mut bytes = Vec::new(); + file.read_to_end(&mut bytes).map_err(to_err)?; + + let metadata = Metadata::decode(&mut &bytes[..])?; + + generate_runtime_api_with_metadata( + self.item_mod, + metadata, + self.derives, + self.type_substitutes, + self.crate_path, + self.should_gen_docs, + self.runtime_types_only, + ) + } + + /// Generate the runtime API from the provided metadata bytes. + pub fn generate_from_bytes(self, bytes: &[u8]) -> Result { + let metadata = Metadata::decode(&mut &bytes[..])?; + + generate_runtime_api_with_metadata( + self.item_mod, + metadata, + self.derives, + self.type_substitutes, + self.crate_path, + self.should_gen_docs, + self.runtime_types_only, + ) + } + + /// Generate the runtime API from URL. + /// + /// The metadata will be downloaded from a node at the provided URL. + /// This function blocks while retrieving the metadata. + /// + /// # Warning + /// + /// Not recommended to be used in production environments. + pub fn generate_from_url(self, url: &Uri) -> Result { + fn fetch_metadata(url: &Uri, version: MetadataVersion) -> Result { + let bytes = fetch_metadata_bytes_blocking(url, version)?; + Ok(Metadata::decode(&mut &bytes[..])?) + } + + let metadata = self + .unstable_metadata + .then(|| fetch_metadata(url, MetadataVersion::Unstable).ok()) + .flatten(); + + let metadata = if let Some(unstable) = metadata { + unstable + } else { + match fetch_metadata(url, MetadataVersion::Version(15)) { + Ok(metadata) => metadata, + Err(_) => fetch_metadata(url, MetadataVersion::Version(14))?, + } + }; + + generate_runtime_api_with_metadata( + self.item_mod, + metadata, + self.derives, + self.type_substitutes, + self.crate_path, + self.should_gen_docs, + self.runtime_types_only, + ) + } +} + +/// Generates the API for interacting with a substrate runtime, using the `subxt::Metadata`. +fn generate_runtime_api_with_metadata( item_mod: syn::ItemMod, - url: &Uri, + metadata: Metadata, derives: DerivesRegistry, type_substitutes: TypeSubstitutes, crate_path: CratePath, should_gen_docs: bool, runtime_types_only: bool, ) -> Result { - // Fetch latest unstable version, if that fails fall back to the latest stable. - let bytes = match fetch_metadata_bytes_blocking(url, MetadataVersion::Unstable) { - Ok(bytes) => bytes, - Err(_) => fetch_metadata_bytes_blocking(url, MetadataVersion::Latest)?, - }; - - generate_runtime_api_from_bytes( - item_mod, - &bytes, - derives, - type_substitutes, - crate_path, - should_gen_docs, - runtime_types_only, - ) -} - -/// Generates the API for interacting with a substrate runtime, using metadata bytes. -/// -/// # Arguments -/// -/// * `item_mod` - The module declaration for which the API is implemented. -/// * `bytes` - The raw metadata bytes. -/// * `derives` - Provide custom derives for the generated types. -/// * `type_substitutes` - Provide custom type substitutes. -/// * `crate_path` - Path to the `subxt` crate. -/// * `should_gen_docs` - True if the generated API contains the documentation from the metadata. -/// * `runtime_types_only` - Whether to limit code generation to only runtime types. -/// -/// **Note:** This is a wrapper over [RuntimeGenerator] for static metadata use-cases. -pub fn generate_runtime_api_from_bytes( - item_mod: syn::ItemMod, - bytes: &[u8], - derives: DerivesRegistry, - type_substitutes: TypeSubstitutes, - crate_path: CratePath, - should_gen_docs: bool, - runtime_types_only: bool, -) -> Result { - let metadata = Metadata::decode(&mut &bytes[..])?; - let generator = RuntimeGenerator::new(metadata); if runtime_types_only { generator.generate_runtime_types( @@ -164,8 +215,7 @@ impl RuntimeGenerator { /// Create a new runtime generator from the provided metadata. /// /// **Note:** If you have the metadata path, URL or bytes to hand, prefer to use - /// one of the `generate_runtime_api_from_*` functions for generating the runtime API - /// from that. + /// `GenerateRuntimeApi` for generating the runtime API from that. /// /// # Panics /// diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 2719c106a6..be2e27e0d2 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -53,10 +53,7 @@ mod types; pub mod utils; pub use self::{ - api::{ - generate_runtime_api_from_bytes, generate_runtime_api_from_path, - generate_runtime_api_from_url, RuntimeGenerator, - }, + api::{GenerateRuntimeApi, RuntimeGenerator}, error::{CodegenError, TypeSubstitutionError}, types::{CratePath, Derives, DerivesRegistry, Module, TypeGenerator, TypeSubstitutes}, }; diff --git a/macro/src/lib.rs b/macro/src/lib.rs index 8954679305..53190aec0e 100644 --- a/macro/src/lib.rs +++ b/macro/src/lib.rs @@ -9,7 +9,9 @@ use std::str::FromStr; use darling::{ast::NestedMeta, FromMeta}; use proc_macro::TokenStream; use proc_macro_error::{abort_call_site, proc_macro_error}; -use subxt_codegen::{utils::Uri, CodegenError, DerivesRegistry, TypeSubstitutes}; +use subxt_codegen::{ + utils::Uri, CodegenError, DerivesRegistry, GenerateRuntimeApi, TypeSubstitutes, +}; use syn::{parse_macro_input, punctuated::Punctuated}; #[derive(Clone, Debug)] @@ -47,6 +49,8 @@ struct RuntimeMetadataArgs { no_default_derives: bool, #[darling(default)] no_default_substitutions: bool, + #[darling(default)] + unstable_metadata: darling::util::Flag, } #[derive(Debug, FromMeta)] @@ -131,36 +135,39 @@ pub fn subxt(args: TokenStream, input: TokenStream) -> TokenStream { } let should_gen_docs = args.generate_docs.is_present(); + let unstable_metadata = args.unstable_metadata.is_present(); + + let runtime_api_generator = GenerateRuntimeApi::new(item_mod, crate_path) + .derives_registry(derives_registry) + .type_substitutes(type_substitutes) + .generate_docs(should_gen_docs) + .runtime_types_only(args.runtime_types_only); + match (args.runtime_metadata_path, args.runtime_metadata_url) { (Some(rest_of_path), None) => { + if unstable_metadata { + abort_call_site!( + "The 'unstable_metadata' attribute requires `runtime_metadata_url`" + ) + } + let root = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".into()); let root_path = std::path::Path::new(&root); let path = root_path.join(rest_of_path); - subxt_codegen::generate_runtime_api_from_path( - item_mod, - path, - derives_registry, - type_substitutes, - crate_path, - should_gen_docs, - args.runtime_types_only, - ) - .map_or_else(|err| err.into_compile_error().into(), Into::into) + + runtime_api_generator + .generate_from_path(path) + .map_or_else(|err| err.into_compile_error().into(), Into::into) } (None, Some(url_string)) => { let url = Uri::from_str(&url_string).unwrap_or_else(|_| { abort_call_site!("Cannot download metadata; invalid url: {}", url_string) }); - subxt_codegen::generate_runtime_api_from_url( - item_mod, - &url, - derives_registry, - type_substitutes, - crate_path, - should_gen_docs, - args.runtime_types_only, - ) - .map_or_else(|err| err.into_compile_error().into(), Into::into) + + runtime_api_generator + .unstable_metadata(unstable_metadata) + .generate_from_url(&url) + .map_or_else(|err| err.into_compile_error().into(), Into::into) } (None, None) => { abort_call_site!( diff --git a/subxt/src/lib.rs b/subxt/src/lib.rs index cb0eb088d9..4623d19aa3 100644 --- a/subxt/src/lib.rs +++ b/subxt/src/lib.rs @@ -279,6 +279,7 @@ pub mod ext { /// )] /// mod polkadot {} /// ``` +/// /// ## `no_default_derives` /// /// By default, the macro will add all derives necessary for the generated code to play nicely with Subxt. Adding this attribute @@ -298,4 +299,19 @@ pub mod ext { /// `scale_decode::DecodeAsType` (because we add `#[codec(..)]` attributes on some fields/types during codegen), and you must use this /// feature in conjunction with `runtime_types_only` (or manually specify a bunch of defaults to make codegen work properly when /// generating the subxt interfaces). +/// +/// ## `unstable_metadata` +/// +/// This attribute works only in combination with `runtime_metadata_url`. By default, the macro will fetch the latest stable +/// version of the metadata from the target node. This attribute makes the codegen attempt to fetch the unstable version of +/// the metadata first. This is **not recommended** in production code, since the unstable metadata a node is providing is likely +/// to be incompatible with Subxt. +/// +/// ```rust,no_run +/// #[subxt::subxt( +/// runtime_metadata_url = "wss://rpc.polkadot.io:443", +/// unstable_metadata +/// )] +/// mod polkadot {} +/// ``` pub use subxt_macro::subxt;