1use reqwest::blocking::Client;
4use serde_json::Value;
5
6use crate::breakpoint::{
7 BreakpointMatcherList, BreakpointMatcherRegistration, BreakpointMatcherResponse,
8 BreakpointRequestHandler, BreakpointResponseHandler, BreakpointStreamFrameHandler,
9 BreakpointWebSocketClient,
10};
11use crate::error::{Error, Result};
12use crate::model::*;
13
14pub struct ClientBuilder {
31 host: String,
32 port: u16,
33 context_path: String,
34 secure: bool,
35 tls_verify: bool,
36}
37
38impl ClientBuilder {
39 pub fn new(host: impl Into<String>, port: u16) -> Self {
41 Self {
42 host: host.into(),
43 port,
44 context_path: String::new(),
45 secure: false,
46 tls_verify: true,
47 }
48 }
49
50 pub fn context_path(mut self, path: impl Into<String>) -> Self {
52 self.context_path = path.into();
53 self
54 }
55
56 pub fn secure(mut self, secure: bool) -> Self {
58 self.secure = secure;
59 self
60 }
61
62 pub fn tls_verify(mut self, verify: bool) -> Self {
64 self.tls_verify = verify;
65 self
66 }
67
68 pub fn build(self) -> Result<MockServerClient> {
70 let scheme = if self.secure { "https" } else { "http" };
71 let ctx = if self.context_path.is_empty() {
72 String::new()
73 } else if self.context_path.starts_with('/') {
74 self.context_path
75 } else {
76 format!("/{}", self.context_path)
77 };
78 let base_url = format!("{scheme}://{}:{}{ctx}", self.host, self.port);
79
80 let http_client = Client::builder()
81 .danger_accept_invalid_certs(!self.tls_verify)
82 .build()?;
83
84 Ok(MockServerClient {
85 base_url,
86 http: http_client,
87 breakpoint_ws: std::sync::Mutex::new(None),
88 })
89 }
90}
91
92pub struct MockServerClient {
100 pub(crate) base_url: String,
101 http: Client,
102 breakpoint_ws: std::sync::Mutex<Option<BreakpointWebSocketClient>>,
103}
104
105impl MockServerClient {
106 pub fn upsert(&self, expectations: &[Expectation]) -> Result<Vec<Expectation>> {
114 let body = serde_json::to_value(expectations)?;
115 let resp = self
116 .http
117 .put(self.url("/mockserver/expectation"))
118 .json(&body)
119 .send()?;
120
121 let status = resp.status().as_u16();
122 match status {
123 200 | 201 => {
124 let text = resp.text()?;
125 if text.is_empty() {
126 Ok(expectations.to_vec())
127 } else {
128 Ok(serde_json::from_str(&text)?)
129 }
130 }
131 400 => Err(Error::InvalidRequest(resp.text()?)),
132 _ => Err(Error::UnexpectedStatus {
133 status,
134 body: resp.text().unwrap_or_default(),
135 }),
136 }
137 }
138
139 pub fn when(&self, request: HttpRequest) -> ForwardChainExpectation<'_> {
155 ForwardChainExpectation {
156 client: self,
157 request,
158 times: None,
159 time_to_live: None,
160 priority: None,
161 id: None,
162 }
163 }
164
165 pub fn verify(&self, request: HttpRequest, times: VerificationTimes) -> Result<()> {
174 let verification = Verification {
175 http_request: request,
176 times: Some(times),
177 };
178 let resp = self
179 .http
180 .put(self.url("/mockserver/verify"))
181 .json(&verification)
182 .send()?;
183
184 let status = resp.status().as_u16();
185 match status {
186 200 | 202 => Ok(()),
187 406 => Err(Error::VerificationFailure(resp.text()?)),
188 400 => Err(Error::InvalidRequest(resp.text()?)),
189 _ => Err(Error::UnexpectedStatus {
190 status,
191 body: resp.text().unwrap_or_default(),
192 }),
193 }
194 }
195
196 pub fn verify_sequence(&self, requests: Vec<HttpRequest>) -> Result<()> {
198 let verification = VerificationSequence {
199 http_requests: requests,
200 };
201 let resp = self
202 .http
203 .put(self.url("/mockserver/verifySequence"))
204 .json(&verification)
205 .send()?;
206
207 let status = resp.status().as_u16();
208 match status {
209 200 | 202 => Ok(()),
210 406 => Err(Error::VerificationFailure(resp.text()?)),
211 400 => Err(Error::InvalidRequest(resp.text()?)),
212 _ => Err(Error::UnexpectedStatus {
213 status,
214 body: resp.text().unwrap_or_default(),
215 }),
216 }
217 }
218
219 pub fn clear(
227 &self,
228 request: Option<&HttpRequest>,
229 clear_type: Option<ClearType>,
230 ) -> Result<()> {
231 let mut url = self.url("/mockserver/clear");
232 if let Some(ct) = clear_type {
233 url = format!("{url}?type={}", ct.as_str());
234 }
235
236 let mut builder = self.http.put(&url);
237 builder = builder.header("Content-Type", "application/json");
238 if let Some(req) = request {
239 builder = builder.json(req);
240 } else {
241 builder = builder.body("");
242 }
243
244 let resp = builder.send()?;
245 let status = resp.status().as_u16();
246 match status {
247 200 => Ok(()),
248 400 => Err(Error::InvalidRequest(resp.text()?)),
249 _ => Err(Error::UnexpectedStatus {
250 status,
251 body: resp.text().unwrap_or_default(),
252 }),
253 }
254 }
255
256 pub fn clear_by_id(
258 &self,
259 expectation_id: impl Into<String>,
260 clear_type: Option<ClearType>,
261 ) -> Result<()> {
262 let mut url = self.url("/mockserver/clear");
263 if let Some(ct) = clear_type {
264 url = format!("{url}?type={}", ct.as_str());
265 }
266
267 let body = serde_json::json!({ "id": expectation_id.into() });
268 let resp = self.http.put(&url).json(&body).send()?;
269
270 let status = resp.status().as_u16();
271 match status {
272 200 => Ok(()),
273 400 => Err(Error::InvalidRequest(resp.text()?)),
274 _ => Err(Error::UnexpectedStatus {
275 status,
276 body: resp.text().unwrap_or_default(),
277 }),
278 }
279 }
280
281 pub fn reset(&self) -> Result<()> {
283 let resp = self
284 .http
285 .put(self.url("/mockserver/reset"))
286 .header("Content-Type", "application/json")
287 .body("")
288 .send()?;
289
290 let status = resp.status().as_u16();
291 match status {
292 200 => Ok(()),
293 _ => Err(Error::UnexpectedStatus {
294 status,
295 body: resp.text().unwrap_or_default(),
296 }),
297 }
298 }
299
300 pub fn retrieve_recorded_requests(
306 &self,
307 request: Option<&HttpRequest>,
308 ) -> Result<Vec<HttpRequest>> {
309 let text =
310 self.do_retrieve(request, RetrieveType::Requests, RetrieveFormat::Json)?;
311 if text.is_empty() {
312 return Ok(vec![]);
313 }
314 Ok(serde_json::from_str(&text)?)
315 }
316
317 pub fn retrieve_active_expectations(
319 &self,
320 request: Option<&HttpRequest>,
321 ) -> Result<Vec<Expectation>> {
322 let text = self.do_retrieve(
323 request,
324 RetrieveType::ActiveExpectations,
325 RetrieveFormat::Json,
326 )?;
327 if text.is_empty() {
328 return Ok(vec![]);
329 }
330 Ok(serde_json::from_str(&text)?)
331 }
332
333 pub fn retrieve_recorded_expectations(
335 &self,
336 request: Option<&HttpRequest>,
337 ) -> Result<Vec<Expectation>> {
338 let text = self.do_retrieve(
339 request,
340 RetrieveType::RecordedExpectations,
341 RetrieveFormat::Json,
342 )?;
343 if text.is_empty() {
344 return Ok(vec![]);
345 }
346 Ok(serde_json::from_str(&text)?)
347 }
348
349 pub fn retrieve_log_messages(
351 &self,
352 request: Option<&HttpRequest>,
353 ) -> Result<Vec<String>> {
354 let text =
355 self.do_retrieve(request, RetrieveType::Logs, RetrieveFormat::LogEntries)?;
356 if text.is_empty() {
357 return Ok(vec![]);
358 }
359 if let Ok(arr) = serde_json::from_str::<Vec<String>>(&text) {
362 return Ok(arr);
363 }
364 Ok(text
366 .split("------------------------------------\n")
367 .map(|s| s.to_string())
368 .filter(|s| !s.is_empty())
369 .collect())
370 }
371
372 pub fn retrieve_request_responses(
374 &self,
375 request: Option<&HttpRequest>,
376 ) -> Result<Vec<Value>> {
377 let text = self.do_retrieve(
378 request,
379 RetrieveType::RequestResponses,
380 RetrieveFormat::Json,
381 )?;
382 if text.is_empty() {
383 return Ok(vec![]);
384 }
385 Ok(serde_json::from_str(&text)?)
386 }
387
388 pub fn status(&self) -> Result<Ports> {
394 let resp = self
395 .http
396 .put(self.url("/mockserver/status"))
397 .header("Content-Type", "application/json")
398 .body("")
399 .send()?;
400
401 let status = resp.status().as_u16();
402 match status {
403 200 => {
404 let text = resp.text()?;
405 Ok(serde_json::from_str(&text)?)
406 }
407 _ => Err(Error::UnexpectedStatus {
408 status,
409 body: resp.text().unwrap_or_default(),
410 }),
411 }
412 }
413
414 pub fn bind(&self, ports: &[u16]) -> Result<Ports> {
416 let body = Ports {
417 ports: ports.to_vec(),
418 };
419 let resp = self
420 .http
421 .put(self.url("/mockserver/bind"))
422 .json(&body)
423 .send()?;
424
425 let status = resp.status().as_u16();
426 match status {
427 200 => {
428 let text = resp.text()?;
429 Ok(serde_json::from_str(&text)?)
430 }
431 400 => Err(Error::InvalidRequest(resp.text()?)),
432 406 => Err(Error::VerificationFailure(resp.text()?)),
433 _ => Err(Error::UnexpectedStatus {
434 status,
435 body: resp.text().unwrap_or_default(),
436 }),
437 }
438 }
439
440 pub fn has_started(&self, attempts: u32, timeout_ms: u64) -> bool {
442 for i in 0..attempts {
443 match self.status() {
444 Ok(_) => return true,
445 Err(_) => {
446 if i < attempts - 1 {
447 std::thread::sleep(std::time::Duration::from_millis(timeout_ms));
448 }
449 }
450 }
451 }
452 false
453 }
454
455 fn ensure_breakpoint_ws(&self) -> Result<String> {
462 let mut guard = self.breakpoint_ws.lock().unwrap();
463 let needs_connect = match guard.as_ref() {
464 None => true,
465 Some(ws) => ws.is_dead(),
466 };
467 if needs_connect {
468 if let Some(old) = guard.take() {
470 old.close();
471 }
472 let ws = BreakpointWebSocketClient::connect(&self.base_url)?;
473 *guard = Some(ws);
474 }
475 Ok(guard.as_ref().unwrap().client_id.clone())
476 }
477
478 pub fn add_breakpoint(
481 &self,
482 matcher: HttpRequest,
483 phases: &[&str],
484 request_handler: Option<BreakpointRequestHandler>,
485 response_handler: Option<BreakpointResponseHandler>,
486 stream_frame_handler: Option<BreakpointStreamFrameHandler>,
487 ) -> Result<String> {
488 if phases.is_empty() {
489 return Err(Error::InvalidRequest(
490 "At least one phase is required".into(),
491 ));
492 }
493
494 let client_id = self.ensure_breakpoint_ws()?;
495
496 let reg = BreakpointMatcherRegistration {
497 http_request: matcher,
498 phases: phases.iter().map(|s| s.to_string()).collect(),
499 client_id: Some(client_id),
500 };
501
502 let resp = self
503 .http
504 .put(self.url("/mockserver/breakpoint/matcher"))
505 .json(®)
506 .send()?;
507
508 let status = resp.status().as_u16();
509 let text = resp.text()?;
510 if status >= 400 {
511 return Err(Error::UnexpectedStatus {
512 status,
513 body: text,
514 });
515 }
516
517 let result: BreakpointMatcherResponse = serde_json::from_str(&text)?;
518 let id = result.id.clone();
519
520 let guard = self.breakpoint_ws.lock().unwrap();
522 if let Some(ws) = guard.as_ref() {
523 if let Some(h) = request_handler {
524 ws.set_request_handler(&id, h);
525 }
526 if let Some(h) = response_handler {
527 ws.set_response_handler(&id, h);
528 }
529 if let Some(h) = stream_frame_handler {
530 ws.set_stream_frame_handler(&id, h);
531 }
532 }
533
534 Ok(id)
535 }
536
537 pub fn add_request_breakpoint(
539 &self,
540 matcher: HttpRequest,
541 handler: BreakpointRequestHandler,
542 ) -> Result<String> {
543 self.add_breakpoint(
544 matcher,
545 &[crate::breakpoint::phase::REQUEST],
546 Some(handler),
547 None,
548 None,
549 )
550 }
551
552 pub fn add_request_response_breakpoint(
554 &self,
555 matcher: HttpRequest,
556 request_handler: BreakpointRequestHandler,
557 response_handler: BreakpointResponseHandler,
558 ) -> Result<String> {
559 self.add_breakpoint(
560 matcher,
561 &[
562 crate::breakpoint::phase::REQUEST,
563 crate::breakpoint::phase::RESPONSE,
564 ],
565 Some(request_handler),
566 Some(response_handler),
567 None,
568 )
569 }
570
571 pub fn add_stream_breakpoint(
573 &self,
574 matcher: HttpRequest,
575 phases: &[&str],
576 handler: BreakpointStreamFrameHandler,
577 ) -> Result<String> {
578 self.add_breakpoint(matcher, phases, None, None, Some(handler))
579 }
580
581 pub fn list_breakpoint_matchers(&self) -> Result<BreakpointMatcherList> {
583 let resp = self
584 .http
585 .get(self.url("/mockserver/breakpoint/matchers"))
586 .send()?;
587
588 let status = resp.status().as_u16();
589 let text = resp.text()?;
590 if status >= 400 {
591 return Err(Error::UnexpectedStatus {
592 status,
593 body: text,
594 });
595 }
596
597 Ok(serde_json::from_str(&text)?)
598 }
599
600 pub fn remove_breakpoint_matcher(&self, id: impl Into<String>) -> Result<()> {
602 let id = id.into();
603 let body = serde_json::json!({ "id": &id });
604 let resp = self
605 .http
606 .put(self.url("/mockserver/breakpoint/matcher/remove"))
607 .json(&body)
608 .send()?;
609
610 let status = resp.status().as_u16();
611 match status {
612 200 => {
613 let guard = self.breakpoint_ws.lock().unwrap();
614 if let Some(ws) = guard.as_ref() {
615 ws.remove_handlers(&id);
616 }
617 Ok(())
618 }
619 404 => Err(Error::InvalidRequest(format!(
620 "Breakpoint matcher not found: {id}"
621 ))),
622 _ => Err(Error::UnexpectedStatus {
623 status,
624 body: resp.text().unwrap_or_default(),
625 }),
626 }
627 }
628
629 pub fn clear_breakpoint_matchers(&self) -> Result<()> {
631 let resp = self
632 .http
633 .put(self.url("/mockserver/breakpoint/matcher/clear"))
634 .header("Content-Type", "application/json")
635 .body("")
636 .send()?;
637
638 let status = resp.status().as_u16();
639 if status >= 400 {
640 return Err(Error::UnexpectedStatus {
641 status,
642 body: resp.text().unwrap_or_default(),
643 });
644 }
645
646 let guard = self.breakpoint_ws.lock().unwrap();
647 if let Some(ws) = guard.as_ref() {
648 ws.clear_handlers();
649 }
650 Ok(())
651 }
652
653 pub fn close_breakpoint_websocket(&self) {
655 let mut guard = self.breakpoint_ws.lock().unwrap();
656 if let Some(ws) = guard.take() {
657 ws.close();
658 }
659 }
660
661 fn url(&self, path: &str) -> String {
666 format!("{}{path}", self.base_url)
667 }
668
669 fn do_retrieve(
670 &self,
671 request: Option<&HttpRequest>,
672 retrieve_type: RetrieveType,
673 format: RetrieveFormat,
674 ) -> Result<String> {
675 let url = format!(
676 "{}?type={}&format={}",
677 self.url("/mockserver/retrieve"),
678 retrieve_type.as_str(),
679 format.as_str(),
680 );
681
682 let mut builder = self.http.put(&url);
683 builder = builder.header("Content-Type", "application/json");
684 if let Some(req) = request {
685 builder = builder.json(req);
686 } else {
687 builder = builder.body("");
688 }
689
690 let resp = builder.send()?;
691 let status = resp.status().as_u16();
692 match status {
693 200 => Ok(resp.text()?),
694 400 => Err(Error::InvalidRequest(resp.text()?)),
695 _ => Err(Error::UnexpectedStatus {
696 status,
697 body: resp.text().unwrap_or_default(),
698 }),
699 }
700 }
701}
702
703pub struct ForwardChainExpectation<'a> {
709 client: &'a MockServerClient,
710 request: HttpRequest,
711 times: Option<Times>,
712 time_to_live: Option<TimeToLive>,
713 priority: Option<i32>,
714 id: Option<String>,
715}
716
717impl<'a> ForwardChainExpectation<'a> {
718 pub fn times(mut self, times: Times) -> Self {
720 self.times = Some(times);
721 self
722 }
723
724 pub fn time_to_live(mut self, ttl: TimeToLive) -> Self {
726 self.time_to_live = Some(ttl);
727 self
728 }
729
730 pub fn priority(mut self, priority: i32) -> Self {
732 self.priority = Some(priority);
733 self
734 }
735
736 pub fn with_id(mut self, id: impl Into<String>) -> Self {
738 self.id = Some(id.into());
739 self
740 }
741
742 pub fn respond(self, response: HttpResponse) -> Result<Vec<Expectation>> {
744 let (client, expectation) = self.into_parts();
745 let expectation = expectation.respond(response);
746 client.upsert(&[expectation])
747 }
748
749 pub fn forward(self, forward: HttpForward) -> Result<Vec<Expectation>> {
751 let (client, expectation) = self.into_parts();
752 let expectation = expectation.forward(forward);
753 client.upsert(&[expectation])
754 }
755
756 pub fn error(self, error: HttpError) -> Result<Vec<Expectation>> {
758 let (client, expectation) = self.into_parts();
759 let expectation = expectation.error(error);
760 client.upsert(&[expectation])
761 }
762
763 fn into_parts(self) -> (&'a MockServerClient, Expectation) {
764 let ForwardChainExpectation {
765 client,
766 request,
767 times,
768 time_to_live,
769 priority,
770 id,
771 } = self;
772 let mut exp = Expectation::new(request);
773 exp.times = times;
774 exp.time_to_live = time_to_live;
775 exp.priority = priority;
776 exp.id = id;
777 (client, exp)
778 }
779}