MochiuWiki : SUSE, EC, PCB
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
検索
個人用ツール
ログイン
Toggle dark mode
名前空間
ページ
議論
表示
閲覧
ソースを閲覧
履歴を表示
Tauriの基礎 - Commandsのエラーハンドリングのソースを表示
提供: MochiuWiki : SUSE, EC, PCB
←
Tauriの基礎 - Commandsのエラーハンドリング
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループのいずれかに属する利用者のみが実行できます:
管理者
、new-group。
このページのソースの閲覧やコピーができます。
== 概要 == TauriのCommandsにおけるエラーハンドリングは、フロントエンドとバックエンド間の通信で発生するエラーを適切に処理するための仕組みである。<br> <br> RustのResult型を使用することにより、型安全なエラー処理を実現し、エラーの原因を明確に特定できる。<br> <br> Commandsのエラーハンドリングでは、エラー型のシリアライズ、エラーメッセージの変換、フロントエンドでのエラー受信等が重要な要素となる。<br> <br> 適切なエラーハンドリングにより、ユーザに分かりやすいエラーメッセージを表示し、デバッグを容易にすることができる。<br> <br> エラーハンドリングの主な特徴は以下の通りである。<br> * 型安全なエラー処理 *: Rustの型システムにより、コンパイル時にエラー処理の漏れを防ぐことができる。 * 構造化されたエラー情報 *: エラーの種類、メッセージ、コンテキスト情報を含む詳細なエラーを返却できる。 * フロントエンドとの統合 *: TypeScript側でcatchブロックを使用してエラーを処理できる。 * カスタムエラー型 *: アプリケーション固有のエラー型を定義して、エラーを分類できる。 <br><br> == 前提条件 == エラーハンドリングを定義するには、以下に示す前提条件を満たしている必要がある。<br> <br> ==== 必要な依存関係 ==== Cargo.tomlファイルに以下に示す依存関係を追加する。<br> <br> <syntaxhighlight lang="toml"> [dependencies] tauri = { version = "2", features = ["unstable"] } serde = { version = "1", features = ["derive"] } serde_json = "1" thiserror = "1" # カスタムエラー型の定義用 anyhow = "1" # 汎用エラーハンドリング用 </syntaxhighlight> <br> ==== エラー処理の基本概念 ==== 下表に、Rustにおけるエラー処理の概念を示す。<br> <br> <center> {| class="wikitable" |+ Rustのエラー処理の基本 |- ! 型 !! 説明 !! 使用場面 |- | <code>Result<T, E></code> || 成功または失敗を表す。 || 回復可能なエラー |- | <code>Option<T></code> || 値の有無を表す。 || 値が存在しない可能性 |- | <code>panic!</code> || プログラムを強制終了 || 回復不可能なエラー |} </center> <br><br> == Result<T, E>による返却 == TauriのCommandsでは、<code>Result<T, E></code> 型を使用してエラーを返却する。<br> <br> ==== 基本的なエラー返却 ==== 最も簡単ななエラー返却の例を以下に示す。<br> <br> <syntaxhighlight lang="rust"> #[tauri::command] fn divide(a: i32, b: i32) -> Result<i32, String> { if b == 0 { Err("Division by zero".to_string()) } else { Ok(a / b) } } </syntaxhighlight> <br> <syntaxhighlight lang="typescript"> // TypeScript側でのエラー処理 import { invoke } from '@tauri-apps/api/core' const divide = async (a: number, b: number): Promise<number> => { try { const result = await invoke<number>('divide', { a, b }) console.log('Result:', result) return result } catch (error) { console.error('Error:', error) throw error } } </syntaxhighlight> <br> ==== 成功 / 失敗のパターン ==== Result型を使用した成功と失敗のパターンを以下に示す。<br> <br> <syntaxhighlight lang="rust"> use serde::Serialize; #[derive(Serialize)] struct User { id: u32, name: String, email: String, } #[tauri::command] fn get_user(user_id: u32) -> Result<User, String> { // データベースからユーザを取得する処理 if user_id == 0 { Err("Invalid user ID".to_string()) } else if user_id > 100 { Err("User not found".to_string()) } else { Ok(User { id: user_id, name: format!("User {}", user_id), email: format!("user{}@example.com", user_id), }) } } </syntaxhighlight> <br> ==== Option型との使い分け ==== Result型とOption型の使い分けを示す。<br> <br> <syntaxhighlight lang="rust"> // Option型 : 値が存在しない可能性がある場合 #[tauri::command] fn find_user_by_email(email: String) -> Option<User> { // ユーザが見つからないことはエラーではなく、通常の状態として扱う users.iter().find(|u| u.email == email).cloned() } // Result型 : エラーとして扱うべき場合 #[tauri::command] fn get_user_by_id(user_id: u32) -> Result<User, String> { users.iter() .find(|u| u.id == user_id) .cloned() .ok_or_else(|| format!("User with ID {} not found", user_id)) } </syntaxhighlight> <br> <syntaxhighlight lang="typescript"> // TypeScript側での処理 // Option型の処理 const findUser = async (email: string) => { const user = await invoke<User | null>('find_user_by_email', { email }) if (user) { console.log('Found:', user) } else { console.log('User not found') } } // Result型の処理 const getUser = async (userId: number) => { try { const user = await invoke<User>('get_user_by_id', { userId }) console.log('Found:', user) } catch (error) { console.error('Error:', error) } } </syntaxhighlight> <br><br> == serde::Serializeの制約 == Commandからエラーを返却する場合、エラー型は <code>serde::Serialize</code> を定義している必要がある。<br> <br> ==== エラー型のシリアライズ要件 ==== エラー型をシリアライズ可能にする基本的な実装を以下に示す。<br> <br> <syntaxhighlight lang="rust"> use serde::Serialize; #[derive(Debug, Serialize)] struct AppError { code: u32, message: String, } impl std::fmt::Display for AppError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "[{}] {}", self.code, self.message) } } impl std::error::Error for AppError {} #[tauri::command] fn validate_input(input: String) -> Result<String, AppError> { if input.is_empty() { Err(AppError { code: 400, message: "Input cannot be empty".to_string(), }) } else if input.len() > 100 { Err(AppError { code: 400, message: "Input too long".to_string(), }) } else { Ok(format!("Processed: {}", input)) } } </syntaxhighlight> <br> ==== Serialize実装の例 ==== カスタムエラー型にSerializeを定義する例を以下に示す。<br> <br> <syntaxhighlight lang="rust"> use serde::{Serialize, Serializer}; #[derive(Debug)] pub enum DatabaseError { ConnectionFailed(String), QueryError(String), NotFound, } impl Serialize for DatabaseError { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { match self { DatabaseError::ConnectionFailed(msg) => { serializer.serialize_str(&format!("Connection failed: {}", msg)) } DatabaseError::QueryError(msg) => { serializer.serialize_str(&format!("Query error: {}", msg)) } DatabaseError::NotFound => { serializer.serialize_str("Record not found") } } } } impl std::fmt::Display for DatabaseError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { DatabaseError::ConnectionFailed(msg) => write!(f, "Connection failed: {}", msg), DatabaseError::QueryError(msg) => write!(f, "Query error: {}", msg), DatabaseError::NotFound => write!(f, "Record not found"), } } } </syntaxhighlight> <br> ==== カスタムシリアライザ ==== より複雑なシリアライズが必要な場合は、カスタムシリアライザを使用する。<br> <br> <syntaxhighlight lang="rust"> use serde::{Serialize, Serializer, ser::SerializeStruct}; #[derive(Debug)] pub struct DetailedError { pub kind: ErrorKind, pub message: String, pub context: Option<String>, } #[derive(Debug)] pub enum ErrorKind { Validation, Database, Network, Permission, } impl Serialize for DetailedError { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { let mut state = serializer.serialize_struct("DetailedError", 3)?; state.serialize_field("kind", &format!("{:?}", self.kind))?; state.serialize_field("message", &self.message)?; state.serialize_field("context", &self.context)?; state.end() } } </syntaxhighlight> <br> <syntaxhighlight lang="typescript"> // TypeScript側での受信 interface DetailedError { kind: 'Validation' | 'Database' | 'Network' | 'Permission' message: string context: string | null } const handleOperation = async () => { try { await invoke('some_operation') } catch (error) { const detailedError = error as DetailedError console.log('Error kind:', detailedError.kind) console.log('Message:', detailedError.message) if (detailedError.context) { console.log('Context:', detailedError.context) } } } </syntaxhighlight> <br><br> == map_errによるエラー変換 == <code>map_err</code> メソッドを使用して、エラー型を変換することができる。<br> <br> ==== エラー型の変換 ==== 標準ライブラリのエラーをアプリケーション独自のエラーに変換する例を以下に示す。<br> <br> <syntaxhighlight lang="rust"> use std::io; #[derive(Debug, serde::Serialize)] pub enum AppError { Io(String), Parse(String), Custom(String), } #[tauri::command] fn read_config_file(path: String) -> Result<String, AppError> { std::fs::read_to_string(&path) .map_err(|e: io::Error| AppError::Io(e.to_string()))?; // 設定ファイルのパース処理 let config = parse_config(&path) .map_err(|e: String| AppError::Parse(e))?; Ok(config) } fn parse_config(path: &str) -> Result<String, String> { // パース処理 Ok("parsed config".to_string()) } </syntaxhighlight> <br> ==== ユーザフレンドリーなエラーメッセージ ==== 技術的なエラーをユーザに分かりやすいメッセージに変換する。<br> <br> <syntaxhighlight lang="rust"> #[tauri::command] fn save_user_data(data: String) -> Result<(), String> { std::fs::write("user_data.json", &data) .map_err(|e| { match e.kind() { std::io::ErrorKind::PermissionDenied => { "ファイルへの書き込み権限がありません".to_string() } std::io::ErrorKind::NotFound => { "指定されたディレクトリが見つかりません".to_string() } std::io::ErrorKind::StorageFull => { "ディスクの空き容量が不足しています".to_string() } _ => format!("ファイルの保存に失敗しました: {}", e) } })?; Ok(()) } </syntaxhighlight> <br> ==== エラーチェーンの構築 ==== エラーの原因を追跡するために、エラーチェーンを構築する。<br> <br> <syntaxhighlight lang="rust"> #[derive(Debug, serde::Serialize)] struct ChainedError { message: String, source: Option<String>, } #[tauri::command] fn complex_operation() -> Result<String, ChainedError> { let result = step1() .map_err(|e| ChainedError { message: "Step 1 failed".to_string(), source: Some(e), })?; let result = step2(&result) .map_err(|e| ChainedError { message: "Step 2 failed".to_string(), source: Some(e), })?; Ok(result) } fn step1() -> Result<String, String> { Ok("step1 result".to_string()) } fn step2(input: &str) -> Result<String, String> { Ok(format!("{} + step2", input)) } </syntaxhighlight> <br><br> == フロントエンドでのcatch処理 == TypeScript側でのエラー処理の定義パターンを示す。<br> <br> ==== TypeScriptでのエラー処理 ==== 基本的なtry-catchパターンを以下に示す。<br> <br> <syntaxhighlight lang="typescript"> import { invoke } from '@tauri-apps/api/core' interface User { id: number name: string } const fetchUser = async (userId: number): Promise<User | null> => { try { const user = await invoke<User>('get_user', { userId }) return user } catch (error) { // エラーは通常、文字列として送信される const errorMessage = error as string console.error('Failed to fetch user:', errorMessage) // エラーの種類に応じた処理 if (errorMessage.includes('not found')) { console.log('User does not exist') } else if (errorMessage.includes('Invalid')) { console.log('Invalid user ID') } return null } } </syntaxhighlight> <br> ==== try-catchパターン ==== 複数のCommandを実行する場合のパターンを以下に示す。<br> <br> <syntaxhighlight lang="typescript"> import { invoke } from '@tauri-apps/api/core' interface OperationResult { success: boolean data?: unknown error?: string } const performOperations = async (): Promise<OperationResult> => { try { // 複数の操作を実行 const user = await invoke('get_current_user') const settings = await invoke('get_user_settings', { userId: user.id }) const data = await invoke('fetch_user_data', { settings }) return { success: true, data: { user, settings, data } } } catch (error) { return { success: false, error: error as string } } } </syntaxhighlight> <br> ==== エラー表示のベストプラクティス ==== Reactでのエラー表示コンポーネントの例を以下に示す。<br> <br> <syntaxhighlight lang="typescript"> import { useState, ReactNode } from 'react' import { invoke } from '@tauri-apps/api/core' interface ErrorBoundaryState { hasError: boolean error: string | null } interface ErrorDisplayProps { error: string onRetry?: () => void onDismiss?: () => void } function ErrorDisplay({ error, onRetry, onDismiss }: ErrorDisplayProps) { return ( <div style={{ padding: '16px', backgroundColor: '#fee', border: '1px solid #f88', borderRadius: '4px', marginBottom: '16px' }} > <div style={{ display: 'flex', alignItems: 'start', gap: '8px' }}> <span style={{ color: '#c00', fontSize: '20px' }}>⚠</span> <div style={{ flex: 1 }}> <h4 style={{ margin: '0 0 8px 0', color: '#c00' }}>エラーが発生しました</h4> <p style={{ margin: 0, color: '#600' }}>{error}</p> </div> {onDismiss && ( <button onClick={onDismiss} style={{ background: 'none', border: 'none', fontSize: '20px', cursor: 'pointer' }} > × </button> )} </div> {onRetry && ( <button onClick={onRetry} style={{ marginTop: '12px', padding: '8px 16px', backgroundColor: '#c00', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer' }} > 再試行 </button> )} </div> ) } function DataFetcher() { const [data, setData] = useState<string | null>(null) const [error, setError] = useState<string | null>(null) const [loading, setLoading] = useState(false) const fetchData = async () => { setLoading(true) setError(null) try { const result = await invoke<string>('fetch_data') setData(result) } catch (err) { setError(err as string) } finally { setLoading(false) } } return ( <div> <h2>Data Fetcher</h2> {error && ( <ErrorDisplay error={error} onRetry={fetchData} onDismiss={() => setError(null)} /> )} {loading && <p>Loading...</p>} {data && ( <div style={{ padding: '16px', backgroundColor: '#f5f5f5' }}> {data} </div> )} <button onClick={fetchData} disabled={loading}> {loading ? 'Fetching...' : 'Fetch Data'} </button> </div> ) } export default DataFetcher </syntaxhighlight> <br><br> == カスタムエラー型の定義 == アプリケーション固有のエラー型を定義することで、エラー処理を体系化できる。<br> <br> ==== thiserrorクレートの使用 ==== <code>thiserror</code> クレートを使用することにより、簡単にカスタムエラー型を定義できる。<br> <br> <syntaxhighlight lang="rust"> use thiserror::Error; use serde::Serialize; #[derive(Debug, Error)] pub enum AppError { #[error("User not found: {0}")] UserNotFound(u32), #[error("Invalid input: {0}")] InvalidInput(String), #[error("Database error: {0}")] DatabaseError(String), #[error("Permission denied")] PermissionDenied, #[error("IO error: {0}")] Io(#[from] std::io::Error), #[error("JSON error: {0}")] Json(#[from] serde_json::Error), } // Serialize実装 impl Serialize for AppError { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer, { serializer.serialize_str(&self.to_string()) } } // 使用例 #[tauri::command] fn get_user(user_id: u32) -> Result<User, AppError> { if user_id == 0 { return Err(AppError::InvalidInput("User ID cannot be 0".to_string())) } let user = database::find_user(user_id) .map_err(|e| AppError::DatabaseError(e.to_string()))? .ok_or(AppError::UserNotFound(user_id))?; Ok(user) } </syntaxhighlight> <br> ==== anyhowクレートの使用 ==== <code>anyhow</code> クレートは、汎用的なエラーハンドリングに適している。<br> <br> <syntaxhighlight lang="rust"> use anyhow::{Context, Result}; #[tauri::command] fn read_config() -> Result<String, String> { let content = std::fs::read_to_string("config.json") .context("Failed to read config file") .map_err(|e| e.to_string())?; let config: Config = serde_json::from_str(&content) .context("Failed to parse config") .map_err(|e| e.to_string())?; Ok(config.name) } // context()メソッドでエラーにコンテキストを追加 #[tauri::command] fn process_file(path: String) -> Result<(), String> { let content = std::fs::read_to_string(&path) .with_context(|| format!("Failed to read file: {}", path)) .map_err(|e| e.to_string())?; // ...処理 Ok(()) } </syntaxhighlight> <br> ==== 独自エラー型の設計 ==== アプリケーション固有のエラー型を設計する例を以下に示す。<br> <br> <syntaxhighlight lang="rust"> use serde::Serialize; #[derive(Debug, Clone, Serialize)] #[serde(tag = "type", content = "details")] pub enum ApiError { // バリデーションエラー Validation { field: String, message: String }, // 認証エラー Authentication { reason: String }, // 認可エラー Authorization { resource: String, action: String }, // リソースエラー NotFound { resource: String, id: String }, // サーバエラー Internal { message: String }, // 外部サービスエラー ExternalService { service: String, error: String }, } impl std::fmt::Display for ApiError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ApiError::Validation { field, message } => { write!(f, "Validation error on '{}': {}", field, message) } ApiError::Authentication { reason } => { write!(f, "Authentication failed: {}", reason) } ApiError::Authorization { resource, action } => { write!(f, "Not authorized to {} on {}", action, resource) } ApiError::NotFound { resource, id } => { write!(f, "{} with id '{}' not found", resource, id) } ApiError::Internal { message } => { write!(f, "Internal server error: {}", message) } ApiError::ExternalService { service, error } => { write!(f, "External service '{}' error: {}", service, error) } } } } // 使用例 #[tauri::command] fn update_user(user_id: u32, data: UpdateData) -> Result<User, ApiError> { // バリデーション if data.name.is_empty() { return Err(ApiError::Validation { field: "name".to_string(), message: "Name cannot be empty".to_string(), }) } // ユーザ取得 let user = database::find_user(user_id) .map_err(|e| ApiError::Internal { message: e.to_string() })? .ok_or(ApiError::NotFound { resource: "User".to_string(), id: user_id.to_string(), })?; // 更新処理 let updated = database::update_user(user_id, data) .map_err(|e| ApiError::Internal { message: e.to_string() })?; Ok(updated) } </syntaxhighlight> <br> <syntaxhighlight lang="typescript"> // TypeScript側での型定義 interface ApiError { type: 'Validation' | 'Authentication' | 'Authorization' | 'NotFound' | 'Internal' | 'ExternalService' details: ValidationDetails | string | NotFoundDetails | InternalDetails | ExternalServiceDetails } interface ValidationDetails { field: string message: string } interface NotFoundDetails { resource: string id: string } interface InternalDetails { message: string } interface ExternalServiceDetails { service: string error: string } const handleApiError = (error: ApiError) => { switch (error.type) { case 'Validation': console.error(`Validation error on ${(error.details as ValidationDetails).field}`) break case 'Authentication': console.error('Please log in again') break case 'Authorization': console.error('You do not have permission') break case 'NotFound': const notFound = error.details as NotFoundDetails console.error(`${notFound.resource} not found`) break case 'Internal': console.error('Server error occurred') break case 'ExternalService': console.error('External service unavailable') break } } </syntaxhighlight> <br><br> == サンプルコード == エラーハンドリング定義の例を以下に示す。<br> <br> ==== Rust側の完全実装 ==== <syntaxhighlight lang="rust"> // src-tauri/src/error.rs use serde::Serialize; use thiserror::Error; #[derive(Debug, Error, Serialize)] pub enum TodoError { #[error("Todo not found: {0}")] NotFound(u32), #[error("Invalid title: {0}")] InvalidTitle(String), #[error("Database error: {0}")] Database(String), #[error("IO error: {0}")] Io(String), } impl From<std::io::Error> for TodoError { fn from(e: std::io::Error) -> Self { TodoError::Io(e.to_string()) } } impl From<rusqlite::Error> for TodoError { fn from(e: rusqlite::Error) -> Self { TodoError::Database(e.to_string()) } } // src-tauri/src/commands/todo.rs use crate::error::TodoError; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Todo { pub id: u32, pub title: String, pub completed: bool, } #[tauri::command] pub fn get_todos() -> Result<Vec<Todo>, TodoError> { // データベースから取得 let todos = vec![ Todo { id: 1, title: "Learn Tauri".to_string(), completed: false }, Todo { id: 2, title: "Build app".to_string(), completed: false }, ]; Ok(todos) } #[tauri::command] pub fn get_todo(id: u32) -> Result<Todo, TodoError> { let todos = get_todos()?; todos.into_iter() .find(|t| t.id == id) .ok_or(TodoError::NotFound(id)) } #[tauri::command] pub fn create_todo(title: String) -> Result<Todo, TodoError> { if title.trim().is_empty() { return Err(TodoError::InvalidTitle("Title cannot be empty".to_string())) } if title.len() > 100 { return Err(TodoError::InvalidTitle("Title too long (max 100 chars)".to_string())) } Ok(Todo { id: rand::random(), title, completed: false, }) } #[tauri::command] pub fn update_todo(id: u32, title: Option<String>, completed: Option<bool>) -> Result<Todo, TodoError> { let mut todo = get_todo(id)?; if let Some(t) = title { if t.trim().is_empty() { return Err(TodoError::InvalidTitle("Title cannot be empty".to_string())) } todo.title = t; } if let Some(c) = completed { todo.completed = c; } Ok(todo) } #[tauri::command] pub fn delete_todo(id: u32) -> Result<(), TodoError> { // 存在確認 get_todo(id)?; // 削除処理 Ok(()) } </syntaxhighlight> <br> ==== Reactでのエラー表示コンポーネント ==== <syntaxhighlight lang="typescript"> // src/components/TodoApp.tsx import { useState, useEffect } from 'react' import { invoke } from '@tauri-apps/api/core' interface Todo { id: number title: string completed: boolean } interface TodoError { type: 'NotFound' | 'InvalidTitle' | 'Database' | 'Io' details: string | number } function TodoApp() { const [todos, setTodos] = useState<Todo[]>([]) const [newTodo, setNewTodo] = useState('') const [loading, setLoading] = useState(false) const [error, setError] = useState<TodoError | null>(null) useEffect(() => { loadTodos() }, []) const loadTodos = async () => { setLoading(true) setError(null) try { const result = await invoke<Todo[]>('get_todos') setTodos(result) } catch (err) { setError(err as TodoError) } finally { setLoading(false) } } const addTodo = async () => { if (!newTodo.trim()) return setLoading(true) setError(null) try { const todo = await invoke<Todo>('create_todo', { title: newTodo }) setTodos([...todos, todo]) setNewTodo('') } catch (err) { setError(err as TodoError) } finally { setLoading(false) } } const toggleTodo = async (id: number, completed: boolean) => { setError(null) try { const updated = await invoke<Todo>('update_todo', { id, title: null, completed: !completed }) setTodos(todos.map(t => t.id === id ? updated : t)) } catch (err) { setError(err as TodoError) } } const deleteTodo = async (id: number) => { setError(null) try { await invoke('delete_todo', { id }) setTodos(todos.filter(t => t.id !== id)) } catch (err) { setError(err as TodoError) } } const renderError = () => { if (!error) return null let message = '' switch (error.type) { case 'NotFound': message = `Todo #${error.details} が見つかりません` break case 'InvalidTitle': message = error.details as string break case 'Database': message = `データベースエラー: ${error.details}` break case 'Io': message = `IOエラー: ${error.details}` break } return ( <div style={{ padding: '12px', backgroundColor: '#fee', border: '1px solid #f88', borderRadius: '4px', marginBottom: '16px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }} > <span style={{ color: '#c00' }}>⚠ {message}</span> <button onClick={() => setError(null)} style={{ background: 'none', border: 'none', fontSize: '18px', cursor: 'pointer' }} > × </button> </div> ) } return ( <div style={{ maxWidth: '600px', margin: '0 auto', padding: '20px' }}> <h1>Todo App</h1> {renderError()} <div style={{ marginBottom: '20px' }}> <input type="text" value={newTodo} onChange={(e) => setNewTodo(e.target.value)} placeholder="新しいTodoを入力" style={{ padding: '8px', marginRight: '8px', width: '300px', fontSize: '16px' }} onKeyDown={(e) => e.key === 'Enter' && addTodo()} /> <button onClick={addTodo} disabled={loading || !newTodo.trim()} style={{ padding: '8px 16px', fontSize: '16px' }} > 追加 </button> </div> {loading && <p>Loading...</p>} <ul style={{ listStyle: 'none', padding: 0 }}> {todos.map((todo) => ( <li key={todo.id} style={{ display: 'flex', alignItems: 'center', padding: '12px', borderBottom: '1px solid #eee' }} > <input type="checkbox" checked={todo.completed} onChange={() => toggleTodo(todo.id, todo.completed)} style={{ marginRight: '12px' }} /> <span style={{ flex: 1, textDecoration: todo.completed ? 'line-through' : 'none', color: todo.completed ? '#888' : 'inherit' }} > {todo.title} </span> <button onClick={() => deleteTodo(todo.id)} style={{ color: '#c00', background: 'none', border: '1px solid #c00', padding: '4px 8px', cursor: 'pointer' }} > 削除 </button> </li> ))} </ul> {todos.length === 0 && !loading && ( <p style={{ textAlign: 'center', color: '#888' }}> Todoがありません </p> )} </div> ) } export default TodoApp </syntaxhighlight> <br> ==== ログ出力とデバッグ ==== エラーのログ出力とデバッグの定義例を以下に示す。<br> <br> <syntaxhighlight lang="rust"> use log::{error, warn, info}; use env_logger; #[tauri::command] fn debug_operation(input: String) -> Result<String, String> { info!("Starting operation with input: {}", input); if input.is_empty() { warn!("Empty input received"); return Err("Input cannot be empty".to_string()) } match risky_operation(&input) { Ok(result) => { info!("Operation succeeded: {}", result); Ok(result) } Err(e) => { error!("Operation failed: {}", e); Err(e) } } } fn risky_operation(input: &str) -> Result<String, String> { // 危険な操作 Ok(format!("Processed: {}", input)) } </syntaxhighlight> <br> <syntaxhighlight lang="rust"> // main.rsでのロガー初期化 fn main() { // ロガーの初期化 env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")) .init(); tauri::Builder::default() .invoke_handler(tauri::generate_handler![debug_operation]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } </syntaxhighlight> <br><br> == 推奨される事柄 == ==== エラーメッセージの設計 ==== ユーザに分かりやすいエラーメッセージを設計する。<br> <br> * 具体的な説明 *: 何が問題だったか、どのように解決できるかを含める。 * 多言語対応 *: 必要に応じてエラーメッセージを国際化する。 * セキュリティ考慮 *: 内部実装の詳細を漏らさない。 <br> ==== エラーの分類 ==== エラーを適切に分類して処理する。<br> <br> <syntaxhighlight lang="rust"> #[derive(Debug, Serialize)] enum ErrorSeverity { Warning, // ユーザに通知のみ Error, // 操作の再試行が可能 Critical, // アプリケーションの再起動が必要 } #[derive(Debug, Serialize)] struct ClassifiedError { severity: ErrorSeverity, code: String, message: String, recoverable: bool, } </syntaxhighlight> <br> ==== エラーの伝播 ==== エラーは適切なレベルで処理して、不要な伝播を避ける。<br> <br> <syntaxhighlight lang="rust"> #[tauri::command] fn outer_operation() -> Result<String, String> { // 内部エラーはここで変換 inner_operation().map_err(|e| { // ユーザ向けのメッセージに変換 format!("操作に失敗しました: {}", e) }) } </syntaxhighlight> <br> ==== テスト可能なエラー処理 ==== エラー処理をテスト可能に設計する。<br> <br> <syntaxhighlight lang="rust"> #[cfg(test)] mod tests { use super::*; #[test] fn test_create_todo_empty_title() { let result = create_todo("".to_string()); assert!(result.is_err()); match result { Err(TodoError::InvalidTitle(_)) => (), _ => panic!("Expected InvalidTitle error"), } } #[test] fn test_create_todo_valid() { let result = create_todo("Test Todo".to_string()); assert!(result.is_ok()); } } </syntaxhighlight> <br><br> == 関連情報 == * [[Tauriの基礎 - Commands]] *: Commandsの基本的な使用方法について * [[Tauriの基礎 - 非同期Commands]] *: 非同期処理、Channel API、プログレス表示について * [https://tauri.app/v2/guide/features/command/ Tauri公式ドキュメント - Commands] *: 公式のCommandsリファレンス * [https://docs.rs/thiserror/latest/thiserror/ thiserrorクレート] *: カスタムエラー型の定義用クレート * [https://docs.rs/anyhow/latest/anyhow/ anyhowクレート] *: 汎用エラーハンドリング用クレート * [https://serde.rs/ Serde公式ドキュメント] *: シリアライズ/デシリアライズの詳細 <br><br> {{#seo: |title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki |keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Tauri,Rust,React,TypeScript,Error Handling,thiserror,anyhow,Result,Serialize,エラーハンドリング,例外処理 |description={{PAGENAME}} - Tauri Commandsのエラーハンドリングガイド | This page is {{PAGENAME}} in our wiki about Tauri error handling |image=/resources/assets/MochiuLogo_Single_Blue.png }} __FORCETOC__ [[カテゴリ:Rust]]
Tauriの基礎 - Commandsのエラーハンドリング
に戻る。
案内
メインページ
最近の更新
おまかせ表示
MediaWiki についてのヘルプ
ツール
リンク元
関連ページの更新状況
特別ページ
ページ情報
We ask for
Donations
Collapse