fix/资源编辑错误通道补 typed 错误并把平台失败原文带出 #589
Reference in New Issue
Block a user
Delete Branch "fix/res-edit-error-handling"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
close #555
- 新增 project/resource_editor/error.rs:ResourceEditError 只有 RemoteGenerationFailed { server_message } 与 Other(String) 两个变体,to_user_msg 是唯一写「资源编辑生成失败:…」的地方,Other 上留 // TODO refactor string-typed - 轮询到 status=failed 时读平台 error 原文装进 RemoteGenerationFailed 带出,账本仍只写 terminal_failure_code,原文不进账本 - 删掉轮询与提交期 HTTP 400 两处首句失败文案的 remote-terminal-failed 前缀;ensure_resource_edit_phase_resumable 里的三个 token 保留 - 两个入口拆成 typed 实现与 Result<_, String> 外观:derive_local_project_resource_typed / resume_local_project_resource_edit_typed,旧名映射 to_user_msg 供既有调用点与 Tauri 命令使用 - 不提供 impl From<String>,每处 String 错误显式 .map_err(ResourceEditError::Other) - terminal_failure_code 加 // TODO clean unnecessary 并写明清理前置条件(字段无 skip_serializing_if、账本带 deny_unknown_fields) - 测试:远端终态断言平台原文进文案且不进账本,抠图远端失败改用 typed 入口覆盖无 error 时的兜底- agent/tool/error.rs 新增共用载体 RemoteResourceEditFailure { serverMessage },两个工具的平台失败文案只写一份 - CreateOrDeriveResourceError 与 RemoveBackgroundError 各加 RemoteGenerationFailed(RemoteResourceEditFailure) 变体,远端终态失败不再压回字符串 - 两个工具各加显式 from_resource_edit_error 翻译,不用 impl From<ResourceEditError>;Other 仍落回各工具原有的「失败:<文案>」变体并留 TODO - 桥里四个资源编辑调用点改用 typed 入口,并 Box::pin 后再 await:资源编辑 future 内联会顶穿 handle_direct_tool_bridge 状态机的调试测试线程栈 - assets.rs 新增 with_direct_editor_api_credentials_as,保留调用方 error 类型,凭据解析失败由调用方显式翻译成 ResourceEditError::Other,旧名与其余调用点零改动 - 工具错误模块补单测:平台原文进 message、进序列化的 typed 错误,无原文与未分类失败各自的文案 - decision-log 补工具层承载、凭据作用域错误类型、future 装箱三条决策与真实验证结果;pitfalls 补工具层原样透传口径1.
account_api.rs:81-90—error_code与error_message重复解析(已修:675dfe0c8)serde_json::from_str+get("error").unwrap_or(&value),只有取的字段名不同("message"/"code")。error_field(body, field),error_message/error_code变成两个薄封装;调用点不动,行为不变。2.
game_distribution_publish.rs:196-200—message.or(code)先于.filter(已修:a0428da5d)message.or(code).filter(|d| !d.trim().is_empty())。Option::or先求值,Some("")会短路掉code。{"error":{"code":"X","message":""}}这种「有空 message + 有 code」时,先选中空 message,再被 filter 丢掉,最后落到「服务器未返回错误信息」,恰好丢掉本次改动想带出的 code。message.filter(非空).or_else(|| code.filter(非空)),每个候选先过滤再退下一个。map_http_error是同一个写法(message.or(code).filter(...)),review 没列,但属同一类 bug,一并修了(同提交)。3.
game_package_upload/runtime.rs:91-95—read_upload_state同款顺序 bug(已修:1a99a2f26)message.or(code).filter(...).unwrap_or_else(|| format!("HTTP {status}"))。HTTP {status}。message.filter(...).or_else(|| code.filter(...))。4.
game_package_upload/runtime.rs:146-151— 三步共用裸HTTP {status},分不出是哪一步(已修:cdccfa6cd)read_upload_state/upload_chunk/complete_upload都返回Result<_, String>,4 个 HTTP 兜底全是HTTP {status}。一次upload_staged_game_package会跨多请求:读上传状态 N+1 次、每个分片 PUT 最多 4 次、最后 complete 1 次,错误一路裸抛到 UI(commands/desktop.rs/game_distribution_publish.rs都没补前缀),用户和模型看不出是哪一步挂的、下一步该做什么。GamePackageUploadStep(ReadUploadState/UploadChunk/CompleteUpload)和GamePackageUploadError { step, detail };三个远端 helper 改返回 typed 错误,detail只放服务端 message / code /HTTP {status}这类事实,步骤名由step枚举统一渲染(to_user_msg)。upload_staged_game_package仍返回String——本地错误(暂存文件、体积不一致等)不是远端步骤,不硬塞进这个枚举;只在三个远端调用点.map_err(|e| e.to_user_msg())。两处调用方零改动。5.
game_package_upload/runtime.rs:155-160—upload_chunkFatal 分支同款顺序 bug(已修:1a99a2f26)message.or(code).filter(...)。6.
game_package_upload/runtime.rs:199-203—complete_upload同款顺序 bug(已修:1a99a2f26)message.or(code).filter(...)。upload_chunkretryable 分支)。7.
agent/generation/canvas_generation.rs:1276-1277— 去掉 phaseDetail 兜底会在这条路上丢信息(breaking,留给你)status=failed时只读平台error;没有就「服务器未返回错误信息」,不再读phaseDetail。String,没有 typed 错误、也没有诊断 sidecar;平台只给phaseDetail不给error时(background_removal_tests.rs的 fixture 就是这个形状),原因只留在「服务器未返回错误信息」里,诊断侧也看不到。phaseDetail存进ResourceEditError::RemoteGenerationFailed { phase_detail },虽然不给用户看,但会随 typed 错误进诊断;canvas 这条没有等价通道。phaseDetail写进 runtime error 诊断,而不是塞进用户 message);或者退一步,只在这条没有诊断通道的路径上保留phaseDetail兜底。我没有动,因为它要么动用户文案、要么要加诊断链路。game_package_upload 三处不再丢弃服务端 code,改成 message → code → HTTP {status} 去掉「读取上传状态失败(HTTP 503)」这类叶子里的操作名前缀,操作名交给调用方话术 game_distribution_publish response_data 用上被丢掉的 error.code game_distribution_publish 封面任务终态兜底统一成「服务器未返回错误信息」 account_api envelope 分支补 error.code 兜底新增 GamePackageUploadStep(ReadUploadState / UploadChunk / CompleteUpload)与 GamePackageUploadError { step, detail } read_upload_state / upload_chunk / complete_upload 改返回 typed 错误,detail 只放服务端 message / code / HTTP {status} 步骤名由 step 枚举渲染,不再靠各调用点硬拼「读取上传状态失败」这类前缀 upload_staged_game_package 在三个远端调用点 .map_err(to_user_msg);本地错误与对外签名保持 String,两处调用方零改动1. game_package_upload/runtime.rs:158-161 —— 「message → code → HTTP 兜底」在 8 处复制粘贴
message.filter(非空).or_else(code.filter(非空)).unwrap_or(兜底)。message.unwrap_or_else(...),没有空串过滤,message=Some("")时会输出permission-denied:(附了空原因)。pub(crate) fn server_error_detail(code, message, fallback),8 处统一改用它;FORBIDDEN 分支同时补上 trim 过滤(兜底文案仍是「当前账号无权执行此操作」)。2. game_package_upload/runtime.rs:123-130 —— detail 与步骤标签语义重复
读取上传状态失败:读取响应失败:{error}、完成发行包上传失败:完成响应不是合法 JSON:{error};「哪一步」已经由 GamePackageUploadStep 承担,detail 再重复一遍是噪音。{error},JSON 解析/缺字段改为「响应不是合法 JSON:{error}」「响应缺少字段:{error}」「响应缺少 receivedBytes」;保留「请求未送达:{error}」「无法连接登录服务…」这类 step 没覆盖的事实。3. game_package_upload/runtime.rs:94-98 —— 结构化错误在边界被反复手动 flatten 成 String
.map_err(|e| e.to_user_msg())拍平成 String,Serialize / step 结构没有进入任何序列化或日志路径。impl From<GamePackageUploadError> for String,把三处.map_err(|e| e.to_user_msg())与 Fatal 分支改成?/error.into();重试次数那条需要拼后缀,继续用 to_user_msg()。4. agent/generation/canvas_generation.rs:1276-1277 —— 已确认不修(无意义)
external_generation.rs:181、editor_generation_queue.rs:706两处固定映射;external_generation.rs:422/452与 AGC 夹具background_removal_tests.rs:691佐证)。error在last_error_message为空时不落值的问题,真要治在后端,另行处理。