5xx 错误响应不再回带 provider task id

- TaskFailure / OutputSchema / 上游兜底分支只回固定文案,完整错误写入服务端日志
- 补一条断言 502 details 不含 task id 与 provider 原文的用例
This commit is contained in:
2026-09-22 15:17:50 +08:00
parent cda7724cae
commit 8473877bc3
@@ -84,21 +84,32 @@ pub(crate) fn map_provider_error(error: TripoError) -> AppError {
"tripo-api-rejected",
error.to_string(),
),
TripoError::TaskFailure { .. } => (
StatusCode::BAD_GATEWAY,
"tripo-task-failed",
error.to_string(),
),
TripoError::OutputSchema { .. } => (
StatusCode::BAD_GATEWAY,
"tripo-output-schema",
error.to_string(),
),
TripoError::Request { .. } | TripoError::Sdk { .. } | TripoError::Api { .. } => (
StatusCode::BAD_GATEWAY,
"tripo-upstream-error",
error.to_string(),
),
// 以下都是 5xx:完整错误只进日志。`TripoError` 的 Display 会带上 provider task id
// 直接回给客户端会破坏本文件「不暴露 task id」的口径,因此对外只给固定文案。
TripoError::TaskFailure { .. } => {
tracing::error!(error = %error, "tripo task failed");
(
StatusCode::BAD_GATEWAY,
"tripo-task-failed",
"3D 生成任务在 provider 侧执行失败。".to_string(),
)
}
TripoError::OutputSchema { .. } => {
tracing::error!(error = %error, "tripo task output schema error");
(
StatusCode::BAD_GATEWAY,
"tripo-output-schema",
"3D 生成任务的结果格式不符合预期,未落库。".to_string(),
)
}
TripoError::Request { .. } | TripoError::Sdk { .. } | TripoError::Api { .. } => {
tracing::error!(error = %error, "tripo upstream error");
(
StatusCode::BAD_GATEWAY,
"tripo-upstream-error",
"3D 生成服务暂时不可用,请稍后重试。".to_string(),
)
}
};
AppError::from_status(status).with_details(json!({
"provider": TRIPO_PROVIDER,
@@ -166,4 +177,29 @@ mod tests {
});
assert_eq!(error.status_code(), StatusCode::BAD_REQUEST);
}
/// 本模块头部承诺「任何分支都不把 provider task id 带进响应」,502 分支必须守住。
#[test]
fn provider_5xx_does_not_leak_task_id() {
let cases = [
TripoError::TaskFailure {
task_id: "task-secret-123".to_string(),
status: "failed".to_string(),
message: Some("provider exploded".to_string()),
},
TripoError::OutputSchema {
task_id: "task-secret-456".to_string(),
message: "output schema error".to_string(),
},
];
for case in cases {
let error = map_provider_error(case);
assert_eq!(error.status_code(), StatusCode::BAD_GATEWAY);
let serialized = serde_json::to_string(error.details().expect("details 必须存在"))
.expect("details 必须可序列化");
for leaked in ["task-secret", "provider exploded", "output schema error"] {
assert!(!serialized.contains(leaked), "{serialized}");
}
}
}
}