名称: rust-errors 描述: 用于Tauri应用的Rust到TypeScript错误处理模式。用于定义将传递给TypeScript的Rust错误、处理Tauri命令错误或创建discriminated union错误类型。
Rust到TypeScript错误处理
Discriminated Union模式用于错误处理
当通过Tauri命令从Rust传递错误到TypeScript时,使用内部标记的枚举来创建TypeScript可以自然处理的discriminated unions。
Rust错误定义
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Error, Debug, Serialize, Deserialize)]
#[serde(tag = "name")]
pub enum TranscriptionError {
#[error("音频读取错误: {message}")]
AudioReadError { message: String },
#[error("GPU错误: {message}")]
GpuError { message: String },
#[error("模型加载错误: {message}")]
ModelLoadError { message: String },
#[error("转录错误: {message}")]
TranscriptionError { message: String },
}
关键Rust模式
- 使用内部标记的枚举:
#[serde(tag = "name")]创建一个区分字段 - 遵循命名约定: 枚举变体应为PascalCase
- 包含结构化数据: 每个变体可以有字段如
message: String - 单变体枚举可行: 当您想要一致的错误结构时使用
// 用于一致性的单变体枚举
#[derive(Error, Debug, Serialize, Deserialize)]
#[serde(tag = "name")]
enum ArchiveExtractionError {
#[error("存档提取失败: {message}")]
ArchiveExtractionError { message: String },
}
TypeScript错误处理
import { type } from 'arktype';
// 定义错误类型以匹配Rust序列化
const TranscriptionErrorType = type({
name: "'AudioReadError' | 'GpuError' | 'ModelLoadError' | 'TranscriptionError'",
message: 'string',
});
// 在错误处理中使用
const result = await tryAsync({
try: () => invoke('transcribe_audio_whisper', params),
catch: (unknownError) => {
const result = TranscriptionErrorType(unknownError);
if (result instanceof type.errors) {
// 处理意外错误形状
return WhisperingErr({
title: '意外错误',
description: extractErrorMessage(unknownError),
action: { type: 'more-details', error: unknownError },
});
}
const error = result;
// 现在我们有了正确类型的discriminated union
switch (error.name) {
case 'ModelLoadError':
return WhisperingErr({
title: '模型加载错误',
description: error.message,
action: {
type: 'more-details',
error: new Error(error.message),
},
});
case 'GpuError':
return WhisperingErr({
title: 'GPU错误',
description: error.message,
action: {
type: 'link',
label: '配置设置',
href: '/settings/transcription',
},
});
// 处理其他情况...
}
},
});
序列化格式
Rust枚举序列化为这种TypeScript友好的格式:
// AudioReadError变体
{ "name": "AudioReadError", "message": "解码音频文件失败" }
// GpuError变体
{ "name": "GpuError", "message": "GPU加速失败" }
最佳实践
- 一致的错误结构: 所有错误具有相同的形状,带有
name和message - TypeScript类型安全: 使用arktype进行运行时验证以确保类型安全
- 穷尽处理: switch语句提供编译时穷尽性检查
- 不要使用
content属性: 避免#[serde(tag = "name", content = "data")],因为它创建嵌套结构 - 尽可能保持枚举私有: 仅在跨模块使用时公开
要避免的反模式
// 不要: 外部标记(默认行为)
#[derive(Serialize)]
pub enum BadError {
ModelLoadError { message: String }
}
// 产生: { "ModelLoadError": { "message": "..." } }
// 不要: 带内容的相邻标记
#[derive(Serialize)]
#[serde(tag = "type", content = "data")]
pub enum BadError {
ModelLoadError { message: String }
}
// 产生: { "type": "ModelLoadError", "data": { "message": "..." } }
// 不要: 当derive有效时手动实现Serialize
impl Serialize for MyError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> {
// 不必要的复杂性
}
}
这个模式确保了在Rust-TypeScript边界上干净、类型安全的错误处理,具有最小的样板代码和最大的类型安全。