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