合并主分支
同步主分支资源卡依赖关系与类型分类预览等最新变更 保留并补齐画布快速编辑前后端正向白名单 拒绝图片编辑接口处理单个图标、角色动作、音频、视频及未知类型 修正快速编辑决策记录与现行编辑器文档 补充后端现役类型和未知类型表驱动测试
This commit is contained in:
@@ -2709,28 +2709,43 @@ fn align_editor_image_edit_dimension(value: u32) -> u32 {
|
||||
value.saturating_add(15) / 16 * 16
|
||||
}
|
||||
|
||||
fn ensure_editor_image_edit_asset_kind_allowed(asset_kind: Option<&str>) -> Result<(), AppError> {
|
||||
let Some(asset_kind) = asset_kind.map(str::trim).filter(|value| !value.is_empty()) else {
|
||||
return Ok(());
|
||||
};
|
||||
if asset_kind != "icon" {
|
||||
fn ensure_editor_image_edit_source_kind_allowed(
|
||||
asset_kind: Option<&str>,
|
||||
media_type: Option<&str>,
|
||||
) -> Result<(), AppError> {
|
||||
let asset_kind = asset_kind.map(str::trim).filter(|value| !value.is_empty());
|
||||
let media_type = media_type.map(str::trim).filter(|value| !value.is_empty());
|
||||
let asset_kind_allowed = matches!(
|
||||
asset_kind,
|
||||
None | Some(
|
||||
"spec"
|
||||
| "character"
|
||||
| "icon-spritesheet"
|
||||
| "icon-spec"
|
||||
| "publication-material"
|
||||
| "ui-design"
|
||||
)
|
||||
);
|
||||
let media_type_allowed = matches!(media_type, None | Some("image"));
|
||||
if asset_kind_allowed && media_type_allowed {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": "editor-image-edit",
|
||||
"message": "单个图标不支持快速编辑",
|
||||
"message": "当前素材类型不支持图片快速编辑",
|
||||
"assetKind": asset_kind,
|
||||
"mediaType": media_type,
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
fn resolve_editor_image_edit_target_layer_asset_kind<'a>(
|
||||
fn resolve_editor_image_edit_target_layer_source_kind<'a>(
|
||||
layers: &'a Value,
|
||||
resources: &'a [EditorProjectResourcePayload],
|
||||
target_layer_id: &str,
|
||||
) -> Result<Option<&'a str>, AppError> {
|
||||
) -> Result<(Option<&'a str>, Option<&'a str>), AppError> {
|
||||
let target_layer = layers
|
||||
.as_array()
|
||||
.and_then(|items| {
|
||||
@@ -2745,9 +2760,22 @@ fn resolve_editor_image_edit_target_layer_asset_kind<'a>(
|
||||
"targetLayerId": target_layer_id,
|
||||
}))
|
||||
})?;
|
||||
let media_type = match target_layer.get("mediaType") {
|
||||
Some(Value::String(value)) if !value.trim().is_empty() => Some(value.trim()),
|
||||
Some(Value::Null) | None => None,
|
||||
Some(_) => {
|
||||
return Err(
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": "editor-image-edit",
|
||||
"message": "快速编辑目标图层的媒体类型无效",
|
||||
"targetLayerId": target_layer_id,
|
||||
})),
|
||||
);
|
||||
}
|
||||
};
|
||||
match target_layer.get("assetKindOverride") {
|
||||
Some(Value::String(value)) if !value.trim().is_empty() => {
|
||||
return Ok(Some(value.trim()));
|
||||
return Ok((Some(value.trim()), media_type));
|
||||
}
|
||||
Some(Value::Null) | None => {}
|
||||
Some(_) => {
|
||||
@@ -2761,12 +2789,13 @@ fn resolve_editor_image_edit_target_layer_asset_kind<'a>(
|
||||
}
|
||||
}
|
||||
let resource_id = target_layer.get("resourceId").and_then(Value::as_str);
|
||||
Ok(resource_id.and_then(|resource_id| {
|
||||
let asset_kind = resource_id.and_then(|resource_id| {
|
||||
resources
|
||||
.iter()
|
||||
.find(|resource| resource.resource_id == resource_id)
|
||||
.and_then(|resource| resource.asset_kind.as_deref())
|
||||
}))
|
||||
});
|
||||
Ok((asset_kind, media_type))
|
||||
}
|
||||
|
||||
async fn ensure_editor_image_edit_source_allowed(
|
||||
@@ -2780,7 +2809,7 @@ async fn ensure_editor_image_edit_source_allowed(
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
else {
|
||||
return ensure_editor_image_edit_asset_kind_allowed(payload.asset_kind.as_deref());
|
||||
return ensure_editor_image_edit_source_kind_allowed(payload.asset_kind.as_deref(), None);
|
||||
};
|
||||
let Some(target_layer_id) = payload
|
||||
.target_layer_id
|
||||
@@ -2788,7 +2817,7 @@ async fn ensure_editor_image_edit_source_allowed(
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
else {
|
||||
return ensure_editor_image_edit_asset_kind_allowed(payload.asset_kind.as_deref());
|
||||
return ensure_editor_image_edit_source_kind_allowed(payload.asset_kind.as_deref(), None);
|
||||
};
|
||||
let project = state
|
||||
.spacetime_client()
|
||||
@@ -2799,13 +2828,15 @@ async fn ensure_editor_image_edit_source_allowed(
|
||||
.await
|
||||
.map_err(map_editor_project_error)?;
|
||||
let project = editor_project_payload_from_record(project);
|
||||
let asset_kind = resolve_editor_image_edit_target_layer_asset_kind(
|
||||
let (asset_kind, media_type) = resolve_editor_image_edit_target_layer_source_kind(
|
||||
&project.layers,
|
||||
&project.resources,
|
||||
target_layer_id,
|
||||
)?
|
||||
.or(payload.asset_kind.as_deref());
|
||||
ensure_editor_image_edit_asset_kind_allowed(asset_kind)
|
||||
)?;
|
||||
ensure_editor_image_edit_source_kind_allowed(
|
||||
asset_kind.or(payload.asset_kind.as_deref()),
|
||||
media_type,
|
||||
)
|
||||
}
|
||||
|
||||
fn encode_editor_image_png(
|
||||
@@ -13422,35 +13453,54 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_image_edit_rejects_individual_icons_but_allows_spritesheets_and_specs() {
|
||||
let error = ensure_editor_image_edit_asset_kind_allowed(Some("icon"))
|
||||
fn editor_image_edit_source_kind_whitelist_covers_current_and_unknown_types() {
|
||||
let cases = [
|
||||
(None, None, true),
|
||||
(None, Some("image"), true),
|
||||
(Some("spec"), Some("image"), true),
|
||||
(Some("character"), Some("image"), true),
|
||||
(Some("icon-spritesheet"), Some("image"), true),
|
||||
(Some("icon-spec"), Some("image"), true),
|
||||
(Some("publication-material"), Some("image"), true),
|
||||
(Some("ui-design"), Some("image"), true),
|
||||
(Some("icon"), Some("image"), false),
|
||||
(Some("character-animation"), Some("image-sequence"), false),
|
||||
(Some("sound-effect"), Some("audio"), false),
|
||||
(Some("background-music"), Some("audio"), false),
|
||||
(Some("video"), Some("video"), false),
|
||||
(Some("future-asset-kind"), Some("image"), false),
|
||||
(None, Some("future-media-type"), false),
|
||||
];
|
||||
for (asset_kind, media_type, expected_allowed) in cases {
|
||||
let result = ensure_editor_image_edit_source_kind_allowed(asset_kind, media_type);
|
||||
assert_eq!(
|
||||
result.is_ok(),
|
||||
expected_allowed,
|
||||
"unexpected whitelist result for assetKind={asset_kind:?}, mediaType={media_type:?}",
|
||||
);
|
||||
}
|
||||
|
||||
let error = ensure_editor_image_edit_source_kind_allowed(Some("icon"), Some("image"))
|
||||
.expect_err("individual icon assets should not support quick edit");
|
||||
assert_eq!(error.status_code(), StatusCode::BAD_REQUEST);
|
||||
assert_eq!(
|
||||
error.details().and_then(|details| details.get("message")),
|
||||
Some(&json!("单个图标不支持快速编辑")),
|
||||
Some(&json!("当前素材类型不支持图片快速编辑")),
|
||||
);
|
||||
assert_eq!(
|
||||
error.details().and_then(|details| details.get("assetKind")),
|
||||
Some(&json!("icon")),
|
||||
);
|
||||
|
||||
assert!(ensure_editor_image_edit_asset_kind_allowed(Some("icon-spritesheet")).is_ok());
|
||||
assert!(ensure_editor_image_edit_asset_kind_allowed(Some("icon-spec")).is_ok());
|
||||
assert!(ensure_editor_image_edit_asset_kind_allowed(Some("image")).is_ok());
|
||||
assert!(ensure_editor_image_edit_asset_kind_allowed(None).is_ok());
|
||||
|
||||
let layers = json!([
|
||||
{
|
||||
"layerId": "layer-icon",
|
||||
"resourceId": "resource-default-spec",
|
||||
"assetKind": "character",
|
||||
"assetKindOverride": "icon"
|
||||
"assetKindOverride": "icon",
|
||||
"mediaType": "image"
|
||||
},
|
||||
{
|
||||
"layerId": "layer-icon-spec",
|
||||
"resourceId": "resource-icon-spec",
|
||||
"assetKind": "icon"
|
||||
"assetKind": "icon",
|
||||
"mediaType": "image"
|
||||
}
|
||||
]);
|
||||
let resources = vec![
|
||||
@@ -13458,19 +13508,23 @@ mod tests {
|
||||
editor_project_resource_for_canvas_test("resource-icon-spec", "icon-spec", 512, 512),
|
||||
];
|
||||
let icon_kind =
|
||||
resolve_editor_image_edit_target_layer_asset_kind(&layers, &resources, "layer-icon")
|
||||
resolve_editor_image_edit_target_layer_source_kind(&layers, &resources, "layer-icon")
|
||||
.expect("icon layer should exist");
|
||||
assert!(ensure_editor_image_edit_asset_kind_allowed(icon_kind).is_err());
|
||||
let icon_spec_kind = resolve_editor_image_edit_target_layer_asset_kind(
|
||||
assert_eq!(icon_kind, (Some("icon"), Some("image")));
|
||||
assert!(ensure_editor_image_edit_source_kind_allowed(icon_kind.0, icon_kind.1).is_err());
|
||||
let icon_spec_kind = resolve_editor_image_edit_target_layer_source_kind(
|
||||
&layers,
|
||||
&resources,
|
||||
"layer-icon-spec",
|
||||
)
|
||||
.expect("icon spec layer should exist");
|
||||
assert_eq!(icon_spec_kind, Some("icon-spec"));
|
||||
assert!(ensure_editor_image_edit_asset_kind_allowed(icon_spec_kind).is_ok());
|
||||
assert_eq!(icon_spec_kind, (Some("icon-spec"), Some("image")));
|
||||
assert!(
|
||||
resolve_editor_image_edit_target_layer_asset_kind(&layers, &[], "missing").is_err()
|
||||
ensure_editor_image_edit_source_kind_allowed(icon_spec_kind.0, icon_spec_kind.1)
|
||||
.is_ok()
|
||||
);
|
||||
assert!(
|
||||
resolve_editor_image_edit_target_layer_source_kind(&layers, &[], "missing").is_err()
|
||||
);
|
||||
let invalid_override = json!([{
|
||||
"layerId": "layer-invalid",
|
||||
@@ -13478,7 +13532,7 @@ mod tests {
|
||||
"assetKindOverride": ""
|
||||
}]);
|
||||
assert!(
|
||||
resolve_editor_image_edit_target_layer_asset_kind(
|
||||
resolve_editor_image_edit_target_layer_source_kind(
|
||||
&invalid_override,
|
||||
&resources,
|
||||
"layer-invalid",
|
||||
|
||||
Reference in New Issue
Block a user