near_sdk_abi_impl/
lib.rs

1use near_abi::{AbiParameters, AbiRoot, AbiType};
2use near_schemafy_lib::{Expander, Generator};
3use quote::{format_ident, quote};
4use std::path::{Path, PathBuf};
5
6pub fn generate_ext(
7    near_abi: AbiRoot,
8    contract_name: proc_macro2::Ident,
9    mod_name: Option<proc_macro2::Ident>,
10) -> proc_macro2::TokenStream {
11    let schema_json = serde_json::to_string(&near_abi.body.root_schema).unwrap();
12
13    let generator = Generator::builder().with_input_json(&schema_json).build();
14    let (mut token_stream, schema) = generator.generate_with_schema();
15    let mut expander = Expander::new(None, "", &schema);
16
17    let methods = near_abi
18        .body
19        .functions
20        .iter()
21        .map(|m| {
22            let name = format_ident!("{}", m.name);
23            let result_type = m
24                .result
25                .clone()
26                .map(|r_param| {
27                    let r_type = match &r_param {
28                        AbiType::Json { type_schema } => {
29                            expand_subschema(&mut expander, type_schema)
30                        }
31                        AbiType::Borsh { type_schema: _ } => {
32                            panic!("Borsh is currently unsupported")
33                        }
34                    };
35                    quote! { -> #r_type }
36                })
37                .unwrap_or_else(|| quote! {});
38            let args = match &m.params {
39                AbiParameters::Json { args } => args
40                    .iter()
41                    .map(|arg| {
42                        let arg_type = expand_subschema(&mut expander, &arg.type_schema);
43                        let arg_name = format_ident!("{}", &arg.name);
44                        quote! { #arg_name: #arg_type }
45                    })
46                    .collect::<Vec<_>>(),
47                AbiParameters::Borsh { args: _ } => panic!("Borsh is currently unsupported"),
48            };
49            quote! { fn #name(&self, #(#args),*) #result_type; }
50        })
51        .collect::<Vec<_>>();
52
53    let ext_contract = mod_name.map_or_else(
54        || quote! { #[near_sdk::ext_contract] },
55        |n| quote! { #[near_sdk::ext_contract(#n)] },
56    );
57
58    token_stream.extend(quote! {
59        #ext_contract
60        pub trait #contract_name {
61            #(#methods)*
62        }
63    });
64
65    token_stream
66}
67
68pub fn read_abi(abi_path: impl AsRef<Path>) -> AbiRoot {
69    let abi_path = if abi_path.as_ref().is_relative() {
70        let crate_root = get_crate_root().unwrap();
71        crate_root.join(&abi_path)
72    } else {
73        PathBuf::from(abi_path.as_ref())
74    };
75
76    let abi_json = std::fs::read_to_string(&abi_path)
77        .unwrap_or_else(|err| panic!("Unable to read `{}`: {}", abi_path.to_string_lossy(), err));
78
79    serde_json::from_str::<AbiRoot>(&abi_json).unwrap_or_else(|err| {
80        panic!(
81            "Cannot parse `{}` as ABI: {}",
82            abi_path.to_string_lossy(),
83            err
84        )
85    })
86}
87
88pub fn get_crate_root() -> std::io::Result<PathBuf> {
89    if let Ok(path) = std::env::var("CARGO_MANIFEST_DIR") {
90        return Ok(PathBuf::from(path));
91    }
92
93    let current_dir = std::env::current_dir()?;
94
95    for p in current_dir.ancestors() {
96        if std::fs::read_dir(p)?
97            .into_iter()
98            .filter_map(Result::ok)
99            .any(|p| p.file_name().eq("Cargo.toml"))
100        {
101            return Ok(PathBuf::from(p));
102        }
103    }
104
105    Ok(current_dir)
106}
107
108fn schemars_schema_to_schemafy(schema: &schemars::schema::Schema) -> near_schemafy_lib::Schema {
109    let schema_json = serde_json::to_string(&schema).unwrap();
110    serde_json::from_str(&schema_json).unwrap_or_else(|err| {
111        panic!(
112            "Could not convert schemars schema to schemafy model: {}",
113            err
114        )
115    })
116}
117
118fn expand_subschema(expander: &mut Expander, schema: &schemars::schema::Schema) -> syn::Ident {
119    let schemafy_schema = schemars_schema_to_schemafy(schema);
120    format_ident!("{}", expander.expand_type_from_schema(&schemafy_schema).typ)
121}