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;