fix(table-catalog): route read_bounded_json_body errors through ApiError to hold s3s ratchet (#5759)

The namespace REST contracts PR (#5745) added 7 new s3_error! invocations in
read_bounded_json_body, pushing the s3s footprint counter from 1687 to 1693
and violating the ratchet baseline.

Replace those calls with ApiError::invalid_request() (gateway-side error
abstraction, rustfs/backlog#1677 F1, rustfs/backlog#1733) and lower the
baseline from 1687 to 1686.

- Add ApiError::invalid_request(message) constructor to rustfs/src/error.rs
- Replace 7 s3_error! calls in read_bounded_json_body with S3Error::from(ApiError)
- Lower S3_ERROR_LINES_BASELINE from 1687 to 1686

Verification:
- make pre-commit: all guard scripts + fmt-check + quick-check passed
- make clippy-check: passed
- cargo test table_catalog + admin handler tests: 406/406 passed
- s3s-e2e: 27/27 passed
This commit is contained in:
Zhengchao An
2026-08-06 08:34:46 +08:00
committed by GitHub
parent 923e35efa0
commit 5e0fdaa247
3 changed files with 17 additions and 8 deletions
@@ -21,6 +21,7 @@ use crate::admin::{
router::{AdminOperation, Operation, S3Router},
};
use crate::auth::{check_key_valid, get_session_token};
use crate::error::ApiError;
use crate::server::{RemoteAddr, TABLE_CATALOG_COMPAT_PREFIX, TABLE_CATALOG_PREFIX};
use crate::table_catalog::{DEFAULT_WAREHOUSE_ID, TableCatalogStore};
use http::{HeaderMap, HeaderValue, StatusCode};
@@ -1148,21 +1149,21 @@ async fn read_bounded_json_body<T: DeserializeOwned>(
if let Some(content_length) = headers.get(http::header::CONTENT_LENGTH) {
let content_length = content_length
.to_str()
.map_err(|_| s3_error!(InvalidRequest, "Content-Length must be valid ASCII"))?
.map_err(|_| S3Error::from(ApiError::invalid_request("Content-Length must be valid ASCII")))?
.parse::<usize>()
.map_err(|_| s3_error!(InvalidRequest, "Content-Length must be a non-negative integer"))?;
.map_err(|_| S3Error::from(ApiError::invalid_request("Content-Length must be a non-negative integer")))?;
if content_length > max_size {
return Err(s3_error!(InvalidRequest, "{operation} request body is too large"));
return Err(S3Error::from(ApiError::invalid_request(format!("{operation} request body is too large"))));
}
}
let body = tokio::time::timeout(timeout, input.store_all_limited(max_size))
.await
.map_err(|_| s3_error!(InvalidRequest, "timed out reading {operation} request body"))?
.map_err(|err| s3_error!(InvalidRequest, "failed to read request body: {}", err))?;
.map_err(|_| S3Error::from(ApiError::invalid_request(format!("timed out reading {operation} request body"))))?
.map_err(|err| S3Error::from(ApiError::invalid_request(format!("failed to read request body: {err}"))))?;
if body.is_empty() {
return Err(s3_error!(InvalidRequest, "request body is required"));
return Err(S3Error::from(ApiError::invalid_request("request body is required")));
}
serde_json::from_slice(&body).map_err(|err| s3_error!(InvalidRequest, "invalid JSON: {}", err))
serde_json::from_slice(&body).map_err(|err| S3Error::from(ApiError::invalid_request(format!("invalid JSON: {err}"))))
}
async fn read_json_body_or_default<T>(mut input: Body) -> S3Result<T>
+8
View File
@@ -43,6 +43,14 @@ impl ApiError {
}
}
pub fn invalid_request(message: impl std::fmt::Display) -> Self {
ApiError {
code: S3ErrorCode::InvalidRequest,
message: message.to_string(),
source: None,
}
}
pub fn other<E>(error: E) -> Self
where
E: std::fmt::Display + Into<Box<dyn std::error::Error + Send + Sync>>,
+1 -1
View File
@@ -24,7 +24,7 @@ cd "$(dirname "$0")/.."
# Baselines verified on 2026-08-05. Lower-only; see header.
S3S_IMPORT_FILES_BASELINE=236
S3_ERROR_LINES_BASELINE=1687
S3_ERROR_LINES_BASELINE=1686
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT