pezkuwi_subxt_codegen/
lib.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 a type safe Subxt interface for a Bizinikiwi runtime from its metadata.
6//! This is used by the `#[subxt]` macro and `subxt codegen` CLI command, but can also
7//! be used directly if preferable.
8
9#![deny(missing_docs)]
10#![cfg_attr(docsrs, feature(doc_cfg))]
11
12mod api;
13pub mod error;
14mod ir;
15
16#[cfg(feature = "web")]
17use getrandom as _;
18
19use api::RuntimeGenerator;
20use proc_macro2::TokenStream as TokenStream2;
21use scale_typegen::{
22	typegen::settings::{substitutes::absolute_path, AllocCratePath},
23	DerivesRegistry, TypeGeneratorSettings, TypeSubstitutes, TypegenError,
24};
25use std::collections::HashMap;
26use syn::parse_quote;
27
28// Part of the public interface, so expose:
29pub use error::CodegenError;
30pub use pezkuwi_subxt_metadata::Metadata;
31pub use syn;
32
33/// Generate a type safe interface to use with `subxt`.
34/// The options exposed here are similar to those exposed via
35/// the `#[subxt]` macro or via the `subxt codegen` CLI command.
36/// Both use this under the hood.
37///
38/// # Example
39///
40/// Generating an interface using all of the defaults:
41///
42/// ```rust,standalone_crate
43/// use codec::Decode;
44/// use pezkuwi_subxt_codegen::{ Metadata, CodegenBuilder };
45///
46/// // Get hold of and decode some metadata:
47/// let encoded = std::fs::read("../artifacts/pezkuwi_metadata_full.scale").unwrap();
48/// let metadata = Metadata::decode(&mut &*encoded).unwrap();
49///
50/// // Generate a TokenStream representing the code for the interface.
51/// // This can be converted to a string, displayed as-is or output from a macro.
52/// let token_stream = CodegenBuilder::new().generate(metadata);
53/// ````
54pub struct CodegenBuilder {
55	crate_path: syn::Path,
56	use_default_derives: bool,
57	use_default_substitutions: bool,
58	generate_docs: bool,
59	runtime_types_only: bool,
60	item_mod: syn::ItemMod,
61	extra_global_derives: Vec<syn::Path>,
62	extra_global_attributes: Vec<syn::Attribute>,
63	type_substitutes: HashMap<syn::Path, syn::Path>,
64	derives_for_type: HashMap<syn::TypePath, Vec<syn::Path>>,
65	attributes_for_type: HashMap<syn::TypePath, Vec<syn::Attribute>>,
66	derives_for_type_recursive: HashMap<syn::TypePath, Vec<syn::Path>>,
67	attributes_for_type_recursive: HashMap<syn::TypePath, Vec<syn::Attribute>>,
68}
69
70impl Default for CodegenBuilder {
71	fn default() -> Self {
72		CodegenBuilder {
73			crate_path: syn::parse_quote!(::pezkuwi_subxt::ext::pezkuwi_subxt_core),
74			use_default_derives: true,
75			use_default_substitutions: true,
76			generate_docs: true,
77			runtime_types_only: false,
78			item_mod: syn::parse_quote!(
79				pub mod api {}
80			),
81			extra_global_derives: Vec::new(),
82			extra_global_attributes: Vec::new(),
83			type_substitutes: HashMap::new(),
84			derives_for_type: HashMap::new(),
85			attributes_for_type: HashMap::new(),
86			derives_for_type_recursive: HashMap::new(),
87			attributes_for_type_recursive: HashMap::new(),
88		}
89	}
90}
91
92impl CodegenBuilder {
93	/// Construct a builder to configure and generate a type-safe interface for Subxt.
94	pub fn new() -> Self {
95		CodegenBuilder::default()
96	}
97
98	/// Disable the default derives that are applied to all types.
99	///
100	/// # Warning
101	///
102	/// This is not recommended, and is highly likely to break some part of the
103	/// generated interface. Expect compile errors.
104	pub fn disable_default_derives(&mut self) {
105		self.use_default_derives = false;
106	}
107
108	/// Disable the default type substitutions that are applied to the generated
109	/// code.
110	///
111	/// # Warning
112	///
113	/// This is not recommended, and is highly likely to break some part of the
114	/// generated interface. Expect compile errors.
115	pub fn disable_default_substitutes(&mut self) {
116		self.use_default_substitutions = false;
117	}
118
119	/// Disable the output of doc comments associated with the generated types and
120	/// methods. This can reduce the generated code size at the expense of losing
121	/// documentation.
122	pub fn no_docs(&mut self) {
123		self.generate_docs = false;
124	}
125
126	/// Only generate the types, and don't generate the rest of the Subxt specific
127	/// interface.
128	pub fn runtime_types_only(&mut self) {
129		self.runtime_types_only = true;
130	}
131
132	/// Set the additional derives that will be applied to all types. By default,
133	/// a set of derives required for Subxt are automatically added for all types.
134	///
135	/// # Warning
136	///
137	/// Invalid derives, or derives that cannot be applied to _all_ of the generated
138	/// types (taking into account that some types are substituted for hand written ones
139	/// that we cannot add extra derives for) will lead to compile errors in the
140	/// generated code.
141	pub fn set_additional_global_derives(&mut self, derives: Vec<syn::Path>) {
142		self.extra_global_derives = derives;
143	}
144
145	/// Set the additional attributes that will be applied to all types. By default,
146	/// a set of attributes required for Subxt are automatically added for all types.
147	///
148	/// # Warning
149	///
150	/// Invalid attributes can very easily lead to compile errors in the generated code.
151	pub fn set_additional_global_attributes(&mut self, attributes: Vec<syn::Attribute>) {
152		self.extra_global_attributes = attributes;
153	}
154
155	/// Set additional derives for a specific type at the path given.
156	///
157	/// If you want to set the additional derives on all contained types recursively as well,
158	/// you can set the `recursive` argument to `true`. If you don't do that,
159	/// there might be compile errors in the generated code, if the derived trait
160	/// relies on the fact that contained types also implement that trait.
161	pub fn add_derives_for_type(
162		&mut self,
163		ty: syn::TypePath,
164		derives: impl IntoIterator<Item = syn::Path>,
165		recursive: bool,
166	) {
167		if recursive {
168			self.derives_for_type_recursive.entry(ty).or_default().extend(derives);
169		} else {
170			self.derives_for_type.entry(ty).or_default().extend(derives);
171		}
172	}
173
174	/// Set additional attributes for a specific type at the path given.
175	///
176	/// Setting the `recursive` argument to `true` will additionally add the specified
177	/// attributes to all contained types recursively.
178	pub fn add_attributes_for_type(
179		&mut self,
180		ty: syn::TypePath,
181		attributes: impl IntoIterator<Item = syn::Attribute>,
182		recursive: bool,
183	) {
184		if recursive {
185			self.attributes_for_type_recursive.entry(ty).or_default().extend(attributes);
186		} else {
187			self.attributes_for_type.entry(ty).or_default().extend(attributes);
188		}
189	}
190
191	/// Substitute a type at the given path with some type at the second path. During codegen,
192	/// we will avoid generating the type at the first path given, and instead point any references
193	/// to that type to the second path given.
194	///
195	/// The substituted type will need to implement the relevant traits to be compatible with the
196	/// original, and it will need to SCALE encode and SCALE decode in a compatible way.
197	pub fn set_type_substitute(&mut self, ty: syn::Path, with: syn::Path) {
198		self.type_substitutes.insert(ty, with);
199	}
200
201	/// By default, all of the code is generated inside a module `pub mod api {}`. We decorate
202	/// this module with a few attributes to reduce compile warnings and things. You can provide a
203	/// target module here, allowing you to add additional attributes or inner code items (with the
204	/// warning that duplicate identifiers will lead to compile errors).
205	pub fn set_target_module(&mut self, item_mod: syn::ItemMod) {
206		self.item_mod = item_mod;
207	}
208
209	/// Set the path to the `subxt` crate. By default, we expect it to be at
210	/// `::pezkuwi_subxt::ext::pezkuwi_subxt_core`.
211	///
212	/// # Panics
213	///
214	/// Panics if the path provided is not an absolute path.
215	pub fn set_subxt_crate_path(&mut self, crate_path: syn::Path) {
216		if absolute_path(crate_path.clone()).is_err() {
217			// Throw an error here, because otherwise we end up with a harder to comprehend error
218			// when substitute types don't begin with an absolute path.
219			panic!(
220				"The provided crate path must be an absolute path, ie prefixed with '::' or 'crate'"
221			);
222		}
223		self.crate_path = crate_path;
224	}
225
226	/// Generate an interface, assuming that the default path to the `subxt` crate is
227	/// `::pezkuwi_subxt::ext::pezkuwi_subxt_core`. If the `subxt` crate is not available as a top
228	/// level dependency, use `generate` and provide a valid path to the `subxt¦ crate.
229	pub fn generate(self, metadata: Metadata) -> Result<TokenStream2, CodegenError> {
230		let crate_path = self.crate_path;
231
232		let mut derives_registry: DerivesRegistry = if self.use_default_derives {
233			default_derives(&crate_path)
234		} else {
235			DerivesRegistry::new()
236		};
237
238		derives_registry.add_derives_for_all(self.extra_global_derives);
239		derives_registry.add_attributes_for_all(self.extra_global_attributes);
240
241		for (ty, derives) in self.derives_for_type {
242			derives_registry.add_derives_for(ty, derives, false);
243		}
244		for (ty, derives) in self.derives_for_type_recursive {
245			derives_registry.add_derives_for(ty, derives, true);
246		}
247		for (ty, attributes) in self.attributes_for_type {
248			derives_registry.add_attributes_for(ty, attributes, false);
249		}
250		for (ty, attributes) in self.attributes_for_type_recursive {
251			derives_registry.add_attributes_for(ty, attributes, true);
252		}
253
254		let mut type_substitutes: TypeSubstitutes = if self.use_default_substitutions {
255			default_substitutes(&crate_path)
256		} else {
257			TypeSubstitutes::new()
258		};
259
260		for (from, with) in self.type_substitutes {
261			let abs_path = absolute_path(with).map_err(TypegenError::from)?;
262			type_substitutes.insert(from, abs_path).map_err(TypegenError::from)?;
263		}
264
265		let item_mod = self.item_mod;
266		let generator = RuntimeGenerator::new(metadata);
267		let should_gen_docs = self.generate_docs;
268
269		if self.runtime_types_only {
270			generator.generate_runtime_types(
271				item_mod,
272				derives_registry,
273				type_substitutes,
274				crate_path,
275				should_gen_docs,
276			)
277		} else {
278			generator.generate_runtime(
279				item_mod,
280				derives_registry,
281				type_substitutes,
282				crate_path,
283				should_gen_docs,
284			)
285		}
286	}
287}
288
289/// The default [`scale_typegen::TypeGeneratorSettings`], subxt is using for generating code.
290/// Useful for emulating subxt's code generation settings from e.g. subxt-explorer.
291pub fn default_subxt_type_gen_settings() -> TypeGeneratorSettings {
292	let crate_path: syn::Path = parse_quote!(::pezkuwi_subxt::ext::pezkuwi_subxt_core);
293	let derives = default_derives(&crate_path);
294	let substitutes = default_substitutes(&crate_path);
295	subxt_type_gen_settings(derives, substitutes, &crate_path, true)
296}
297
298fn subxt_type_gen_settings(
299	derives: scale_typegen::DerivesRegistry,
300	substitutes: scale_typegen::TypeSubstitutes,
301	crate_path: &syn::Path,
302	should_gen_docs: bool,
303) -> TypeGeneratorSettings {
304	// Are we using codec::Encode or codec::Decode derives?
305	let are_codec_derives_used = derives.default_derives().derives().iter().any(|path| {
306		let mut segments_backwards = path.segments.iter().rev();
307		let ident = segments_backwards.next();
308		let module = segments_backwards.next();
309
310		let is_ident_match = ident.is_some_and(|s| s.ident == "Encode" || s.ident == "Decode");
311		let is_module_match = module.is_some_and(|s| s.ident == "codec");
312
313		is_ident_match && is_module_match
314	});
315
316	// If we're inserting the codec derives, we also should use `CompactAs` where necessary.
317	let compact_as_type_path =
318		are_codec_derives_used.then(|| parse_quote!(#crate_path::ext::codec::CompactAs));
319
320	TypeGeneratorSettings {
321		types_mod_ident: parse_quote!(runtime_types),
322		should_gen_docs,
323		derives,
324		substitutes,
325		decoded_bits_type_path: Some(parse_quote!(#crate_path::utils::bits::DecodedBits)),
326		compact_as_type_path,
327		compact_type_path: Some(parse_quote!(#crate_path::ext::codec::Compact)),
328		alloc_crate_path: AllocCratePath::Custom(parse_quote!(#crate_path::alloc)),
329		// Note: even when we don't use codec::Encode and codec::Decode, we need to keep
330		// #[codec(...)] attributes because `#[codec(skip)]` is still used/important with
331		// `EncodeAsType` and `DecodeAsType`.
332		insert_codec_attributes: true,
333	}
334}
335
336fn default_derives(crate_path: &syn::Path) -> DerivesRegistry {
337	let encode_crate_path = quote::quote! { #crate_path::ext::scale_encode }.to_string();
338	let decode_crate_path = quote::quote! { #crate_path::ext::scale_decode }.to_string();
339
340	let derives: [syn::Path; 3] = [
341		parse_quote!(#crate_path::ext::scale_encode::EncodeAsType),
342		parse_quote!(#crate_path::ext::scale_decode::DecodeAsType),
343		parse_quote!(Debug),
344	];
345
346	let attributes: [syn::Attribute; 2] = [
347		parse_quote!(#[encode_as_type(crate_path = #encode_crate_path)]),
348		parse_quote!(#[decode_as_type(crate_path = #decode_crate_path)]),
349	];
350
351	let mut derives_registry = DerivesRegistry::new();
352	derives_registry.add_derives_for_all(derives);
353	derives_registry.add_attributes_for_all(attributes);
354	derives_registry
355}
356
357fn default_substitutes(crate_path: &syn::Path) -> TypeSubstitutes {
358	let mut type_substitutes = TypeSubstitutes::new();
359
360	let defaults: [(syn::Path, syn::Path); 13] = [
361		(parse_quote!(bitvec::order::Lsb0), parse_quote!(#crate_path::utils::bits::Lsb0)),
362		(parse_quote!(bitvec::order::Msb0), parse_quote!(#crate_path::utils::bits::Msb0)),
363		(
364			parse_quote!(pezsp_core::crypto::AccountId32),
365			parse_quote!(#crate_path::utils::AccountId32),
366		),
367		(parse_quote!(fp_account::AccountId20), parse_quote!(#crate_path::utils::AccountId20)),
368		(
369			parse_quote!(pezsp_runtime::multiaddress::MultiAddress),
370			parse_quote!(#crate_path::utils::MultiAddress),
371		),
372		(parse_quote!(primitive_types::H160), parse_quote!(#crate_path::utils::H160)),
373		(parse_quote!(primitive_types::H256), parse_quote!(#crate_path::utils::H256)),
374		(parse_quote!(primitive_types::H512), parse_quote!(#crate_path::utils::H512)),
375		(
376			parse_quote!(pezframe_support::traits::misc::WrapperKeepOpaque),
377			parse_quote!(#crate_path::utils::WrapperKeepOpaque),
378		),
379		// BTreeMap and BTreeSet impose an `Ord` constraint on their key types. This
380		// can cause an issue with generated code that doesn't impl `Ord` by default.
381		// Decoding them to Vec by default (KeyedVec is just an alias for Vec with
382		// suitable type params) avoids these issues.
383		(parse_quote!(BTreeMap), parse_quote!(#crate_path::utils::KeyedVec)),
384		(parse_quote!(BinaryHeap), parse_quote!(#crate_path::alloc::vec::Vec)),
385		(parse_quote!(BTreeSet), parse_quote!(#crate_path::alloc::vec::Vec)),
386		// The `UncheckedExtrinsic(pub Vec<u8>)` is part of the runtime API calls.
387		// The inner bytes represent the encoded extrinsic, however when deriving the
388		// `EncodeAsType` the bytes would be re-encoded. This leads to the bytes
389		// being altered by adding the length prefix in front of them.
390
391		// Note: Not sure if this is appropriate or not. The most recent pezkuwi.rs file does not
392		// have these.
393		(
394			parse_quote!(pezsp_runtime::generic::unchecked_extrinsic::UncheckedExtrinsic),
395			parse_quote!(#crate_path::utils::UncheckedExtrinsic),
396		),
397	];
398
399	let defaults = defaults.into_iter().map(|(from, to)| {
400		(from, absolute_path(to).expect("default substitutes above are absolute paths; qed"))
401	});
402	type_substitutes
403		.extend(defaults)
404		.expect("default substitutes can always be parsed; qed");
405	type_substitutes
406}