use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::prelude::*;
use crate::Binary;
use super::{Attribute, CosmosMsg, Empty, Event, SubMsg};
#[derive(
Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, cw_schema::Schemaifier,
)]
#[non_exhaustive]
pub struct Response<T = Empty> {
pub messages: Vec<SubMsg<T>>,
pub attributes: Vec<Attribute>,
pub events: Vec<Event>,
pub data: Option<Binary>,
}
impl<T> Default for Response<T> {
fn default() -> Self {
Response {
messages: vec![],
attributes: vec![],
events: vec![],
data: None,
}
}
}
impl<T> Response<T> {
pub fn new() -> Self {
Self::default()
}
pub fn add_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.attributes.push(Attribute::new(key, value));
self
}
pub fn add_message(mut self, msg: impl Into<CosmosMsg<T>>) -> Self {
self.messages.push(SubMsg::new(msg));
self
}
pub fn add_submessage(mut self, msg: SubMsg<T>) -> Self {
self.messages.push(msg);
self
}
pub fn add_event(mut self, event: impl Into<Event>) -> Self {
self.events.push(event.into());
self
}
pub fn add_attributes<A: Into<Attribute>>(
mut self,
attrs: impl IntoIterator<Item = A>,
) -> Self {
self.attributes.extend(attrs.into_iter().map(A::into));
self
}
pub fn add_messages<M: Into<CosmosMsg<T>>>(self, msgs: impl IntoIterator<Item = M>) -> Self {
self.add_submessages(msgs.into_iter().map(SubMsg::new))
}
pub fn add_submessages(mut self, msgs: impl IntoIterator<Item = SubMsg<T>>) -> Self {
self.messages.extend(msgs);
self
}
pub fn add_events<E>(mut self, events: impl IntoIterator<Item = E>) -> Self
where
E: Into<Event>,
{
self.events.extend(events.into_iter().map(|e| e.into()));
self
}
pub fn set_data(mut self, data: impl Into<Binary>) -> Self {
self.data = Some(data.into());
self
}
pub fn change_custom<U>(self) -> Option<Response<U>> {
Some(Response {
messages: self
.messages
.into_iter()
.map(|msg| msg.change_custom())
.collect::<Option<Vec<_>>>()?,
attributes: self.attributes,
events: self.events,
data: self.data,
})
}
}
#[cfg(test)]
mod tests {
use super::super::BankMsg;
use super::*;
use crate::results::submessages::{ReplyOn, UNUSED_MSG_ID};
use crate::{coins, from_json, to_json_vec, ContractResult};
#[test]
fn response_add_attributes_works() {
let res = Response::<Empty>::new().add_attributes(core::iter::empty::<Attribute>());
assert_eq!(res.attributes.len(), 0);
let res = Response::<Empty>::new().add_attributes([Attribute::new("test", "ing")]);
assert_eq!(res.attributes.len(), 1);
assert_eq!(
res.attributes[0],
Attribute {
key: "test".to_string(),
value: "ing".to_string(),
}
);
let attrs = vec![
("action", "reaction"),
("answer", "42"),
("another", "attribute"),
];
let res: Response = Response::new().add_attributes(attrs.clone());
assert_eq!(res.attributes, attrs);
let optional = Option::<Attribute>::None;
let res: Response = Response::new().add_attributes(optional);
assert_eq!(res.attributes.len(), 0);
let optional = Option::<Attribute>::Some(Attribute::new("test", "ing"));
let res: Response = Response::new().add_attributes(optional);
assert_eq!(res.attributes.len(), 1);
assert_eq!(
res.attributes[0],
Attribute {
key: "test".to_string(),
value: "ing".to_string(),
}
);
}
#[test]
fn can_serialize_and_deserialize_init_response() {
let original = Response {
messages: vec![
SubMsg {
id: 12,
payload: Binary::new(vec![9, 8, 7, 6, 5]),
msg: BankMsg::Send {
to_address: String::from("checker"),
amount: coins(888, "moon"),
}
.into(),
gas_limit: Some(12345u64),
reply_on: ReplyOn::Always,
},
SubMsg {
id: UNUSED_MSG_ID,
payload: Binary::default(),
msg: BankMsg::Send {
to_address: String::from("you"),
amount: coins(1015, "earth"),
}
.into(),
gas_limit: None,
reply_on: ReplyOn::Never,
},
],
attributes: vec![Attribute {
key: "action".to_string(),
value: "release".to_string(),
}],
events: vec![],
data: Some(Binary::from([0xAA, 0xBB])),
};
let serialized = to_json_vec(&original).expect("encode contract result");
let deserialized: Response = from_json(serialized).expect("decode contract result");
assert_eq!(deserialized, original);
}
#[test]
fn contract_result_is_ok_works() {
let success = ContractResult::<()>::Ok(());
let failure = ContractResult::<()>::Err("broken".to_string());
assert!(success.is_ok());
assert!(!failure.is_ok());
}
#[test]
fn contract_result_is_err_works() {
let success = ContractResult::<()>::Ok(());
let failure = ContractResult::<()>::Err("broken".to_string());
assert!(failure.is_err());
assert!(!success.is_err());
}
#[derive(Clone)]
struct OurEvent {
msg: String,
}
#[allow(clippy::from_over_into)]
impl Into<Event> for OurEvent {
fn into(self) -> Event {
Event::new("our_event").add_attribute("msg", self.msg)
}
}
#[test]
fn add_event_takes_into_event() {
let msg = "message".to_string();
let our_event = OurEvent { msg };
let event: Event = our_event.clone().into();
let actual = Response::<Empty>::new().add_event(our_event);
let expected = Response::<Empty>::new().add_event(event);
assert_eq!(expected, actual);
}
#[test]
fn add_events_takes_into_event() {
let msg1 = "foo".to_string();
let msg2 = "bare".to_string();
let our_event1 = OurEvent { msg: msg1 };
let our_event2 = OurEvent { msg: msg2 };
let events: Vec<Event> = vec![our_event1.clone().into(), our_event2.clone().into()];
let actual = Response::<Empty>::new().add_events([our_event1, our_event2]);
let expected = Response::<Empty>::new().add_events(events);
assert_eq!(expected, actual);
}
#[test]
fn change_custom_works() {
let response: Response<Empty> = Response {
messages: vec![SubMsg::new(BankMsg::Send {
to_address: "address".to_string(),
amount: coins(123, "earth"),
})],
attributes: vec![Attribute::new("foo", "bar")],
events: vec![Event::new("our_event").add_attribute("msg", "hello")],
data: None,
};
let converted_resp: Response<String> = response.clone().change_custom().unwrap();
assert_eq!(
converted_resp.messages,
vec![SubMsg::new(BankMsg::Send {
to_address: "address".to_string(),
amount: coins(123, "earth"),
})]
);
assert_eq!(converted_resp.attributes, response.attributes);
assert_eq!(converted_resp.events, response.events);
assert_eq!(converted_resp.data, response.data);
let response = Response {
messages: vec![SubMsg::new(CosmosMsg::Custom(Empty {}))],
attributes: vec![Attribute::new("foo", "bar")],
events: vec![Event::new("our_event").add_attribute("msg", "hello")],
data: None,
};
assert_eq!(response.change_custom::<String>(), None);
}
}