1use bytes::{Buf, BufMut};
13
14use super::{PATTERN_TYPE_ANY, PATTERN_TYPE_LITERAL};
15#[cfg(test)]
16use crate::krabka::test_support::sample_tagged_fields;
17use crate::{
18 Decode, Encode, ProtocolError, ProtocolRequest, UnknownTaggedFields,
19 krabka::common::{
20 I8_LEN, I16_LEN, I32_LEN, I64_LEN, UUID_LEN, array_len, check_version, decode_array,
21 encode_array, read_unknown_tagged_fields, unknown_tagged_fields_len,
22 write_unknown_tagged_fields,
23 },
24 primitives::{
25 fixed::{get_i8, get_i16, get_i32, get_i64, put_i8, put_i16, put_i32, put_i64},
26 string_bytes::{
27 compact_bytes_len, compact_nullable_string_len, compact_string_len,
28 get_compact_bytes_owned, get_compact_nullable_string_owned, get_compact_string_owned,
29 put_compact_bytes, put_compact_nullable_string, put_compact_string,
30 },
31 uuid::{Uuid, get_uuid, put_uuid},
32 },
33};
34
35pub const API_KEY: i16 = 1016;
37pub const MIN_VERSION: i16 = 0;
39pub const MAX_VERSION: i16 = 0;
41pub const FLEXIBLE_MIN: i16 = 0;
43
44#[inline]
49#[must_use]
50pub fn is_flexible(version: i16) -> bool {
51 version >= FLEXIBLE_MIN
52}
53
54#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct DescribeTopicFreezesRequest {
57 pub scope_filter: Option<String>,
60 pub pattern_type_filter: i8,
65 pub unknown_tagged_fields: UnknownTaggedFields,
67}
68
69impl Default for DescribeTopicFreezesRequest {
70 fn default() -> Self {
71 Self {
72 scope_filter: None,
73 pattern_type_filter: PATTERN_TYPE_ANY,
74 unknown_tagged_fields: UnknownTaggedFields::default(),
75 }
76 }
77}
78
79impl Encode for DescribeTopicFreezesRequest {
80 fn encode<B: BufMut>(&self, buf: &mut B, version: i16) -> Result<(), ProtocolError> {
81 check_version(API_KEY, version, MIN_VERSION, MAX_VERSION)?;
82 put_compact_nullable_string(buf, self.scope_filter.as_deref())?;
83 put_i8(buf, self.pattern_type_filter);
84 write_unknown_tagged_fields(buf, &self.unknown_tagged_fields)?;
85 Ok(())
86 }
87
88 fn encoded_len(&self, _version: i16) -> usize {
89 compact_nullable_string_len(self.scope_filter.as_deref())
90 + I8_LEN
91 + unknown_tagged_fields_len(&self.unknown_tagged_fields)
92 }
93}
94
95impl Decode<'_> for DescribeTopicFreezesRequest {
96 fn decode<B: Buf>(buf: &mut B, version: i16) -> Result<Self, ProtocolError> {
97 check_version(API_KEY, version, MIN_VERSION, MAX_VERSION)?;
98 Ok(Self {
99 scope_filter: get_compact_nullable_string_owned(buf)?,
100 pattern_type_filter: get_i8(buf)?,
101 unknown_tagged_fields: read_unknown_tagged_fields(buf)?,
102 })
103 }
104}
105
106impl ProtocolRequest for DescribeTopicFreezesRequest {
107 const API_KEY: i16 = API_KEY;
108 const MIN_VERSION: i16 = MIN_VERSION;
109 const MAX_VERSION: i16 = MAX_VERSION;
110 const LATEST_STABLE_VERSION: i16 = MAX_VERSION;
111 const FLEXIBLE_MIN: i16 = FLEXIBLE_MIN;
112
113 type Response = DescribeTopicFreezesResponse;
114}
115
116#[derive(Debug, Clone, PartialEq, Eq)]
118pub struct DescribedTopicFreeze {
119 pub scope: String,
121 pub pattern_type: i8,
124 pub reason: String,
126 pub set_by: String,
128 pub set_at_ms: i64,
131 pub proposal_id: Uuid,
134 pub key_id: String,
137 pub signature: Vec<u8>,
141 pub unknown_tagged_fields: UnknownTaggedFields,
143}
144
145impl Default for DescribedTopicFreeze {
146 fn default() -> Self {
147 Self {
148 scope: String::new(),
149 pattern_type: PATTERN_TYPE_LITERAL,
150 reason: String::new(),
151 set_by: String::new(),
152 set_at_ms: 0,
153 proposal_id: Uuid::ZERO,
154 key_id: String::new(),
155 signature: Vec::new(),
156 unknown_tagged_fields: UnknownTaggedFields::default(),
157 }
158 }
159}
160
161impl Encode for DescribedTopicFreeze {
162 fn encode<B: BufMut>(&self, buf: &mut B, _version: i16) -> Result<(), ProtocolError> {
163 put_compact_string(buf, &self.scope)?;
164 put_i8(buf, self.pattern_type);
165 put_compact_string(buf, &self.reason)?;
166 put_compact_string(buf, &self.set_by)?;
167 put_i64(buf, self.set_at_ms);
168 put_uuid(buf, self.proposal_id);
169 put_compact_string(buf, &self.key_id)?;
170 put_compact_bytes(buf, &self.signature)?;
171 write_unknown_tagged_fields(buf, &self.unknown_tagged_fields)?;
172 Ok(())
173 }
174
175 fn encoded_len(&self, _version: i16) -> usize {
176 compact_string_len(&self.scope)
177 + I8_LEN
178 + compact_string_len(&self.reason)
179 + compact_string_len(&self.set_by)
180 + I64_LEN
181 + UUID_LEN
182 + compact_string_len(&self.key_id)
183 + compact_bytes_len(&self.signature)
184 + unknown_tagged_fields_len(&self.unknown_tagged_fields)
185 }
186}
187
188impl Decode<'_> for DescribedTopicFreeze {
189 fn decode<B: Buf>(buf: &mut B, _version: i16) -> Result<Self, ProtocolError> {
190 Ok(Self {
191 scope: get_compact_string_owned(buf)?,
192 pattern_type: get_i8(buf)?,
193 reason: get_compact_string_owned(buf)?,
194 set_by: get_compact_string_owned(buf)?,
195 set_at_ms: get_i64(buf)?,
196 proposal_id: get_uuid(buf)?,
197 key_id: get_compact_string_owned(buf)?,
198 signature: get_compact_bytes_owned(buf)?.to_vec(),
199 unknown_tagged_fields: read_unknown_tagged_fields(buf)?,
200 })
201 }
202}
203
204#[derive(Debug, Clone, PartialEq, Eq, Default)]
206pub struct DescribeTopicFreezesResponse {
207 pub throttle_time_ms: i32,
209 pub error_code: i16,
211 pub error_message: Option<String>,
214 pub freezes: Vec<DescribedTopicFreeze>,
216 pub unknown_tagged_fields: UnknownTaggedFields,
218}
219
220impl Encode for DescribeTopicFreezesResponse {
221 fn encode<B: BufMut>(&self, buf: &mut B, version: i16) -> Result<(), ProtocolError> {
222 check_version(API_KEY, version, MIN_VERSION, MAX_VERSION)?;
223 put_i32(buf, self.throttle_time_ms);
224 put_i16(buf, self.error_code);
225 put_compact_nullable_string(buf, self.error_message.as_deref())?;
226 encode_array(buf, &self.freezes, version)?;
227 write_unknown_tagged_fields(buf, &self.unknown_tagged_fields)?;
228 Ok(())
229 }
230
231 fn encoded_len(&self, version: i16) -> usize {
232 I32_LEN
233 + I16_LEN
234 + compact_nullable_string_len(self.error_message.as_deref())
235 + array_len(&self.freezes, version)
236 + unknown_tagged_fields_len(&self.unknown_tagged_fields)
237 }
238}
239
240impl Decode<'_> for DescribeTopicFreezesResponse {
241 fn decode<B: Buf>(buf: &mut B, version: i16) -> Result<Self, ProtocolError> {
242 check_version(API_KEY, version, MIN_VERSION, MAX_VERSION)?;
243 Ok(Self {
244 throttle_time_ms: get_i32(buf)?,
245 error_code: get_i16(buf)?,
246 error_message: get_compact_nullable_string_owned(buf)?,
247 freezes: decode_array(buf, version)?,
248 unknown_tagged_fields: read_unknown_tagged_fields(buf)?,
249 })
250 }
251}
252
253#[cfg(test)]
254impl DescribeTopicFreezesRequest {
255 fn populated() -> Self {
257 Self {
258 scope_filter: Some("tenant-a.".to_owned()),
259 pattern_type_filter: super::PATTERN_TYPE_PREFIXED,
260 unknown_tagged_fields: sample_tagged_fields(),
261 }
262 }
263}
264
265#[cfg(test)]
266impl DescribeTopicFreezesResponse {
267 fn populated() -> Self {
269 Self {
270 throttle_time_ms: 3,
271 error_code: 0,
272 error_message: None,
273 freezes: vec![
274 DescribedTopicFreeze {
275 scope: "orders".to_owned(),
276 pattern_type: PATTERN_TYPE_LITERAL,
277 reason: "DR cutover".to_owned(),
278 set_by: "User:alice".to_owned(),
279 set_at_ms: 1_770_000_000_000,
280 proposal_id: Uuid::ZERO,
281 key_id: "alice-yubi".to_owned(),
282 signature: (0..64).collect(),
283 unknown_tagged_fields: sample_tagged_fields(),
284 },
285 DescribedTopicFreeze {
286 scope: "tenant-a.".to_owned(),
287 pattern_type: super::PATTERN_TYPE_PREFIXED,
288 reason: "tenant offboarding".to_owned(),
289 set_by: "User:bob".to_owned(),
290 set_at_ms: 1_770_000_100_000,
291 ..DescribedTopicFreeze::default()
292 },
293 ],
294 unknown_tagged_fields: sample_tagged_fields(),
295 }
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use assert2::assert;
302
303 use super::*;
304 use crate::krabka::{
305 freeze::PATTERN_TYPE_PREFIXED,
306 test_support::{reject_truncated, roundtrip_case, unsupported_versions},
307 };
308
309 #[test]
310 fn requests_roundtrip() {
311 let cases = [
312 (
313 "default asks for every entry",
314 DescribeTopicFreezesRequest::default(),
315 ),
316 (
317 "prefixed scope filter",
318 DescribeTopicFreezesRequest::populated(),
319 ),
320 (
321 "literal scope filter",
322 DescribeTopicFreezesRequest {
323 scope_filter: Some("orders".to_owned()),
324 pattern_type_filter: PATTERN_TYPE_LITERAL,
325 unknown_tagged_fields: UnknownTaggedFields::default(),
326 },
327 ),
328 (
329 "unknown pattern type reads every entry",
330 DescribeTopicFreezesRequest {
331 scope_filter: None,
332 pattern_type_filter: 0,
333 unknown_tagged_fields: UnknownTaggedFields::default(),
334 },
335 ),
336 ];
337 for (label, request) in &cases {
338 for version in MIN_VERSION..=MAX_VERSION {
339 roundtrip_case(label, request, version);
340 }
341 }
342 }
343
344 #[test]
345 fn responses_roundtrip() {
346 let cases = [
347 ("default", DescribeTopicFreezesResponse::default()),
348 (
349 "signed and unsigned entries",
350 DescribeTopicFreezesResponse::populated(),
351 ),
352 (
353 "refusal with no entries",
354 DescribeTopicFreezesResponse {
355 throttle_time_ms: 11,
356 error_code: 31,
357 error_message: Some("cluster authorization failed".to_owned()),
358 freezes: Vec::new(),
359 unknown_tagged_fields: UnknownTaggedFields::default(),
360 },
361 ),
362 ];
363 for (label, response) in &cases {
364 for version in MIN_VERSION..=MAX_VERSION {
365 roundtrip_case(label, response, version);
366 }
367 }
368 }
369
370 #[test]
371 fn no_filter_is_the_request_default() {
372 assert!(
373 DescribeTopicFreezesRequest::default()
374 == DescribeTopicFreezesRequest {
375 scope_filter: None,
376 pattern_type_filter: PATTERN_TYPE_ANY,
377 unknown_tagged_fields: UnknownTaggedFields::default(),
378 }
379 );
380 }
381
382 #[test]
383 fn an_unsigned_entry_carries_an_empty_signature() {
384 let response = DescribeTopicFreezesResponse::populated();
385 let unsigned = &response.freezes[1];
386 assert!(unsigned.key_id.is_empty());
387 assert!(unsigned.signature.is_empty());
388 assert!(unsigned.pattern_type == PATTERN_TYPE_PREFIXED);
389 }
390
391 #[test]
392 fn truncated_input_is_rejected() {
393 reject_truncated(&DescribeTopicFreezesRequest::populated(), MIN_VERSION);
394 reject_truncated(&DescribeTopicFreezesResponse::populated(), MIN_VERSION);
395 }
396
397 #[test]
398 fn rejects_unsupported_versions() {
399 for version in unsupported_versions(MIN_VERSION, MAX_VERSION) {
400 let request = DescribeTopicFreezesRequest::populated();
401 assert!(matches!(
402 request.encode(&mut Vec::<u8>::new(), version),
403 Err(ProtocolError::UnsupportedVersion {
404 api_key: API_KEY,
405 ..
406 })
407 ));
408 let response = DescribeTopicFreezesResponse::populated();
409 assert!(matches!(
410 response.encode(&mut Vec::<u8>::new(), version),
411 Err(ProtocolError::UnsupportedVersion {
412 api_key: API_KEY,
413 ..
414 })
415 ));
416 }
417 }
418}