本机IO失败去掉detail载荷
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m20s
Project CI / Backend tests (pull_request) Failing after 21s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m10s
Project CI / Frontend tests (pull_request) Successful in 2m27s
Project CI / Repository checks (pull_request) Failing after 15s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m50s
Project CI / Native shell tests (pull_request) Successful in 6m25s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled

- Rust 错误枚举:clientSessionPersistFailed / runtimeSessionInstallFailed 改无字段变体,删除会带 AppData 路径的 payload 结构体
- auth_session:本机凭据/运行时会话 IO 失败改成写本地日志后返回无字段变体,不再把原始 error 放进载荷
- 前端:登录检查/发码/登录三处 catch 去掉 as 取 detail,改用本操作的固定文案
- ts-rs 重新生成 ClientAuthError 联合,删除两个已不存在的载荷绑定文件
- 补充 auth_error 用例:两个变体序列化为仅含 type 的无字段形状
This commit is contained in:
2026-10-02 11:57:31 +08:00
parent 8edbab9f3e
commit 53e2889bbd
7 changed files with 41 additions and 89 deletions
@@ -7,8 +7,9 @@
//! 响应契约的 5 种破损各自只占一个变体:前端 `switch (failure.type)` 选到类别后,再用
//! `switch (payload.reason)` 在**类型化**的细分上分流,仍然不碰文案。
//!
//! Rust **不预拼用户可见文案**:载荷只装原始事实(服务端 400 的原文、HTTP 状态码、本机 IO 的
//! `detail`),服务端没给原文就是 `None`;前缀与句式由前端调用方在自己的 catch 分支按当前操作拼接。
//! Rust **不预拼用户可见文案**:载荷只装原始事实(服务端 400 的原文、HTTP 状态码),服务端没给原文
//! 就是 `None`;前缀与句式由前端调用方在自己的 catch 分支按当前操作拼接。本机 IO 的原始错误只写本地
//! 日志,不进载荷(避免把 AppData 路径带进登录页与上报包),对应变体无字段、前端给固定文案。
use serde::Serialize;
use ts_rs::TS;
@@ -55,10 +56,10 @@ pub(crate) enum ClientAuthError {
UnexpectedRejection(UnexpectedRejection),
/// 登录服务响应的契约破损,细分原因见 [`AuthResponseInvalidReason`]。
AuthResponseInvalid(AuthResponseInvalid),
/// 本机登录凭据文件读写失败。
ClientSessionPersistFailed(ClientSessionPersistFailed),
/// 本机运行时会话安装 / 清理失败。
RuntimeSessionInstallFailed(RuntimeSessionInstallFailed),
/// 本机登录凭据文件读写失败;原始错误只写本地日志。
ClientSessionPersistFailed,
/// 本机运行时会话安装 / 清理失败;原始错误只写本地日志。
RuntimeSessionInstallFailed,
/// 认证网络客户端构建失败。
AuthClientInitFailed,
}
@@ -186,22 +187,6 @@ pub(crate) struct AuthResponseInvalid {
pub(crate) server_message: Option<String>,
}
/// 本机凭据文件读写的原始错误明细。
#[derive(Clone, Debug, PartialEq, Eq, Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export, export_to = concat!(env!("CARGO_MANIFEST_DIR"), "/../src/services/generated/"))]
pub(crate) struct ClientSessionPersistFailed {
pub(crate) detail: String,
}
/// 本机运行时会话安装 / 清理的原始错误明细。
#[derive(Clone, Debug, PartialEq, Eq, Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export, export_to = concat!(env!("CARGO_MANIFEST_DIR"), "/../src/services/generated/"))]
pub(crate) struct RuntimeSessionInstallFailed {
pub(crate) detail: String,
}
impl ClientAuthError {
/// 会话路由的 401/403 是「登录态失效」:调用方据此清会话、按未登录处理,
/// 既不给用户报错,也不进错误报告池。
@@ -277,15 +262,11 @@ mod tests {
"authResponseInvalid",
),
(
ClientAuthError::ClientSessionPersistFailed(ClientSessionPersistFailed {
detail: "x".to_string(),
}),
ClientAuthError::ClientSessionPersistFailed,
"clientSessionPersistFailed",
),
(
ClientAuthError::RuntimeSessionInstallFailed(RuntimeSessionInstallFailed {
detail: "x".to_string(),
}),
ClientAuthError::RuntimeSessionInstallFailed,
"runtimeSessionInstallFailed",
),
(
@@ -304,9 +285,20 @@ mod tests {
#[test]
fn unit_variants_serialize_without_payload_fields() {
let value =
serde_json::to_value(ClientAuthError::LoginCodeMissing).expect("serialize auth error");
assert_eq!(value, serde_json::json!({ "type": "loginCodeMissing" }));
for (error, expected_type) in [
(ClientAuthError::LoginCodeMissing, "loginCodeMissing"),
(
ClientAuthError::ClientSessionPersistFailed,
"clientSessionPersistFailed",
),
(
ClientAuthError::RuntimeSessionInstallFailed,
"runtimeSessionInstallFailed",
),
] {
let value = serde_json::to_value(error).expect("serialize auth error");
assert_eq!(value, serde_json::json!({ "type": expected_type }));
}
}
#[test]
@@ -354,14 +346,6 @@ mod tests {
.expect("serialize auth error");
assert_eq!(invalid["reason"], "serverRejected");
assert_eq!(invalid["serverMessage"], "登录服务请求失败");
let persist = serde_json::to_value(ClientAuthError::ClientSessionPersistFailed(
ClientSessionPersistFailed {
detail: "磁盘只读".to_string(),
},
))
.expect("serialize auth error");
assert_eq!(persist["detail"], "磁盘只读");
}
#[test]
@@ -6,9 +6,8 @@
use crate::auth_error::{
AuthNetworkFailure, AuthNetworkReason, AuthResponseInvalid, AuthResponseInvalidReason,
AuthServiceUnavailable, ClientAuthError, ClientSessionPersistFailed, PasswordLoginRejected,
PhoneCodeLoginRejected, RuntimeSessionInstallFailed, SendCodeRejected, ServerAddressReason,
ServerAddressRejected, UnexpectedRejection,
AuthServiceUnavailable, ClientAuthError, PasswordLoginRejected, PhoneCodeLoginRejected,
SendCodeRejected, ServerAddressReason, ServerAddressRejected, UnexpectedRejection,
};
use crate::http_client::agc_main_site_client_builder;
use crate::platform_session::{current_platform_session, PlatformSessionSnapshot};
@@ -222,24 +221,21 @@ fn session_file_path(app: &tauri::AppHandle) -> Result<PathBuf, ClientAuthError>
.app_data_dir()
.map(|root| root.join(SESSION_FILE_NAME))
.map_err(|error| {
ClientAuthError::ClientSessionPersistFailed(ClientSessionPersistFailed {
detail: format!("无法读取 AGC 应用数据目录:{error}"),
})
app_log!("无法读取 AGC 应用数据目录:{error}");
ClientAuthError::ClientSessionPersistFailed
})
}
/// 凭据文件相关的字符串错误统一收口成凭据落盘失败变体。
fn session_persist_error(message: impl Into<String>) -> ClientAuthError {
ClientAuthError::ClientSessionPersistFailed(ClientSessionPersistFailed {
detail: message.into(),
})
app_log!("{}", message.into());
ClientAuthError::ClientSessionPersistFailed
}
/// 本机运行时安装 / 清理失败的字符串错误统一收口。
fn runtime_session_error(message: impl Into<String>) -> ClientAuthError {
ClientAuthError::RuntimeSessionInstallFailed(RuntimeSessionInstallFailed {
detail: message.into(),
})
app_log!("{}", message.into());
ClientAuthError::RuntimeSessionInstallFailed
}
/// 服务地址校验失败统一收口成带类型化原因的变体。
@@ -38,10 +38,8 @@ import {
import type { AuthNetworkFailure } from '../services/generated/AuthNetworkFailure';
import type { AuthResponseInvalid } from '../services/generated/AuthResponseInvalid';
import type { AuthServiceUnavailable } from '../services/generated/AuthServiceUnavailable';
import type { ClientSessionPersistFailed } from '../services/generated/ClientSessionPersistFailed';
import type { PasswordLoginRejected } from '../services/generated/PasswordLoginRejected';
import type { PhoneCodeLoginRejected } from '../services/generated/PhoneCodeLoginRejected';
import type { RuntimeSessionInstallFailed } from '../services/generated/RuntimeSessionInstallFailed';
import type { SendCodeRejected } from '../services/generated/SendCodeRejected';
import type { ServerAddressRejected } from '../services/generated/ServerAddressRejected';
import type { UnexpectedRejection } from '../services/generated/UnexpectedRejection';
@@ -438,15 +436,13 @@ export function AuthenticatedClient({
throw error;
}
case 'clientSessionPersistFailed': {
const payload = failure as ClientSessionPersistFailed;
const text = `本机登录凭据读写失败:${payload.detail}`;
const text = '本机登录凭据读写失败';
setAuthCheckError(text);
setLoginStatus(text);
throw error;
}
case 'runtimeSessionInstallFailed': {
const payload = failure as RuntimeSessionInstallFailed;
const text = `本机运行时会话安装失败:${payload.detail}`;
const text = '本机运行时会话安装失败';
setAuthCheckError(text);
setLoginStatus(text);
throw error;
@@ -708,17 +704,11 @@ export function AuthenticatedClient({
throw error;
}
case 'clientSessionPersistFailed': {
const payload = failure as ClientSessionPersistFailed;
setLoginStatus(
`发送验证码失败:本机登录凭据读写失败:${payload.detail}`,
);
setLoginStatus('发送验证码失败:本机登录凭据读写失败');
throw error;
}
case 'runtimeSessionInstallFailed': {
const payload = failure as RuntimeSessionInstallFailed;
setLoginStatus(
`发送验证码失败:本机运行时会话安装失败:${payload.detail}`,
);
setLoginStatus('发送验证码失败:本机运行时会话安装失败');
throw error;
}
case 'authClientInitFailed': {
@@ -945,13 +935,11 @@ export function AuthenticatedClient({
throw error;
}
case 'clientSessionPersistFailed': {
const payload = failure as ClientSessionPersistFailed;
setLoginStatus(`登录失败:本机登录凭据读写失败:${payload.detail}`);
setLoginStatus('登录失败:本机登录凭据读写失败');
throw error;
}
case 'runtimeSessionInstallFailed': {
const payload = failure as RuntimeSessionInstallFailed;
setLoginStatus(`登录失败:本机运行时会话安装失败:${payload.detail}`);
setLoginStatus('登录失败:本机运行时会话安装失败');
throw error;
}
case 'authClientInitFailed': {
@@ -2,10 +2,8 @@
import type { AuthNetworkFailure } from './AuthNetworkFailure';
import type { AuthResponseInvalid } from './AuthResponseInvalid';
import type { AuthServiceUnavailable } from './AuthServiceUnavailable';
import type { ClientSessionPersistFailed } from './ClientSessionPersistFailed';
import type { PasswordLoginRejected } from './PasswordLoginRejected';
import type { PhoneCodeLoginRejected } from './PhoneCodeLoginRejected';
import type { RuntimeSessionInstallFailed } from './RuntimeSessionInstallFailed';
import type { SendCodeRejected } from './SendCodeRejected';
import type { ServerAddressRejected } from './ServerAddressRejected';
import type { UnexpectedRejection } from './UnexpectedRejection';
@@ -30,6 +28,6 @@ export type ClientAuthError =
| ({ type: 'authServiceUnavailable' } & AuthServiceUnavailable)
| ({ type: 'unexpectedRejection' } & UnexpectedRejection)
| ({ type: 'authResponseInvalid' } & AuthResponseInvalid)
| ({ type: 'clientSessionPersistFailed' } & ClientSessionPersistFailed)
| ({ type: 'runtimeSessionInstallFailed' } & RuntimeSessionInstallFailed)
| { type: 'clientSessionPersistFailed' }
| { type: 'runtimeSessionInstallFailed' }
| { type: 'authClientInitFailed' };
@@ -1,6 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
/**
* 本机凭据文件读写的原始错误明细。
*/
export type ClientSessionPersistFailed = { detail: string };
@@ -1,6 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
/**
* 本机运行时会话安装 / 清理的原始错误明细。
*/
export type RuntimeSessionInstallFailed = { detail: string };
@@ -16,10 +16,8 @@ import type { AuthNetworkFailure } from '../src/services/generated/AuthNetworkFa
import type { AuthResponseInvalid } from '../src/services/generated/AuthResponseInvalid';
import type { AuthServiceUnavailable } from '../src/services/generated/AuthServiceUnavailable';
import type { ClientAuthError } from '../src/services/generated/ClientAuthError';
import type { ClientSessionPersistFailed } from '../src/services/generated/ClientSessionPersistFailed';
import type { PasswordLoginRejected } from '../src/services/generated/PasswordLoginRejected';
import type { PhoneCodeLoginRejected } from '../src/services/generated/PhoneCodeLoginRejected';
import type { RuntimeSessionInstallFailed } from '../src/services/generated/RuntimeSessionInstallFailed';
import type { SendCodeRejected } from '../src/services/generated/SendCodeRejected';
import type { ServerAddressRejected } from '../src/services/generated/ServerAddressRejected';
import type { UnexpectedRejection } from '../src/services/generated/UnexpectedRejection';
@@ -115,9 +113,9 @@ function variantMessage(error: ClientAuthError): string {
}
}
case 'clientSessionPersistFailed':
return (error as ClientSessionPersistFailed).detail;
return '本机登录凭据读写失败';
case 'runtimeSessionInstallFailed':
return (error as RuntimeSessionInstallFailed).detail;
return '本机运行时会话安装失败';
case 'authClientInitFailed':
return '网络客户端构建失败';
default: {