subxt_codegen/api/
mod.rs

1// Copyright 2019-2025 Parity Technologies (UK) Ltd.
2// This file is dual-licensed as Apache-2.0 or GPL-3.0.
3// see LICENSE for license details.
4
5//! Generate code for submitting extrinsics and query storage of a Substrate runtime.
6
7mod calls;
8mod constants;
9mod custom_values;
10mod errors;
11mod events;
12mod pallet_view_functions;
13mod runtime_apis;
14mod storage;
15
16use scale_typegen::TypeGenerator;
17use scale_typegen::typegen::ir::ToTokensWithSettings;
18use scale_typegen::typegen::ir::type_ir::{CompositeFieldIR, CompositeIR, CompositeIRKind};
19use scale_typegen::typegen::type_params::TypeParameters;
20use scale_typegen::typegen::type_path::TypePath;
21use subxt_metadata::Metadata;
22use syn::{Ident, parse_quote};
23
24use crate::error::CodegenError;
25use crate::subxt_type_gen_settings;
26use crate::{api::custom_values::generate_custom_values, ir};
27
28use heck::{ToSnakeCase as _, ToUpperCamelCase};
29use proc_macro2::TokenStream as TokenStream2;
30use quote::{format_ident, quote};
31
32/// Create the API for interacting with a Substrate runtime.
33pub struct RuntimeGenerator {
34    metadata: Metadata,
35}
36
37impl RuntimeGenerator {
38    /// Create a new runtime generator from the provided metadata.
39    ///
40    /// **Note:** If you have the metadata path, URL or bytes to hand, prefer to use
41    /// `GenerateRuntimeApi` for generating the runtime API from that.
42    ///
43    /// # Panics
44    ///
45    /// Panics if the runtime metadata version is not supported.
46    ///
47    /// Supported versions: v14 and v15.
48    pub fn new(mut metadata: Metadata) -> Self {
49        scale_typegen::utils::ensure_unique_type_paths(metadata.types_mut())
50            .expect("Duplicate type paths in metadata; this is bug please file an issue.");
51        RuntimeGenerator { metadata }
52    }
53
54    /// Generate the API for interacting with a Substrate runtime.
55    ///
56    /// # Arguments
57    ///
58    /// * `item_mod` - The module declaration for which the API is implemented.
59    /// * `derives` - Provide custom derives for the generated types.
60    /// * `type_substitutes` - Provide custom type substitutes.
61    /// * `crate_path` - Path to the `subxt` crate.
62    /// * `should_gen_docs` - True if the generated API contains the documentation from the metadata.
63    pub fn generate_runtime_types(
64        &self,
65        item_mod: syn::ItemMod,
66        derives: scale_typegen::DerivesRegistry,
67        type_substitutes: scale_typegen::TypeSubstitutes,
68        crate_path: syn::Path,
69        should_gen_docs: bool,
70    ) -> Result<TokenStream2, CodegenError> {
71        let item_mod_attrs = item_mod.attrs.clone();
72        let item_mod_ir = ir::ItemMod::try_from(item_mod)?;
73
74        let settings =
75            subxt_type_gen_settings(derives, type_substitutes, &crate_path, should_gen_docs);
76
77        let type_gen = TypeGenerator::new(self.metadata.types(), &settings);
78        let types_mod = type_gen
79            .generate_types_mod()?
80            .to_token_stream(type_gen.settings());
81        let mod_ident = &item_mod_ir.ident;
82        let rust_items = item_mod_ir.rust_items();
83
84        Ok(quote! {
85            #( #item_mod_attrs )*
86            #[allow(dead_code, unused_imports, non_camel_case_types, unreachable_patterns)]
87            #[allow(clippy::all)]
88            #[allow(rustdoc::broken_intra_doc_links)]
89            pub mod #mod_ident {
90                // Preserve any Rust items that were previously defined in the adorned module
91                #( #rust_items ) *
92
93                // Make it easy to access the root items via `root_mod` at different levels
94                // without reaching out of this module.
95                #[allow(unused_imports)]
96                mod root_mod {
97                    pub use super::*;
98                }
99
100                #types_mod
101            }
102        })
103    }
104
105    /// Generate the API for interacting with a Substrate runtime.
106    ///
107    /// # Arguments
108    ///
109    /// * `item_mod` - The module declaration for which the API is implemented.
110    /// * `derives` - Provide custom derives for the generated types.
111    /// * `type_substitutes` - Provide custom type substitutes.
112    /// * `crate_path` - Path to the `subxt` crate.
113    /// * `should_gen_docs` - True if the generated API contains the documentation from the metadata.
114    pub fn generate_runtime(
115        &self,
116        item_mod: syn::ItemMod,
117        derives: scale_typegen::DerivesRegistry,
118        type_substitutes: scale_typegen::TypeSubstitutes,
119        crate_path: syn::Path,
120        should_gen_docs: bool,
121    ) -> Result<TokenStream2, CodegenError> {
122        let item_mod_attrs = item_mod.attrs.clone();
123        let item_mod_ir = ir::ItemMod::try_from(item_mod)?;
124
125        let settings =
126            subxt_type_gen_settings(derives, type_substitutes, &crate_path, should_gen_docs);
127
128        let type_gen = TypeGenerator::new(self.metadata.types(), &settings);
129        let types_mod = type_gen
130            .generate_types_mod()?
131            .to_token_stream(type_gen.settings());
132        let types_mod_ident = type_gen.types_mod_ident();
133        let pallets_with_mod_names = self
134            .metadata
135            .pallets()
136            .map(|pallet| {
137                (
138                    pallet,
139                    format_ident!("{}", pallet.name().to_string().to_snake_case()),
140                )
141            })
142            .collect::<Vec<_>>();
143
144        // Pallet names and their length are used to create PALLETS array.
145        // The array is used to identify the pallets composing the metadata for
146        // validation of just those pallets.
147        let pallet_names: Vec<_> = self
148            .metadata
149            .pallets()
150            .map(|pallet| pallet.name())
151            .collect();
152        let pallet_names_len = pallet_names.len();
153
154        let runtime_api_names: Vec<_> = self
155            .metadata
156            .runtime_api_traits()
157            .map(|api| api.name().to_string())
158            .collect();
159        let runtime_api_names_len = runtime_api_names.len();
160
161        let modules = pallets_with_mod_names
162            .iter()
163            .map(|(pallet, mod_name)| {
164                let calls = calls::generate_calls(&type_gen, pallet, &crate_path)?;
165
166                let event = events::generate_events(&type_gen, pallet, &crate_path)?;
167
168                let storage_mod = storage::generate_storage(&type_gen, pallet, &crate_path)?;
169
170                let constants_mod = constants::generate_constants(&type_gen, pallet, &crate_path)?;
171
172                let errors = errors::generate_error_type_alias(&type_gen, pallet)?;
173
174                let view_functions = pallet_view_functions::generate_pallet_view_functions(
175                    &type_gen,
176                    pallet,
177                    &crate_path,
178                )?;
179
180                Ok(quote! {
181                    pub mod #mod_name {
182                        use super::root_mod;
183                        use super::#types_mod_ident;
184                        #errors
185                        #calls
186                        #view_functions
187                        #event
188                        #storage_mod
189                        #constants_mod
190                    }
191                })
192            })
193            .collect::<Result<Vec<_>, CodegenError>>()?;
194
195        let mod_ident = &item_mod_ir.ident;
196        let pallets_with_constants: Vec<_> = pallets_with_mod_names
197            .iter()
198            .filter_map(|(pallet, pallet_mod_name)| {
199                pallet
200                    .constants()
201                    .next()
202                    .is_some()
203                    .then_some(pallet_mod_name)
204            })
205            .collect();
206
207        let pallets_with_storage: Vec<_> = pallets_with_mod_names
208            .iter()
209            .filter_map(|(pallet, pallet_mod_name)| pallet.storage().map(|_| pallet_mod_name))
210            .collect();
211
212        let pallets_with_calls: Vec<_> = pallets_with_mod_names
213            .iter()
214            .filter_map(|(pallet, pallet_mod_name)| pallet.call_ty_id().map(|_| pallet_mod_name))
215            .collect();
216
217        let pallets_with_view_functions: Vec<_> = pallets_with_mod_names
218            .iter()
219            .filter(|(pallet, _pallet_mod_name)| pallet.has_view_functions())
220            .map(|(_, pallet_mod_name)| pallet_mod_name)
221            .collect();
222
223        let rust_items = item_mod_ir.rust_items();
224
225        let apis_mod = runtime_apis::generate_runtime_apis(
226            &self.metadata,
227            &type_gen,
228            types_mod_ident,
229            &crate_path,
230        )?;
231
232        // Fetch the paths of the outer enums.
233        // Substrate exposes those under `kitchensink_runtime`, while Polkadot under `polkadot_runtime`.
234        let call_path = type_gen
235            .resolve_type_path(self.metadata.outer_enums().call_enum_ty())?
236            .to_token_stream(type_gen.settings());
237        let event_path = type_gen
238            .resolve_type_path(self.metadata.outer_enums().event_enum_ty())?
239            .to_token_stream(type_gen.settings());
240        let error_path = type_gen
241            .resolve_type_path(self.metadata.outer_enums().error_enum_ty())?
242            .to_token_stream(type_gen.settings());
243
244        let metadata_hash = self.metadata.hasher().hash();
245
246        let custom_values = generate_custom_values(&self.metadata, &type_gen, &crate_path);
247
248        Ok(quote! {
249            #( #item_mod_attrs )*
250            #[allow(dead_code, unused_imports, non_camel_case_types, unreachable_patterns)]
251            #[allow(clippy::all)]
252            #[allow(rustdoc::broken_intra_doc_links)]
253            pub mod #mod_ident {
254                // Preserve any Rust items that were previously defined in the adorned module.
255                #( #rust_items ) *
256
257                // Make it easy to access the root items via `root_mod` at different levels
258                // without reaching out of this module.
259                #[allow(unused_imports)]
260                mod root_mod {
261                    pub use super::*;
262                }
263
264                // Identify the pallets composing the static metadata by name.
265                pub static PALLETS: [&str; #pallet_names_len] = [ #(#pallet_names,)* ];
266
267                // Runtime APIs in the metadata by name.
268                pub static RUNTIME_APIS: [&str; #runtime_api_names_len] = [ #(#runtime_api_names,)* ];
269
270                /// The error type that is returned when there is a runtime issue.
271                pub type DispatchError = #types_mod_ident::sp_runtime::DispatchError;
272
273                /// The outer event enum.
274                pub type Event = #event_path;
275
276                /// The outer extrinsic enum.
277                pub type Call = #call_path;
278
279                /// The outer error enum represents the DispatchError's Module variant.
280                pub type Error = #error_path;
281
282                pub fn constants() -> ConstantsApi {
283                    ConstantsApi
284                }
285
286                pub fn storage() -> StorageApi {
287                    StorageApi
288                }
289
290                pub fn tx() -> TransactionApi {
291                    TransactionApi
292                }
293
294                pub fn apis() -> runtime_apis::RuntimeApi {
295                    runtime_apis::RuntimeApi
296                }
297
298                #apis_mod
299
300                pub fn view_functions() -> ViewFunctionsApi {
301                    ViewFunctionsApi
302                }
303
304                pub fn custom() -> CustomValuesApi {
305                    CustomValuesApi
306                }
307
308                #custom_values
309
310                pub struct ConstantsApi;
311                impl ConstantsApi {
312                    #(
313                        pub fn #pallets_with_constants(&self) -> #pallets_with_constants::constants::ConstantsApi {
314                            #pallets_with_constants::constants::ConstantsApi
315                        }
316                    )*
317                }
318
319                pub struct StorageApi;
320                impl StorageApi {
321                    #(
322                        pub fn #pallets_with_storage(&self) -> #pallets_with_storage::storage::StorageApi {
323                            #pallets_with_storage::storage::StorageApi
324                        }
325                    )*
326                }
327
328                pub struct TransactionApi;
329                impl TransactionApi {
330                    #(
331                        pub fn #pallets_with_calls(&self) -> #pallets_with_calls::calls::TransactionApi {
332                            #pallets_with_calls::calls::TransactionApi
333                        }
334                    )*
335                }
336
337                pub struct ViewFunctionsApi;
338                impl ViewFunctionsApi {
339                    #(
340                        pub fn #pallets_with_view_functions(&self) -> #pallets_with_view_functions::view_functions::ViewFunctionsApi {
341                            #pallets_with_view_functions::view_functions::ViewFunctionsApi
342                        }
343                    )*
344                }
345
346                /// check whether the metadata provided is aligned with this statically generated code.
347                pub fn is_codegen_valid_for(metadata: &#crate_path::Metadata) -> bool {
348                    let runtime_metadata_hash = metadata
349                        .hasher()
350                        .only_these_pallets(&PALLETS)
351                        .only_these_runtime_apis(&RUNTIME_APIS)
352                        .hash();
353                    runtime_metadata_hash == [ #(#metadata_hash,)* ]
354                }
355
356                #( #modules )*
357                #types_mod
358            }
359        })
360    }
361}
362
363/// Return a vector of tuples of variant names and corresponding struct definitions.
364pub fn generate_structs_from_variants<F>(
365    type_gen: &TypeGenerator,
366    type_id: u32,
367    variant_to_struct_name: F,
368    error_message_type_name: &str,
369) -> Result<Vec<StructFromVariant>, CodegenError>
370where
371    F: Fn(&str) -> std::borrow::Cow<str>,
372{
373    let ty = type_gen.resolve_type(type_id)?;
374
375    let scale_info::TypeDef::Variant(variant) = &ty.type_def else {
376        return Err(CodegenError::InvalidType(error_message_type_name.into()));
377    };
378
379    variant
380        .variants
381        .iter()
382        .map(|var| {
383            let mut type_params = TypeParameters::from_scale_info(&[]);
384            let composite_ir_kind =
385                type_gen.create_composite_ir_kind(&var.fields, &mut type_params)?;
386            let struct_name = variant_to_struct_name(&var.name);
387            let mut composite = CompositeIR::new(
388                syn::parse_str(&struct_name).expect("enum variant is a valid ident; qed"),
389                composite_ir_kind,
390                type_gen.docs_from_scale_info(&var.docs),
391            );
392
393            let type_alias_mod = generate_type_alias_mod(&mut composite, type_gen);
394            Ok(StructFromVariant {
395                variant_name: var.name.to_string(),
396                composite,
397                type_alias_mod,
398            })
399        })
400        .collect()
401}
402
403pub struct StructFromVariant {
404    variant_name: String,
405    composite: CompositeIR,
406    type_alias_mod: TokenStream2,
407}
408
409/// Modifies the composite, by replacing its types with references to the generated type alias module.
410/// Returns the TokenStream of the type alias module.
411///
412/// E.g a struct like this:
413///
414/// ```rust,ignore
415/// pub struct SetMaxCodeSize {
416///     pub new: ::core::primitive::u32,
417/// }
418/// ```
419///
420/// will be made into this:
421///
422/// ```rust,ignore
423/// pub struct SetMaxCodeSize {
424///     pub new: set_max_code_size::New,
425/// }
426/// ```
427///
428/// And the type alias module will look like this:
429///
430/// ```rust,ignore
431/// pub mod set_max_code_size {
432///     use super::runtime_types;
433///     pub type New = ::core::primitive::u32;
434/// }
435/// ```
436pub fn generate_type_alias_mod(
437    composite: &mut CompositeIR,
438    type_gen: &TypeGenerator,
439) -> TokenStream2 {
440    let mut aliases: Vec<TokenStream2> = vec![];
441    let alias_mod_name: Ident = syn::parse_str(&composite.name.to_string().to_snake_case())
442        .expect("composite name in snake_case should be a valid identifier");
443
444    let mut modify_field_to_be_type_alias = |field: &mut CompositeFieldIR, alias_name: Ident| {
445        let type_path = field.type_path.to_token_stream(type_gen.settings());
446        aliases.push(quote!(pub type #alias_name = #type_path;));
447
448        let type_alias_path: syn::Path = parse_quote!(#alias_mod_name::#alias_name);
449        field.type_path = TypePath::from_syn_path(type_alias_path);
450    };
451
452    match &mut composite.kind {
453        CompositeIRKind::NoFields => {
454            return quote!(); // no types mod generated for unit structs.
455        }
456        CompositeIRKind::Named(named) => {
457            for (name, field) in named.iter_mut() {
458                let alias_name = format_ident!("{}", name.to_string().to_upper_camel_case());
459                modify_field_to_be_type_alias(field, alias_name);
460            }
461        }
462        CompositeIRKind::Unnamed(unnamed) => {
463            for (i, field) in unnamed.iter_mut().enumerate() {
464                let alias_name = format_ident!("Field{}", i);
465                modify_field_to_be_type_alias(field, alias_name);
466            }
467        }
468    };
469
470    let types_mod_ident = type_gen.types_mod_ident();
471    quote!(pub mod #alias_mod_name {
472        use super::#types_mod_ident;
473        #( #aliases )*
474    })
475}