fix: change ContentLength internal type from usize to u64

This fixes the test failure on 32-bit platforms where the large
value test (u64::MAX) would fail because `usize` cannot hold values
larger than 4GB on 32-bit systems.

According to RFC 9110, Content-Length can be any valid unsigned
64-bit integer, and it's reasonable to have content larger than
4GB even on 32-bit systems (e.g., streaming large files).

Changes:
- Change ContentLength inner type from usize to u64
- Update into_inner() return type to u64
- Update FromStr to use u64 parsing
- Add From<u64> implementation
- Update From<usize> to convert to u64
- Add PartialEq/PartialOrd implementations for u64
- Update usize comparisons to properly convert
- Fix test to use u64 literal suffix
- Update json.rs to convert u64 to usize with try_from

Fixes #3812

Co-Authored-By: Claude (claude-opus-4-5) <noreply@anthropic.com>
This commit is contained in:
Nivesh Dandyan 2026-02-05 16:06:49 +00:00
parent 6efc4bdfb5
commit 15ea560863
2 changed files with 46 additions and 14 deletions

View File

@ -39,17 +39,17 @@ use crate::{
/// ///
/// [RFC 9110 §8.6]: https://www.rfc-editor.org/rfc/rfc9110#name-content-length /// [RFC 9110 §8.6]: https://www.rfc-editor.org/rfc/rfc9110#name-content-length
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deref, DerefMut)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deref, DerefMut)]
pub struct ContentLength(pub usize); pub struct ContentLength(pub u64);
impl ContentLength { impl ContentLength {
/// Returns Content-Length value. /// Returns Content-Length value.
pub fn into_inner(&self) -> usize { pub fn into_inner(&self) -> u64 {
self.0 self.0
} }
} }
impl str::FromStr for ContentLength { impl str::FromStr for ContentLength {
type Err = <usize as str::FromStr>::Err; type Err = <u64 as str::FromStr>::Err;
#[inline] #[inline]
fn from_str(val: &str) -> Result<Self, Self::Err> { fn from_str(val: &str) -> Result<Self, Self::Err> {
@ -85,39 +85,69 @@ impl Header for ContentLength {
} }
} }
impl From<ContentLength> for usize { impl From<ContentLength> for u64 {
fn from(ContentLength(len): ContentLength) -> Self { fn from(ContentLength(len): ContentLength) -> Self {
len len
} }
} }
impl From<usize> for ContentLength { impl From<u64> for ContentLength {
fn from(len: usize) -> Self { fn from(len: u64) -> Self {
ContentLength(len) ContentLength(len)
} }
} }
impl PartialEq<usize> for ContentLength { impl From<usize> for ContentLength {
fn eq(&self, other: &usize) -> bool { fn from(len: usize) -> Self {
ContentLength(len as u64)
}
}
impl PartialEq<u64> for ContentLength {
fn eq(&self, other: &u64) -> bool {
self.0 == *other self.0 == *other
} }
} }
impl PartialEq<ContentLength> for usize { impl PartialEq<ContentLength> for u64 {
fn eq(&self, other: &ContentLength) -> bool { fn eq(&self, other: &ContentLength) -> bool {
*self == other.0 *self == other.0
} }
} }
impl PartialOrd<u64> for ContentLength {
fn partial_cmp(&self, other: &u64) -> Option<std::cmp::Ordering> {
self.0.partial_cmp(other)
}
}
impl PartialOrd<ContentLength> for u64 {
fn partial_cmp(&self, other: &ContentLength) -> Option<std::cmp::Ordering> {
self.partial_cmp(&other.0)
}
}
impl PartialEq<usize> for ContentLength {
fn eq(&self, other: &usize) -> bool {
self.0 == *other as u64
}
}
impl PartialEq<ContentLength> for usize {
fn eq(&self, other: &ContentLength) -> bool {
(*self as u64) == other.0
}
}
impl PartialOrd<usize> for ContentLength { impl PartialOrd<usize> for ContentLength {
fn partial_cmp(&self, other: &usize) -> Option<std::cmp::Ordering> { fn partial_cmp(&self, other: &usize) -> Option<std::cmp::Ordering> {
self.0.partial_cmp(other) self.0.partial_cmp(&(*other as u64))
} }
} }
impl PartialOrd<ContentLength> for usize { impl PartialOrd<ContentLength> for usize {
fn partial_cmp(&self, other: &ContentLength) -> Option<std::cmp::Ordering> { fn partial_cmp(&self, other: &ContentLength) -> Option<std::cmp::Ordering> {
self.partial_cmp(&other.0) (*self as u64).partial_cmp(&other.0)
} }
} }
@ -215,10 +245,10 @@ mod tests {
assert_parse_eq::<ContentLength, _, _>(["0 "], ContentLength(0)); assert_parse_eq::<ContentLength, _, _>(["0 "], ContentLength(0));
assert_parse_eq::<ContentLength, _, _>([" 0 "], ContentLength(0)); assert_parse_eq::<ContentLength, _, _>([" 0 "], ContentLength(0));
// large value (2^64 - 1) // large value (2^64 - 1), now works on 32-bit platforms too
assert_parse_eq::<ContentLength, _, _>( assert_parse_eq::<ContentLength, _, _>(
["18446744073709551615"], ["18446744073709551615"],
ContentLength(18_446_744_073_709_551_615), ContentLength(18_446_744_073_709_551_615_u64),
); );
} }

View File

@ -347,7 +347,9 @@ impl<T: DeserializeOwned> JsonBody<T> {
return JsonBody::Error(Some(JsonPayloadError::ContentType)); return JsonBody::Error(Some(JsonPayloadError::ContentType));
} }
let length = ContentLength::parse(req).ok().map(|x| x.0); let length = ContentLength::parse(req)
.ok()
.and_then(|x| usize::try_from(x.0).ok());
// Notice the content-length is not checked against limit of json config here. // Notice the content-length is not checked against limit of json config here.
// As the internal usage always call JsonBody::limit after JsonBody::new. // As the internal usage always call JsonBody::limit after JsonBody::new.