1use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use std::collections::HashMap;
6use std::sync::Arc;
7use tokio::sync::RwLock;
8
9pub type DynamicResponseFn = Arc<dyn Fn(&RequestContext) -> Value + Send + Sync>;
11
12#[derive(Debug, Clone)]
14pub struct RequestContext {
15 pub method: String,
17 pub path: String,
19 pub path_params: HashMap<String, String>,
21 pub query_params: HashMap<String, String>,
23 pub headers: HashMap<String, String>,
25 pub body: Option<Value>,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct ResponseStub {
32 pub method: String,
34 pub path: String,
36 pub status: u16,
38 pub headers: HashMap<String, String>,
40 pub body: Value,
42 pub latency_ms: Option<u64>,
44}
45
46impl ResponseStub {
47 pub fn new(method: impl Into<String>, path: impl Into<String>, body: Value) -> Self {
49 Self {
50 method: method.into(),
51 path: path.into(),
52 status: 200,
53 headers: HashMap::new(),
54 body,
55 latency_ms: None,
56 }
57 }
58
59 pub fn status(mut self, status: u16) -> Self {
61 self.status = status;
62 self
63 }
64
65 pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
67 self.headers.insert(key.into(), value.into());
68 self
69 }
70
71 pub fn latency(mut self, ms: u64) -> Self {
73 self.latency_ms = Some(ms);
74 self
75 }
76}
77
78pub struct DynamicStub {
80 pub method: String,
82 pub path: String,
84 pub status: Arc<RwLock<u16>>,
86 pub headers: Arc<RwLock<HashMap<String, String>>>,
88 pub response_fn: DynamicResponseFn,
90 pub latency_ms: Option<u64>,
92}
93
94impl DynamicStub {
95 pub fn new<F>(method: impl Into<String>, path: impl Into<String>, response_fn: F) -> Self
97 where
98 F: Fn(&RequestContext) -> Value + Send + Sync + 'static,
99 {
100 Self {
101 method: method.into(),
102 path: path.into(),
103 status: Arc::new(RwLock::new(200)),
104 headers: Arc::new(RwLock::new(HashMap::new())),
105 response_fn: Arc::new(response_fn),
106 latency_ms: None,
107 }
108 }
109
110 pub async fn set_status(&self, status: u16) {
112 *self.status.write().await = status;
113 }
114
115 pub async fn get_status(&self) -> u16 {
117 *self.status.read().await
118 }
119
120 pub async fn add_header(&self, key: String, value: String) {
122 self.headers.write().await.insert(key, value);
123 }
124
125 pub async fn remove_header(&self, key: &str) {
127 self.headers.write().await.remove(key);
128 }
129
130 pub async fn get_headers(&self) -> HashMap<String, String> {
134 self.headers.read().await.clone()
135 }
136
137 pub async fn with_headers<F, R>(&self, f: F) -> R
158 where
159 F: FnOnce(&HashMap<String, String>) -> R,
160 {
161 let headers = self.headers.read().await;
162 f(&headers)
163 }
164
165 pub fn generate_response(&self, ctx: &RequestContext) -> Value {
167 (self.response_fn)(ctx)
168 }
169
170 pub fn with_latency(mut self, ms: u64) -> Self {
172 self.latency_ms = Some(ms);
173 self
174 }
175}
176
177pub struct StubBuilder {
179 method: String,
180 path: String,
181 status: u16,
182 headers: HashMap<String, String>,
183 body: Value,
184 latency_ms: Option<u64>,
185}
186
187impl StubBuilder {
188 pub fn new(method: impl Into<String>, path: impl Into<String>) -> Self {
190 Self {
191 method: method.into(),
192 path: path.into(),
193 status: 200,
194 headers: HashMap::new(),
195 body: Value::Null,
196 latency_ms: None,
197 }
198 }
199
200 pub fn status(mut self, status: u16) -> Self {
202 self.status = status;
203 self
204 }
205
206 pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
208 self.headers.insert(key.into(), value.into());
209 self
210 }
211
212 pub fn body(mut self, body: Value) -> Self {
214 self.body = body;
215 self
216 }
217
218 pub fn latency(mut self, ms: u64) -> Self {
220 self.latency_ms = Some(ms);
221 self
222 }
223
224 pub fn build(self) -> ResponseStub {
226 ResponseStub {
227 method: self.method,
228 path: self.path,
229 status: self.status,
230 headers: self.headers,
231 body: self.body,
232 latency_ms: self.latency_ms,
233 }
234 }
235}