This commit is contained in:
2026-05-10 22:20:54 +08:00
parent d6219f1a0c
commit 192accd796
92 changed files with 7045 additions and 1559 deletions

View File

@@ -136,7 +136,7 @@ async fn submit_text_to_model(
)?)
}
async fn submit_image_to_model(
pub(crate) async fn submit_image_to_model(
state: &AppState,
payload: contract::Hyper3dImageToModelRequest,
) -> Result<contract::Hyper3dTaskSubmitResponse, AppError> {
@@ -212,14 +212,15 @@ async fn submit_image_to_model(
)?)
}
async fn query_task_status(
pub(crate) async fn query_task_status(
state: &AppState,
payload: contract::Hyper3dTaskStatusRequest,
) -> Result<contract::Hyper3dTaskStatusResponse, AppError> {
let settings = require_hyper3d_settings(state)?;
let http_client = build_hyper3d_http_client(&settings)?;
// 中文注释Hyper3D 返回的 subscriptionKey 是上游 opaque token只做非空校验不做人为 256 字符截断。
let subscription_key =
normalize_required_text(&payload.subscription_key, "subscriptionKey", 256)?;
normalize_required_opaque_text(&payload.subscription_key, "subscriptionKey")?;
let response = post_hyper3d_json(
&http_client,
&settings,
@@ -246,7 +247,7 @@ async fn query_task_status(
})
}
async fn query_downloads(
pub(crate) async fn query_downloads(
state: &AppState,
payload: contract::Hyper3dDownloadRequest,
) -> Result<contract::Hyper3dDownloadResponse, AppError> {
@@ -380,6 +381,7 @@ fn require_hyper3d_settings(state: &AppState) -> Result<Hyper3dSettings, AppErro
AppError::from_status(StatusCode::SERVICE_UNAVAILABLE).with_details(json!({
"provider": HYPER3D_PROVIDER,
"reason": "HYPER3D_BASE_URL 未配置",
"message": "Hyper3D Rodin 服务地址未配置,请设置 HYPER3D_BASE_URL 或 RODIN_BASE_URL 后重启 api-server。",
})),
);
}
@@ -394,6 +396,7 @@ fn require_hyper3d_settings(state: &AppState) -> Result<Hyper3dSettings, AppErro
AppError::from_status(StatusCode::SERVICE_UNAVAILABLE).with_details(json!({
"provider": HYPER3D_PROVIDER,
"reason": "HYPER3D_API_KEY 未配置",
"message": "Hyper3D Rodin API Key 未配置,请在本地私密环境设置 HYPER3D_API_KEY 或 RODIN_API_KEY 后重启 api-server。",
}))
})?;
@@ -689,6 +692,21 @@ fn normalize_required_text(
Ok(normalized)
}
fn normalize_required_opaque_text(value: &str, field: &'static str) -> Result<String, AppError> {
let normalized = value.trim().to_string();
if normalized.is_empty() {
return Err(
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
"provider": HYPER3D_PROVIDER,
"field": field,
"message": format!("{field} 不能为空"),
})),
);
}
Ok(normalized)
}
fn normalize_optional_limited_text(
value: Option<&str>,
max_chars: usize,
@@ -997,6 +1015,16 @@ mod tests {
assert_eq!(error.status_code(), StatusCode::BAD_REQUEST);
}
#[test]
fn accepts_opaque_subscription_key_without_length_cap() {
let long_key = "a".repeat(300);
let normalized =
normalize_required_opaque_text(&format!(" {long_key} "), "subscriptionKey")
.expect("subscription key should be accepted");
assert_eq!(normalized, long_key);
}
#[test]
fn decodes_png_data_url() {
let data_url = format!(