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 InvalidFixtureMetadata,
94}
95
96pub struct MockTransport<'a> {
98 exchanges: &'a [MockExchange<'a>],
99 cursor: usize,
100}
101
102impl<'a> MockTransport<'a> {
103 #[must_use]
105 pub const fn new(exchanges: &'a [MockExchange<'a>]) -> Self {
106 Self {
107 exchanges,
108 cursor: 0,
109 }
110 }
111
112 #[must_use]
114 pub const fn remaining(&self) -> usize {
115 self.exchanges.len().saturating_sub(self.cursor)
116 }
117
118 #[must_use]
120 pub const fn is_complete(&self) -> bool {
121 self.remaining() == 0
122 }
123
124 fn send_inner<'buffer>(
125 &mut self,
126 request: TransportRequest<'_>,
127 response_body: &'buffer mut [u8],
128 ) -> Result<TransportResponse<'buffer>, MockError> {
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 let response = TransportResponse::new(exchange.response.status(), initialized);
161 let rate_limit = exchange
162 .response
163 .rate_limit()
164 .map(|value| value.into_rate_limit())
165 .transpose()
166 .map_err(|_| MockError::InvalidFixtureMetadata)?;
167 Ok(rate_limit.map_or(response, |value| response.with_rate_limit(value)))
168 }
169}
170
171impl BlockingTransport for MockTransport<'_> {
172 type Error = MockError;
173
174 fn send<'buffer>(
175 &mut self,
176 request: TransportRequest<'_>,
177 response_body: &'buffer mut [u8],
178 ) -> Result<TransportResponse<'buffer>, Self::Error> {
179 self.send_inner(request, response_body)
180 }
181}
182
183impl AsyncTransport for MockTransport<'_> {
184 type Error = MockError;
185
186 async fn send<'transport, 'request, 'buffer>(
187 &'transport mut self,
188 request: TransportRequest<'request>,
189 response_body: &'buffer mut [u8],
190 ) -> Result<TransportResponse<'buffer>, Self::Error>
191 where
192 'request: 'transport,
193 'buffer: 'transport,
194 {
195 self.send_inner(request, response_body)
196 }
197}
198
199impl fmt::Debug for MockTransport<'_> {
200 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
201 formatter
202 .debug_struct("MockTransport")
203 .field("remaining", &self.remaining())
204 .finish_non_exhaustive()
205 }
206}