1use crate::prelude::*;
2use crate::{DryContext, OpMetadata, WetContext};
3use async_trait::async_trait;
4
5#[async_trait]
6pub trait Op<T>: Send + Sync {
7 async fn perform(&self, dry: &mut DryContext, wet: &mut WetContext) -> OpResult<T>;
8
9 fn metadata(&self) -> OpMetadata;
10
11 async fn rollback(&self, _dry: &mut DryContext, _wet: &mut WetContext) -> OpResult<()> {
15 Ok(())
16 }
17}
18
19#[cfg(test)]
20mod tests {
21 use super::*;
22 use serde_json::json;
23
24 struct TestOp {
25 value: i32,
26 }
27
28 #[async_trait]
29 impl Op<i32> for TestOp {
30 async fn perform(&self, _dry: &mut DryContext, _wet: &mut WetContext) -> OpResult<i32> {
31 Ok(self.value)
32 }
33
34 fn metadata(&self) -> OpMetadata {
35 OpMetadata::builder("TestOp")
36 .description("Simple test op")
37 .output_schema(json!({ "type": "integer" }))
38 .build()
39 }
40 }
41
42 #[tokio::test]
44 async fn test0001_op_execution() {
45 let op = TestOp { value: 42 };
46 let mut dry = DryContext::new();
47 let mut wet = WetContext::new();
48
49 let result = op.perform(&mut dry, &mut wet).await;
50 assert_eq!(result.unwrap(), 42);
51 }
52
53 #[tokio::test]
55 async fn test0002_op_with_contexts() {
56 struct ContextUsingOp;
57
58 #[async_trait]
59 impl Op<String> for ContextUsingOp {
60 async fn perform(
61 &self,
62 dry: &mut DryContext,
63 _wet: &mut WetContext,
64 ) -> OpResult<String> {
65 let name = dry.get_required::<String>("name")?;
66 Ok(format!("Hello, {}!", name))
67 }
68
69 fn metadata(&self) -> OpMetadata {
70 OpMetadata::builder("ContextUsingOp")
71 .input_schema(json!({
72 "type": "object",
73 "properties": {
74 "name": { "type": "string" }
75 },
76 "required": ["name"]
77 }))
78 .output_schema(json!({ "type": "string" }))
79 .build()
80 }
81 }
82
83 let op = ContextUsingOp;
84 let mut dry = DryContext::new().with_value("name", "World");
85 let mut wet = WetContext::new();
86
87 let result = op.perform(&mut dry, &mut wet).await;
88 assert_eq!(result.unwrap(), "Hello, World!");
89 }
90
91 #[tokio::test]
93 async fn test0003_op_default_rollback() {
94 struct SimpleOp;
95
96 #[async_trait]
97 impl Op<()> for SimpleOp {
98 async fn perform(&self, _dry: &mut DryContext, _wet: &mut WetContext) -> OpResult<()> {
99 Ok(())
100 }
101
102 fn metadata(&self) -> OpMetadata {
103 OpMetadata::builder("SimpleOp").build()
104 }
105 }
106
107 let op = SimpleOp;
108 let mut dry = DryContext::new();
109 let mut wet = WetContext::new();
110
111 let result = op.rollback(&mut dry, &mut wet).await;
113 assert!(result.is_ok());
114 }
115
116 #[tokio::test]
118 async fn test0004_op_custom_rollback() {
119 use std::sync::{Arc, Mutex};
120
121 struct RollbackTrackingOp {
122 performed: Arc<Mutex<bool>>,
123 rolled_back: Arc<Mutex<bool>>,
124 }
125
126 #[async_trait]
127 impl Op<()> for RollbackTrackingOp {
128 async fn perform(&self, _dry: &mut DryContext, _wet: &mut WetContext) -> OpResult<()> {
129 *self.performed.lock().unwrap() = true;
130 Ok(())
131 }
132
133 async fn rollback(&self, _dry: &mut DryContext, _wet: &mut WetContext) -> OpResult<()> {
134 *self.rolled_back.lock().unwrap() = true;
135 Ok(())
136 }
137
138 fn metadata(&self) -> OpMetadata {
139 OpMetadata::builder("RollbackTrackingOp").build()
140 }
141 }
142
143 let performed = Arc::new(Mutex::new(false));
144 let rolled_back = Arc::new(Mutex::new(false));
145
146 let op = RollbackTrackingOp {
147 performed: performed.clone(),
148 rolled_back: rolled_back.clone(),
149 };
150
151 let mut dry = DryContext::new();
152 let mut wet = WetContext::new();
153
154 op.perform(&mut dry, &mut wet).await.unwrap();
156 assert!(*performed.lock().unwrap());
157 assert!(!*rolled_back.lock().unwrap());
158
159 op.rollback(&mut dry, &mut wet).await.unwrap();
161 assert!(*performed.lock().unwrap());
162 assert!(*rolled_back.lock().unwrap());
163 }
164}