1use std::{fs, io};
2
3pub mod types;
4
5use sha2::{Digest, Sha256};
6
7use stellar_xdr::curr::ScSpecEntry;
8use types::Entry;
9
10use soroban_spec::read::{from_wasm, FromWasmError};
11
12#[derive(thiserror::Error, Debug)]
13pub enum GenerateFromFileError {
14 #[error("reading file: {0}")]
15 Io(io::Error),
16 #[error("sha256 does not match, expected: {expected}")]
17 VerifySha256 { expected: String },
18 #[error("parsing contract spec: {0}")]
19 Parse(stellar_xdr::curr::Error),
20 #[error("getting contract spec: {0}")]
21 GetSpec(FromWasmError),
22}
23
24pub fn generate_from_file(
28 file: &str,
29 verify_sha256: Option<&str>,
30) -> Result<String, GenerateFromFileError> {
31 let wasm = fs::read(file).map_err(GenerateFromFileError::Io)?;
33
34 let sha256 = Sha256::digest(&wasm);
36 let sha256 = format!("{sha256:x}");
37
38 if let Some(verify_sha256) = verify_sha256 {
39 if verify_sha256 != sha256 {
40 return Err(GenerateFromFileError::VerifySha256 { expected: sha256 });
41 }
42 }
43
44 let json = generate_from_wasm(&wasm).map_err(GenerateFromFileError::GetSpec)?;
46 Ok(json)
47}
48
49pub fn generate_from_wasm(wasm: &[u8]) -> Result<String, FromWasmError> {
53 let spec = from_wasm(wasm)?;
54 let json = generate(&spec);
55 Ok(json)
56}
57
58pub fn generate(spec: &[ScSpecEntry]) -> String {
62 let collected: Vec<_> = spec.iter().map(Entry::from).collect();
63 serde_json::to_string_pretty(&collected).expect("serialization of the spec entries should not have any failure cases as all keys are strings and the serialize implementations are derived")
64}
65
66#[allow(clippy::too_many_lines)]
67#[cfg(test)]
68mod test {
69 use pretty_assertions::assert_eq;
70 use soroban_spec::read::from_wasm;
71
72 use super::generate;
73
74 const EXAMPLE_WASM: &[u8] =
75 include_bytes!("../../../../target/wasm32v1-none/test-wasms/test_udt.wasm");
76
77 #[test]
78 fn example() {
79 let entries = from_wasm(EXAMPLE_WASM).unwrap();
80 let json = generate(&entries);
81 assert_eq!(
82 json,
83 r#"[
84 {
85 "type": "enum",
86 "doc": "",
87 "name": "UdtEnum2",
88 "cases": [
89 {
90 "doc": "",
91 "name": "A",
92 "value": 10
93 },
94 {
95 "doc": "",
96 "name": "B",
97 "value": 15
98 }
99 ]
100 },
101 {
102 "type": "union",
103 "doc": "",
104 "name": "UdtEnum",
105 "cases": [
106 {
107 "doc": "",
108 "name": "UdtA",
109 "values": []
110 },
111 {
112 "doc": "",
113 "name": "UdtB",
114 "values": [
115 {
116 "type": "custom",
117 "name": "UdtStruct"
118 }
119 ]
120 },
121 {
122 "doc": "",
123 "name": "UdtC",
124 "values": [
125 {
126 "type": "custom",
127 "name": "UdtEnum2"
128 }
129 ]
130 },
131 {
132 "doc": "",
133 "name": "UdtD",
134 "values": [
135 {
136 "type": "custom",
137 "name": "UdtTuple"
138 }
139 ]
140 }
141 ]
142 },
143 {
144 "type": "struct",
145 "doc": "",
146 "name": "UdtTuple",
147 "fields": [
148 {
149 "doc": "",
150 "name": "0",
151 "value": {
152 "type": "i64"
153 }
154 },
155 {
156 "doc": "",
157 "name": "1",
158 "value": {
159 "type": "vec",
160 "element": {
161 "type": "i64"
162 }
163 }
164 }
165 ]
166 },
167 {
168 "type": "struct",
169 "doc": "",
170 "name": "UdtStruct",
171 "fields": [
172 {
173 "doc": "",
174 "name": "a",
175 "value": {
176 "type": "i64"
177 }
178 },
179 {
180 "doc": "",
181 "name": "b",
182 "value": {
183 "type": "i64"
184 }
185 },
186 {
187 "doc": "",
188 "name": "c",
189 "value": {
190 "type": "vec",
191 "element": {
192 "type": "i64"
193 }
194 }
195 }
196 ]
197 },
198 {
199 "type": "function",
200 "doc": "",
201 "name": "add",
202 "inputs": [
203 {
204 "doc": "",
205 "name": "a",
206 "value": {
207 "type": "custom",
208 "name": "UdtEnum"
209 }
210 },
211 {
212 "doc": "",
213 "name": "b",
214 "value": {
215 "type": "custom",
216 "name": "UdtEnum"
217 }
218 }
219 ],
220 "outputs": [
221 {
222 "type": "i64"
223 }
224 ]
225 }
226]"#,
227 );
228 }
229}