awaken_contract/model/
effect.rs1use serde::{Deserialize, Serialize, de::DeserializeOwned};
2
3use crate::error::StateError;
4
5use super::{JsonValue, decode_json, encode_json};
6
7pub trait EffectSpec: 'static + Send + Sync {
8 const KEY: &'static str;
9
10 type Payload: Serialize + DeserializeOwned + Send + Sync + 'static;
11}
12
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14pub struct TypedEffect {
15 pub key: String,
16 pub payload: JsonValue,
17}
18
19impl TypedEffect {
20 pub fn from_spec<E: EffectSpec>(payload: &E::Payload) -> Result<Self, StateError> {
21 Ok(Self {
22 key: E::KEY.to_string(),
23 payload: encode_json(E::KEY, payload)?,
24 })
25 }
26
27 pub fn decode<E: EffectSpec>(&self) -> Result<E::Payload, StateError> {
28 decode_json(E::KEY, self.payload.clone())
29 }
30}
31
32#[cfg(test)]
33mod tests {
34 use super::*;
35
36 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
37 struct TestPayload {
38 value: String,
39 }
40
41 struct TestEffect;
42 impl EffectSpec for TestEffect {
43 const KEY: &'static str = "test.effect";
44 type Payload = TestPayload;
45 }
46
47 #[test]
48 fn typed_effect_round_trip_works() {
49 let payload = TestPayload {
50 value: "hello".into(),
51 };
52 let encoded =
53 TypedEffect::from_spec::<TestEffect>(&payload).expect("encoding should succeed");
54 let decoded = encoded
55 .decode::<TestEffect>()
56 .expect("decoding should succeed");
57 assert_eq!(decoded, payload);
58 }
59}