发行包上传抽 server_error_detail,统一 message/code/兜底顺序

- game_package_upload.rs 新增 pub(crate) server_error_detail,统一先取非空 message 再退非空 code 的顺序
- runtime.rs 五处 HTTP 错误分支改用该 helper,删除各自复制的过滤逻辑
- game_distribution_publish.rs 的 response_data 与 map_http_error 共用同一 helper
- FORBIDDEN 分支改用 helper,补上此前缺失的空 message trim 过滤
This commit is contained in:
2026-10-03 10:51:29 +08:00
parent c0ad48798f
commit b3ed1c0f80
3 changed files with 26 additions and 31 deletions
@@ -5,8 +5,9 @@
//! 其它素材上传链路。
use crate::game_package_upload::{
game_package_upload_staging_dir, stage_game_package_bytes, upload_staged_game_package,
GamePackageUploadOutcome, GamePackageUploadRequest, StagedGamePackage,
game_package_upload_staging_dir, server_error_detail, stage_game_package_bytes,
upload_staged_game_package, GamePackageUploadOutcome, GamePackageUploadRequest,
StagedGamePackage,
};
use crate::http_client::agc_main_site_client_builder;
use crate::platform_session::{
@@ -194,10 +195,9 @@ fn response_data(body: &str) -> Result<Value, String> {
serde_json::from_str(body).map_err(|error| format!("服务端响应不是合法 JSON:{error}"))?;
if value.get("ok").and_then(Value::as_bool) == Some(false) {
let (code, message) = parse_error_payload(body);
return Err(message
.filter(|detail| !detail.trim().is_empty())
.or_else(|| code.filter(|detail| !detail.trim().is_empty()))
.unwrap_or_else(|| "服务器未返回错误信息".to_string()));
return Err(server_error_detail(code, message, || {
"服务器未返回错误信息".to_string()
}));
}
Ok(value.get("data").cloned().unwrap_or(value))
}
@@ -211,13 +211,10 @@ fn map_http_error(status: StatusCode, body: &str, fallback: &str) -> String {
if status == StatusCode::FORBIDDEN {
return format!(
"permission-denied: {}",
message.unwrap_or_else(|| "当前账号无权执行此操作".to_string())
server_error_detail(code, message, || "当前账号无权执行此操作".to_string())
);
}
let detail = message
.filter(|value| !value.trim().is_empty())
.or_else(|| code.filter(|value| !value.trim().is_empty()))
.unwrap_or_else(|| format!("HTTP {}", status.as_u16()));
let detail = server_error_detail(code, message, || format!("HTTP {}", status.as_u16()));
format!("{fallback}:{detail}")
}
@@ -134,6 +134,19 @@ fn parse_server_error(status: u16, body: &str) -> (Option<String>, Option<String
(code, message)
}
/// 服务端错误信封里「给用户看的那句」的统一取法:优先非空 `message`,退非空 `code`,
/// 都没有才用调用点的 `fallback`。所有 HTTP 错误分支共用这一处顺序,避免各自漂移。
pub(crate) fn server_error_detail(
code: Option<String>,
message: Option<String>,
fallback: impl FnOnce() -> String,
) -> String {
message
.filter(|detail| !detail.trim().is_empty())
.or_else(|| code.filter(|detail| !detail.trim().is_empty()))
.unwrap_or_else(fallback)
}
/// 从 409 响应里取权威已收字节;取不到就返回 `None`,由调用方按失败处理。
fn parse_received_bytes(body: &str) -> Option<u64> {
let parsed = serde_json::from_str::<Value>(body).ok()?;
@@ -155,10 +155,7 @@ async fn read_upload_state(
})?;
if status >= 400 {
let (code, message) = parse_server_error(status, &body);
let detail = message
.filter(|detail| !detail.trim().is_empty())
.or_else(|| code.filter(|detail| !detail.trim().is_empty()))
.unwrap_or_else(|| format!("HTTP {status}"));
let detail = server_error_detail(code, message, || format!("HTTP {status}"));
return Err(GamePackageUploadError::read_upload_state(detail));
}
let parsed: Value = serde_json::from_str(&body).map_err(|error| {
@@ -213,30 +210,21 @@ async fn upload_chunk(
})?;
return Ok(received);
}
let detail = message
.filter(|detail| !detail.trim().is_empty())
.or_else(|| code.filter(|detail| !detail.trim().is_empty()))
.unwrap_or_else(|| format!("HTTP {status}"));
let detail = server_error_detail(code, message, || format!("HTTP {status}"));
return Err(ChunkUploadError::Fatal(
GamePackageUploadError::upload_chunk(detail),
));
}
if status >= 500 || status == 408 || status == 429 {
let (code, message) = parse_server_error(status, &text);
let detail = message
.filter(|detail| !detail.trim().is_empty())
.or_else(|| code.filter(|detail| !detail.trim().is_empty()))
.unwrap_or_else(|| format!("HTTP {status}"));
let detail = server_error_detail(code, message, || format!("HTTP {status}"));
return Err(ChunkUploadError::Retryable(
GamePackageUploadError::upload_chunk(detail),
));
}
if status >= 400 {
let (code, message) = parse_server_error(status, &text);
let detail = message
.filter(|detail| !detail.trim().is_empty())
.or_else(|| code.filter(|detail| !detail.trim().is_empty()))
.unwrap_or_else(|| format!("HTTP {status}"));
let detail = server_error_detail(code, message, || format!("HTTP {status}"));
return Err(ChunkUploadError::Fatal(
GamePackageUploadError::upload_chunk(detail),
));
@@ -285,10 +273,7 @@ async fn complete_upload(
})?;
if status >= 400 {
let (code, message) = parse_server_error(status, &text);
let detail = message
.filter(|detail| !detail.trim().is_empty())
.or_else(|| code.filter(|detail| !detail.trim().is_empty()))
.unwrap_or_else(|| format!("HTTP {status}"));
let detail = server_error_detail(code, message, || format!("HTTP {status}"));
return Err(GamePackageUploadError::complete_upload(detail));
}
let parsed: Value = serde_json::from_str(&text).map_err(|error| {