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