|
1 | 1 | use schemars::JsonSchema;
|
2 | 2 | use serde::{Deserialize, Serialize};
|
3 | 3 |
|
4 |
| -use crate::{Binary, ContractResult}; |
| 4 | +use crate::Binary; |
5 | 5 |
|
6 | 6 | use super::{CosmosMsg, Empty, Event};
|
7 | 7 |
|
@@ -100,12 +100,210 @@ pub struct Reply {
|
100 | 100 | /// The ID that the contract set when emitting the `SubMsg`.
|
101 | 101 | /// Use this to identify which submessage triggered the `reply`.
|
102 | 102 | pub id: u64,
|
103 |
| - pub result: ContractResult<SubMsgExecutionResponse>, |
| 103 | + pub result: SubMsgResult, |
104 | 104 | }
|
105 | 105 |
|
106 |
| -/// The information we get back from a successful sub-call, with full sdk events |
| 106 | +/// This is the result type that is returned from a sub message execution. |
| 107 | +/// |
| 108 | +/// We use a custom type here instead of Rust's Result because we want to be able to |
| 109 | +/// define the serialization, which is a public interface. Every language that compiles |
| 110 | +/// to Wasm and runs in the ComsWasm VM needs to create the same JSON representation. |
| 111 | +/// |
| 112 | +/// Until version 1.0.0-beta5, `ContractResult<SubMsgExecutionResponse>` was used instead |
| 113 | +/// of this type. Once serialized, the two types are the same. However, in the Rust type |
| 114 | +/// system we want different types for clarity and documenation reasons. |
| 115 | +/// |
| 116 | +/// # Examples |
| 117 | +/// |
| 118 | +/// Success: |
| 119 | +/// |
| 120 | +/// ``` |
| 121 | +/// # use cosmwasm_std::{to_vec, Binary, Event, SubMsgExecutionResponse, SubMsgResult}; |
| 122 | +/// let response = SubMsgExecutionResponse { |
| 123 | +/// data: Some(Binary::from_base64("MTIzCg==").unwrap()), |
| 124 | +/// events: vec![Event::new("wasm").add_attribute("fo", "ba")], |
| 125 | +/// }; |
| 126 | +/// let result: SubMsgResult = SubMsgResult::Ok(response); |
| 127 | +/// assert_eq!(to_vec(&result).unwrap(), br#"{"ok":{"events":[{"type":"wasm","attributes":[{"key":"fo","value":"ba"}]}],"data":"MTIzCg=="}}"#); |
| 128 | +/// ``` |
| 129 | +/// |
| 130 | +/// Failure: |
| 131 | +/// |
| 132 | +/// ``` |
| 133 | +/// # use cosmwasm_std::{to_vec, SubMsgResult, Response}; |
| 134 | +/// let error_msg = String::from("Something went wrong"); |
| 135 | +/// let result = SubMsgResult::Err(error_msg); |
| 136 | +/// assert_eq!(to_vec(&result).unwrap(), br#"{"error":"Something went wrong"}"#); |
| 137 | +/// ``` |
| 138 | +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] |
| 139 | +#[serde(rename_all = "snake_case")] |
| 140 | +pub enum SubMsgResult { |
| 141 | + Ok(SubMsgExecutionResponse), |
| 142 | + /// An error type that every custom error created by contract developers can be converted to. |
| 143 | + /// This could potientially have more structure, but String is the easiest. |
| 144 | + #[serde(rename = "error")] |
| 145 | + Err(String), |
| 146 | +} |
| 147 | + |
| 148 | +// Implementations here mimic the Result API and should be implemented via a conversion to Result |
| 149 | +// to ensure API consistency |
| 150 | +impl SubMsgResult { |
| 151 | + /// Converts a `SubMsgResult<S>` to a `Result<S, String>` as a convenient way |
| 152 | + /// to access the full Result API. |
| 153 | + pub fn into_result(self) -> Result<SubMsgExecutionResponse, String> { |
| 154 | + Result::<SubMsgExecutionResponse, String>::from(self) |
| 155 | + } |
| 156 | + |
| 157 | + pub fn unwrap(self) -> SubMsgExecutionResponse { |
| 158 | + self.into_result().unwrap() |
| 159 | + } |
| 160 | + |
| 161 | + pub fn unwrap_err(self) -> String { |
| 162 | + self.into_result().unwrap_err() |
| 163 | + } |
| 164 | + |
| 165 | + pub fn is_ok(&self) -> bool { |
| 166 | + matches!(self, SubMsgResult::Ok(_)) |
| 167 | + } |
| 168 | + |
| 169 | + pub fn is_err(&self) -> bool { |
| 170 | + matches!(self, SubMsgResult::Err(_)) |
| 171 | + } |
| 172 | +} |
| 173 | + |
| 174 | +impl<E: ToString> From<Result<SubMsgExecutionResponse, E>> for SubMsgResult { |
| 175 | + fn from(original: Result<SubMsgExecutionResponse, E>) -> SubMsgResult { |
| 176 | + match original { |
| 177 | + Ok(value) => SubMsgResult::Ok(value), |
| 178 | + Err(err) => SubMsgResult::Err(err.to_string()), |
| 179 | + } |
| 180 | + } |
| 181 | +} |
| 182 | + |
| 183 | +impl From<SubMsgResult> for Result<SubMsgExecutionResponse, String> { |
| 184 | + fn from(original: SubMsgResult) -> Result<SubMsgExecutionResponse, String> { |
| 185 | + match original { |
| 186 | + SubMsgResult::Ok(value) => Ok(value), |
| 187 | + SubMsgResult::Err(err) => Err(err), |
| 188 | + } |
| 189 | + } |
| 190 | +} |
| 191 | + |
| 192 | +/// The information we get back from a successful sub message execution, |
| 193 | +/// with full Cosmos SDK events. |
107 | 194 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
108 | 195 | pub struct SubMsgExecutionResponse {
|
109 | 196 | pub events: Vec<Event>,
|
110 | 197 | pub data: Option<Binary>,
|
111 | 198 | }
|
| 199 | + |
| 200 | +#[cfg(test)] |
| 201 | +mod tests { |
| 202 | + use super::*; |
| 203 | + use crate::{from_slice, to_vec, StdError, StdResult}; |
| 204 | + |
| 205 | + #[test] |
| 206 | + fn sub_msg_result_serialization_works() { |
| 207 | + let result = SubMsgResult::Ok(SubMsgExecutionResponse { |
| 208 | + data: None, |
| 209 | + events: vec![], |
| 210 | + }); |
| 211 | + assert_eq!( |
| 212 | + &to_vec(&result).unwrap(), |
| 213 | + br#"{"ok":{"events":[],"data":null}}"# |
| 214 | + ); |
| 215 | + |
| 216 | + let result = SubMsgResult::Ok(SubMsgExecutionResponse { |
| 217 | + data: Some(Binary::from_base64("MTIzCg==").unwrap()), |
| 218 | + events: vec![Event::new("wasm").add_attribute("fo", "ba")], |
| 219 | + }); |
| 220 | + assert_eq!( |
| 221 | + &to_vec(&result).unwrap(), |
| 222 | + br#"{"ok":{"events":[{"type":"wasm","attributes":[{"key":"fo","value":"ba"}]}],"data":"MTIzCg=="}}"# |
| 223 | + ); |
| 224 | + |
| 225 | + let result: SubMsgResult = SubMsgResult::Err("broken".to_string()); |
| 226 | + assert_eq!(&to_vec(&result).unwrap(), b"{\"error\":\"broken\"}"); |
| 227 | + } |
| 228 | + |
| 229 | + #[test] |
| 230 | + fn sub_msg_result_deserialization_works() { |
| 231 | + let result: SubMsgResult = from_slice(br#"{"ok":{"events":[],"data":null}}"#).unwrap(); |
| 232 | + assert_eq!( |
| 233 | + result, |
| 234 | + SubMsgResult::Ok(SubMsgExecutionResponse { |
| 235 | + events: vec![], |
| 236 | + data: None, |
| 237 | + }) |
| 238 | + ); |
| 239 | + |
| 240 | + let result: SubMsgResult = from_slice( |
| 241 | + br#"{"ok":{"events":[{"type":"wasm","attributes":[{"key":"fo","value":"ba"}]}],"data":"MTIzCg=="}}"#).unwrap(); |
| 242 | + assert_eq!( |
| 243 | + result, |
| 244 | + SubMsgResult::Ok(SubMsgExecutionResponse { |
| 245 | + data: Some(Binary::from_base64("MTIzCg==").unwrap()), |
| 246 | + events: vec![Event::new("wasm").add_attribute("fo", "ba")], |
| 247 | + }) |
| 248 | + ); |
| 249 | + |
| 250 | + let result: SubMsgResult = from_slice(br#"{"error":"broken"}"#).unwrap(); |
| 251 | + assert_eq!(result, SubMsgResult::Err("broken".to_string())); |
| 252 | + |
| 253 | + // fails for additional attributes |
| 254 | + let parse: StdResult<SubMsgResult> = from_slice(br#"{"unrelated":321,"error":"broken"}"#); |
| 255 | + match parse.unwrap_err() { |
| 256 | + StdError::ParseErr { .. } => {} |
| 257 | + err => panic!("Unexpected error: {:?}", err), |
| 258 | + } |
| 259 | + let parse: StdResult<SubMsgResult> = from_slice(br#"{"error":"broken","unrelated":321}"#); |
| 260 | + match parse.unwrap_err() { |
| 261 | + StdError::ParseErr { .. } => {} |
| 262 | + err => panic!("Unexpected error: {:?}", err), |
| 263 | + } |
| 264 | + } |
| 265 | + |
| 266 | + #[test] |
| 267 | + fn sub_msg_result_can_convert_from_core_result() { |
| 268 | + let original: Result<SubMsgExecutionResponse, StdError> = Ok(SubMsgExecutionResponse { |
| 269 | + data: Some(Binary::from_base64("MTIzCg==").unwrap()), |
| 270 | + events: vec![], |
| 271 | + }); |
| 272 | + let converted: SubMsgResult = original.into(); |
| 273 | + assert_eq!( |
| 274 | + converted, |
| 275 | + SubMsgResult::Ok(SubMsgExecutionResponse { |
| 276 | + data: Some(Binary::from_base64("MTIzCg==").unwrap()), |
| 277 | + events: vec![], |
| 278 | + }) |
| 279 | + ); |
| 280 | + |
| 281 | + let original: Result<SubMsgExecutionResponse, StdError> = |
| 282 | + Err(StdError::generic_err("broken")); |
| 283 | + let converted: SubMsgResult = original.into(); |
| 284 | + assert_eq!( |
| 285 | + converted, |
| 286 | + SubMsgResult::Err("Generic error: broken".to_string()) |
| 287 | + ); |
| 288 | + } |
| 289 | + |
| 290 | + #[test] |
| 291 | + fn sub_msg_result_can_convert_to_core_result() { |
| 292 | + let original = SubMsgResult::Ok(SubMsgExecutionResponse { |
| 293 | + data: Some(Binary::from_base64("MTIzCg==").unwrap()), |
| 294 | + events: vec![], |
| 295 | + }); |
| 296 | + let converted: Result<SubMsgExecutionResponse, String> = original.into(); |
| 297 | + assert_eq!( |
| 298 | + converted, |
| 299 | + Ok(SubMsgExecutionResponse { |
| 300 | + data: Some(Binary::from_base64("MTIzCg==").unwrap()), |
| 301 | + events: vec![], |
| 302 | + }) |
| 303 | + ); |
| 304 | + |
| 305 | + let original = SubMsgResult::Err("went wrong".to_string()); |
| 306 | + let converted: Result<SubMsgExecutionResponse, String> = original.into(); |
| 307 | + assert_eq!(converted, Err("went wrong".to_string())); |
| 308 | + } |
| 309 | +} |
0 commit comments