unc_abi_client_impl/
lib.rs1use unc_abi::{AbiFunctionKind, AbiParameters, AbiRoot, AbiType};
2use near_schemafy_lib::{Expander, Generator, Schema};
3use quote::{format_ident, quote};
4use std::path::{Path, PathBuf};
5
6pub fn generate_abi_client(
7 unc_abi: AbiRoot,
8 contract_name: proc_macro2::Ident,
9) -> proc_macro2::TokenStream {
10 let schema_json = serde_json::to_string(&unc_abi.body.root_schema).unwrap();
11
12 let generator = Generator::builder().with_input_json(&schema_json).build();
13 let (mut token_stream, schema) = generator.generate_with_schema();
14 let mut expander = Expander::new(None, "", &schema);
15
16 token_stream.extend(quote! {
17 pub struct #contract_name {
18 pub contract: utility_workspaces::Contract,
19 }
20 });
21
22 let mut methods_stream = proc_macro2::TokenStream::new();
23 for function in unc_abi.body.functions {
24 let name = format_ident!("{}", function.name);
25
26 let mut param_names = vec![];
27 let params = match &function.params {
28 AbiParameters::Borsh { .. } => panic!("Borsh is currently unsupported"),
29 AbiParameters::Json { args } => args
30 .iter()
31 .map(|arg| {
32 param_names.push(format_ident!("{}", arg.name));
33 let arg_name = param_names.last().unwrap();
34 let arg_type = expand_subschema(&mut expander, &arg.type_schema);
35 quote! { #arg_name: #arg_type }
36 })
37 .collect::<Vec<_>>(),
38 };
39
40 let return_type = function
41 .result
42 .map(|r_type| match r_type {
43 AbiType::Json { type_schema } => expand_subschema(&mut expander, &type_schema),
44 AbiType::Borsh { type_schema: _ } => panic!("Borsh is currently unsupported"),
45 })
46 .unwrap_or_else(|| format_ident!("{}", "()"));
47 let name_str = name.to_string();
48 let args = if param_names.is_empty() {
49 quote! { () }
52 } else {
53 quote! { [#(#param_names),*] }
54 };
55 if function.kind == AbiFunctionKind::View {
56 methods_stream.extend(quote! {
57 pub async fn #name(
58 &self,
59 #(#params),*
60 ) -> anyhow::Result<#return_type> {
61 let result = self.contract
62 .call(#name_str)
63 .args_json(#args)
64 .view()
65 .await?;
66 Ok(result.json::<#return_type>()?)
67 }
68 });
69 } else {
70 methods_stream.extend(quote! {
71 pub async fn #name(
72 &self,
73 gas: utility_workspaces::types::Gas,
74 deposit: utility_workspaces::types::Balance,
75 #(#params),*
76 ) -> anyhow::Result<#return_type> {
77 let result = self.contract
78 .call(#name_str)
79 .args_json(#args)
80 .gas(gas)
81 .deposit(deposit)
82 .transact()
83 .await?;
84 Ok(result.json::<#return_type>()?)
85 }
86 });
87 }
88 }
89
90 token_stream.extend(quote! {
91 impl #contract_name {
92 #methods_stream
93 }
94 });
95
96 token_stream
97}
98
99pub fn read_abi(abi_path: impl AsRef<Path>) -> AbiRoot {
100 let abi_path = if abi_path.as_ref().is_relative() {
101 let crate_root = get_crate_root().unwrap();
102 crate_root.join(&abi_path)
103 } else {
104 PathBuf::from(abi_path.as_ref())
105 };
106
107 let abi_json = std::fs::read_to_string(&abi_path)
108 .unwrap_or_else(|err| panic!("Unable to read `{}`: {}", abi_path.to_string_lossy(), err));
109
110 serde_json::from_str::<AbiRoot>(&abi_json).unwrap_or_else(|err| {
111 panic!(
112 "Cannot parse `{}` as ABI: {}",
113 abi_path.to_string_lossy(),
114 err
115 )
116 })
117}
118
119fn get_crate_root() -> std::io::Result<PathBuf> {
120 if let Ok(path) = std::env::var("CARGO_MANIFEST_DIR") {
121 return Ok(PathBuf::from(path));
122 }
123
124 let current_dir = std::env::current_dir()?;
125
126 for p in current_dir.ancestors() {
127 if std::fs::read_dir(p)?
128 .filter_map(Result::ok)
129 .any(|p| p.file_name().eq("Cargo.toml"))
130 {
131 return Ok(PathBuf::from(p));
132 }
133 }
134
135 Ok(current_dir)
136}
137
138fn schemars_schema_to_schemafy(schema: &schemars::schema::Schema) -> Schema {
139 let schema_json = serde_json::to_string(&schema).unwrap();
140 serde_json::from_str(&schema_json).unwrap_or_else(|err| {
141 panic!(
142 "Could not convert schemars schema to schemafy model: {}",
143 err
144 )
145 })
146}
147
148fn expand_subschema(
149 expander: &mut Expander,
150 schema: &schemars::schema::Schema,
151) -> proc_macro2::Ident {
152 let schemafy_schema = schemars_schema_to_schemafy(schema);
153 format_ident!("{}", expander.expand_type_from_schema(&schemafy_schema).typ)
154}