契约枚举的线上取值只由serde定义
- shared-contracts 新增 wire_str 与 wire_str_lossy:线上取值只从 serde rename / rename_all 取,不再维护第二份字面量 - 删除模型版本、纹理质量、纹理版本、纹理对齐、几何质量、输入方向、导出方向、压缩共 8 个枚举上手工维护的 as_str - platform-tripo 新增 common/wire.rs 负责契约枚举到 provider 请求字符串的转换,三个入口的 to_sdk_params 改走它并把失败归一成 provider 错误 - api-server 的定价文案与 job 模型版本取值改用 serde 取值,job 的 model_version 由 &'static str 改为 String - 取值来源只剩 serde 一处,改 rename 不会再静默分叉
This commit is contained in:
Generated
+1
@@ -4162,6 +4162,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shared-contracts",
|
||||
"tokio",
|
||||
|
||||
@@ -78,11 +78,13 @@ impl Model3dJobRequest {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn model_version(&self) -> &'static str {
|
||||
match self {
|
||||
Self::TextToModel(request) => request.generation.model.as_str(),
|
||||
Self::ImageToModel(request) => request.generation.model.as_str(),
|
||||
}
|
||||
/// 审计与落库用的模型版本线上取值;取值只由契约枚举的 serde rename 定义。
|
||||
pub(crate) fn model_version(&self) -> String {
|
||||
let model = match self {
|
||||
Self::TextToModel(request) => request.generation.model,
|
||||
Self::ImageToModel(request) => request.generation.model,
|
||||
};
|
||||
shared_contracts::model3d::wire_str_lossy(&model)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ use serde::{Deserialize, Serialize};
|
||||
use shared_contracts::model3d::common::{
|
||||
Model3dGeometryQuality, Model3dModelVersion, Model3dTextureQuality,
|
||||
};
|
||||
use shared_contracts::model3d::wire_str_lossy;
|
||||
|
||||
/// 生成端点。批量价键与请求入口一一对应,不做 provider 侧的变形。
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
@@ -158,7 +159,7 @@ impl std::fmt::Display for Model3dPricingError {
|
||||
f,
|
||||
"Tripo 3D 定价缺少底价:endpoint={} model={}",
|
||||
endpoint.as_str(),
|
||||
model_version.as_str()
|
||||
wire_str_lossy(model_version)
|
||||
),
|
||||
Self::MissingAddOnPrice(add_on) => {
|
||||
write!(f, "Tripo 3D 定价缺少 add-on:{add_on:?}")
|
||||
@@ -182,7 +183,7 @@ impl Model3dPricingConfig {
|
||||
return Err(format!(
|
||||
"缺少 endpoint {} 模型 {} 的底价",
|
||||
endpoint.as_str(),
|
||||
model_version.as_str()
|
||||
wire_str_lossy(&model_version)
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -191,7 +192,7 @@ impl Model3dPricingConfig {
|
||||
return Err(format!(
|
||||
"endpoint {} 出现了契约不支持的模型版本 {}",
|
||||
endpoint.as_str(),
|
||||
model_version.as_str()
|
||||
wire_str_lossy(model_version)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
shared-contracts = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
tripo3d-sdk = { workspace = true }
|
||||
bytes = { workspace = true }
|
||||
|
||||
@@ -9,23 +9,23 @@ use std::collections::HashMap;
|
||||
use serde_json::Value;
|
||||
use shared_contracts::model3d::common::Model3dTextureVersion;
|
||||
|
||||
use super::{TripoError, wire};
|
||||
|
||||
const EXTRA_TEXTURE_VERSION: &str = "texture_version";
|
||||
const EXTRA_DELIGHT: &str = "delight";
|
||||
|
||||
/// 按 provider 需要的键名构造 `extra`;未提供的参数不写入,由 provider 取默认值。
|
||||
/// 按 provider 需要的键名构造 `extra`;未提供的参数不写入,由 provider 取默认值;
|
||||
/// `texture_version` 的取值仍由契约枚举的 serde rename 定义。
|
||||
pub(crate) fn extra_fields(
|
||||
texture_version: Option<Model3dTextureVersion>,
|
||||
delight: Option<bool>,
|
||||
) -> HashMap<String, Value> {
|
||||
) -> Result<HashMap<String, Value>, TripoError> {
|
||||
let mut extra = HashMap::new();
|
||||
if let Some(value) = texture_version {
|
||||
extra.insert(
|
||||
EXTRA_TEXTURE_VERSION.into(),
|
||||
Value::String(value.as_str().into()),
|
||||
);
|
||||
extra.insert(EXTRA_TEXTURE_VERSION.into(), Value::String(wire(&value)?));
|
||||
}
|
||||
if let Some(value) = delight {
|
||||
extra.insert(EXTRA_DELIGHT.into(), Value::Bool(value));
|
||||
}
|
||||
extra
|
||||
Ok(extra)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ mod extra;
|
||||
mod mapping;
|
||||
mod types;
|
||||
mod validation;
|
||||
mod wire;
|
||||
|
||||
pub use client::TripoProviderClient;
|
||||
pub use config::TripoSettings;
|
||||
@@ -18,3 +19,4 @@ pub use types::{
|
||||
pub(crate) use validation::{
|
||||
TripoGenerationOptions, validate_generation_options, validate_task_id,
|
||||
};
|
||||
pub(crate) use wire::{wire, wire_option};
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
//! 契约枚举 → provider 请求里要发的线上取值。
|
||||
//!
|
||||
//! 取值只由 `shared-contracts` 里的 serde `rename` / `rename_all` 定义:provider 侧
|
||||
//! 不再维护第二份字面量,两处各写一份、改一处就静默分叉的问题不会再出现。
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
use super::TripoError;
|
||||
|
||||
/// 契约枚举 → provider 线上取值。
|
||||
///
|
||||
/// 契约枚举都是「序列化成字符串」的形态;真拿到非字符串说明契约被改坏,这里归一成
|
||||
/// provider 内部错误,而不是 panic 或静默兜底。
|
||||
pub(crate) fn wire<T: Serialize + ?Sized>(value: &T) -> Result<String, TripoError> {
|
||||
shared_contracts::model3d::wire_str(value).map_err(|error| TripoError::Sdk {
|
||||
message: format!("契约枚举未按字符串序列化:{error}"),
|
||||
})
|
||||
}
|
||||
|
||||
/// [`wire`] 的 `Option` 形态:缺省仍是缺省。
|
||||
pub(crate) fn wire_option<T: Serialize + ?Sized>(
|
||||
value: Option<&T>,
|
||||
) -> Result<Option<String>, TripoError> {
|
||||
value.map(wire).transpose()
|
||||
}
|
||||
@@ -1,13 +1,9 @@
|
||||
use shared_contracts::model3d::common::{
|
||||
Model3dCompression, Model3dExportOrientation, Model3dGeometryQuality, Model3dInputOrientation,
|
||||
Model3dTextureAlignment, Model3dTextureQuality,
|
||||
};
|
||||
use shared_contracts::model3d::image_to_model::Model3dImageToModelParams;
|
||||
use tripo3d_sdk::{models::FileInput, params::ImageToModelParams};
|
||||
|
||||
use crate::common::{
|
||||
TripoError, TripoField, TripoProviderClient, TripoTaskHandle, TripoValidationReason,
|
||||
extra_fields,
|
||||
extra_fields, wire, wire_option,
|
||||
};
|
||||
|
||||
use super::validation::validate_image_to_model_params;
|
||||
@@ -101,53 +97,38 @@ impl TripoProviderClient {
|
||||
|
||||
let task_id = self
|
||||
.client
|
||||
.image_to_model(to_sdk_params(input, params))
|
||||
.image_to_model(to_sdk_params(input, params)?)
|
||||
.await
|
||||
.map_err(TripoError::from)?;
|
||||
Ok(TripoTaskHandle { task_id })
|
||||
}
|
||||
}
|
||||
|
||||
fn to_sdk_params(input: FileInput, params: &Model3dImageToModelParams) -> ImageToModelParams {
|
||||
ImageToModelParams {
|
||||
fn to_sdk_params(
|
||||
input: FileInput,
|
||||
params: &Model3dImageToModelParams,
|
||||
) -> Result<ImageToModelParams, TripoError> {
|
||||
Ok(ImageToModelParams {
|
||||
input,
|
||||
model: Some(params.model.as_str().to_owned()),
|
||||
model: Some(wire(¶ms.model)?),
|
||||
enable_image_autofix: params.enable_image_autofix,
|
||||
model_seed: params.model_seed,
|
||||
texture_seed: params.texture_seed,
|
||||
texture: params.texture,
|
||||
pbr: params.pbr,
|
||||
texture_quality: params
|
||||
.texture_quality
|
||||
.map(Model3dTextureQuality::as_str)
|
||||
.map(str::to_owned),
|
||||
extra: extra_fields(params.texture_version, params.delight),
|
||||
texture_alignment: params
|
||||
.texture_alignment
|
||||
.map(Model3dTextureAlignment::as_str)
|
||||
.map(str::to_owned),
|
||||
geometry_quality: params
|
||||
.geometry_quality
|
||||
.map(Model3dGeometryQuality::as_str)
|
||||
.map(str::to_owned),
|
||||
texture_quality: wire_option(params.texture_quality.as_ref())?,
|
||||
extra: extra_fields(params.texture_version, params.delight)?,
|
||||
texture_alignment: wire_option(params.texture_alignment.as_ref())?,
|
||||
geometry_quality: wire_option(params.geometry_quality.as_ref())?,
|
||||
face_limit: params.face_limit,
|
||||
auto_size: params.auto_size,
|
||||
orientation: params
|
||||
.orientation
|
||||
.map(Model3dInputOrientation::as_str)
|
||||
.map(str::to_owned),
|
||||
orientation: wire_option(params.orientation.as_ref())?,
|
||||
quad: params.quad,
|
||||
smart_low_poly: params.smart_low_poly,
|
||||
generate_parts: params.generate_parts,
|
||||
compress: params
|
||||
.compress
|
||||
.map(Model3dCompression::as_str)
|
||||
.map(str::to_owned),
|
||||
compress: wire_option(params.compress.as_ref())?,
|
||||
export_uv: params.export_uv,
|
||||
export_orientation: params
|
||||
.export_orientation
|
||||
.map(Model3dExportOrientation::as_str)
|
||||
.map(str::to_owned),
|
||||
export_orientation: wire_option(params.export_orientation.as_ref())?,
|
||||
style: None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
use shared_contracts::model3d::common::{
|
||||
Model3dCompression, Model3dExportOrientation, Model3dGeometryQuality, Model3dInputOrientation,
|
||||
Model3dTextureAlignment, Model3dTextureQuality,
|
||||
};
|
||||
use shared_contracts::model3d::multiview_to_model::{
|
||||
Model3dMultiviewInputs, Model3dMultiviewToModelRequest,
|
||||
};
|
||||
@@ -9,7 +5,8 @@ use tripo3d_sdk::{models::FileInput, params::MultiviewToModelParams};
|
||||
|
||||
use crate::common::{
|
||||
TripoError, TripoField, TripoGenerationOptions, TripoProviderClient, TripoTaskHandle,
|
||||
TripoValidationReason, extra_fields, validate_generation_options, validate_task_id,
|
||||
TripoValidationReason, extra_fields, validate_generation_options, validate_task_id, wire,
|
||||
wire_option,
|
||||
};
|
||||
|
||||
impl TripoProviderClient {
|
||||
@@ -66,7 +63,7 @@ impl TripoProviderClient {
|
||||
})?;
|
||||
let task_id = self
|
||||
.client
|
||||
.multiview_to_model(to_sdk_params(request))
|
||||
.multiview_to_model(to_sdk_params(request)?)
|
||||
.await
|
||||
.map_err(TripoError::from)?;
|
||||
Ok(TripoTaskHandle { task_id })
|
||||
@@ -80,7 +77,9 @@ fn optional_view(value: &Option<String>) -> Option<FileInput> {
|
||||
.map(|value| FileInput::from(value.trim()))
|
||||
}
|
||||
|
||||
fn to_sdk_params(request: &Model3dMultiviewToModelRequest) -> MultiviewToModelParams {
|
||||
fn to_sdk_params(
|
||||
request: &Model3dMultiviewToModelRequest,
|
||||
) -> Result<MultiviewToModelParams, TripoError> {
|
||||
let mut params = match &request.inputs {
|
||||
Model3dMultiviewInputs::Views {
|
||||
front,
|
||||
@@ -97,41 +96,23 @@ fn to_sdk_params(request: &Model3dMultiviewToModelRequest) -> MultiviewToModelPa
|
||||
MultiviewToModelParams::from_task_id(task_id.trim().to_owned())
|
||||
}
|
||||
};
|
||||
params.model = Some(request.model.as_str().to_owned());
|
||||
params.model = Some(wire(&request.model)?);
|
||||
params.model_seed = request.model_seed;
|
||||
params.texture_seed = request.texture_seed;
|
||||
params.texture = request.texture;
|
||||
params.pbr = request.pbr;
|
||||
params.texture_quality = request
|
||||
.texture_quality
|
||||
.map(Model3dTextureQuality::as_str)
|
||||
.map(str::to_owned);
|
||||
params.extra = extra_fields(request.texture_version, request.delight);
|
||||
params.geometry_quality = request
|
||||
.geometry_quality
|
||||
.map(Model3dGeometryQuality::as_str)
|
||||
.map(str::to_owned);
|
||||
params.texture_alignment = request
|
||||
.texture_alignment
|
||||
.map(Model3dTextureAlignment::as_str)
|
||||
.map(str::to_owned);
|
||||
params.texture_quality = wire_option(request.texture_quality.as_ref())?;
|
||||
params.extra = extra_fields(request.texture_version, request.delight)?;
|
||||
params.geometry_quality = wire_option(request.geometry_quality.as_ref())?;
|
||||
params.texture_alignment = wire_option(request.texture_alignment.as_ref())?;
|
||||
params.face_limit = request.face_limit;
|
||||
params.auto_size = request.auto_size;
|
||||
params.orientation = request
|
||||
.orientation
|
||||
.map(Model3dInputOrientation::as_str)
|
||||
.map(str::to_owned);
|
||||
params.orientation = wire_option(request.orientation.as_ref())?;
|
||||
params.quad = request.quad;
|
||||
params.smart_low_poly = request.smart_low_poly;
|
||||
params.generate_parts = request.generate_parts;
|
||||
params.compress = request
|
||||
.compress
|
||||
.map(Model3dCompression::as_str)
|
||||
.map(str::to_owned);
|
||||
params.compress = wire_option(request.compress.as_ref())?;
|
||||
params.export_uv = request.export_uv;
|
||||
params.export_orientation = request
|
||||
.export_orientation
|
||||
.map(Model3dExportOrientation::as_str)
|
||||
.map(str::to_owned);
|
||||
params
|
||||
params.export_orientation = wire_option(request.export_orientation.as_ref())?;
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::common::{TripoError, TripoProviderClient, TripoTaskHandle, extra_fields};
|
||||
use shared_contracts::model3d::common::{
|
||||
Model3dCompression, Model3dExportOrientation, Model3dGeometryQuality, Model3dTextureQuality,
|
||||
use crate::common::{
|
||||
TripoError, TripoProviderClient, TripoTaskHandle, extra_fields, wire, wire_option,
|
||||
};
|
||||
use shared_contracts::model3d::text_to_model::Model3dTextToModelParams;
|
||||
|
||||
@@ -14,46 +13,36 @@ impl TripoProviderClient {
|
||||
validate_text_to_model_params(params)?;
|
||||
let task_id = self
|
||||
.client
|
||||
.text_to_model(to_sdk_params(params))
|
||||
.text_to_model(to_sdk_params(params)?)
|
||||
.await
|
||||
.map_err(TripoError::from)?;
|
||||
Ok(TripoTaskHandle { task_id })
|
||||
}
|
||||
}
|
||||
|
||||
fn to_sdk_params(params: &Model3dTextToModelParams) -> tripo3d_sdk::params::TextToModelParams {
|
||||
tripo3d_sdk::params::TextToModelParams {
|
||||
fn to_sdk_params(
|
||||
params: &Model3dTextToModelParams,
|
||||
) -> Result<tripo3d_sdk::params::TextToModelParams, TripoError> {
|
||||
Ok(tripo3d_sdk::params::TextToModelParams {
|
||||
prompt: params.prompt.clone(),
|
||||
model: Some(params.model.as_str().to_owned()),
|
||||
model: Some(wire(¶ms.model)?),
|
||||
negative_prompt: params.negative_prompt.clone(),
|
||||
image_seed: params.image_seed,
|
||||
model_seed: params.model_seed,
|
||||
texture_seed: params.texture_seed,
|
||||
texture: params.texture,
|
||||
pbr: params.pbr,
|
||||
texture_quality: params
|
||||
.texture_quality
|
||||
.map(Model3dTextureQuality::as_str)
|
||||
.map(str::to_owned),
|
||||
extra: extra_fields(params.texture_version, params.delight),
|
||||
geometry_quality: params
|
||||
.geometry_quality
|
||||
.map(Model3dGeometryQuality::as_str)
|
||||
.map(str::to_owned),
|
||||
texture_quality: wire_option(params.texture_quality.as_ref())?,
|
||||
extra: extra_fields(params.texture_version, params.delight)?,
|
||||
geometry_quality: wire_option(params.geometry_quality.as_ref())?,
|
||||
face_limit: params.face_limit,
|
||||
auto_size: params.auto_size,
|
||||
quad: params.quad,
|
||||
smart_low_poly: params.smart_low_poly,
|
||||
generate_parts: params.generate_parts,
|
||||
compress: params
|
||||
.compress
|
||||
.map(Model3dCompression::as_str)
|
||||
.map(str::to_owned),
|
||||
compress: wire_option(params.compress.as_ref())?,
|
||||
export_uv: params.export_uv,
|
||||
export_orientation: params
|
||||
.export_orientation
|
||||
.map(Model3dExportOrientation::as_str)
|
||||
.map(str::to_owned),
|
||||
export_orientation: wire_option(params.export_orientation.as_ref())?,
|
||||
style: None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,11 +8,3 @@ pub enum Model3dCompression {
|
||||
#[serde(rename = "geometry")]
|
||||
Geometry,
|
||||
}
|
||||
|
||||
impl Model3dCompression {
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Geometry => "geometry",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,14 +14,3 @@ pub enum Model3dExportOrientation {
|
||||
#[serde(rename = "-y")]
|
||||
MinusY,
|
||||
}
|
||||
|
||||
impl Model3dExportOrientation {
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::PlusX => "+x",
|
||||
Self::MinusX => "-x",
|
||||
Self::PlusY => "+y",
|
||||
Self::MinusY => "-y",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,3 @@ pub enum Model3dGeometryQuality {
|
||||
Standard,
|
||||
Detailed,
|
||||
}
|
||||
|
||||
impl Model3dGeometryQuality {
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Standard => "standard",
|
||||
Self::Detailed => "detailed",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,3 @@ pub enum Model3dInputOrientation {
|
||||
Default,
|
||||
AlignImage,
|
||||
}
|
||||
|
||||
impl Model3dInputOrientation {
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Default => "default",
|
||||
Self::AlignImage => "align_image",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,14 +20,4 @@ pub enum Model3dModelVersion {
|
||||
impl Model3dModelVersion {
|
||||
/// 契约支持的全部模型版本;定价配置必须为每个版本给出两个端点的底价。
|
||||
pub const ALL: [Self; 5] = [Self::H31, Self::H30, Self::H25, Self::P1, Self::P2];
|
||||
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::H31 => "v3.1-20260211",
|
||||
Self::H30 => "v3.0-20250812",
|
||||
Self::H25 => "v2.5-20250123",
|
||||
Self::P1 => "P1-20260311",
|
||||
Self::P2 => "P2-20260801",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,3 @@ pub enum Model3dTextureAlignment {
|
||||
OriginalImage,
|
||||
Geometry,
|
||||
}
|
||||
|
||||
impl Model3dTextureAlignment {
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::OriginalImage => "original_image",
|
||||
Self::Geometry => "geometry",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,14 +12,3 @@ pub enum Model3dTextureQuality {
|
||||
Detailed,
|
||||
Extreme,
|
||||
}
|
||||
|
||||
impl Model3dTextureQuality {
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Fast => "fast",
|
||||
Self::Standard => "standard",
|
||||
Self::Detailed => "detailed",
|
||||
Self::Extreme => "extreme",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,13 +12,3 @@ pub enum Model3dTextureVersion {
|
||||
#[serde(rename = "v2.5-20250123")]
|
||||
V25,
|
||||
}
|
||||
|
||||
impl Model3dTextureVersion {
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::V35 => "v3.5-20260815",
|
||||
Self::V30 => "v3.0-20250812",
|
||||
Self::V25 => "v2.5-20250123",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,3 +24,32 @@ pub mod text_to_model;
|
||||
pub use generation_result::Model3dGenerationResult;
|
||||
|
||||
pub const MODEL3D_CONTRACT_VERSION: &str = "model3d.v1";
|
||||
|
||||
/// 契约枚举的线上取值。
|
||||
///
|
||||
/// 取值只由枚举上的 serde `rename` / `rename_all` 定义:调用方需要字符串时走这里,
|
||||
/// 不要在枚举旁边再手写一份 `as_str` —— 同一份取值出现两处,改一处就会静默分叉。
|
||||
/// 契约里的枚举都是「序列化成字符串」的形态;序列化失败或形态不符说明契约被改坏,
|
||||
/// 因此按错误返回,而不是 panic 或静默兜底。
|
||||
pub fn wire_str<T>(value: &T) -> Result<String, serde_json::Error>
|
||||
where
|
||||
T: serde::Serialize + ?Sized,
|
||||
{
|
||||
match serde_json::to_value(value)? {
|
||||
serde_json::Value::String(text) => Ok(text),
|
||||
other => Err(serde::ser::Error::custom(format!(
|
||||
"model3d 契约枚举应序列化成字符串,实际为 {other}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
/// 与 [`wire_str`] 相同,但失败时退回 `Debug` 名称。
|
||||
///
|
||||
/// 只用于日志与审计文案:这些位置必须给出字符串且不能失败,退回值明显不是线上取值,
|
||||
/// 不会被误当成正常结果。
|
||||
pub fn wire_str_lossy<T>(value: &T) -> String
|
||||
where
|
||||
T: serde::Serialize + std::fmt::Debug + ?Sized,
|
||||
{
|
||||
wire_str(value).unwrap_or_else(|_| format!("{value:?}"))
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ fn multiview_inputs_reject_unknown_fields_inside_variants() {
|
||||
fn multiview_request_rejects_unknown_top_level_fields() {
|
||||
let error = serde_json::from_value::<Model3dMultiviewToModelRequest>(json!({
|
||||
"inputs": { "kind": "taskId", "taskId": "tripo-task-1" },
|
||||
"model": Model3dModelVersion::H31.as_str(),
|
||||
"model": "v3.1-20260211",
|
||||
"unexpected": true
|
||||
}))
|
||||
.expect_err("请求体顶层出现未知字段应被拒绝");
|
||||
|
||||
Reference in New Issue
Block a user