diff --git a/Cargo.lock b/Cargo.lock index fc4ac0c6c8..abcf705853 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4312,6 +4312,7 @@ name = "subxt-macro" version = "0.32.1" dependencies = [ "darling 0.20.3", + "parity-scale-codec", "proc-macro-error", "subxt-codegen", "syn 2.0.37", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 68d67c96e9..9b5ef68298 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -17,7 +17,7 @@ name = "subxt" path = "src/main.rs" [dependencies] -subxt-codegen = { workspace = true } +subxt-codegen = { workspace = true, features = ["fetch-metadata"] } subxt-metadata = { workspace = true } subxt = { workspace = true, features = ["native", "jsonrpsee"] } clap = { workspace = true } diff --git a/cli/src/commands/codegen.rs b/cli/src/commands/codegen.rs index 6d05b3e545..2f1a2abef1 100644 --- a/cli/src/commands/codegen.rs +++ b/cli/src/commands/codegen.rs @@ -4,9 +4,9 @@ use crate::utils::FileOrUrl; use clap::Parser as ClapParser; -use color_eyre::eyre; +use codec::Decode; use color_eyre::eyre::eyre; -use subxt_codegen::{DerivesRegistry, GenerateRuntimeApi, TypeSubstitutes, TypeSubstitutionError}; +use subxt_codegen::CodegenBuilder; /// Generate runtime API client code from metadata. /// @@ -132,67 +132,77 @@ fn codegen( no_default_substitutions: bool, output: &mut impl std::io::Write, ) -> color_eyre::Result<()> { - let item_mod = syn::parse_quote!( - pub mod api {} - ); + let mut codegen = CodegenBuilder::new(); - let universal_derives = raw_derives + // Use the provided crate path: + if let Some(crate_path) = crate_path { + let crate_path = + syn::parse_str(&crate_path).map_err(|e| eyre!("Cannot parse crate path: {e}"))?; + codegen.set_subxt_crate_path(crate_path); + } + + // Respect the boolean flags: + if runtime_types_only { + codegen.runtime_types_only() + } + if no_default_derives { + codegen.disable_default_derives() + } + if no_default_substitutions { + codegen.disable_default_substitutes() + } + if no_docs { + codegen.no_docs() + } + + // Configure derives: + let global_derives = raw_derives .iter() .map(|raw| syn::parse_str(raw)) - .collect::, _>>()?; + .collect::, _>>() + .map_err(|e| eyre!("Cannot parse global derives: {e}"))?; + codegen.set_additional_global_derives(global_derives); + + for (ty_str, derive) in derives_for_type { + let ty: syn::TypePath = syn::parse_str(&ty_str) + .map_err(|e| eyre!("Cannot parse derive for type {ty_str}: {e}"))?; + let derive = syn::parse_str(&derive) + .map_err(|e| eyre!("Cannot parse derive for type {ty_str}: {e}"))?; + codegen.add_derives_for_type(ty, std::iter::once(derive)); + } + + // Configure attribtues: let universal_attributes = raw_attributes .iter() .map(|raw| syn::parse_str(raw)) .map(|attr: syn::Result| attr.map(|attr| attr.0)) - .collect::, _>>()?; - - let crate_path = crate_path.map(Into::into).unwrap_or_default(); - let mut derives = if no_default_derives { - DerivesRegistry::new() - } else { - DerivesRegistry::with_default_derives(&crate_path) - }; - derives.extend_for_all(universal_derives, universal_attributes); - - for (ty, derive) in derives_for_type { - let ty = syn::parse_str(&ty)?; - let derive = syn::parse_str(&derive)?; - derives.extend_for_type(ty, std::iter::once(derive), vec![]); + .collect::, _>>() + .map_err(|e| eyre!("Cannot parse global attributes: {e}"))?; + codegen.set_additional_global_attributes(universal_attributes); + + for (ty_str, attr) in attributes_for_type { + let ty = syn::parse_str(&ty_str) + .map_err(|e| eyre!("Cannot parse attribute for type {ty_str}: {e}"))?; + let attribute: OuterAttribute = syn::parse_str(&attr) + .map_err(|e| eyre!("Cannot parse attribute for type {ty_str}: {e}"))?; + codegen.add_attributes_for_type(ty, std::iter::once(attribute.0)); } - for (ty, attr) in attributes_for_type { - let ty = syn::parse_str(&ty)?; - let attribute: OuterAttribute = syn::parse_str(&attr)?; - derives.extend_for_type(ty, vec![], std::iter::once(attribute.0)); - } - - let mut type_substitutes = if no_default_substitutions { - TypeSubstitutes::new() - } else { - TypeSubstitutes::with_default_substitutes(&crate_path) - }; + // Insert type substitutions: for (from_str, to_str) in substitute_types { - let from: syn::Path = syn::parse_str(&from_str)?; - let to: syn::Path = syn::parse_str(&to_str)?; - let to = to.try_into().map_err(|e: TypeSubstitutionError| { - eyre::eyre!("Cannot parse substitute '{from_str}={to_str}': {e}") - })?; - type_substitutes - .insert(from, to) - .map_err(|e: TypeSubstitutionError| { - eyre::eyre!("Cannot parse substitute '{from_str}={to_str}': {e}") - })?; + let from: syn::Path = syn::parse_str(&from_str) + .map_err(|e| eyre!("Cannot parse type substitution for path {from_str}: {e}"))?; + let to: syn::Path = syn::parse_str(&to_str) + .map_err(|e| eyre!("Cannot parse type substitution for path {from_str}: {e}"))?; + codegen.set_type_substitute(from, to); } - let should_gen_docs = !no_docs; + let metadata = subxt_metadata::Metadata::decode(&mut &*metadata_bytes) + .map_err(|e| eyre!("Cannot decode the provided metadata: {e}"))?; + let code = codegen + .generate(metadata) + .map_err(|e| eyre!("Cannot generate code: {e}"))?; - 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}")?; + writeln!(output, "{code}")?; Ok(()) } diff --git a/cli/src/commands/compatibility.rs b/cli/src/commands/compatibility.rs index 614af7f08b..d4264ddd55 100644 --- a/cli/src/commands/compatibility.rs +++ b/cli/src/commands/compatibility.rs @@ -8,7 +8,7 @@ use color_eyre::eyre::WrapErr; use jsonrpsee::client_transport::ws::Url; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use subxt_codegen::utils::MetadataVersion; +use subxt_codegen::fetch_metadata::MetadataVersion; use subxt_metadata::Metadata; /// Verify metadata compatibility between substrate nodes. @@ -128,7 +128,7 @@ async fn fetch_runtime_metadata( url: Url, version: MetadataVersion, ) -> color_eyre::Result { - let bytes = subxt_codegen::utils::fetch_metadata_bytes(url, version).await?; + let bytes = subxt_codegen::fetch_metadata::fetch_metadata_from_url(url, version).await?; let metadata = Metadata::decode(&mut &bytes[..])?; Ok(metadata) } diff --git a/cli/src/utils.rs b/cli/src/utils.rs index f1f788fb64..82f202a92b 100644 --- a/cli/src/utils.rs +++ b/cli/src/utils.rs @@ -8,7 +8,7 @@ use color_eyre::eyre; use std::str::FromStr; use std::{fs, io::Read, path::PathBuf}; -use subxt_codegen::utils::{MetadataVersion, Url}; +use subxt_codegen::fetch_metadata::{fetch_metadata_from_url, MetadataVersion, Url}; pub mod type_description; pub mod type_example; @@ -57,18 +57,13 @@ impl FileOrUrl { eyre::bail!("`--file` is incompatible with `--version`") } // Fetch from --url - (None, Some(uri), version) => Ok(subxt_codegen::utils::fetch_metadata_bytes( - uri.clone(), - version.unwrap_or_default(), - ) - .await?), + (None, Some(uri), version) => { + Ok(fetch_metadata_from_url(uri.clone(), version.unwrap_or_default()).await?) + } // Default if neither is provided; fetch from local url (None, None, version) => { let url = Url::parse("ws://localhost:9944").expect("Valid URL; qed"); - Ok( - subxt_codegen::utils::fetch_metadata_bytes(url, version.unwrap_or_default()) - .await?, - ) + Ok(fetch_metadata_from_url(url, version.unwrap_or_default()).await?) } } } diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index a48b3d546d..57c985282b 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -13,7 +13,7 @@ homepage.workspace = true description = "Generate an API for interacting with a substrate node from FRAME metadata" [features] -default = ["fetch-metadata"] +default = [] fetch-metadata = ["dep:jsonrpsee", "dep:tokio", "dep:frame-metadata"] web = ["jsonrpsee?/async-wasm-client", "jsonrpsee?/client-web-transport", "getrandom/js"] diff --git a/codegen/src/api/calls.rs b/codegen/src/api/calls.rs index 3d57575eac..74356266e0 100644 --- a/codegen/src/api/calls.rs +++ b/codegen/src/api/calls.rs @@ -3,10 +3,7 @@ // see LICENSE for license details. use super::CodegenError; -use crate::{ - types::{CompositeDefFields, TypeGenerator}, - CratePath, -}; +use crate::types::{CompositeDefFields, TypeGenerator}; use heck::{ToSnakeCase as _, ToUpperCamelCase as _}; use proc_macro2::TokenStream as TokenStream2; use quote::{format_ident, quote}; @@ -25,7 +22,7 @@ pub fn generate_calls( type_gen: &TypeGenerator, pallet: &PalletMetadata, types_mod_ident: &syn::Ident, - crate_path: &CratePath, + crate_path: &syn::Path, should_gen_docs: bool, ) -> Result { // Early return if the pallet has no calls. diff --git a/codegen/src/api/constants.rs b/codegen/src/api/constants.rs index 3a7ad9ed0f..31fa7526c4 100644 --- a/codegen/src/api/constants.rs +++ b/codegen/src/api/constants.rs @@ -2,7 +2,7 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -use crate::{types::TypeGenerator, CratePath}; +use crate::types::TypeGenerator; use heck::ToSnakeCase as _; use proc_macro2::TokenStream as TokenStream2; use quote::{format_ident, quote}; @@ -37,7 +37,7 @@ pub fn generate_constants( type_gen: &TypeGenerator, pallet: &PalletMetadata, types_mod_ident: &syn::Ident, - crate_path: &CratePath, + crate_path: &syn::Path, should_gen_docs: bool, ) -> Result { // Early return if the pallet has no constants. diff --git a/codegen/src/api/custom_values.rs b/codegen/src/api/custom_values.rs index 2a0f297d4b..1ef1381e19 100644 --- a/codegen/src/api/custom_values.rs +++ b/codegen/src/api/custom_values.rs @@ -4,7 +4,7 @@ use std::collections::HashSet; -use crate::{types::TypeGenerator, CratePath}; +use crate::types::TypeGenerator; use heck::ToSnakeCase as _; use subxt_metadata::{CustomValueMetadata, Metadata}; @@ -12,10 +12,10 @@ use proc_macro2::TokenStream as TokenStream2; use quote::{quote, ToTokens}; /// Generate the custom values mod, if there are any custom values in the metadata. Else returns None. -pub fn generate_custom_values<'a>( - metadata: &'a Metadata, - type_gen: &'a TypeGenerator, - crate_path: &'a CratePath, +pub fn generate_custom_values( + metadata: &Metadata, + type_gen: &TypeGenerator, + crate_path: &syn::Path, ) -> TokenStream2 { let mut fn_names_taken = HashSet::new(); let custom = metadata.custom(); @@ -37,7 +37,7 @@ pub fn generate_custom_values<'a>( fn generate_custom_value_fn( custom_value: CustomValueMetadata, type_gen: &TypeGenerator, - crate_path: &CratePath, + crate_path: &syn::Path, fn_names_taken: &mut HashSet, ) -> Option { // names are transformed to snake case to make for good function identifiers. diff --git a/codegen/src/api/events.rs b/codegen/src/api/events.rs index 755f494c65..383ed52694 100644 --- a/codegen/src/api/events.rs +++ b/codegen/src/api/events.rs @@ -2,7 +2,7 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -use crate::{types::TypeGenerator, CratePath}; +use crate::types::TypeGenerator; use proc_macro2::TokenStream as TokenStream2; use quote::quote; use subxt_metadata::PalletMetadata; @@ -42,7 +42,7 @@ pub fn generate_events( type_gen: &TypeGenerator, pallet: &PalletMetadata, types_mod_ident: &syn::Ident, - crate_path: &CratePath, + crate_path: &syn::Path, should_gen_docs: bool, ) -> Result { // Early return if the pallet has no events. diff --git a/codegen/src/api/mod.rs b/codegen/src/api/mod.rs index 1d1897e3d2..640e804203 100644 --- a/codegen/src/api/mod.rs +++ b/codegen/src/api/mod.rs @@ -14,203 +14,19 @@ mod storage; use subxt_metadata::Metadata; -use super::DerivesRegistry; use crate::api::custom_values::generate_custom_values; use crate::error::CodegenError; +use crate::types::DerivesRegistry; use crate::{ ir, types::{CompositeDef, CompositeDefFields, TypeGenerator, TypeSubstitutes}, - CratePath, }; -use codec::Decode; use heck::ToSnakeCase as _; use proc_macro2::TokenStream as TokenStream2; use quote::{format_ident, quote}; -use std::{fs, io::Read, path, string::ToString}; use syn::parse_quote; -/// Generate the runtime API for interacting with a substrate runtime. -pub struct GenerateRuntimeApi { - item_mod: syn::ItemMod, - derives: DerivesRegistry, - type_substitutes: TypeSubstitutes, - crate_path: CratePath, - should_gen_docs: bool, - runtime_types_only: bool, - unstable_metadata: bool, -} - -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. - #[cfg(feature = "fetch-metadata")] - pub fn generate_from_url(self, url: crate::utils::Url) -> Result { - use crate::utils::{fetch_metadata_bytes_blocking, MetadataVersion, Url}; - - fn fetch_metadata(url: Url, 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.clone(), MetadataVersion::Unstable).ok()) - .flatten(); - - let metadata = if let Some(unstable) = metadata { - unstable - } else { - match fetch_metadata(url.clone(), 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, - metadata: Metadata, - derives: DerivesRegistry, - type_substitutes: TypeSubstitutes, - crate_path: CratePath, - should_gen_docs: bool, - runtime_types_only: bool, -) -> Result { - let generator = RuntimeGenerator::new(metadata); - if runtime_types_only { - generator.generate_runtime_types( - item_mod, - derives, - type_substitutes, - crate_path, - should_gen_docs, - ) - } else { - generator.generate_runtime( - item_mod, - derives, - type_substitutes, - crate_path, - should_gen_docs, - ) - } -} - /// Create the API for interacting with a Substrate runtime. pub struct RuntimeGenerator { metadata: Metadata, @@ -246,7 +62,7 @@ impl RuntimeGenerator { item_mod: syn::ItemMod, derives: DerivesRegistry, type_substitutes: TypeSubstitutes, - crate_path: CratePath, + crate_path: syn::Path, should_gen_docs: bool, ) -> Result { let item_mod_attrs = item_mod.attrs.clone(); @@ -300,7 +116,7 @@ impl RuntimeGenerator { item_mod: syn::ItemMod, derives: DerivesRegistry, type_substitutes: TypeSubstitutes, - crate_path: CratePath, + crate_path: syn::Path, should_gen_docs: bool, ) -> Result { let item_mod_attrs = item_mod.attrs.clone(); @@ -545,7 +361,7 @@ pub fn generate_structs_from_variants( type_id: u32, variant_to_struct_name: F, error_message_type_name: &str, - crate_path: &CratePath, + crate_path: &syn::Path, should_gen_docs: bool, ) -> Result, CodegenError> where diff --git a/codegen/src/api/runtime_apis.rs b/codegen/src/api/runtime_apis.rs index bc363a2cf2..ea864903ff 100644 --- a/codegen/src/api/runtime_apis.rs +++ b/codegen/src/api/runtime_apis.rs @@ -2,7 +2,7 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -use crate::{types::TypeGenerator, CodegenError, CratePath}; +use crate::{types::TypeGenerator, CodegenError}; use heck::ToSnakeCase as _; use heck::ToUpperCamelCase as _; use subxt_metadata::{Metadata, RuntimeApiMetadata}; @@ -15,7 +15,7 @@ fn generate_runtime_api( api: RuntimeApiMetadata, type_gen: &TypeGenerator, types_mod_ident: &syn::Ident, - crate_path: &CratePath, + crate_path: &syn::Path, should_gen_docs: bool, ) -> Result<(TokenStream2, TokenStream2), CodegenError> { // Trait name must remain as is (upper case) to identity the runtime call. @@ -130,7 +130,7 @@ pub fn generate_runtime_apis( metadata: &Metadata, type_gen: &TypeGenerator, types_mod_ident: &syn::Ident, - crate_path: &CratePath, + crate_path: &syn::Path, should_gen_docs: bool, ) -> Result { let runtime_fns: Vec<_> = metadata diff --git a/codegen/src/api/storage.rs b/codegen/src/api/storage.rs index 988679e7a9..f012f9ed56 100644 --- a/codegen/src/api/storage.rs +++ b/codegen/src/api/storage.rs @@ -2,8 +2,8 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. +use crate::types::TypeGenerator; use crate::types::TypePath; -use crate::{types::TypeGenerator, CratePath}; use heck::ToSnakeCase as _; use proc_macro2::{Ident, TokenStream as TokenStream2, TokenStream}; use quote::{format_ident, quote}; @@ -27,7 +27,7 @@ pub fn generate_storage( type_gen: &TypeGenerator, pallet: &PalletMetadata, types_mod_ident: &syn::Ident, - crate_path: &CratePath, + crate_path: &syn::Path, should_gen_docs: bool, ) -> Result { let Some(storage) = pallet.storage() else { @@ -59,7 +59,7 @@ fn generate_storage_entry_fns( type_gen: &TypeGenerator, pallet: &PalletMetadata, storage_entry: &StorageEntryMetadata, - crate_path: &CratePath, + crate_path: &syn::Path, should_gen_docs: bool, ) -> Result { let keys: Vec<(Ident, TypePath)> = match storage_entry.entry_type() { @@ -269,7 +269,7 @@ mod tests { item_mod, Default::default(), Default::default(), - "::subxt_path".into(), + syn::parse_str("::subxt_path").unwrap(), false, ) .expect("should be able to generate runtime"); diff --git a/codegen/src/error.rs b/codegen/src/error.rs index 60437f5de7..5b4dc90fa4 100644 --- a/codegen/src/error.rs +++ b/codegen/src/error.rs @@ -16,9 +16,6 @@ pub enum CodegenError { /// Cannot fetch the metadata bytes. #[error("Failed to fetch metadata, make sure that you're pointing at a node which is providing substrate-based metadata: {0}")] Fetch(#[from] FetchMetadataError), - /// Failed IO for the metadata file. - #[error("Failed IO for {0}, make sure that you are providing the correct file path for metadata: {1}")] - Io(String, std::io::Error), /// Cannot decode the metadata bytes. #[error("Could not decode metadata, only V14 and V15 metadata are supported: {0}")] Decode(#[from] codec::Error), @@ -85,15 +82,23 @@ impl CodegenError { #[derive(Debug, thiserror::Error)] #[non_exhaustive] pub enum FetchMetadataError { + /// Error decoding from a hex value. #[error("Cannot decode hex value: {0}")] DecodeError(#[from] hex::FromHexError), + /// Some SCALE codec error. #[error("Cannot scale encode/decode value: {0}")] CodecError(#[from] codec::Error), + /// JSON-RPC error fetching metadata. #[cfg(feature = "fetch-metadata")] #[error("Request error: {0}")] RequestError(#[from] jsonrpsee::core::Error), + /// Failed IO when fetching from a file. + #[error("Failed IO for {0}, make sure that you are providing the correct file path for metadata: {1}")] + Io(String, std::io::Error), + /// URL scheme is not http, https, ws or wss. #[error("'{0}' not supported, supported URI schemes are http, https, ws or wss.")] InvalidScheme(String), + /// Some other error. #[error("Other error: {0}")] Other(String), } diff --git a/codegen/src/utils/fetch_metadata.rs b/codegen/src/fetch_metadata.rs similarity index 90% rename from codegen/src/utils/fetch_metadata.rs rename to codegen/src/fetch_metadata.rs index 66689b37c7..c32377c085 100644 --- a/codegen/src/utils/fetch_metadata.rs +++ b/codegen/src/fetch_metadata.rs @@ -2,17 +2,22 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. +//! Helper methods for fetching metadata from a file or URL. + use crate::error::FetchMetadataError; use codec::{Decode, Encode}; use jsonrpsee::{ async_client::ClientBuilder, - client_transport::ws::{Url, WsTransportClientBuilder}, + client_transport::ws::WsTransportClientBuilder, core::{client::ClientT, Error}, http_client::HttpClientBuilder, rpc_params, }; use std::time::Duration; +// Part of the public interface: +pub use jsonrpsee::client_transport::ws::Url; + /// The metadata version that is fetched from the node. #[derive(Default, Debug, Clone, Copy)] pub enum MetadataVersion { @@ -44,20 +49,24 @@ impl std::str::FromStr for MetadataVersion { } } -/// Returns the metadata bytes from the provided URL, blocking the current thread. -pub fn fetch_metadata_bytes_blocking( - url: Url, - version: MetadataVersion, +/// Fetch metadata from a file. +pub fn fetch_metadata_from_file_blocking( + path: &std::path::Path, ) -> Result, FetchMetadataError> { - tokio_block_on(fetch_metadata_bytes(url, version)) + use std::io::Read; + let to_err = |err| FetchMetadataError::Io(path.to_string_lossy().into(), err); + let mut file = std::fs::File::open(path).map_err(to_err)?; + let mut bytes = Vec::new(); + file.read_to_end(&mut bytes).map_err(to_err)?; + Ok(bytes) } -/// Returns the raw, 0x prefixed metadata hex from the provided URL, blocking the current thread. -pub fn fetch_metadata_hex_blocking( +/// Returns the metadata bytes from the provided URL, blocking the current thread. +pub fn fetch_metadata_from_url_blocking( url: Url, version: MetadataVersion, -) -> Result { - tokio_block_on(fetch_metadata_hex(url, version)) +) -> Result, FetchMetadataError> { + tokio_block_on(fetch_metadata_from_url(url, version)) } // Block on some tokio runtime for sync contexts @@ -70,7 +79,7 @@ fn tokio_block_on>(fut: Fut) -> T { } /// Returns the metadata bytes from the provided URL. -pub async fn fetch_metadata_bytes( +pub async fn fetch_metadata_from_url( url: Url, version: MetadataVersion, ) -> Result, FetchMetadataError> { @@ -83,16 +92,6 @@ pub async fn fetch_metadata_bytes( Ok(bytes) } -/// Returns the raw, 0x prefixed metadata hex from the provided URL. -pub async fn fetch_metadata_hex( - url: Url, - version: MetadataVersion, -) -> Result { - let bytes = fetch_metadata_bytes(url, version).await?; - let hex_data = format!("0x{}", hex::encode(bytes)); - Ok(hex_data) -} - async fn fetch_metadata_ws( url: Url, version: MetadataVersion, diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 8171c2ee49..e319b1aaff 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -2,65 +2,267 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -//! Library to generate an API for a Substrate runtime from its metadata. -//! -//! ## Generated Structure -//! -//! The API generator logic: -//! - At the root there is the `item_mod` provided (ie `pub mod api {}`) -//! - Pallets are represented by a child module (ie `pub mod PalletName {}`) of the root -//! - Each pallet exposes as child modules (if applicable): -//! - Calls (`pub mod calls {}`) -//! - Events (`pub mod events {}`) -//! - Storage (`pub mod storage {}`) -//! - Constants (`pub mod constants {}`) -//! -//! ## Example -//! -//! ```no_run -//! use std::fs; -//! use codec::Decode; -//! use subxt_metadata::Metadata; -//! use subxt_codegen::{CratePath, DerivesRegistry, TypeSubstitutes}; -//! -//! let encoded = fs::read("../artifacts/polkadot_metadata_full.scale").unwrap(); -//! -//! // Runtime metadata obtained from a node. -//! let metadata = Metadata::decode(&mut &*encoded).unwrap(); -//! // Module under which the API is generated. -//! let item_mod = syn::parse_quote!( -//! pub mod api {} -//! ); -//! // Default module derivatives. -//! let mut derives = DerivesRegistry::with_default_derives(&CratePath::default()); -//! // Default type substitutes. -//! let substs = TypeSubstitutes::with_default_substitutes(&CratePath::default()); -//! // Generate the Runtime API. -//! let generator = subxt_codegen::RuntimeGenerator::new(metadata); -//! // Include metadata documentation in the Runtime API. -//! let generate_docs = true; -//! let runtime_api = generator.generate_runtime(item_mod, derives, substs, CratePath::default(), generate_docs).unwrap(); -//! println!("{}", runtime_api); -//! ``` +//! Generate a type safe Subxt interface for a Substrate runtime from its metadata. +//! This is used by the `#[subxt]` macro and `subxt codegen` CLI command, but can also +//! be used directly if preferable. #![deny(unused_crate_dependencies, missing_docs)] mod api; -mod error; mod ir; mod types; +pub mod error; + +// These should probably be in a separate crate; they are used by the +// macro and CLI tool, so they only live here because this is a common +// crate that both depend on. #[cfg(feature = "fetch-metadata")] -pub mod utils; +pub mod fetch_metadata; #[cfg(feature = "web")] use getrandom as _; -pub use self::{ - api::{GenerateRuntimeApi, RuntimeGenerator}, - error::{CodegenError, TypeSubstitutionError}, - types::{ - CratePath, Derives, DerivesRegistry, Module, TypeDefGen, TypeDefParameters, TypeGenerator, - TypeSubstitutes, - }, -}; +use api::RuntimeGenerator; +use proc_macro2::TokenStream as TokenStream2; +use std::collections::HashMap; + +// We expose these only because they are currently needed in subxt-explorer. +// Eventually we'll move the type generation stuff out into a separate crate. +#[doc(hidden)] +pub mod __private { + pub use crate::types::{DerivesRegistry, TypeDefGen, TypeGenerator, TypeSubstitutes}; +} + +// Part of the public interface, so expose: +pub use error::CodegenError; +pub use subxt_metadata::Metadata; +pub use syn; + +/// Generate a type safe interface to use with `subxt`. +/// The options exposed here are similar to those exposed via +/// the `#[subxt]` macro or via the `subxt codegen` CLI command. +/// Both use this under the hood. +/// +/// # Example +/// +/// Generating an interface using all of the defaults: +/// +/// ```rust +/// use codec::Decode; +/// use subxt_codegen::{ Metadata, CodegenBuilder }; +/// +/// // Get hold of and decode some metadata: +/// let encoded = std::fs::read("../artifacts/polkadot_metadata_full.scale").unwrap(); +/// let metadata = Metadata::decode(&mut &*encoded).unwrap(); +/// +/// // Generate a TokenStream representing the code for the interface. +/// // This can be converted to a string, displayed as-is or output from a macro. +/// let token_stream = CodegenBuilder::new().generate(metadata); +/// ```` +pub struct CodegenBuilder { + crate_path: syn::Path, + use_default_derives: bool, + use_default_substitutions: bool, + generate_docs: bool, + runtime_types_only: bool, + item_mod: syn::ItemMod, + extra_global_derives: Vec, + extra_global_attributes: Vec, + type_substitutes: HashMap, + derives_for_type: HashMap>, + attributes_for_type: HashMap>, +} + +impl Default for CodegenBuilder { + fn default() -> Self { + CodegenBuilder { + crate_path: syn::parse_quote!(::subxt), + use_default_derives: true, + use_default_substitutions: true, + generate_docs: true, + runtime_types_only: false, + item_mod: syn::parse_quote!( + pub mod api {} + ), + extra_global_derives: Vec::new(), + extra_global_attributes: Vec::new(), + type_substitutes: HashMap::new(), + derives_for_type: HashMap::new(), + attributes_for_type: HashMap::new(), + } + } +} + +impl CodegenBuilder { + /// Construct a builder to configure and generate a type-safe interface for Subxt. + pub fn new() -> Self { + CodegenBuilder::default() + } + + /// Disable the default derives that are applied to all types. + /// + /// # Warning + /// + /// This is not recommended, and is highly likely to break some part of the + /// generated interface. Expect compile errors. + pub fn disable_default_derives(&mut self) { + self.use_default_derives = false; + } + + /// Disable the default type substitutions that are applied to the generated + /// code. + /// + /// # Warning + /// + /// This is not recommended, and is highly likely to break some part of the + /// generated interface. Expect compile errors. + pub fn disable_default_substitutes(&mut self) { + self.use_default_substitutions = false; + } + + /// Disable the output of doc comments associated with the generated types and + /// methods. This can reduce the generated code size at the expense of losing + /// documentation. + pub fn no_docs(&mut self) { + self.generate_docs = false; + } + + /// Only generate the types, and don't generate the rest of the Subxt specific + /// interface. + pub fn runtime_types_only(&mut self) { + self.runtime_types_only = true; + } + + /// Set the additional derives that will be applied to all types. By default, + /// a set of derives required for Subxt are automatically added for all types. + /// + /// # Warning + /// + /// Invalid derives, or derives that cannot be applied to _all_ of the generated + /// types (taking into account that some types are substituted for hand written ones + /// that we cannot add extra derives for) will lead to compile errors in the + /// generated code. + pub fn set_additional_global_derives(&mut self, derives: Vec) { + self.extra_global_derives = derives; + } + + /// Set the additional attributes that will be applied to all types. By default, + /// a set of attributes required for Subxt are automatically added for all types. + /// + /// # Warning + /// + /// Invalid attributes can very easily lead to compile errors in the generated code. + pub fn set_additional_global_attributes(&mut self, attributes: Vec) { + self.extra_global_attributes = attributes; + } + + /// Set additional derives for a specific type at the path given. + /// + /// # Warning + /// + /// For composite types, you may also need to set the same additional derives on all of + /// the contained types as well to avoid compile errors in the generated code. + pub fn add_derives_for_type( + &mut self, + ty: syn::TypePath, + derives: impl IntoIterator, + ) { + self.derives_for_type.entry(ty).or_default().extend(derives); + } + + /// Set additional attributes for a specific type at the path given. + /// + /// # Warning + /// + /// For composite types, you may also need to consider contained types and whether they need + /// similar attributes setting. + pub fn add_attributes_for_type( + &mut self, + ty: syn::TypePath, + attributes: impl IntoIterator, + ) { + self.attributes_for_type + .entry(ty) + .or_default() + .extend(attributes); + } + + /// Substitute a type at the given path with some type at the second path. During codegen, + /// we will avoid generating the type at the first path given, and instead point any references + /// to that type to the second path given. + /// + /// The substituted type will need to implement the relevant traits to be compatible with the + /// original, and it will need to SCALE encode and SCALE decode in a compatible way. + pub fn set_type_substitute(&mut self, ty: syn::Path, with: syn::Path) { + self.type_substitutes.insert(ty, with); + } + + /// By default, all of the code is generated inside a module `pub mod api {}`. We decorate + /// this module with a few attributes to reduce compile warnings and things. You can provide a + /// target module here, allowing you to add additional attributes or inner code items (with the + /// warning that duplicate identifiers will lead to compile errors). + pub fn set_target_module(&mut self, item_mod: syn::ItemMod) { + self.item_mod = item_mod; + } + + /// Set the path to the `subxt` crate. By default, we expect it to be at `::subxt`. + pub fn set_subxt_crate_path(&mut self, crate_path: syn::Path) { + self.crate_path = crate_path; + } + + /// Generate an interface, assuming that the default path to the `subxt` crate is `::subxt`. + /// If the `subxt` crate is not available as a top level dependency, use `generate` and provide + /// a valid path to the `subxt¦ crate. + pub fn generate(self, metadata: Metadata) -> Result { + let crate_path = self.crate_path; + + let mut derives_registry = if self.use_default_derives { + types::DerivesRegistry::with_default_derives(&crate_path) + } else { + types::DerivesRegistry::new() + }; + + derives_registry.extend_for_all(self.extra_global_derives, self.extra_global_attributes); + + for (ty, derives) in self.derives_for_type { + derives_registry.extend_for_type(ty, derives, vec![]); + } + for (ty, attributes) in self.attributes_for_type { + derives_registry.extend_for_type(ty, vec![], attributes); + } + + let mut type_substitutes = if self.use_default_substitutions { + types::TypeSubstitutes::with_default_substitutes(&crate_path) + } else { + types::TypeSubstitutes::new() + }; + + for (from, with) in self.type_substitutes { + let abs_path = with.try_into()?; + type_substitutes.insert(from, abs_path)?; + } + + let item_mod = self.item_mod; + let generator = RuntimeGenerator::new(metadata); + let should_gen_docs = self.generate_docs; + + if self.runtime_types_only { + generator.generate_runtime_types( + item_mod, + derives_registry, + type_substitutes, + crate_path, + should_gen_docs, + ) + } else { + generator.generate_runtime( + item_mod, + derives_registry, + type_substitutes, + crate_path, + should_gen_docs, + ) + } + } +} diff --git a/codegen/src/types/composite_def.rs b/codegen/src/types/composite_def.rs index ca63aea78f..37787df083 100644 --- a/codegen/src/types/composite_def.rs +++ b/codegen/src/types/composite_def.rs @@ -4,7 +4,7 @@ use crate::error::CodegenError; -use super::{CratePath, Derives, Field, TypeDefParameters, TypeGenerator, TypeParameter, TypePath}; +use super::{Derives, Field, TypeDefParameters, TypeGenerator, TypeParameter, TypePath}; use proc_macro2::TokenStream; use quote::{format_ident, quote}; use scale_info::{form::PortableForm, Type, TypeDef, TypeDefPrimitive}; @@ -36,7 +36,7 @@ impl CompositeDef { field_visibility: Option, type_gen: &TypeGenerator, docs: &[String], - crate_path: &CratePath, + crate_path: &syn::Path, ) -> Result { let mut derives = type_gen.type_derives(ty)?; let fields: Vec<_> = fields_def.field_types().collect(); diff --git a/codegen/src/types/derives.rs b/codegen/src/types/derives.rs index 61a89f1b5b..429591ed8a 100644 --- a/codegen/src/types/derives.rs +++ b/codegen/src/types/derives.rs @@ -2,7 +2,6 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -use crate::CratePath; use syn::{parse_quote, Path}; use std::collections::{HashMap, HashSet}; @@ -35,7 +34,7 @@ impl DerivesRegistry { /// /// The `crate_path` denotes the `subxt` crate access path in the /// generated code. - pub fn with_default_derives(crate_path: &CratePath) -> Self { + pub fn with_default_derives(crate_path: &syn::Path) -> Self { Self { default_derives: Derives::with_defaults(crate_path), specific_type_derives: Default::default(), @@ -117,7 +116,7 @@ impl Derives { /// Creates a new instance of `Derives` with the `crate_path` prepended /// to the set of default derives that reside in `subxt`. - pub fn with_defaults(crate_path: &CratePath) -> Self { + pub fn with_defaults(crate_path: &syn::Path) -> Self { let mut derives = HashSet::new(); let mut attributes = HashSet::new(); @@ -148,7 +147,7 @@ impl Derives { } /// Add `#crate_path::ext::codec::CompactAs` to the derives. - pub fn insert_codec_compact_as(&mut self, crate_path: &CratePath) { + pub fn insert_codec_compact_as(&mut self, crate_path: &syn::Path) { self.insert_derive(parse_quote!(#crate_path::ext::codec::CompactAs)); } diff --git a/codegen/src/types/mod.rs b/codegen/src/types/mod.rs index f565fb0460..4d1738f1e0 100644 --- a/codegen/src/types/mod.rs +++ b/codegen/src/types/mod.rs @@ -41,7 +41,7 @@ pub struct TypeGenerator<'a> { /// Set of derives with which to annotate generated types. derives: DerivesRegistry, /// The `subxt` crate access path in the generated code. - crate_path: CratePath, + crate_path: syn::Path, /// True if codegen should generate the documentation for the API. should_gen_docs: bool, } @@ -53,7 +53,7 @@ impl<'a> TypeGenerator<'a> { root_mod: &'static str, type_substitutes: TypeSubstitutes, derives: DerivesRegistry, - crate_path: CratePath, + crate_path: syn::Path, should_gen_docs: bool, ) -> Self { let root_mod_ident = Ident::new(root_mod, Span::call_site()); @@ -334,55 +334,3 @@ impl Module { &self.root_mod } } - -/// A newtype wrapper which stores the path to the Subxt crate. -#[derive(Debug, Clone)] -pub struct CratePath(syn::Path); - -impl CratePath { - /// Create a new `CratePath` from a `syn::Path`. - pub fn new(path: syn::Path) -> Self { - Self(path) - } -} - -impl Default for CratePath { - fn default() -> Self { - Self(syn::parse_quote!(::subxt)) - } -} - -impl From for CratePath { - fn from(path: syn::Path) -> Self { - CratePath::new(path) - } -} - -impl ToTokens for CratePath { - fn to_tokens(&self, tokens: &mut TokenStream) { - self.0.to_tokens(tokens) - } -} - -impl From<&str> for CratePath { - fn from(crate_path: &str) -> Self { - Self(syn::parse_str(crate_path).unwrap_or_else(|err| { - panic!("failed converting {crate_path:?} to `syn::Path`: {err:?}"); - })) - } -} - -impl From for CratePath { - fn from(crate_path: String) -> Self { - CratePath::from(crate_path.as_str()) - } -} - -impl From> for CratePath { - fn from(maybe_crate_path: Option) -> Self { - match maybe_crate_path { - None => CratePath::default(), - Some(crate_path) => crate_path.into(), - } - } -} diff --git a/codegen/src/types/substitutes.rs b/codegen/src/types/substitutes.rs index 740de95cc0..c2639c3736 100644 --- a/codegen/src/types/substitutes.rs +++ b/codegen/src/types/substitutes.rs @@ -2,7 +2,7 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -use crate::{error::TypeSubstitutionError, CratePath}; +use crate::error::TypeSubstitutionError; use std::{borrow::Borrow, collections::HashMap}; use syn::{parse_quote, spanned::Spanned as _}; @@ -55,7 +55,7 @@ impl TypeSubstitutes { /// /// The `crate_path` denotes the `subxt` crate access path in the /// generated code. - pub fn with_default_substitutes(crate_path: &CratePath) -> Self { + pub fn with_default_substitutes(crate_path: &syn::Path) -> Self { // Some hardcoded default type substitutes, can be overridden by user let defaults = [ ( diff --git a/codegen/src/types/tests.rs b/codegen/src/types/tests.rs index e1f4caddc5..7aa91e21e1 100644 --- a/codegen/src/types/tests.rs +++ b/codegen/src/types/tests.rs @@ -34,7 +34,7 @@ fn generate_struct_with_primitives() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); - let crate_path = "::subxt_path".into(); + let crate_path = syn::parse_str("::subxt_path").unwrap(); let type_gen = TypeGenerator::new( &portable_types, "root", @@ -86,7 +86,7 @@ fn generate_struct_with_a_struct_field() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); - let crate_path = "::subxt_path".into(); + let crate_path = syn::parse_str("::subxt_path").unwrap(); let type_gen = TypeGenerator::new( &portable_types, "root", @@ -140,7 +140,7 @@ fn generate_tuple_struct() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); - let crate_path = "::subxt_path".into(); + let crate_path = syn::parse_str("::subxt_path").unwrap(); let type_gen = TypeGenerator::new( &portable_types, "root", @@ -231,7 +231,7 @@ fn derive_compact_as_for_uint_wrapper_structs() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); - let crate_path = "::subxt_path".into(); + let crate_path = syn::parse_str("::subxt_path").unwrap(); let type_gen = TypeGenerator::new( &portable_types, "root", @@ -328,7 +328,7 @@ fn generate_enum() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); - let crate_path = "::subxt_path".into(); + let crate_path = syn::parse_str("::subxt_path").unwrap(); let type_gen = TypeGenerator::new( &portable_types, "root", @@ -392,7 +392,7 @@ fn compact_fields() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); - let crate_path = "::subxt_path".into(); + let crate_path = syn::parse_str("::subxt_path").unwrap(); let type_gen = TypeGenerator::new( &portable_types, "root", @@ -460,7 +460,7 @@ fn compact_generic_parameter() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); - let crate_path = "::subxt_path".into(); + let crate_path = syn::parse_str("::subxt_path").unwrap(); let type_gen = TypeGenerator::new( &portable_types, "root", @@ -507,7 +507,7 @@ fn generate_array_field() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); - let crate_path = "::subxt_path".into(); + let crate_path = syn::parse_str("::subxt_path").unwrap(); let type_gen = TypeGenerator::new( &portable_types, "root", @@ -550,7 +550,7 @@ fn option_fields() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); - let crate_path = "::subxt_path".into(); + let crate_path = syn::parse_str("::subxt_path").unwrap(); let type_gen = TypeGenerator::new( &portable_types, "root", @@ -596,7 +596,7 @@ fn box_fields_struct() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); - let crate_path = "::subxt_path".into(); + let crate_path = syn::parse_str("::subxt_path").unwrap(); let type_gen = TypeGenerator::new( &portable_types, "root", @@ -642,7 +642,7 @@ fn box_fields_enum() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); - let crate_path = "::subxt_path".into(); + let crate_path = syn::parse_str("::subxt_path").unwrap(); let type_gen = TypeGenerator::new( &portable_types, "root", @@ -688,7 +688,7 @@ fn range_fields() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); - let crate_path = "::subxt_path".into(); + let crate_path = syn::parse_str("::subxt_path").unwrap(); let type_gen = TypeGenerator::new( &portable_types, "root", @@ -738,7 +738,7 @@ fn generics() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); - let crate_path = "::subxt_path".into(); + let crate_path = syn::parse_str("::subxt_path").unwrap(); let type_gen = TypeGenerator::new( &portable_types, "root", @@ -795,7 +795,7 @@ fn generics_nested() { registry.register_type(&meta_type::>()); let portable_types: PortableRegistry = registry.into(); - let crate_path = "::subxt_path".into(); + let crate_path = syn::parse_str("::subxt_path").unwrap(); let type_gen = TypeGenerator::new( &portable_types, "root", @@ -852,7 +852,7 @@ fn generate_bitvec() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); - let crate_path = "::subxt_path".into(); + let crate_path = syn::parse_str("::subxt_path").unwrap(); let type_gen = TypeGenerator::new( &portable_types, "root", @@ -911,7 +911,7 @@ fn generics_with_alias_adds_phantom_data_marker() { registry.register_type(&meta_type::>()); let portable_types: PortableRegistry = registry.into(); - let crate_path = "::subxt_path".into(); + let crate_path = syn::parse_str("::subxt_path").unwrap(); let type_gen = TypeGenerator::new( &portable_types, "root", @@ -982,7 +982,7 @@ fn modules() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); - let crate_path = "::subxt_path".into(); + let crate_path = syn::parse_str("::subxt_path").unwrap(); let type_gen = TypeGenerator::new( &portable_types, "root", @@ -1051,7 +1051,7 @@ fn dont_force_struct_names_camel_case() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); - let crate_path = "::subxt_path".into(); + let crate_path = syn::parse_str("::subxt_path").unwrap(); let type_gen = TypeGenerator::new( &portable_types, "root", @@ -1094,7 +1094,7 @@ fn apply_user_defined_derives_for_all_types() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); - let crate_path = "::subxt_path".into(); + let crate_path = syn::parse_str("::subxt_path").unwrap(); // configure derives let mut derives = DerivesRegistry::with_default_derives(&crate_path); derives.extend_for_all( @@ -1156,7 +1156,7 @@ fn apply_user_defined_derives_for_specific_types() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); - let crate_path = "::subxt_path".into(); + let crate_path = syn::parse_str("::subxt_path").unwrap(); // configure derives let mut derives = DerivesRegistry::with_default_derives(&crate_path); // for all types @@ -1233,7 +1233,7 @@ fn opt_out_from_default_derives() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); - let crate_path = "::subxt_path".into(); + let crate_path = syn::parse_str("::subxt_path").unwrap(); // configure derives let mut derives = DerivesRegistry::new(); derives.extend_for_all( @@ -1293,7 +1293,7 @@ fn opt_out_from_default_substitutes() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); - let crate_path = "::subxt_path".into(); + let crate_path = syn::parse_str("::subxt_path").unwrap(); let type_gen = TypeGenerator::new( &portable_types, "root", diff --git a/codegen/src/types/type_def.rs b/codegen/src/types/type_def.rs index 57470abf7f..b257c90308 100644 --- a/codegen/src/types/type_def.rs +++ b/codegen/src/types/type_def.rs @@ -5,8 +5,7 @@ use crate::error::CodegenError; use super::{ - CompositeDef, CompositeDefFields, CratePath, Derives, TypeDefParameters, TypeGenerator, - TypeParameter, + CompositeDef, CompositeDefFields, Derives, TypeDefParameters, TypeGenerator, TypeParameter, }; use proc_macro2::TokenStream; use quote::{format_ident, quote}; @@ -34,7 +33,7 @@ impl TypeDefGen { pub fn from_type( ty: &Type, type_gen: &TypeGenerator, - crate_path: &CratePath, + crate_path: &syn::Path, should_gen_docs: bool, ) -> Result { let derives = type_gen.type_derives(ty)?; diff --git a/codegen/src/types/type_path.rs b/codegen/src/types/type_path.rs index 9dafb62b5f..044ee2df6e 100644 --- a/codegen/src/types/type_path.rs +++ b/codegen/src/types/type_path.rs @@ -2,8 +2,6 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -use crate::CratePath; - use proc_macro2::{Ident, TokenStream}; use quote::format_ident; use scale_info::{form::PortableForm, Path, TypeDefPrimitive}; @@ -121,12 +119,12 @@ pub enum TypePathType { Compact { inner: Box, is_field: bool, - crate_path: CratePath, + crate_path: syn::Path, }, BitVec { bit_order_type: Box, bit_store_type: Box, - crate_path: CratePath, + crate_path: syn::Path, }, } diff --git a/codegen/src/utils/mod.rs b/codegen/src/utils/mod.rs deleted file mode 100644 index af5c00c2ad..0000000000 --- a/codegen/src/utils/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2019-2023 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -//! Utilities to help with fetching and decoding metadata. - -mod fetch_metadata; - -// easy access to this type needed for fetching metadata: -pub use jsonrpsee::client_transport::ws::Url; - -pub use fetch_metadata::{ - fetch_metadata_bytes, fetch_metadata_bytes_blocking, fetch_metadata_hex, - fetch_metadata_hex_blocking, MetadataVersion, -}; diff --git a/macro/Cargo.toml b/macro/Cargo.toml index 2845e0f3c0..b374dbf572 100644 --- a/macro/Cargo.toml +++ b/macro/Cargo.toml @@ -20,7 +20,8 @@ web = ["subxt-codegen/web"] proc-macro = true [dependencies] +codec = { package = "parity-scale-codec", workspace = true } darling = { workspace = true } proc-macro-error = { workspace = true } syn = { workspace = true } -subxt-codegen = { workspace = true } +subxt-codegen = { workspace = true, features = ["fetch-metadata"] } diff --git a/macro/src/lib.rs b/macro/src/lib.rs index cde0c28427..d53c15af4e 100644 --- a/macro/src/lib.rs +++ b/macro/src/lib.rs @@ -4,11 +4,15 @@ extern crate proc_macro; +use codec::Decode; use darling::{ast::NestedMeta, FromMeta}; use proc_macro::TokenStream; use proc_macro_error::{abort_call_site, proc_macro_error}; use subxt_codegen::{ - utils::Url, CodegenError, DerivesRegistry, GenerateRuntimeApi, TypeSubstitutes, + fetch_metadata::{ + fetch_metadata_from_file_blocking, fetch_metadata_from_url_blocking, MetadataVersion, Url, + }, + CodegenBuilder, CodegenError, }; use syn::{parse_macro_input, punctuated::Punctuated}; @@ -38,7 +42,7 @@ struct RuntimeMetadataArgs { #[darling(multiple)] substitute_type: Vec, #[darling(default, rename = "crate")] - crate_path: Option, + crate_path: Option, #[darling(default)] generate_docs: darling::util::Flag, #[darling(default)] @@ -85,61 +89,60 @@ pub fn subxt(args: TokenStream, input: TokenStream) -> TokenStream { Err(e) => return TokenStream::from(e.write_errors()), }; - let crate_path = match args.crate_path { - Some(crate_path) => crate_path.into(), - None => subxt_codegen::CratePath::default(), - }; - let mut derives_registry = if args.no_default_derives { - DerivesRegistry::new() - } else { - DerivesRegistry::with_default_derives(&crate_path) - }; + let mut codegen = CodegenBuilder::new(); - let universal_derives = args.derive_for_all_types.unwrap_or_default(); - let universal_attributes = args.attributes_for_all_types.unwrap_or_default(); - derives_registry.extend_for_all( - universal_derives, - universal_attributes.iter().map(|a| a.0.clone()), - ); + // Use the item module that the macro is on: + codegen.set_target_module(item_mod); + + // Use the provided crate path: + if let Some(crate_path) = args.crate_path { + codegen.set_subxt_crate_path(crate_path) + } - for derives in &args.derive_for_type { - derives_registry.extend_for_type( - derives.path.clone(), - derives.derive.iter().cloned(), - vec![], - ) + // Respect the boolean flags: + if args.runtime_types_only { + codegen.runtime_types_only(); } - for attributes in &args.attributes_for_type { - derives_registry.extend_for_type( - attributes.path.clone(), - vec![], - attributes.attributes.iter().map(|a| a.0.clone()), - ) + if args.no_default_derives { + codegen.disable_default_derives(); + } + if args.no_default_substitutions { + codegen.disable_default_substitutes(); + } + if !args.generate_docs.is_present() { + codegen.no_docs() } - let mut type_substitutes = if args.no_default_substitutions { - TypeSubstitutes::new() - } else { - TypeSubstitutes::with_default_substitutes(&crate_path) - }; - let substitute_args_res: Result<(), _> = args.substitute_type.into_iter().try_for_each(|sub| { - sub.with - .try_into() - .and_then(|with| type_substitutes.insert(sub.path, with)) - }); - - if let Err(err) = substitute_args_res { - return CodegenError::from(err).into_compile_error().into(); + // Configure derives: + codegen.set_additional_global_derives( + args.derive_for_all_types + .unwrap_or_default() + .into_iter() + .collect(), + ); + for d in args.derive_for_type { + codegen.add_derives_for_type(d.path, d.derive.into_iter()); } - let should_gen_docs = args.generate_docs.is_present(); - let unstable_metadata = args.unstable_metadata.is_present(); + // Configure attributes: + codegen.set_additional_global_attributes( + args.attributes_for_all_types + .unwrap_or_default() + .into_iter() + .map(|a| a.0) + .collect(), + ); + for d in args.attributes_for_type { + codegen.add_attributes_for_type(d.path, d.attributes.into_iter().map(|a| a.0)) + } + + // Insert type substitutions: + for sub in args.substitute_type.into_iter() { + codegen.set_type_substitute(sub.path, sub.with); + } - 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); + // Do we want to fetch unstable metadata? This only works if fetching from a URL. + let unstable_metadata = args.unstable_metadata.is_present(); match (args.runtime_metadata_path, args.runtime_metadata_url) { (Some(rest_of_path), None) => { @@ -152,20 +155,31 @@ pub fn subxt(args: TokenStream, input: TokenStream) -> TokenStream { 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); + let generated_code = fetch_metadata_from_file_blocking(&path) + .map_err(CodegenError::from) + .and_then(|b| subxt_codegen::Metadata::decode(&mut &*b).map_err(Into::into)) + .and_then(|m| codegen.generate(m).map_err(Into::into)) + .unwrap_or_else(|e| e.into_compile_error()); - runtime_api_generator - .generate_from_path(path) - .map_or_else(|err| err.into_compile_error().into(), Into::into) + generated_code.into() } (None, Some(url_string)) => { let url = Url::parse(&url_string).unwrap_or_else(|_| { abort_call_site!("Cannot download metadata; invalid url: {}", url_string) }); - runtime_api_generator - .unstable_metadata(unstable_metadata) - .generate_from_url(url) - .map_or_else(|err| err.into_compile_error().into(), Into::into) + let version = match unstable_metadata { + true => MetadataVersion::Unstable, + false => MetadataVersion::Latest, + }; + + let generated_code = fetch_metadata_from_url_blocking(url, version) + .map_err(CodegenError::from) + .and_then(|b| subxt_codegen::Metadata::decode(&mut &*b).map_err(Into::into)) + .and_then(|m| codegen.generate(m).map_err(Into::into)) + .unwrap_or_else(|e| e.into_compile_error()); + + generated_code.into() } (None, None) => { abort_call_site!( diff --git a/subxt/src/lib.rs b/subxt/src/lib.rs index ae60494f8d..e2a31f5b4e 100644 --- a/subxt/src/lib.rs +++ b/subxt/src/lib.rs @@ -309,7 +309,7 @@ pub mod ext { /// 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 +/// ```rust,ignore /// #[subxt::subxt( /// runtime_metadata_url = "wss://rpc.polkadot.io:443", /// unstable_metadata diff --git a/testing/integration-tests/src/full_client/codegen/codegen_tests.rs b/testing/integration-tests/src/full_client/codegen/codegen_tests.rs index f3a00ea654..5722270e24 100644 --- a/testing/integration-tests/src/full_client/codegen/codegen_tests.rs +++ b/testing/integration-tests/src/full_client/codegen/codegen_tests.rs @@ -7,7 +7,7 @@ use frame_metadata::{ RuntimeMetadataPrefixed, }; use scale_info::{meta_type, IntoPortable, PortableRegistry, Registry, TypeInfo}; -use subxt_codegen::{CratePath, DerivesRegistry, RuntimeGenerator, TypeSubstitutes}; +use subxt_codegen::CodegenBuilder; use syn::__private::quote; fn generate_runtime_interface_from_metadata(metadata: RuntimeMetadataPrefixed) -> String { @@ -15,15 +15,10 @@ fn generate_runtime_interface_from_metadata(metadata: RuntimeMetadataPrefixed) - let metadata = metadata .try_into() .expect("frame_metadata should be convertible into Metadata"); - let generator = RuntimeGenerator::new(metadata); - let item_mod = syn::parse_quote!( - pub mod api {} - ); - let crate_path = CratePath::default(); - let derives = DerivesRegistry::with_default_derives(&crate_path); - let type_substitutes = TypeSubstitutes::with_default_substitutes(&crate_path); - generator - .generate_runtime(item_mod, derives, type_substitutes, crate_path, false) + + CodegenBuilder::new() + .no_docs() + .generate(metadata) .expect("API generation must be valid") .to_string() } diff --git a/testing/integration-tests/src/full_client/codegen/documentation.rs b/testing/integration-tests/src/full_client/codegen/documentation.rs index 9eb79c0ecc..8eead47760 100644 --- a/testing/integration-tests/src/full_client/codegen/documentation.rs +++ b/testing/integration-tests/src/full_client/codegen/documentation.rs @@ -4,7 +4,7 @@ use codec::Decode; use regex::Regex; -use subxt_codegen::{CratePath, DerivesRegistry, RuntimeGenerator, TypeSubstitutes}; +use subxt_codegen::{ syn, CodegenBuilder }; use subxt_metadata::Metadata; fn load_test_metadata() -> Metadata { @@ -48,25 +48,18 @@ fn metadata_docs() -> Vec { docs } -fn generate_runtime_interface(crate_path: CratePath, should_gen_docs: bool) -> String { +fn generate_runtime_interface(should_gen_docs: bool) -> String { // Load the runtime metadata downloaded from a node via `test-runtime`. let metadata = load_test_metadata(); - // Generate a runtime interface from the provided metadata. - let generator = RuntimeGenerator::new(metadata); - let item_mod = syn::parse_quote!( - pub mod api {} - ); - let derives = DerivesRegistry::with_default_derives(&crate_path); - let type_substitutes = TypeSubstitutes::with_default_substitutes(&crate_path); - generator - .generate_runtime( - item_mod, - derives, - type_substitutes, - crate_path, - should_gen_docs, - ) + let mut codegen = CodegenBuilder::new(); + + if !should_gen_docs { + codegen.no_docs(); + } + + codegen + .generate(metadata) .expect("API generation must be valid") .to_string() } @@ -74,7 +67,7 @@ fn generate_runtime_interface(crate_path: CratePath, should_gen_docs: bool) -> S fn interface_docs(should_gen_docs: bool) -> Vec { // Generate the runtime interface from the node's metadata. // Note: the API is generated on a single line. - let runtime_api = generate_runtime_interface(CratePath::default(), should_gen_docs); + let runtime_api = generate_runtime_interface(should_gen_docs); // Documentation lines have the following format: // # [ doc = "Upward message is invalid XCM."] @@ -145,18 +138,10 @@ fn check_root_attrs_preserved() { pub mod api {} ); - // Generate a runtime interface from the provided metadata. - let generator = RuntimeGenerator::new(metadata); - let derives = DerivesRegistry::with_default_derives(&CratePath::default()); - let type_substitutes = TypeSubstitutes::with_default_substitutes(&CratePath::default()); - let generated_code = generator - .generate_runtime( - item_mod, - derives, - type_substitutes, - CratePath::default(), - true, - ) + let mut codegen = CodegenBuilder::new(); + codegen.set_target_module(item_mod); + let generated_code = codegen + .generate(metadata) .expect("API generation must be valid") .to_string();