cosmwasm_schema/
idl.rs

1//! The Cosmwasm IDL (Interface Description Language)
2
3use std::collections::BTreeMap;
4
5use schemars::schema::RootSchema;
6use thiserror::Error;
7
8/// The version of the CosmWasm IDL.
9///
10/// Follows Semantic Versioning 2.0.0: <https://semver.org/>
11// To determine if a change is breaking, assume consumers allow unknown fields and bump accordingly.
12pub const IDL_VERSION: &str = "1.0.0";
13
14/// Rust representation of a contract's API.
15pub struct CwApi {
16    pub contract_name: String,
17    pub contract_version: String,
18    pub instantiate: Option<cw_schema::Schema>,
19    pub execute: Option<cw_schema::Schema>,
20    pub query: Option<cw_schema::Schema>,
21    pub migrate: Option<cw_schema::Schema>,
22    pub sudo: Option<cw_schema::Schema>,
23    /// A mapping of query variants to response types
24    pub responses: Option<BTreeMap<String, cw_schema::Schema>>,
25}
26
27impl CwApi {
28    pub fn render(self) -> JsonCwApi {
29        JsonCwApi {
30            contract_name: self.contract_name,
31            contract_version: self.contract_version,
32            idl_version: IDL_VERSION.to_string(),
33            instantiate: self.instantiate,
34            execute: self.execute,
35            query: self.query,
36            migrate: self.migrate,
37            sudo: self.sudo,
38            responses: self.responses,
39        }
40    }
41}
42
43/// Rust representation of a contract's API.
44pub struct Api {
45    pub contract_name: String,
46    pub contract_version: String,
47    pub instantiate: Option<RootSchema>,
48    pub execute: Option<RootSchema>,
49    pub query: Option<RootSchema>,
50    pub migrate: Option<RootSchema>,
51    pub sudo: Option<RootSchema>,
52    /// A mapping of query variants to response types
53    pub responses: Option<BTreeMap<String, RootSchema>>,
54}
55
56impl Api {
57    pub fn render(self) -> JsonApi {
58        let mut json_api = JsonApi {
59            contract_name: self.contract_name,
60            contract_version: self.contract_version,
61            idl_version: IDL_VERSION.to_string(),
62            instantiate: self.instantiate,
63            execute: self.execute,
64            query: self.query,
65            migrate: self.migrate,
66            sudo: self.sudo,
67            responses: self.responses,
68        };
69
70        if let Some(instantiate) = &mut json_api.instantiate {
71            if let Some(metadata) = &mut instantiate.schema.metadata {
72                metadata.title = Some("InstantiateMsg".to_string());
73            }
74        }
75        if let Some(execute) = &mut json_api.execute {
76            if let Some(metadata) = &mut execute.schema.metadata {
77                metadata.title = Some("ExecuteMsg".to_string());
78            }
79        }
80        if let Some(query) = &mut json_api.query {
81            if let Some(metadata) = &mut query.schema.metadata {
82                metadata.title = Some("QueryMsg".to_string());
83            }
84        }
85        if let Some(migrate) = &mut json_api.migrate {
86            if let Some(metadata) = &mut migrate.schema.metadata {
87                metadata.title = Some("MigrateMsg".to_string());
88            }
89        }
90        if let Some(sudo) = &mut json_api.sudo {
91            if let Some(metadata) = &mut sudo.schema.metadata {
92                metadata.title = Some("SudoMsg".to_string());
93            }
94        }
95
96        json_api
97    }
98}
99
100/// A JSON representation of a contract's API.
101#[derive(serde::Deserialize, serde::Serialize)]
102pub struct JsonCwApi {
103    pub contract_name: String,
104    pub contract_version: String,
105    pub idl_version: String,
106    pub instantiate: Option<cw_schema::Schema>,
107    pub execute: Option<cw_schema::Schema>,
108    pub query: Option<cw_schema::Schema>,
109    pub migrate: Option<cw_schema::Schema>,
110    pub sudo: Option<cw_schema::Schema>,
111    pub responses: Option<BTreeMap<String, cw_schema::Schema>>,
112}
113
114impl JsonCwApi {
115    pub fn to_string(&self) -> Result<String, EncodeError> {
116        serde_json::to_string_pretty(&self).map_err(Into::into)
117    }
118
119    pub fn to_schema_files(&self) -> Result<Vec<(String, String)>, EncodeError> {
120        let mut result = Vec::new();
121
122        if let Some(instantiate) = &self.instantiate {
123            result.push((
124                "instantiate.json".to_string(),
125                serde_json::to_string_pretty(&instantiate)?,
126            ));
127        }
128
129        if let Some(execute) = &self.execute {
130            result.push((
131                "execute.json".to_string(),
132                serde_json::to_string_pretty(&execute)?,
133            ));
134        }
135        if let Some(query) = &self.query {
136            result.push((
137                "query.json".to_string(),
138                serde_json::to_string_pretty(&query)?,
139            ));
140        }
141        if let Some(migrate) = &self.migrate {
142            result.push((
143                "migrate.json".to_string(),
144                serde_json::to_string_pretty(&migrate)?,
145            ));
146        }
147        if let Some(sudo) = &self.sudo {
148            result.push((
149                "sudo.json".to_string(),
150                serde_json::to_string_pretty(&sudo)?,
151            ));
152        }
153        if let Some(responses) = &self.responses {
154            for (name, response) in responses {
155                result.push((
156                    format!("response_to_{name}.json"),
157                    serde_json::to_string_pretty(&response)?,
158                ));
159            }
160        }
161
162        Ok(result)
163    }
164
165    pub fn to_writer(&self, writer: impl std::io::Write) -> Result<(), EncodeError> {
166        serde_json::to_writer_pretty(writer, self).map_err(Into::into)
167    }
168}
169
170/// A JSON representation of a contract's API.
171#[derive(serde::Serialize)]
172pub struct JsonApi {
173    contract_name: String,
174    contract_version: String,
175    idl_version: String,
176    instantiate: Option<RootSchema>,
177    execute: Option<RootSchema>,
178    query: Option<RootSchema>,
179    migrate: Option<RootSchema>,
180    sudo: Option<RootSchema>,
181    responses: Option<BTreeMap<String, RootSchema>>,
182}
183
184impl JsonApi {
185    pub fn to_string(&self) -> Result<String, EncodeError> {
186        serde_json::to_string_pretty(&self).map_err(Into::into)
187    }
188
189    pub fn to_schema_files(&self) -> Result<Vec<(String, String)>, EncodeError> {
190        let mut result = Vec::new();
191
192        if let Some(instantiate) = &self.instantiate {
193            result.push((
194                "instantiate.json".to_string(),
195                serde_json::to_string_pretty(&instantiate)?,
196            ));
197        }
198
199        if let Some(execute) = &self.execute {
200            result.push((
201                "execute.json".to_string(),
202                serde_json::to_string_pretty(&execute)?,
203            ));
204        }
205        if let Some(query) = &self.query {
206            result.push((
207                "query.json".to_string(),
208                serde_json::to_string_pretty(&query)?,
209            ));
210        }
211        if let Some(migrate) = &self.migrate {
212            result.push((
213                "migrate.json".to_string(),
214                serde_json::to_string_pretty(&migrate)?,
215            ));
216        }
217        if let Some(sudo) = &self.sudo {
218            result.push((
219                "sudo.json".to_string(),
220                serde_json::to_string_pretty(&sudo)?,
221            ));
222        }
223        if let Some(responses) = &self.responses {
224            for (name, response) in responses {
225                result.push((
226                    format!("response_to_{name}.json"),
227                    serde_json::to_string_pretty(&response)?,
228                ));
229            }
230        }
231
232        Ok(result)
233    }
234
235    pub fn to_writer(&self, writer: impl std::io::Write) -> Result<(), EncodeError> {
236        serde_json::to_writer_pretty(writer, self).map_err(Into::into)
237    }
238}
239
240#[derive(Error, Debug)]
241pub enum EncodeError {
242    #[error("{0}")]
243    JsonError(#[from] serde_json::Error),
244}
245
246#[cfg(test)]
247mod tests {
248    use crate::schema_for;
249
250    use super::*;
251
252    #[test]
253    fn version_is_semver() {
254        semver::Version::parse(IDL_VERSION).unwrap();
255    }
256
257    #[test]
258    fn to_schema_files_works() {
259        let empty = Api {
260            contract_name: "my_contract".to_string(),
261            contract_version: "1.2.3".to_string(),
262            instantiate: None,
263            execute: None,
264            query: None,
265            migrate: None,
266            sudo: None,
267            responses: None,
268        };
269
270        let files = empty.render().to_schema_files().unwrap();
271        assert_eq!(files, []);
272
273        #[derive(schemars::JsonSchema)]
274        struct TestMsg {}
275
276        let full = Api {
277            contract_name: "my_contract".to_string(),
278            contract_version: "1.2.3".to_string(),
279            instantiate: Some(schema_for!(TestMsg)),
280            execute: Some(schema_for!(TestMsg)),
281            query: Some(schema_for!(TestMsg)),
282            migrate: Some(schema_for!(TestMsg)),
283            sudo: Some(schema_for!(TestMsg)),
284            responses: Some(BTreeMap::from([(
285                "TestMsg".to_string(),
286                schema_for!(TestMsg),
287            )])),
288        };
289
290        let files = full.render().to_schema_files().unwrap();
291        assert_eq!(files.len(), 6);
292        assert_eq!(files[0].0, "instantiate.json");
293        assert_eq!(files[1].0, "execute.json");
294        assert_eq!(files[2].0, "query.json");
295        assert_eq!(files[3].0, "migrate.json");
296        assert_eq!(files[4].0, "sudo.json");
297        assert_eq!(files[5].0, "response_to_TestMsg.json");
298    }
299}