cloud_sdk_testkit/
mock.rs1use core::fmt;
4
5use cloud_sdk::Method;
6use cloud_sdk::transport::{BlockingTransport, RequestTarget, TransportRequest, TransportResponse};
7
8use crate::{FixtureBodyError, ResponseFixture};
9
10#[derive(Clone, Copy, Eq, PartialEq)]
12pub struct ExpectedRequest<'a> {
13 method: Method,
14 target: RequestTarget<'a>,
15 body: &'a [u8],
16}
17
18impl<'a> ExpectedRequest<'a> {
19 #[must_use]
21 pub const fn new(method: Method, target: RequestTarget<'a>) -> Self {
22 Self {
23 method,
24 target,
25 body: &[],
26 }
27 }
28
29 #[must_use]
31 pub const fn with_body(mut self, body: &'a [u8]) -> Self {
32 self.body = body;
33 self
34 }
35
36 const fn method(self) -> Method {
37 self.method
38 }
39
40 const fn target(self) -> RequestTarget<'a> {
41 self.target
42 }
43
44 const fn body(self) -> &'a [u8] {
45 self.body
46 }
47}
48
49impl fmt::Debug for ExpectedRequest<'_> {
50 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
51 formatter
52 .debug_struct("ExpectedRequest")
53 .field("method", &self.method)
54 .field("target", &"[redacted]")
55 .field("body", &"[redacted]")
56 .finish()
57 }
58}
59
60#[derive(Clone, Copy, Debug, Eq, PartialEq)]
62pub struct MockExchange<'a> {
63 request: ExpectedRequest<'a>,
64 response: ResponseFixture<'a>,
65}
66
67impl<'a> MockExchange<'a> {
68 #[must_use]
70 pub const fn new(request: ExpectedRequest<'a>, response: ResponseFixture<'a>) -> Self {
71 Self { request, response }
72 }
73}
74
75#[derive(Clone, Copy, Debug, Eq, PartialEq)]
77pub enum MockError {
78 Exhausted,
80 MethodMismatch,
82 TargetMismatch,
84 BodyMismatch,
86 ResponseBufferTooSmall,
88 CursorOverflow,
90}
91
92pub struct MockTransport<'a> {
94 exchanges: &'a [MockExchange<'a>],
95 cursor: usize,
96}
97
98impl<'a> MockTransport<'a> {
99 #[must_use]
101 pub const fn new(exchanges: &'a [MockExchange<'a>]) -> Self {
102 Self {
103 exchanges,
104 cursor: 0,
105 }
106 }
107
108 #[must_use]
110 pub const fn remaining(&self) -> usize {
111 self.exchanges.len().saturating_sub(self.cursor)
112 }
113
114 #[must_use]
116 pub const fn is_complete(&self) -> bool {
117 self.remaining() == 0
118 }
119}
120
121impl BlockingTransport for MockTransport<'_> {
122 type Error = MockError;
123
124 fn send<'buffer>(
125 &mut self,
126 request: TransportRequest<'_>,
127 response_body: &'buffer mut [u8],
128 ) -> Result<TransportResponse<'buffer>, Self::Error> {
129 let exchange = self
130 .exchanges
131 .get(self.cursor)
132 .ok_or(MockError::Exhausted)?;
133 if request.method() != exchange.request.method() {
134 return Err(MockError::MethodMismatch);
135 }
136 if request.target() != exchange.request.target() {
137 return Err(MockError::TargetMismatch);
138 }
139 if request.body() != exchange.request.body() {
140 return Err(MockError::BodyMismatch);
141 }
142 let next_cursor = self
143 .cursor
144 .checked_add(1)
145 .ok_or(MockError::CursorOverflow)?;
146 let body_len =
147 exchange
148 .response
149 .body()
150 .write_to(response_body)
151 .map_err(|error| match error {
152 FixtureBodyError::OutputTooSmall | FixtureBodyError::TooLarge => {
153 MockError::ResponseBufferTooSmall
154 }
155 })?;
156 let initialized = response_body
157 .get(..body_len)
158 .ok_or(MockError::ResponseBufferTooSmall)?;
159 self.cursor = next_cursor;
160 Ok(TransportResponse::new(
161 exchange.response.status(),
162 initialized,
163 ))
164 }
165}
166
167impl fmt::Debug for MockTransport<'_> {
168 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
169 formatter
170 .debug_struct("MockTransport")
171 .field("remaining", &self.remaining())
172 .finish_non_exhaustive()
173 }
174}