soroban_spec_json/
lib.rs

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
24/// # Errors
25///
26/// Will return an error if the file cannot be read, or the wasm cannot be parsed.
27pub fn generate_from_file(
28    file: &str,
29    verify_sha256: Option<&str>,
30) -> Result<String, GenerateFromFileError> {
31    // Read file.
32    let wasm = fs::read(file).map_err(GenerateFromFileError::Io)?;
33
34    // Produce hash for file.
35    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    // Generate code.
45    let json = generate_from_wasm(&wasm).map_err(GenerateFromFileError::GetSpec)?;
46    Ok(json)
47}
48
49/// # Errors
50///
51/// Will return an error if the wasm cannot be parsed.
52pub 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
58/// # Panics
59///
60/// If `serde_json::to_string_pretty` fails to serialize the spec entries.
61pub 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}