rust-errors

安装量: 67
排名: #11334

安装

npx skills add https://github.com/epicenterhq/epicenter --skill rust-errors

When passing errors from Rust to TypeScript through Tauri commands, use internally-tagged enums to create discriminated unions that TypeScript can handle naturally.

Rust Error Definition

use serde::{Deserialize, Serialize};
use thiserror::Error;

#[derive(Error, Debug, Serialize, Deserialize)]
#[serde(tag = "name")]
pub enum TranscriptionError {
    #[error("Audio read error: {message}")]
    AudioReadError { message: String },

    #[error("GPU error: {message}")]
    GpuError { message: String },

    #[error("Model load error: {message}")]
    ModelLoadError { message: String },

    #[error("Transcription error: {message}")]
    TranscriptionError { message: String },
}

Key Rust Patterns

  • Use internally tagged enums: #[serde(tag = "name")] creates a discriminator field

  • Follow naming conventions: Enum variants should be PascalCase

  • Include structured data: Each variant can have fields like message: String

  • Single-variant enums are okay: Use when you want consistent error structure

// Single-variant enum for consistency
#[derive(Error, Debug, Serialize, Deserialize)]
#[serde(tag = "name")]
enum ArchiveExtractionError {
    #[error("Archive extraction failed: {message}")]
    ArchiveExtractionError { message: String },
}

TypeScript Error Handling

import { type } from 'arktype';

// Define the error type to match Rust serialization
const TranscriptionErrorType = type({
    name: "'AudioReadError' | 'GpuError' | 'ModelLoadError' | 'TranscriptionError'",
    message: 'string',
});

// Use in error handling
const result = await tryAsync({
    try: () => invoke('transcribe_audio_whisper', params),
    catch: (unknownError) => {
        const result = TranscriptionErrorType(unknownError);
        if (result instanceof type.errors) {
            // Handle unexpected error shape
            return WhisperingErr({
                title: 'Unexpected Error',
                description: extractErrorMessage(unknownError),
                action: { type: 'more-details', error: unknownError },
            });
        }

        const error = result;
        // Now we have properly typed discriminated union
        switch (error.name) {
            case 'ModelLoadError':
                return WhisperingErr({
                    title: 'Model Loading Error',
                    description: error.message,
                    action: {
                        type: 'more-details',
                        error: new Error(error.message),
                    },
                });

            case 'GpuError':
                return WhisperingErr({
                    title: 'GPU Error',
                    description: error.message,
                    action: {
                        type: 'link',
                        label: 'Configure settings',
                        href: '/settings/transcription',
                    },
                });

            // Handle other cases...
        }
    },
});

Serialization Format

The Rust enum serializes to this TypeScript-friendly format:

// AudioReadError variant
{ "name": "AudioReadError", "message": "Failed to decode audio file" }

// GpuError variant
{ "name": "GpuError", "message": "GPU acceleration failed" }

Best Practices

  • Consistent error structure: All errors have the same shape with name and message

  • TypeScript type safety: Use runtime validation with arktype to ensure type safety

  • Exhaustive handling: Switch statements provide compile-time exhaustiveness checking

  • Don't use content attribute: Avoid #[serde(tag = "name", content = "data")] as it creates nested structures

  • Keep enums private when possible: Only make public if used across modules

Anti-Patterns to Avoid

// DON'T: External tagging (default behavior)
#[derive(Serialize)]
pub enum BadError {
    ModelLoadError { message: String }
}
// Produces: { "ModelLoadError": { "message": "..." } }

// DON'T: Adjacent tagging with content
#[derive(Serialize)]
#[serde(tag = "type", content = "data")]
pub enum BadError {
    ModelLoadError { message: String }
}
// Produces: { "type": "ModelLoadError", "data": { "message": "..." } }

// DON'T: Manual Serialize implementation when derive works
impl Serialize for MyError {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        // Unnecessary complexity
    }
}

This pattern ensures clean, type-safe error handling across the Rust-TypeScript boundary with minimal boilerplate and maximum type safety.

返回排行榜