入参给了非字符串值时按字段报类型错,不再混进「不能为空」

- ToolTextField 增加 not_text(got),optional_text 在 as_str() 失败时返回它;原来数字/布尔/数组/对象会和空串、null 一起落到 field.empty(),{"prompt":5} 报成「prompt 不能为空」
- 9 个工具 enum 按字段各加一个 NotText 变体(KindNotText、PromptNotText、AspectRatioNotText…),文案形如「生成图片失败:aspectRatio 必须是字符串,当前是「5」。」,仍是一 case 一变体、文案写在变体上
- GenerateImageTextField 改为实现 ToolTextField,删掉 generate_image_optional_text / generate_image_required_text 这两份与共享实现重复的拷贝,generate_image 改走 optional_text / required_text
- 空串 / null / 缺省仍按原来的空值语义处理,只有「有值但不是字符串」换成类型错
- 新增 non_text_field_values_report_a_type_error_instead_of_an_empty_value 覆盖 create_or_derive、prepare_game_art、generate_image 三条路径

验证:cargo test --bin genarrative-ai-game-creator-shell -- agent::direct_tool_bridge:: 41 passed;全量 agent:: 952 passed(唯一失败 concurrent_distinct_frozen_root_steers_create_only_one_replacement 是既有 flaky,单独重跑通过)
This commit is contained in:
2026-10-01 15:01:20 +08:00
parent 4e5adb2a30
commit 66e4ece692
11 changed files with 242 additions and 42 deletions
@@ -928,6 +928,18 @@ impl ToolTextField<CreateOrDeriveResourceError> for ResourceGenerationTextField
Self::AssetName => CreateOrDeriveResourceError::AssetNameHasControlCharacters,
}
}
fn not_text(self, got: String) -> CreateOrDeriveResourceError {
match self {
Self::Kind => CreateOrDeriveResourceError::KindNotText { got },
Self::Mode => CreateOrDeriveResourceError::ModeNotText { got },
Self::SourceLocalAssetId => {
CreateOrDeriveResourceError::SourceLocalAssetIdNotText { got }
}
Self::Prompt => CreateOrDeriveResourceError::PromptNotText { got },
Self::AssetName => CreateOrDeriveResourceError::AssetNameNotText { got },
}
}
}
/// `agc_create_or_derive_resource` 的入参规则;工具桥与 MCP 预检共用这一份实现。
@@ -1148,6 +1160,13 @@ impl ToolTextField<ListRegisteredAssetsError> for RegisteredAssetTextField {
Self::AssetId => ListRegisteredAssetsError::AssetIdHasControlCharacters,
}
}
fn not_text(self, got: String) -> ListRegisteredAssetsError {
match self {
Self::Kind => ListRegisteredAssetsError::KindNotText { got },
Self::AssetId => ListRegisteredAssetsError::AssetIdNotText { got },
}
}
}
/// `agc_list_registered_assets` 的入参解析结果。
@@ -1493,6 +1512,14 @@ impl ToolTextField<ListProjectFilesError> for ListProjectFilesTextField {
Self::Kind => ListProjectFilesError::KindHasControlCharacters,
}
}
fn not_text(self, got: String) -> ListProjectFilesError {
match self {
Self::Path => ListProjectFilesError::PathNotText { got },
Self::Query => ListProjectFilesError::QueryNotText { got },
Self::Kind => ListProjectFilesError::KindNotText { got },
}
}
}
/// `agc_list_project_files` 的入参解析结果。
@@ -1934,6 +1961,13 @@ impl ToolTextField<ListAccountAssetsError> for AccountAssetTextField {
Self::Query => ListAccountAssetsError::QueryHasControlCharacters,
}
}
fn not_text(self, got: String) -> ListAccountAssetsError {
match self {
Self::FolderId => ListAccountAssetsError::FolderIdNotText { got },
Self::Query => ListAccountAssetsError::QueryNotText { got },
}
}
}
/// `agc_list_account_assets` 的入参解析结果。
@@ -2312,6 +2346,13 @@ impl ToolTextField<RemoveBackgroundError> for RemoveBackgroundTextField {
Self::AssetName => RemoveBackgroundError::AssetNameHasControlCharacters,
}
}
fn not_text(self, got: String) -> RemoveBackgroundError {
match self {
Self::SourceLocalAssetId => RemoveBackgroundError::SourceLocalAssetIdNotText { got },
Self::AssetName => RemoveBackgroundError::AssetNameNotText { got },
}
}
}
pub(in crate::agent) struct RemoveBackgroundInput {
@@ -2646,6 +2687,12 @@ impl ToolTextField<PrepareGameArtError> for PrepareGameArtTextField {
Self::Brief => PrepareGameArtError::BriefHasControlCharacters,
}
}
fn not_text(self, got: String) -> PrepareGameArtError {
match self {
Self::Brief => PrepareGameArtError::BriefNotText { got },
}
}
}
/// `brief` 必填;`mode` 缺省为 `reuse-or-create`。
@@ -2739,7 +2786,7 @@ const GENERATE_IMAGE_ARGUMENTS: &[&str] = &[
"screenColor",
];
/// 会做文本边界校验的字段。三者(空值/超长/控制字符)各自对应工具 enum 里自己的变体。
/// 会做文本边界校验的字段。四者(空值/超长/控制字符/不是字符串)各自对应工具 enum 里自己的变体。
#[derive(Clone, Copy)]
enum GenerateImageTextField {
Prompt,
@@ -2750,7 +2797,7 @@ enum GenerateImageTextField {
SliceMode,
}
impl GenerateImageTextField {
impl ToolTextField<GenerateImageError> for GenerateImageTextField {
fn key(self) -> &'static str {
match self {
Self::Prompt => "prompt",
@@ -2823,35 +2870,17 @@ impl GenerateImageTextField {
Self::SliceMode => GenerateImageError::SliceModeHasControlCharacters,
}
}
}
/// 可选文本字段:缺省或 null 视为未提供;给了就必须在字段自己的边界内。
fn generate_image_optional_text(
arguments: &Value,
field: GenerateImageTextField,
) -> Result<Option<String>, GenerateImageError> {
let Some(value) = arguments.get(field.key()).filter(|value| !value.is_null()) else {
return Ok(None);
};
let value = value
.as_str()
.map(str::trim)
.filter(|value| !value.is_empty())
.ok_or_else(|| field.empty())?;
if value.chars().count() > field.max_chars() {
return Err(field.too_long(value.chars().count(), field.max_chars()));
fn not_text(self, got: String) -> GenerateImageError {
match self {
Self::Prompt => GenerateImageError::PromptNotText { got },
Self::AspectRatio => GenerateImageError::AspectRatioNotText { got },
Self::ImageSize => GenerateImageError::ImageSizeNotText { got },
Self::AssetName => GenerateImageError::AssetNameNotText { got },
Self::OutputPath => GenerateImageError::OutputPathNotText { got },
Self::SliceMode => GenerateImageError::SliceModeNotText { got },
}
}
if value.chars().any(char::is_control) {
return Err(field.has_control_characters());
}
Ok(Some(value.to_string()))
}
fn generate_image_required_text(
arguments: &Value,
field: GenerateImageTextField,
) -> Result<String, GenerateImageError> {
generate_image_optional_text(arguments, field)?.ok_or_else(|| field.empty())
}
fn generate_image_grid_axis(
@@ -2970,23 +2999,22 @@ pub(in crate::agent) fn generate_image_input(
arguments: &Value,
) -> Result<GenerateImageInput, GenerateImageError> {
ToolArgumentsRejection::check(arguments, GENERATE_IMAGE_ARGUMENTS)?;
let prompt = generate_image_required_text(arguments, GenerateImageTextField::Prompt)?;
let prompt = required_text(arguments, GenerateImageTextField::Prompt)?;
let kind = bridge_image_generation_kind(arguments)?;
let aspect_ratio =
generate_image_optional_text(arguments, GenerateImageTextField::AspectRatio)?
.unwrap_or_else(|| "1:1".to_string());
let aspect_ratio = optional_text(arguments, GenerateImageTextField::AspectRatio)?
.unwrap_or_else(|| "1:1".to_string());
if !GENERATE_IMAGE_ASPECT_RATIOS.contains(&aspect_ratio.as_str()) {
return Err(GenerateImageError::AspectRatioUnsupported { got: aspect_ratio });
}
let image_size = generate_image_optional_text(arguments, GenerateImageTextField::ImageSize)?
let image_size = optional_text(arguments, GenerateImageTextField::ImageSize)?
.unwrap_or_else(|| "1K".to_string());
if !GENERATE_IMAGE_IMAGE_SIZES.contains(&image_size.as_str()) {
return Err(GenerateImageError::ImageSizeUnsupported { got: image_size });
}
let asset_name = generate_image_optional_text(arguments, GenerateImageTextField::AssetName)?
let asset_name = optional_text(arguments, GenerateImageTextField::AssetName)?
.unwrap_or_else(|| "AI 生成图片".to_string());
let output_path = generate_image_optional_text(arguments, GenerateImageTextField::OutputPath)?;
let slice_mode = generate_image_optional_text(arguments, GenerateImageTextField::SliceMode)?;
let output_path = optional_text(arguments, GenerateImageTextField::OutputPath)?;
let slice_mode = optional_text(arguments, GenerateImageTextField::SliceMode)?;
if slice_mode
.as_deref()
.is_some_and(|mode| !GENERATE_IMAGE_SLICE_MODES.contains(&mode))
@@ -3197,6 +3225,14 @@ impl ToolTextField<EditImageError> for EditImageTextField {
Self::AssetName => EditImageError::AssetNameHasControlCharacters,
}
}
fn not_text(self, got: String) -> EditImageError {
match self {
Self::SourceLocalAssetId => EditImageError::SourceLocalAssetIdNotText { got },
Self::Prompt => EditImageError::PromptNotText { got },
Self::AssetName => EditImageError::AssetNameNotText { got },
}
}
}
/// `agc_edit_image` 只是 `agc_create_or_derive_resource` 的图片派生入口:把三个字段投影成
@@ -3416,6 +3452,12 @@ impl ToolTextField<WebSearchError> for WebSearchTextField {
Self::Query => WebSearchError::QueryHasControlCharacters,
}
}
fn not_text(self, got: String) -> WebSearchError {
match self {
Self::Query => WebSearchError::QueryNotText { got },
}
}
}
/// `agc_web_search` 的入参规则:`query` 的文本边界与 `maxResults` 的取值区间。
@@ -4816,6 +4858,50 @@ mod tests {
.is_ok());
}
#[test]
fn non_text_field_values_report_a_type_error_instead_of_an_empty_value() {
let prompt_not_text = match resource_generation_input(&json!({
"kind": "video",
"mode": "create",
"prompt": 5,
"assetName": "开场"
})) {
Ok(_) => panic!("prompt 不是字符串时必须被拒绝"),
Err(error) => error.to_user_msg(),
};
assert!(
prompt_not_text.contains("prompt 必须是字符串"),
"{prompt_not_text}"
);
assert!(!prompt_not_text.contains("不能为空"), "{prompt_not_text}");
let brief_not_text = required_text(&json!({ "brief": 5 }), PrepareGameArtTextField::Brief)
.expect_err("brief 不是字符串时必须被拒绝")
.to_user_msg();
assert!(
brief_not_text.contains("brief 必须是字符串"),
"{brief_not_text}"
);
assert!(!brief_not_text.contains("不能为空"), "{brief_not_text}");
let empty_brief = required_text(&json!({ "brief": "" }), PrepareGameArtTextField::Brief)
.expect_err("空 brief 仍然按空值处理")
.to_user_msg();
assert!(empty_brief.contains("brief 不能为空"), "{empty_brief}");
let aspect_ratio_not_text = match generate_image_input(&json!({
"prompt": "像素风主角",
"aspectRatio": 5
})) {
Ok(_) => panic!("aspectRatio 不是字符串时必须被拒绝"),
Err(error) => error.to_user_msg(),
};
assert!(
aspect_ratio_not_text.contains("aspectRatio 必须是字符串"),
"{aspect_ratio_not_text}"
);
}
#[test]
fn large_mcp_images_are_reduced_to_bounded_jpeg_previews() {
let image = image::RgbaImage::from_fn(1600, 1200, |x, y| {
@@ -14,6 +14,8 @@ pub(crate) trait ToolTextField<E>: Copy {
fn empty(self) -> E;
fn too_long(self, got_chars: usize, max_chars: usize) -> E;
fn has_control_characters(self) -> E;
/// 字段给了值但不是字符串:必须报类型错,不能混进「不能为空」。
fn not_text(self, got: String) -> E;
}
/// 必填文本字段:缺省、`null`、空串都算空值。
@@ -32,11 +34,13 @@ pub(crate) fn optional_text<F: ToolTextField<E>, E>(
let Some(value) = arguments.get(field.key()).filter(|value| !value.is_null()) else {
return Ok(None);
};
let value = value
.as_str()
.map(str::trim)
.filter(|value| !value.is_empty())
.ok_or_else(|| field.empty())?;
let Some(value) = value.as_str() else {
return Err(field.not_text(value.to_string()));
};
let value = value.trim();
if value.is_empty() {
return Err(field.empty());
}
if value.chars().count() > field.max_chars() {
return Err(field.too_long(value.chars().count(), field.max_chars()));
}
@@ -26,6 +26,9 @@ pub(crate) enum CreateOrDeriveResourceError {
max_chars: usize,
},
KindHasControlCharacters,
KindNotText {
got: String,
},
KindUnsupported {
got: String,
},
@@ -35,6 +38,9 @@ pub(crate) enum CreateOrDeriveResourceError {
max_chars: usize,
},
ModeHasControlCharacters,
ModeNotText {
got: String,
},
ModeUnsupported {
got: String,
},
@@ -44,12 +50,18 @@ pub(crate) enum CreateOrDeriveResourceError {
max_chars: usize,
},
SourceLocalAssetIdHasControlCharacters,
SourceLocalAssetIdNotText {
got: String,
},
PromptEmpty,
PromptTooLong {
got_chars: usize,
max_chars: usize,
},
PromptHasControlCharacters,
PromptNotText {
got: String,
},
PromptTooLongForKind {
kind: DirectResourceGenerationKind,
got_chars: usize,
@@ -61,6 +73,9 @@ pub(crate) enum CreateOrDeriveResourceError {
max_chars: usize,
},
AssetNameHasControlCharacters,
AssetNameNotText {
got: String,
},
ImageCreationRequiresRegisteredSource,
CharacterAnimationRequiresRegisteredSource,
CreateModeWithSource,
@@ -127,6 +142,9 @@ impl ToolFailure for CreateOrDeriveResourceError {
Self::KindHasControlCharacters => {
"生成或派生资源失败:kind 含有控制字符。".to_string()
}
Self::KindNotText { got } => format!(
"生成或派生资源失败:kind 必须是字符串,当前是「{got}」。"
),
Self::KindUnsupported { got } => format!(
"生成或派生资源失败:kind「{got}」不是受支持的媒体资源类型,只允许 image、video、character-animation、sound-effect、background-music。"
),
@@ -138,6 +156,9 @@ impl ToolFailure for CreateOrDeriveResourceError {
Self::ModeHasControlCharacters => {
"生成或派生资源失败:mode 含有控制字符。".to_string()
}
Self::ModeNotText { got } => format!(
"生成或派生资源失败:mode 必须是字符串,当前是「{got}」。"
),
Self::ModeUnsupported { got } => format!(
"生成或派生资源失败:mode「{got}」不是受支持的模式,只能是 create 或 derive。"
),
@@ -153,6 +174,9 @@ impl ToolFailure for CreateOrDeriveResourceError {
Self::SourceLocalAssetIdHasControlCharacters => {
"生成或派生资源失败:sourceLocalAssetId 含有控制字符。".to_string()
}
Self::SourceLocalAssetIdNotText { got } => format!(
"生成或派生资源失败:sourceLocalAssetId 必须是字符串,当前是「{got}」。"
),
Self::PromptEmpty => "生成或派生资源失败:prompt 不能为空。".to_string(),
Self::PromptTooLong {
got_chars,
@@ -163,6 +187,9 @@ impl ToolFailure for CreateOrDeriveResourceError {
Self::PromptHasControlCharacters => {
"生成或派生资源失败:prompt 含有控制字符。".to_string()
}
Self::PromptNotText { got } => format!(
"生成或派生资源失败:prompt 必须是字符串,当前是「{got}」。"
),
Self::PromptTooLongForKind {
kind,
got_chars,
@@ -182,6 +209,9 @@ impl ToolFailure for CreateOrDeriveResourceError {
Self::AssetNameHasControlCharacters => {
"生成或派生资源失败:assetName 含有控制字符。".to_string()
}
Self::AssetNameNotText { got } => format!(
"生成或派生资源失败:assetName 必须是字符串,当前是「{got}」。"
),
Self::ImageCreationRequiresRegisteredSource => {
"生成或派生资源失败:图片编辑必须基于已登记图片资源派生。".to_string()
}
@@ -12,12 +12,15 @@ pub(crate) enum EditImageError {
SourceLocalAssetIdEmpty,
SourceLocalAssetIdTooLong { got_chars: usize, max_chars: usize },
SourceLocalAssetIdHasControlCharacters,
SourceLocalAssetIdNotText { got: String },
PromptEmpty,
PromptTooLong { got_chars: usize, max_chars: usize },
PromptHasControlCharacters,
PromptNotText { got: String },
AssetNameEmpty,
AssetNameTooLong { got_chars: usize, max_chars: usize },
AssetNameHasControlCharacters,
AssetNameNotText { got: String },
ResourceEdit(CreateOrDeriveResourceError),
}
@@ -49,12 +52,18 @@ impl ToolFailure for EditImageError {
Self::SourceLocalAssetIdHasControlCharacters => {
"编辑图片失败:sourceLocalAssetId 含有控制字符。".to_string()
}
Self::SourceLocalAssetIdNotText { got } => {
format!("编辑图片失败:sourceLocalAssetId 必须是字符串,当前是「{got}」。")
}
Self::PromptEmpty => "编辑图片失败:prompt 不能为空。".to_string(),
Self::PromptTooLong {
got_chars,
max_chars,
} => format!("编辑图片失败:prompt 有 {got_chars} 个字符,超过上限 {max_chars}。"),
Self::PromptHasControlCharacters => "编辑图片失败:prompt 含有控制字符。".to_string(),
Self::PromptNotText { got } => {
format!("编辑图片失败:prompt 必须是字符串,当前是「{got}」。")
}
Self::AssetNameEmpty => "编辑图片失败:assetName 不能为空。".to_string(),
Self::AssetNameTooLong {
got_chars,
@@ -63,6 +72,9 @@ impl ToolFailure for EditImageError {
Self::AssetNameHasControlCharacters => {
"编辑图片失败:assetName 含有控制字符。".to_string()
}
Self::AssetNameNotText { got } => {
format!("编辑图片失败:assetName 必须是字符串,当前是「{got}」。")
}
Self::ResourceEdit(rejection) => rejection.to_user_msg(),
}
}
@@ -19,24 +19,30 @@ pub(crate) enum GenerateImageError {
PromptEmpty,
PromptTooLong { got_chars: usize, max_chars: usize },
PromptHasControlCharacters,
PromptNotText { got: String },
KindUnsupported { got: String },
AspectRatioEmpty,
AspectRatioTooLong { got_chars: usize, max_chars: usize },
AspectRatioHasControlCharacters,
AspectRatioNotText { got: String },
AspectRatioUnsupported { got: String },
ImageSizeEmpty,
ImageSizeTooLong { got_chars: usize, max_chars: usize },
ImageSizeHasControlCharacters,
ImageSizeNotText { got: String },
ImageSizeUnsupported { got: String },
AssetNameEmpty,
AssetNameTooLong { got_chars: usize, max_chars: usize },
AssetNameHasControlCharacters,
AssetNameNotText { got: String },
OutputPathEmpty,
OutputPathTooLong { got_chars: usize, max_chars: usize },
OutputPathHasControlCharacters,
OutputPathNotText { got: String },
SliceModeEmpty,
SliceModeTooLong { got_chars: usize, max_chars: usize },
SliceModeHasControlCharacters,
SliceModeNotText { got: String },
SliceModeUnsupported { got: String },
GridAxisNotInteger { axis: &'static str },
GridAxesMissing,
@@ -74,6 +80,9 @@ impl ToolFailure for GenerateImageError {
max_chars,
} => format!("生成图片失败:prompt 有 {got_chars} 个字符,超过上限 {max_chars}。"),
Self::PromptHasControlCharacters => "生成图片失败:prompt 含有控制字符。".to_string(),
Self::PromptNotText { got } => format!(
"生成图片失败:prompt 必须是字符串,当前是「{got}」。"
),
Self::KindUnsupported { got } => format!(
"生成图片失败:kind「{got}」不是受支持的素材类型,只允许 {}。",
platform_art_asset_generation_kind_list()
@@ -88,6 +97,9 @@ impl ToolFailure for GenerateImageError {
Self::AspectRatioHasControlCharacters => {
"生成图片失败:aspectRatio 含有控制字符。".to_string()
}
Self::AspectRatioNotText { got } => format!(
"生成图片失败:aspectRatio 必须是字符串,当前是「{got}」。"
),
Self::AspectRatioUnsupported { got } => format!(
"生成图片失败:aspectRatio「{got}」不是受支持的比例,只能是 {}。",
GENERATE_IMAGE_ASPECT_RATIOS.join("、")
@@ -100,6 +112,9 @@ impl ToolFailure for GenerateImageError {
Self::ImageSizeHasControlCharacters => {
"生成图片失败:imageSize 含有控制字符。".to_string()
}
Self::ImageSizeNotText { got } => format!(
"生成图片失败:imageSize 必须是字符串,当前是「{got}」。"
),
Self::ImageSizeUnsupported { got } => format!(
"生成图片失败:imageSize「{got}」不是受支持的图片尺寸,只能是 {}。",
GENERATE_IMAGE_IMAGE_SIZES.join("、")
@@ -112,6 +127,9 @@ impl ToolFailure for GenerateImageError {
Self::AssetNameHasControlCharacters => {
"生成图片失败:assetName 含有控制字符。".to_string()
}
Self::AssetNameNotText { got } => format!(
"生成图片失败:assetName 必须是字符串,当前是「{got}」。"
),
Self::OutputPathEmpty => "生成图片失败:outputPath 传了空值。".to_string(),
Self::OutputPathTooLong {
got_chars,
@@ -120,6 +138,9 @@ impl ToolFailure for GenerateImageError {
Self::OutputPathHasControlCharacters => {
"生成图片失败:outputPath 含有控制字符。".to_string()
}
Self::OutputPathNotText { got } => format!(
"生成图片失败:outputPath 必须是字符串,当前是「{got}」。"
),
Self::SliceModeEmpty => "生成图片失败:sliceMode 传了空值。".to_string(),
Self::SliceModeTooLong {
got_chars,
@@ -128,6 +149,9 @@ impl ToolFailure for GenerateImageError {
Self::SliceModeHasControlCharacters => {
"生成图片失败:sliceMode 含有控制字符。".to_string()
}
Self::SliceModeNotText { got } => format!(
"生成图片失败:sliceMode 必须是字符串,当前是「{got}」。"
),
Self::SliceModeUnsupported { got } => format!(
"生成图片失败:sliceMode「{got}」不受支持,只允许 {}。",
GENERATE_IMAGE_SLICE_MODES.join("、")
@@ -16,9 +16,11 @@ pub(crate) enum ListAccountAssetsError {
FolderIdEmpty,
FolderIdTooLong { got_chars: usize, max_chars: usize },
FolderIdHasControlCharacters,
FolderIdNotText { got: String },
QueryEmpty,
QueryTooLong { got_chars: usize, max_chars: usize },
QueryHasControlCharacters,
QueryNotText { got: String },
AccountAssetsUnreadable { cause: String },
}
@@ -56,12 +58,18 @@ impl ToolFailure for ListAccountAssetsError {
Self::FolderIdHasControlCharacters => {
"列出账号素材失败:folderId 含有控制字符。".to_string()
}
Self::FolderIdNotText { got } => {
format!("列出账号素材失败:folderId 必须是字符串,当前是「{got}」。")
}
Self::QueryEmpty => "列出账号素材失败:query 传了空值。".to_string(),
Self::QueryTooLong {
got_chars,
max_chars,
} => format!("列出账号素材失败:query 有 {got_chars} 个字符,超过上限 {max_chars}。"),
Self::QueryHasControlCharacters => "列出账号素材失败:query 含有控制字符。".to_string(),
Self::QueryNotText { got } => {
format!("列出账号素材失败:query 必须是字符串,当前是「{got}」。")
}
Self::AccountAssetsUnreadable { cause } => {
format!("列出账号素材失败:读取账号素材列表失败:{cause}")
}
@@ -22,15 +22,18 @@ pub(crate) enum ListProjectFilesError {
PathEmpty,
PathTooLong { got_chars: usize, max_chars: usize },
PathHasControlCharacters,
PathNotText { got: String },
PathNotReadable { cause: String },
PathProtectedProjectControl,
PathProtectedExternal,
QueryEmpty,
QueryTooLong { got_chars: usize, max_chars: usize },
QueryHasControlCharacters,
QueryNotText { got: String },
KindEmpty,
KindTooLong { got_chars: usize, max_chars: usize },
KindHasControlCharacters,
KindNotText { got: String },
KindUnsupported { got: String },
ProjectListingUnreadable { cause: String },
ExternalListingUnreadable { cause: String },
@@ -73,6 +76,9 @@ impl ToolFailure for ListProjectFilesError {
max_chars,
} => format!("列出项目文件失败:path 有 {got_chars} 个字符,超过上限 {max_chars}。"),
Self::PathHasControlCharacters => "列出项目文件失败:path 含有控制字符。".to_string(),
Self::PathNotText { got } => {
format!("列出项目文件失败:path 必须是字符串,当前是「{got}」。")
}
Self::PathNotReadable { cause } => {
format!("列出项目文件失败:path 读不出来:{cause}")
}
@@ -88,12 +94,18 @@ impl ToolFailure for ListProjectFilesError {
max_chars,
} => format!("列出项目文件失败:query 有 {got_chars} 个字符,超过上限 {max_chars}。"),
Self::QueryHasControlCharacters => "列出项目文件失败:query 含有控制字符。".to_string(),
Self::QueryNotText { got } => {
format!("列出项目文件失败:query 必须是字符串,当前是「{got}」。")
}
Self::KindEmpty => "列出项目文件失败:kind 传了空值。".to_string(),
Self::KindTooLong {
got_chars,
max_chars,
} => format!("列出项目文件失败:kind 有 {got_chars} 个字符,超过上限 {max_chars}。"),
Self::KindHasControlCharacters => "列出项目文件失败:kind 含有控制字符。".to_string(),
Self::KindNotText { got } => {
format!("列出项目文件失败:kind 必须是字符串,当前是「{got}」。")
}
Self::KindUnsupported { got } => format!(
"列出项目文件失败:kind「{got}」不是受支持的项目文件类别,只允许 {}。",
LIST_PROJECT_FILES_KINDS.join("、")
@@ -18,10 +18,12 @@ pub(crate) enum ListRegisteredAssetsError {
KindEmpty,
KindTooLong { got_chars: usize, max_chars: usize },
KindHasControlCharacters,
KindNotText { got: String },
KindNotManifestClass { got: String },
AssetIdEmpty,
AssetIdTooLong { got_chars: usize, max_chars: usize },
AssetIdHasControlCharacters,
AssetIdNotText { got: String },
IncludeSequenceFramesNotBoolean { got: String },
ProjectPathUnprojectable,
PendingEditsUnreadable { cause: String },
@@ -66,6 +68,9 @@ impl ToolFailure for ListRegisteredAssetsError {
Self::KindHasControlCharacters => {
"列出已登记资源失败:kind 含有控制字符。".to_string()
}
Self::KindNotText { got } => format!(
"列出已登记资源失败:kind 必须是字符串,当前是「{got}」。"
),
Self::KindNotManifestClass { got } => format!(
"列出已登记资源失败:kind「{got}」不是已知的 manifest 资源 kind;请改用 canonical kind。"
),
@@ -79,6 +84,9 @@ impl ToolFailure for ListRegisteredAssetsError {
Self::AssetIdHasControlCharacters => {
"列出已登记资源失败:assetId 含有控制字符。".to_string()
}
Self::AssetIdNotText { got } => format!(
"列出已登记资源失败:assetId 必须是字符串,当前是「{got}」。"
),
Self::IncludeSequenceFramesNotBoolean { got } => format!(
"列出已登记资源失败:includeSequenceFrames 必须是布尔值,当前是「{got}」。"
),
@@ -47,6 +47,7 @@ pub(crate) enum PrepareGameArtError {
BriefEmpty,
BriefTooLong { got_chars: usize, max_chars: usize },
BriefHasControlCharacters,
BriefNotText { got: String },
ModeNotText { got: String },
ModeUnsupported { got: String },
RegenerationGateClosed { cause: String },
@@ -86,6 +87,9 @@ impl ToolFailure for PrepareGameArtError {
Self::BriefHasControlCharacters => {
"准备美术资产失败:brief 含有控制字符。".to_string()
}
Self::BriefNotText { got } => format!(
"准备美术资产失败:brief 必须是字符串,当前是「{got}」。"
),
Self::ModeNotText { got } => format!(
"准备美术资产失败:mode 必须是字符串,当前是「{got}」。"
),
@@ -18,9 +18,11 @@ pub(crate) enum RemoveBackgroundError {
SourceLocalAssetIdEmpty,
SourceLocalAssetIdTooLong { got_chars: usize, max_chars: usize },
SourceLocalAssetIdHasControlCharacters,
SourceLocalAssetIdNotText { got: String },
AssetNameEmpty,
AssetNameTooLong { got_chars: usize, max_chars: usize },
AssetNameHasControlCharacters,
AssetNameNotText { got: String },
BackgroundModeNotText { got: String },
BackgroundModeUnsupported { got: String },
ScreenColorNotText { got: String },
@@ -81,6 +83,9 @@ impl ToolFailure for RemoveBackgroundError {
Self::SourceLocalAssetIdHasControlCharacters => {
"抠图失败:sourceLocalAssetId 含有控制字符。".to_string()
}
Self::SourceLocalAssetIdNotText { got } => format!(
"抠图失败:sourceLocalAssetId 必须是字符串,当前是「{got}」。"
),
Self::AssetNameEmpty => "抠图失败:assetName 不能为空。".to_string(),
Self::AssetNameTooLong {
got_chars,
@@ -89,6 +94,9 @@ impl ToolFailure for RemoveBackgroundError {
Self::AssetNameHasControlCharacters => {
"抠图失败:assetName 含有控制字符。".to_string()
}
Self::AssetNameNotText { got } => {
format!("抠图失败:assetName 必须是字符串,当前是「{got}」。")
}
Self::BackgroundModeNotText { got } => format!(
"抠图失败:backgroundMode 必须是 complex 或 flat,当前是「{got}」。"
),
@@ -13,6 +13,7 @@ pub(crate) enum WebSearchError {
QueryEmpty,
QueryTooLong { got_chars: usize, max_chars: usize },
QueryHasControlCharacters,
QueryNotText { got: String },
MaxResultsNotInteger { got: String, max: u64 },
MaxResultsOutOfRange { got: u64, max: u64 },
ClientUnavailable { cause: String },
@@ -47,6 +48,9 @@ impl ToolFailure for WebSearchError {
max_chars,
} => format!("联网搜索失败:query 有 {got_chars} 个字符,超过上限 {max_chars}。"),
Self::QueryHasControlCharacters => "联网搜索失败:query 含有控制字符。".to_string(),
Self::QueryNotText { got } => {
format!("联网搜索失败:query 必须是字符串,当前是「{got}」。")
}
Self::MaxResultsNotInteger { got, max } => {
format!("联网搜索失败:maxResults 必须是 1 到 {max} 的整数,当前是「{got}」。")
}