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: Some(request),
176 http_response: None,
177 times: Some(times),
178 maximum_number_of_request_to_return_in_verification_failure: None,
179 };
180 self.do_verify(&verification)
181 }
182
183 pub fn verify_request_and_response(
190 &self,
191 request: HttpRequest,
192 response: HttpResponse,
193 times: VerificationTimes,
194 ) -> Result<()> {
195 let verification = Verification {
196 http_request: Some(request),
197 http_response: Some(response),
198 times: Some(times),
199 maximum_number_of_request_to_return_in_verification_failure: None,
200 };
201 self.do_verify(&verification)
202 }
203
204 pub fn verify_response(
210 &self,
211 response: HttpResponse,
212 times: VerificationTimes,
213 ) -> Result<()> {
214 let verification = Verification {
215 http_request: None,
216 http_response: Some(response),
217 times: Some(times),
218 maximum_number_of_request_to_return_in_verification_failure: None,
219 };
220 self.do_verify(&verification)
221 }
222
223 pub fn verify_raw(&self, verification: &Verification) -> Result<()> {
228 self.do_verify(verification)
229 }
230
231 pub fn verify_sequence(&self, requests: Vec<HttpRequest>) -> Result<()> {
233 let verification = VerificationSequence {
234 http_requests: Some(requests),
235 http_responses: None,
236 };
237 self.do_verify_sequence(&verification)
238 }
239
240 pub fn verify_sequence_with_responses(
245 &self,
246 requests: Vec<HttpRequest>,
247 responses: Vec<HttpResponse>,
248 ) -> Result<()> {
249 let verification = VerificationSequence {
250 http_requests: Some(requests),
251 http_responses: Some(responses),
252 };
253 self.do_verify_sequence(&verification)
254 }
255
256 pub fn verify_sequence_raw(&self, verification: &VerificationSequence) -> Result<()> {
258 self.do_verify_sequence(verification)
259 }
260
261 pub fn clear(
269 &self,
270 request: Option<&HttpRequest>,
271 clear_type: Option<ClearType>,
272 ) -> Result<()> {
273 let mut url = self.url("/mockserver/clear");
274 if let Some(ct) = clear_type {
275 url = format!("{url}?type={}", ct.as_str());
276 }
277
278 let mut builder = self.http.put(&url);
279 builder = builder.header("Content-Type", "application/json");
280 if let Some(req) = request {
281 builder = builder.json(req);
282 } else {
283 builder = builder.body("");
284 }
285
286 let resp = builder.send()?;
287 let status = resp.status().as_u16();
288 match status {
289 200 => Ok(()),
290 400 => Err(Error::InvalidRequest(resp.text()?)),
291 _ => Err(Error::UnexpectedStatus {
292 status,
293 body: resp.text().unwrap_or_default(),
294 }),
295 }
296 }
297
298 pub fn clear_by_id(
300 &self,
301 expectation_id: impl Into<String>,
302 clear_type: Option<ClearType>,
303 ) -> Result<()> {
304 let mut url = self.url("/mockserver/clear");
305 if let Some(ct) = clear_type {
306 url = format!("{url}?type={}", ct.as_str());
307 }
308
309 let body = serde_json::json!({ "id": expectation_id.into() });
310 let resp = self.http.put(&url).json(&body).send()?;
311
312 let status = resp.status().as_u16();
313 match status {
314 200 => Ok(()),
315 400 => Err(Error::InvalidRequest(resp.text()?)),
316 _ => Err(Error::UnexpectedStatus {
317 status,
318 body: resp.text().unwrap_or_default(),
319 }),
320 }
321 }
322
323 pub fn reset(&self) -> Result<()> {
325 let resp = self
326 .http
327 .put(self.url("/mockserver/reset"))
328 .header("Content-Type", "application/json")
329 .body("")
330 .send()?;
331
332 let status = resp.status().as_u16();
333 match status {
334 200 => Ok(()),
335 _ => Err(Error::UnexpectedStatus {
336 status,
337 body: resp.text().unwrap_or_default(),
338 }),
339 }
340 }
341
342 pub fn retrieve_recorded_requests(
348 &self,
349 request: Option<&HttpRequest>,
350 ) -> Result<Vec<HttpRequest>> {
351 let text =
352 self.do_retrieve(request, RetrieveType::Requests, RetrieveFormat::Json)?;
353 if text.is_empty() {
354 return Ok(vec![]);
355 }
356 Ok(serde_json::from_str(&text)?)
357 }
358
359 pub fn retrieve_active_expectations(
361 &self,
362 request: Option<&HttpRequest>,
363 ) -> Result<Vec<Expectation>> {
364 let text = self.do_retrieve(
365 request,
366 RetrieveType::ActiveExpectations,
367 RetrieveFormat::Json,
368 )?;
369 if text.is_empty() {
370 return Ok(vec![]);
371 }
372 Ok(serde_json::from_str(&text)?)
373 }
374
375 pub fn retrieve_recorded_expectations(
377 &self,
378 request: Option<&HttpRequest>,
379 ) -> Result<Vec<Expectation>> {
380 let text = self.do_retrieve(
381 request,
382 RetrieveType::RecordedExpectations,
383 RetrieveFormat::Json,
384 )?;
385 if text.is_empty() {
386 return Ok(vec![]);
387 }
388 Ok(serde_json::from_str(&text)?)
389 }
390
391 pub fn retrieve_log_messages(
393 &self,
394 request: Option<&HttpRequest>,
395 ) -> Result<Vec<String>> {
396 let text =
397 self.do_retrieve(request, RetrieveType::Logs, RetrieveFormat::LogEntries)?;
398 if text.is_empty() {
399 return Ok(vec![]);
400 }
401 if let Ok(arr) = serde_json::from_str::<Vec<String>>(&text) {
404 return Ok(arr);
405 }
406 Ok(text
408 .split("------------------------------------\n")
409 .map(|s| s.to_string())
410 .filter(|s| !s.is_empty())
411 .collect())
412 }
413
414 pub fn retrieve_request_responses(
416 &self,
417 request: Option<&HttpRequest>,
418 ) -> Result<Vec<Value>> {
419 let text = self.do_retrieve(
420 request,
421 RetrieveType::RequestResponses,
422 RetrieveFormat::Json,
423 )?;
424 if text.is_empty() {
425 return Ok(vec![]);
426 }
427 Ok(serde_json::from_str(&text)?)
428 }
429
430 pub fn status(&self) -> Result<Ports> {
436 let resp = self
437 .http
438 .put(self.url("/mockserver/status"))
439 .header("Content-Type", "application/json")
440 .body("")
441 .send()?;
442
443 let status = resp.status().as_u16();
444 match status {
445 200 => {
446 let text = resp.text()?;
447 Ok(serde_json::from_str(&text)?)
448 }
449 _ => Err(Error::UnexpectedStatus {
450 status,
451 body: resp.text().unwrap_or_default(),
452 }),
453 }
454 }
455
456 pub fn bind(&self, ports: &[u16]) -> Result<Ports> {
458 let body = Ports {
459 ports: ports.to_vec(),
460 };
461 let resp = self
462 .http
463 .put(self.url("/mockserver/bind"))
464 .json(&body)
465 .send()?;
466
467 let status = resp.status().as_u16();
468 match status {
469 200 => {
470 let text = resp.text()?;
471 Ok(serde_json::from_str(&text)?)
472 }
473 400 => Err(Error::InvalidRequest(resp.text()?)),
474 406 => Err(Error::VerificationFailure(resp.text()?)),
475 _ => Err(Error::UnexpectedStatus {
476 status,
477 body: resp.text().unwrap_or_default(),
478 }),
479 }
480 }
481
482 pub fn has_started(&self, attempts: u32, timeout_ms: u64) -> bool {
484 for i in 0..attempts {
485 match self.status() {
486 Ok(_) => return true,
487 Err(_) => {
488 if i < attempts - 1 {
489 std::thread::sleep(std::time::Duration::from_millis(timeout_ms));
490 }
491 }
492 }
493 }
494 false
495 }
496
497 fn ensure_breakpoint_ws(&self) -> Result<String> {
504 let mut guard = self.breakpoint_ws.lock().unwrap();
505 let needs_connect = match guard.as_ref() {
506 None => true,
507 Some(ws) => ws.is_dead(),
508 };
509 if needs_connect {
510 if let Some(old) = guard.take() {
512 old.close();
513 }
514 let ws = BreakpointWebSocketClient::connect(&self.base_url)?;
515 *guard = Some(ws);
516 }
517 Ok(guard.as_ref().unwrap().client_id.clone())
518 }
519
520 pub fn add_breakpoint(
523 &self,
524 matcher: HttpRequest,
525 phases: &[&str],
526 request_handler: Option<BreakpointRequestHandler>,
527 response_handler: Option<BreakpointResponseHandler>,
528 stream_frame_handler: Option<BreakpointStreamFrameHandler>,
529 ) -> Result<String> {
530 if phases.is_empty() {
531 return Err(Error::InvalidRequest(
532 "At least one phase is required".into(),
533 ));
534 }
535
536 let client_id = self.ensure_breakpoint_ws()?;
537
538 let reg = BreakpointMatcherRegistration {
539 http_request: matcher,
540 phases: phases.iter().map(|s| s.to_string()).collect(),
541 client_id: Some(client_id),
542 };
543
544 let resp = self
545 .http
546 .put(self.url("/mockserver/breakpoint/matcher"))
547 .json(®)
548 .send()?;
549
550 let status = resp.status().as_u16();
551 let text = resp.text()?;
552 if status >= 400 {
553 return Err(Error::UnexpectedStatus {
554 status,
555 body: text,
556 });
557 }
558
559 let result: BreakpointMatcherResponse = serde_json::from_str(&text)?;
560 let id = result.id.clone();
561
562 let guard = self.breakpoint_ws.lock().unwrap();
564 if let Some(ws) = guard.as_ref() {
565 if let Some(h) = request_handler {
566 ws.set_request_handler(&id, h);
567 }
568 if let Some(h) = response_handler {
569 ws.set_response_handler(&id, h);
570 }
571 if let Some(h) = stream_frame_handler {
572 ws.set_stream_frame_handler(&id, h);
573 }
574 }
575
576 Ok(id)
577 }
578
579 pub fn add_request_breakpoint(
581 &self,
582 matcher: HttpRequest,
583 handler: BreakpointRequestHandler,
584 ) -> Result<String> {
585 self.add_breakpoint(
586 matcher,
587 &[crate::breakpoint::phase::REQUEST],
588 Some(handler),
589 None,
590 None,
591 )
592 }
593
594 pub fn add_request_response_breakpoint(
596 &self,
597 matcher: HttpRequest,
598 request_handler: BreakpointRequestHandler,
599 response_handler: BreakpointResponseHandler,
600 ) -> Result<String> {
601 self.add_breakpoint(
602 matcher,
603 &[
604 crate::breakpoint::phase::REQUEST,
605 crate::breakpoint::phase::RESPONSE,
606 ],
607 Some(request_handler),
608 Some(response_handler),
609 None,
610 )
611 }
612
613 pub fn add_stream_breakpoint(
615 &self,
616 matcher: HttpRequest,
617 phases: &[&str],
618 handler: BreakpointStreamFrameHandler,
619 ) -> Result<String> {
620 self.add_breakpoint(matcher, phases, None, None, Some(handler))
621 }
622
623 pub fn list_breakpoint_matchers(&self) -> Result<BreakpointMatcherList> {
625 let resp = self
626 .http
627 .get(self.url("/mockserver/breakpoint/matchers"))
628 .send()?;
629
630 let status = resp.status().as_u16();
631 let text = resp.text()?;
632 if status >= 400 {
633 return Err(Error::UnexpectedStatus {
634 status,
635 body: text,
636 });
637 }
638
639 Ok(serde_json::from_str(&text)?)
640 }
641
642 pub fn remove_breakpoint_matcher(&self, id: impl Into<String>) -> Result<()> {
644 let id = id.into();
645 let body = serde_json::json!({ "id": &id });
646 let resp = self
647 .http
648 .put(self.url("/mockserver/breakpoint/matcher/remove"))
649 .json(&body)
650 .send()?;
651
652 let status = resp.status().as_u16();
653 match status {
654 200 => {
655 let guard = self.breakpoint_ws.lock().unwrap();
656 if let Some(ws) = guard.as_ref() {
657 ws.remove_handlers(&id);
658 }
659 Ok(())
660 }
661 404 => Err(Error::InvalidRequest(format!(
662 "Breakpoint matcher not found: {id}"
663 ))),
664 _ => Err(Error::UnexpectedStatus {
665 status,
666 body: resp.text().unwrap_or_default(),
667 }),
668 }
669 }
670
671 pub fn clear_breakpoint_matchers(&self) -> Result<()> {
673 let resp = self
674 .http
675 .put(self.url("/mockserver/breakpoint/matcher/clear"))
676 .header("Content-Type", "application/json")
677 .body("")
678 .send()?;
679
680 let status = resp.status().as_u16();
681 if status >= 400 {
682 return Err(Error::UnexpectedStatus {
683 status,
684 body: resp.text().unwrap_or_default(),
685 });
686 }
687
688 let guard = self.breakpoint_ws.lock().unwrap();
689 if let Some(ws) = guard.as_ref() {
690 ws.clear_handlers();
691 }
692 Ok(())
693 }
694
695 pub fn close_breakpoint_websocket(&self) {
697 let mut guard = self.breakpoint_ws.lock().unwrap();
698 if let Some(ws) = guard.take() {
699 ws.close();
700 }
701 }
702
703 fn do_verify(&self, verification: &Verification) -> Result<()> {
708 let resp = self
709 .http
710 .put(self.url("/mockserver/verify"))
711 .json(verification)
712 .send()?;
713
714 let status = resp.status().as_u16();
715 match status {
716 200 | 202 => Ok(()),
717 406 => Err(Error::VerificationFailure(resp.text()?)),
718 400 => Err(Error::InvalidRequest(resp.text()?)),
719 _ => Err(Error::UnexpectedStatus {
720 status,
721 body: resp.text().unwrap_or_default(),
722 }),
723 }
724 }
725
726 fn do_verify_sequence(&self, verification: &VerificationSequence) -> Result<()> {
727 let resp = self
728 .http
729 .put(self.url("/mockserver/verifySequence"))
730 .json(verification)
731 .send()?;
732
733 let status = resp.status().as_u16();
734 match status {
735 200 | 202 => Ok(()),
736 406 => Err(Error::VerificationFailure(resp.text()?)),
737 400 => Err(Error::InvalidRequest(resp.text()?)),
738 _ => Err(Error::UnexpectedStatus {
739 status,
740 body: resp.text().unwrap_or_default(),
741 }),
742 }
743 }
744
745 fn url(&self, path: &str) -> String {
746 format!("{}{path}", self.base_url)
747 }
748
749 fn do_retrieve(
750 &self,
751 request: Option<&HttpRequest>,
752 retrieve_type: RetrieveType,
753 format: RetrieveFormat,
754 ) -> Result<String> {
755 let url = format!(
756 "{}?type={}&format={}",
757 self.url("/mockserver/retrieve"),
758 retrieve_type.as_str(),
759 format.as_str(),
760 );
761
762 let mut builder = self.http.put(&url);
763 builder = builder.header("Content-Type", "application/json");
764 if let Some(req) = request {
765 builder = builder.json(req);
766 } else {
767 builder = builder.body("");
768 }
769
770 let resp = builder.send()?;
771 let status = resp.status().as_u16();
772 match status {
773 200 => Ok(resp.text()?),
774 400 => Err(Error::InvalidRequest(resp.text()?)),
775 _ => Err(Error::UnexpectedStatus {
776 status,
777 body: resp.text().unwrap_or_default(),
778 }),
779 }
780 }
781}
782
783pub struct ForwardChainExpectation<'a> {
789 client: &'a MockServerClient,
790 request: HttpRequest,
791 times: Option<Times>,
792 time_to_live: Option<TimeToLive>,
793 priority: Option<i32>,
794 id: Option<String>,
795}
796
797impl<'a> ForwardChainExpectation<'a> {
798 pub fn times(mut self, times: Times) -> Self {
800 self.times = Some(times);
801 self
802 }
803
804 pub fn time_to_live(mut self, ttl: TimeToLive) -> Self {
806 self.time_to_live = Some(ttl);
807 self
808 }
809
810 pub fn priority(mut self, priority: i32) -> Self {
812 self.priority = Some(priority);
813 self
814 }
815
816 pub fn with_id(mut self, id: impl Into<String>) -> Self {
818 self.id = Some(id.into());
819 self
820 }
821
822 pub fn respond(self, response: HttpResponse) -> Result<Vec<Expectation>> {
824 let (client, expectation) = self.into_parts();
825 let expectation = expectation.respond(response);
826 client.upsert(&[expectation])
827 }
828
829 pub fn forward(self, forward: HttpForward) -> Result<Vec<Expectation>> {
831 let (client, expectation) = self.into_parts();
832 let expectation = expectation.forward(forward);
833 client.upsert(&[expectation])
834 }
835
836 pub fn error(self, error: HttpError) -> Result<Vec<Expectation>> {
838 let (client, expectation) = self.into_parts();
839 let expectation = expectation.error(error);
840 client.upsert(&[expectation])
841 }
842
843 fn into_parts(self) -> (&'a MockServerClient, Expectation) {
844 let ForwardChainExpectation {
845 client,
846 request,
847 times,
848 time_to_live,
849 priority,
850 id,
851 } = self;
852 let mut exp = Expectation::new(request);
853 exp.times = times;
854 exp.time_to_live = time_to_live;
855 exp.priority = priority;
856 exp.id = id;
857 (client, exp)
858 }
859}