1use codec::Decode;
8use darling::{FromMeta, ast::NestedMeta};
9use proc_macro::TokenStream;
10use proc_macro_error2::{abort_call_site, proc_macro_error};
11use quote::ToTokens;
12use scale_typegen::typegen::{
13 settings::substitutes::path_segments,
14 validation::{registry_contains_type_path, similar_type_paths_in_registry},
15};
16use subxt_codegen::{CodegenBuilder, CodegenError, Metadata};
17use syn::{parse_macro_input, punctuated::Punctuated};
18
19#[cfg(feature = "runtime-wasm-path")]
20mod wasm_loader;
21
22#[derive(Clone, Debug)]
23struct OuterAttribute(syn::Attribute);
24
25impl syn::parse::Parse for OuterAttribute {
26 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
27 Ok(Self(input.call(syn::Attribute::parse_outer)?[0].clone()))
28 }
29}
30
31#[derive(Debug, FromMeta)]
32struct RuntimeMetadataArgs {
33 #[darling(default)]
34 runtime_metadata_path: Option<String>,
35 #[darling(default)]
36 runtime_metadata_insecure_url: Option<String>,
37 #[darling(default)]
38 derive_for_all_types: Option<Punctuated<syn::Path, syn::Token![,]>>,
39 #[darling(default)]
40 attributes_for_all_types: Option<Punctuated<OuterAttribute, syn::Token![,]>>,
41 #[darling(multiple)]
42 derive_for_type: Vec<DeriveForType>,
43 #[darling(multiple)]
44 attributes_for_type: Vec<AttributesForType>,
45 #[darling(multiple)]
46 substitute_type: Vec<SubstituteType>,
47 #[darling(default, rename = "crate")]
48 crate_path: Option<syn::Path>,
49 #[darling(default)]
50 generate_docs: darling::util::Flag,
51 #[darling(default)]
52 runtime_types_only: bool,
53 #[darling(default)]
54 no_default_derives: bool,
55 #[darling(default)]
56 no_default_substitutions: bool,
57 #[darling(default)]
58 unstable_metadata: darling::util::Flag,
59 #[cfg(feature = "runtime-wasm-path")]
60 #[darling(default)]
61 runtime_path: Option<String>,
62}
63
64#[derive(Debug, FromMeta)]
65struct DeriveForType {
66 path: syn::TypePath,
67 derive: Punctuated<syn::Path, syn::Token![,]>,
68 #[darling(default)]
69 recursive: bool,
70}
71
72#[derive(Debug, FromMeta)]
73struct AttributesForType {
74 path: syn::TypePath,
75 attributes: Punctuated<OuterAttribute, syn::Token![,]>,
76 #[darling(default)]
77 recursive: bool,
78}
79
80#[derive(Debug, FromMeta)]
81struct SubstituteType {
82 path: syn::Path,
83 with: syn::Path,
84}
85
86#[allow(missing_docs)]
88#[proc_macro_attribute]
89#[proc_macro_error]
90pub fn subxt(args: TokenStream, input: TokenStream) -> TokenStream {
91 match subxt_inner(args, parse_macro_input!(input as syn::ItemMod)) {
92 Ok(e) => e,
93 Err(e) => e,
94 }
95}
96
97fn subxt_inner(args: TokenStream, item_mod: syn::ItemMod) -> Result<TokenStream, TokenStream> {
99 let attr_args = NestedMeta::parse_meta_list(args.into())
100 .map_err(|e| TokenStream::from(darling::Error::from(e).write_errors()))?;
101 let args = RuntimeMetadataArgs::from_list(&attr_args)
102 .map_err(|e| TokenStream::from(e.write_errors()))?;
103
104 let metadata = fetch_metadata(&args)?;
106
107 let mut codegen = CodegenBuilder::new();
108
109 codegen.set_target_module(item_mod);
111
112 if let Some(crate_path) = args.crate_path {
114 codegen.set_subxt_crate_path(crate_path)
115 }
116
117 if args.runtime_types_only {
119 codegen.runtime_types_only();
120 }
121 if args.no_default_derives {
122 codegen.disable_default_derives();
123 }
124 if args.no_default_substitutions {
125 codegen.disable_default_substitutes();
126 }
127 if !args.generate_docs.is_present() {
128 codegen.no_docs()
129 }
130
131 codegen.set_additional_global_derives(
133 args.derive_for_all_types
134 .unwrap_or_default()
135 .into_iter()
136 .collect(),
137 );
138 for d in args.derive_for_type {
139 validate_type_path(&d.path.path, &metadata);
140 codegen.add_derives_for_type(d.path, d.derive.into_iter(), d.recursive);
141 }
142
143 codegen.set_additional_global_attributes(
145 args.attributes_for_all_types
146 .unwrap_or_default()
147 .into_iter()
148 .map(|a| a.0)
149 .collect(),
150 );
151 for d in args.attributes_for_type {
152 validate_type_path(&d.path.path, &metadata);
153 codegen.add_attributes_for_type(d.path, d.attributes.into_iter().map(|a| a.0), d.recursive)
154 }
155
156 for sub in args.substitute_type.into_iter() {
158 validate_type_path(&sub.path, &metadata);
159 codegen.set_type_substitute(sub.path, sub.with);
160 }
161
162 let code = codegen
163 .generate(metadata)
164 .map_err(|e| e.into_compile_error())?;
165
166 Ok(code.into())
167}
168
169fn validate_type_path(path: &syn::Path, metadata: &Metadata) {
172 let path_segments = path_segments(path);
173 let ident = &path
174 .segments
175 .last()
176 .expect("Empty path should be filtered out before already")
177 .ident;
178 if !registry_contains_type_path(metadata.types(), &path_segments) {
179 let alternatives = similar_type_paths_in_registry(metadata.types(), path);
180 let alternatives: String = if alternatives.is_empty() {
181 format!("There is no Type with name `{ident}` in the provided metadata.")
182 } else {
183 let mut s = "A type with the same name is present at: ".to_owned();
184 for p in alternatives {
185 s.push('\n');
186 s.push_str(&pretty_path(&p));
187 }
188 s
189 };
190
191 abort_call_site!(
192 "Type `{}` does not exist at path `{}`\n\n{}",
193 ident.to_string(),
194 pretty_path(path),
195 alternatives
196 );
197 }
198
199 fn pretty_path(path: &syn::Path) -> String {
200 path.to_token_stream().to_string().replace(' ', "")
201 }
202}
203
204fn resolve_path(path_str: &str) -> std::path::PathBuf {
208 if path_str.contains("$OUT_DIR") {
209 let out_dir = std::env::var("OUT_DIR").unwrap_or_else(|_| {
210 abort_call_site!("$OUT_DIR is used in path but OUT_DIR environment variable is not set")
211 });
212 std::path::Path::new(&path_str.replace("$OUT_DIR", &out_dir)).into()
213 } else {
214 let root = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".into());
215 let root_path = std::path::Path::new(&root);
216 root_path.join(path_str)
217 }
218}
219
220fn fetch_metadata(args: &RuntimeMetadataArgs) -> Result<subxt_codegen::Metadata, TokenStream> {
222 let unstable_metadata = args.unstable_metadata.is_present();
224
225 #[cfg(feature = "runtime-wasm-path")]
226 if let Some(path) = &args.runtime_path {
227 if args.runtime_metadata_insecure_url.is_some() || args.runtime_metadata_path.is_some() {
228 abort_call_site!(
229 "Only one of 'runtime_metadata_path', 'runtime_metadata_insecure_url' or `runtime_path` must be provided"
230 );
231 };
232 let path = resolve_path(path);
233
234 let metadata = wasm_loader::from_wasm_file(&path).map_err(|e| e.into_compile_error())?;
235 return Ok(metadata);
236 };
237
238 let metadata = match (
239 &args.runtime_metadata_path,
240 &args.runtime_metadata_insecure_url,
241 ) {
242 (Some(rest_of_path), None) => {
243 if unstable_metadata {
244 abort_call_site!(
245 "The 'unstable_metadata' attribute requires `runtime_metadata_insecure_url`"
246 )
247 }
248
249 let path = resolve_path(rest_of_path);
250
251 subxt_utils_fetchmetadata::from_file_blocking(&path)
252 .and_then(|b| subxt_codegen::Metadata::decode(&mut &*b).map_err(Into::into))
253 .map_err(|e| CodegenError::Other(e.to_string()).into_compile_error())?
254 }
255 #[cfg(feature = "runtime-metadata-insecure-url")]
256 (None, Some(url_string)) => {
257 use subxt_utils_fetchmetadata::{MetadataVersion, Url, from_url_blocking};
258
259 let url = Url::parse(url_string).unwrap_or_else(|_| {
260 abort_call_site!("Cannot download metadata; invalid url: {}", url_string)
261 });
262
263 let version = match unstable_metadata {
264 true => MetadataVersion::Unstable,
265 false => MetadataVersion::Latest,
266 };
267
268 from_url_blocking(url, version)
269 .map_err(|e| CodegenError::Other(e.to_string()))
270 .and_then(|b| subxt_codegen::Metadata::decode(&mut &*b).map_err(Into::into))
271 .map_err(|e| e.into_compile_error())?
272 }
273 #[cfg(not(feature = "runtime-metadata-insecure-url"))]
274 (None, Some(_)) => {
275 abort_call_site!(
276 "'runtime_metadata_insecure_url' requires the 'runtime-metadata-insecure-url' feature to be enabled"
277 )
278 }
279 #[cfg(feature = "runtime-wasm-path")]
280 (None, None) => {
281 abort_call_site!(
282 "At least one of 'runtime_metadata_path', 'runtime_metadata_insecure_url' or 'runtime_path` can be provided"
283 )
284 }
285 #[cfg(not(feature = "runtime-wasm-path"))]
286 (None, None) => {
287 abort_call_site!(
288 "At least one of 'runtime_metadata_path', 'runtime_metadata_insecure_url' can be provided"
289 )
290 }
291 #[cfg(feature = "runtime-wasm-path")]
292 _ => {
293 abort_call_site!(
294 "Only one of 'runtime_metadata_path', 'runtime_metadata_insecure_url' or 'runtime_path` can be provided"
295 )
296 }
297 #[cfg(not(feature = "runtime-wasm-path"))]
298 _ => {
299 abort_call_site!(
300 "Only one of 'runtime_metadata_path' or 'runtime_metadata_insecure_url' can be provided"
301 )
302 }
303 };
304 Ok(metadata)
305}