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}