1use bytes::BytesMut;
16use cheetah_string::CheetahString;
17use rocketmq_model::boundary_type::BoundaryType;
18
19use super::private::Sealed;
20use super::write_json_string;
21use super::ProtocolContractViolation;
22
23#[derive(Clone, Copy, Debug, Eq, PartialEq)]
25pub enum HeaderValueKind {
26 String,
28 Bool,
30 I32,
32 I64,
34 U32,
36 U64,
38 BoundaryType,
40}
41
42#[derive(Clone, Copy, Debug, Eq, PartialEq)]
44pub enum HeaderRange {
45 I32,
47 I64,
49}
50
51#[derive(Clone, Copy, Debug, Eq, PartialEq)]
56pub struct HeaderFieldContext {
57 pub header: &'static str,
59 pub key: &'static str,
61 pub kind: HeaderValueKind,
63 pub range: Option<HeaderRange>,
65}
66
67impl HeaderFieldContext {
68 pub const fn new(
70 header: &'static str,
71 key: &'static str,
72 kind: HeaderValueKind,
73 range: Option<HeaderRange>,
74 ) -> Self {
75 Self {
76 header,
77 key,
78 kind,
79 range,
80 }
81 }
82}
83
84pub trait HeaderValue: Sealed + Sized {
90 const KIND: HeaderValueKind;
92
93 const ALWAYS_FITS_WIRE_LENGTH: bool = false;
95
96 fn to_map_value(&self) -> CheetahString;
98
99 fn encoded_len(&self) -> usize;
101
102 fn write_ascii(&self, out: &mut BytesMut);
105
106 #[inline]
111 fn write_json_string(&self, out: &mut BytesMut) {
112 out.extend_from_slice(b"\"");
113 self.write_ascii(out);
114 out.extend_from_slice(b"\"");
115 }
116
117 fn decode(raw: &str, context: HeaderFieldContext) -> Result<Self, ProtocolContractViolation>;
126}
127
128#[inline]
139pub fn validate_unsigned_java_range(value: u64, context: HeaderFieldContext) -> Result<(), ProtocolContractViolation> {
140 let in_range = match context.range {
141 None => true,
142 Some(HeaderRange::I32) => value <= i32::MAX as u64,
143 Some(HeaderRange::I64) => value <= i64::MAX as u64,
144 };
145 if in_range {
146 Ok(())
147 } else {
148 Err(ProtocolContractViolation::JavaRange {
149 header: context.header,
150 key: context.key,
151 })
152 }
153}
154
155#[inline]
156const fn invalid_value(context: HeaderFieldContext) -> ProtocolContractViolation {
157 ProtocolContractViolation::InvalidValue {
158 header: context.header,
159 key: context.key,
160 expected: context.kind,
161 }
162}
163
164#[inline]
165fn signed_decimal_len(value: i64) -> usize {
166 unsigned_decimal_len(value.unsigned_abs()) + usize::from(value < 0)
167}
168
169#[inline]
170fn unsigned_decimal_len(value: u64) -> usize {
171 if value == 0 {
172 1
173 } else {
174 value.ilog10() as usize + 1
175 }
176}
177
178impl Sealed for CheetahString {}
179
180impl HeaderValue for CheetahString {
181 const KIND: HeaderValueKind = HeaderValueKind::String;
182 const ALWAYS_FITS_WIRE_LENGTH: bool = false;
183
184 #[inline]
185 fn to_map_value(&self) -> CheetahString {
186 self.clone()
187 }
188
189 #[inline]
190 fn encoded_len(&self) -> usize {
191 self.len()
192 }
193
194 #[inline]
195 fn write_ascii(&self, out: &mut BytesMut) {
196 out.extend_from_slice(self.as_bytes());
197 }
198
199 #[inline]
200 fn write_json_string(&self, out: &mut BytesMut) {
201 write_json_string(out, self.as_str());
202 }
203
204 #[inline]
205 fn decode(raw: &str, _context: HeaderFieldContext) -> Result<Self, ProtocolContractViolation> {
206 Ok(CheetahString::from_slice(raw))
207 }
208}
209
210impl Sealed for String {}
211
212impl HeaderValue for String {
213 const KIND: HeaderValueKind = HeaderValueKind::String;
214 const ALWAYS_FITS_WIRE_LENGTH: bool = false;
215
216 #[inline]
217 fn to_map_value(&self) -> CheetahString {
218 CheetahString::from_slice(self)
219 }
220
221 #[inline]
222 fn encoded_len(&self) -> usize {
223 self.len()
224 }
225
226 #[inline]
227 fn write_ascii(&self, out: &mut BytesMut) {
228 out.extend_from_slice(self.as_bytes());
229 }
230
231 #[inline]
232 fn write_json_string(&self, out: &mut BytesMut) {
233 write_json_string(out, self);
234 }
235
236 #[inline]
237 fn decode(raw: &str, _context: HeaderFieldContext) -> Result<Self, ProtocolContractViolation> {
238 Ok(raw.to_owned())
239 }
240}
241
242impl Sealed for bool {}
243
244impl HeaderValue for bool {
245 const KIND: HeaderValueKind = HeaderValueKind::Bool;
246 const ALWAYS_FITS_WIRE_LENGTH: bool = true;
247
248 #[inline]
249 fn to_map_value(&self) -> CheetahString {
250 CheetahString::from_static_str(if *self { "true" } else { "false" })
251 }
252
253 #[inline]
254 fn encoded_len(&self) -> usize {
255 if *self {
256 4
257 } else {
258 5
259 }
260 }
261
262 #[inline]
263 fn write_ascii(&self, out: &mut BytesMut) {
264 out.extend_from_slice(if *self { b"true" } else { b"false" });
265 }
266
267 #[inline]
268 fn decode(raw: &str, context: HeaderFieldContext) -> Result<Self, ProtocolContractViolation> {
269 if raw.eq_ignore_ascii_case("true") {
270 Ok(true)
271 } else if raw.eq_ignore_ascii_case("false") {
272 Ok(false)
273 } else {
274 Err(invalid_value(context))
275 }
276 }
277}
278
279macro_rules! impl_signed_header_value {
280 ($ty:ty, $kind:ident) => {
281 impl Sealed for $ty {}
282
283 impl HeaderValue for $ty {
284 const KIND: HeaderValueKind = HeaderValueKind::$kind;
285 const ALWAYS_FITS_WIRE_LENGTH: bool = true;
286
287 #[inline]
288 fn to_map_value(&self) -> CheetahString {
289 let mut buffer = itoa::Buffer::new();
290 CheetahString::from_slice(buffer.format(*self))
291 }
292
293 #[inline]
294 fn encoded_len(&self) -> usize {
295 signed_decimal_len(*self as i64)
296 }
297
298 #[inline]
299 fn write_ascii(&self, out: &mut BytesMut) {
300 let mut buffer = itoa::Buffer::new();
301 out.extend_from_slice(buffer.format(*self).as_bytes());
302 }
303
304 #[inline]
305 fn decode(raw: &str, context: HeaderFieldContext) -> Result<Self, ProtocolContractViolation> {
306 raw.parse::<$ty>().map_err(|_| invalid_value(context))
307 }
308 }
309 };
310}
311
312macro_rules! impl_unsigned_header_value {
313 ($ty:ty, $kind:ident) => {
314 impl Sealed for $ty {}
315
316 impl HeaderValue for $ty {
317 const KIND: HeaderValueKind = HeaderValueKind::$kind;
318 const ALWAYS_FITS_WIRE_LENGTH: bool = true;
319
320 #[inline]
321 fn to_map_value(&self) -> CheetahString {
322 let mut buffer = itoa::Buffer::new();
323 CheetahString::from_slice(buffer.format(*self))
324 }
325
326 #[inline]
327 fn encoded_len(&self) -> usize {
328 unsigned_decimal_len(*self as u64)
329 }
330
331 #[inline]
332 fn write_ascii(&self, out: &mut BytesMut) {
333 let mut buffer = itoa::Buffer::new();
334 out.extend_from_slice(buffer.format(*self).as_bytes());
335 }
336
337 #[inline]
338 fn decode(raw: &str, context: HeaderFieldContext) -> Result<Self, ProtocolContractViolation> {
339 let value = raw.parse::<$ty>().map_err(|_| invalid_value(context))?;
340 validate_unsigned_java_range(value as u64, context)?;
341 Ok(value)
342 }
343 }
344 };
345}
346
347impl_signed_header_value!(i32, I32);
348impl_signed_header_value!(i64, I64);
349impl_unsigned_header_value!(u32, U32);
350impl_unsigned_header_value!(u64, U64);
351
352impl Sealed for BoundaryType {}
353
354impl HeaderValue for BoundaryType {
355 const KIND: HeaderValueKind = HeaderValueKind::BoundaryType;
356 const ALWAYS_FITS_WIRE_LENGTH: bool = true;
357
358 #[inline]
359 fn to_map_value(&self) -> CheetahString {
360 CheetahString::from_static_str(match self {
361 BoundaryType::Lower => "LOWER",
362 BoundaryType::Upper => "UPPER",
363 })
364 }
365
366 #[inline]
367 fn encoded_len(&self) -> usize {
368 match self {
369 BoundaryType::Lower => 5,
370 BoundaryType::Upper => 5,
371 }
372 }
373
374 #[inline]
375 fn write_ascii(&self, out: &mut BytesMut) {
376 out.extend_from_slice(match self {
377 BoundaryType::Lower => b"LOWER",
378 BoundaryType::Upper => b"UPPER",
379 });
380 }
381
382 #[inline]
383 fn decode(raw: &str, _context: HeaderFieldContext) -> Result<Self, ProtocolContractViolation> {
384 Ok(BoundaryType::get_type(raw))
385 }
386}
387
388#[cfg(test)]
389mod tests {
390 use super::*;
391
392 const fn context(kind: HeaderValueKind, range: Option<HeaderRange>) -> HeaderFieldContext {
393 HeaderFieldContext::new("ExampleHeader", "value", kind, range)
394 }
395
396 fn assert_encoding<T>(value: T, expected: &str)
397 where
398 T: HeaderValue,
399 {
400 let mut out = BytesMut::new();
401 value.write_ascii(&mut out);
402 assert_eq!(value.encoded_len(), expected.len());
403 assert_eq!(out.as_ref(), expected.as_bytes());
404 assert_eq!(value.to_map_value().as_str(), expected);
405 }
406
407 #[test]
408 fn strings_preserve_empty_unicode_and_exact_lengths() {
409 for raw in ["", "topic", "主题-🚀"] {
410 let cheetah = CheetahString::decode(raw, context(HeaderValueKind::String, None)).unwrap();
411 assert_encoding(cheetah, raw);
412
413 let owned = String::decode(raw, context(HeaderValueKind::String, None)).unwrap();
414 assert_encoding(owned, raw);
415 }
416 }
417
418 #[test]
419 fn signed_integers_cover_extrema_zero_and_overflow() {
420 for value in [i32::MIN, -1, 0, 1, i32::MAX] {
421 let text = value.to_string();
422 assert_eq!(i32::decode(&text, context(HeaderValueKind::I32, None)).unwrap(), value);
423 assert_encoding(value, &text);
424 }
425 for value in [i64::MIN, -1, 0, 1, i64::MAX] {
426 let text = value.to_string();
427 assert_eq!(i64::decode(&text, context(HeaderValueKind::I64, None)).unwrap(), value);
428 assert_encoding(value, &text);
429 }
430
431 assert_eq!(i32::decode("+1", context(HeaderValueKind::I32, None)).unwrap(), 1);
432 for raw in ["2147483648", "-2147483649", " 1", "1 "] {
433 assert!(matches!(
434 i32::decode(raw, context(HeaderValueKind::I32, None)),
435 Err(ProtocolContractViolation::InvalidValue { .. })
436 ));
437 }
438 for raw in ["9223372036854775808", "-9223372036854775809"] {
439 assert!(matches!(
440 i64::decode(raw, context(HeaderValueKind::I64, None)),
441 Err(ProtocolContractViolation::InvalidValue { .. })
442 ));
443 }
444 }
445
446 #[test]
447 fn unsigned_integers_cover_extrema_overflow_and_java_ranges() {
448 for value in [0, 1, u32::MAX] {
449 let text = value.to_string();
450 assert_eq!(u32::decode(&text, context(HeaderValueKind::U32, None)).unwrap(), value);
451 assert_encoding(value, &text);
452 }
453 for value in [0, 1, u64::MAX] {
454 let text = value.to_string();
455 assert_eq!(u64::decode(&text, context(HeaderValueKind::U64, None)).unwrap(), value);
456 assert_encoding(value, &text);
457 }
458
459 assert!(matches!(
460 u32::decode("4294967296", context(HeaderValueKind::U32, None)),
461 Err(ProtocolContractViolation::InvalidValue { .. })
462 ));
463 assert!(matches!(
464 u64::decode("18446744073709551616", context(HeaderValueKind::U64, None)),
465 Err(ProtocolContractViolation::InvalidValue { .. })
466 ));
467 assert!(matches!(
468 u64::decode("-1", context(HeaderValueKind::U64, None)),
469 Err(ProtocolContractViolation::InvalidValue { .. })
470 ));
471
472 assert_eq!(
473 u32::decode(
474 &i32::MAX.to_string(),
475 context(HeaderValueKind::U32, Some(HeaderRange::I32)),
476 )
477 .unwrap(),
478 i32::MAX as u32
479 );
480 assert!(matches!(
481 u32::decode(
482 &(i32::MAX as u32 + 1).to_string(),
483 context(HeaderValueKind::U32, Some(HeaderRange::I32)),
484 ),
485 Err(ProtocolContractViolation::JavaRange { .. })
486 ));
487 assert_eq!(
488 u64::decode(
489 &i64::MAX.to_string(),
490 context(HeaderValueKind::U64, Some(HeaderRange::I64)),
491 )
492 .unwrap(),
493 i64::MAX as u64
494 );
495 assert!(matches!(
496 u64::decode(
497 &(i64::MAX as u64 + 1).to_string(),
498 context(HeaderValueKind::U64, Some(HeaderRange::I64)),
499 ),
500 Err(ProtocolContractViolation::JavaRange { .. })
501 ));
502 }
503
504 #[test]
505 fn encode_range_helper_keeps_rust_extensions_unrestricted() {
506 let unrestricted = context(HeaderValueKind::U64, None);
507 assert!(validate_unsigned_java_range(u64::MAX, unrestricted).is_ok());
508
509 let java_long = context(HeaderValueKind::U32, Some(HeaderRange::I64));
510 assert!(validate_unsigned_java_range(u32::MAX as u64, java_long).is_ok());
511
512 let java_int = context(HeaderValueKind::U32, Some(HeaderRange::I32));
513 let error = validate_unsigned_java_range(i32::MAX as u64 + 1, java_int).unwrap_err();
514 assert!(matches!(error, ProtocolContractViolation::JavaRange { .. }));
515 }
516
517 #[test]
518 fn booleans_decode_case_insensitively_but_encode_canonically() {
519 for raw in ["true", "TRUE", "TrUe"] {
520 assert!(bool::decode(raw, context(HeaderValueKind::Bool, None)).unwrap());
521 }
522 for raw in ["false", "FALSE", "FaLsE"] {
523 assert!(!bool::decode(raw, context(HeaderValueKind::Bool, None)).unwrap());
524 }
525 for raw in ["", "1", "yes", " true"] {
526 assert!(matches!(
527 bool::decode(raw, context(HeaderValueKind::Bool, None)),
528 Err(ProtocolContractViolation::InvalidValue { .. })
529 ));
530 }
531 assert_encoding(true, "true");
532 assert_encoding(false, "false");
533 }
534
535 #[test]
536 fn boundary_type_preserves_java_fallback_and_canonical_encoding() {
537 assert_eq!(
538 BoundaryType::decode("upper", context(HeaderValueKind::BoundaryType, None)).unwrap(),
539 BoundaryType::Upper
540 );
541 for raw in ["lower", "unknown", "", "上界"] {
542 assert_eq!(
543 BoundaryType::decode(raw, context(HeaderValueKind::BoundaryType, None)).unwrap(),
544 BoundaryType::Lower
545 );
546 }
547 assert_encoding(BoundaryType::Lower, "LOWER");
548 assert_encoding(BoundaryType::Upper, "UPPER");
549 }
550
551 #[test]
552 fn errors_do_not_echo_malformed_values() {
553 let raw = "secret-invalid-value";
554 let error = bool::decode(raw, context(HeaderValueKind::Bool, None)).unwrap_err();
555 let rendered = error.to_string();
556
557 assert!(!rendered.contains(raw));
558 assert_eq!(rendered, "invalid header field ExampleHeader.value: expected Bool");
559 }
560}