claude-mpm 5.4.55__py3-none-any.whl → 5.4.85__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/CLAUDE_MPM_FOUNDERS_OUTPUT_STYLE.md +405 -0
- claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +63 -241
- claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +109 -1925
- claude_mpm/agents/PM_INSTRUCTIONS.md +36 -9
- claude_mpm/cli/__init__.py +5 -1
- claude_mpm/cli/commands/agents.py +2 -4
- claude_mpm/cli/commands/agents_reconcile.py +197 -0
- claude_mpm/cli/commands/configure.py +620 -21
- claude_mpm/cli/commands/skills.py +166 -14
- claude_mpm/cli/executor.py +1 -0
- claude_mpm/cli/interactive/__init__.py +10 -0
- claude_mpm/cli/interactive/agent_wizard.py +30 -50
- claude_mpm/cli/interactive/questionary_styles.py +65 -0
- claude_mpm/cli/interactive/skill_selector.py +481 -0
- claude_mpm/cli/parsers/base_parser.py +5 -0
- claude_mpm/cli/startup.py +223 -388
- claude_mpm/constants.py +1 -0
- claude_mpm/core/claude_runner.py +2 -2
- claude_mpm/core/interactive_session.py +7 -7
- claude_mpm/core/output_style_manager.py +21 -13
- claude_mpm/core/unified_config.py +50 -8
- claude_mpm/core/unified_paths.py +30 -13
- claude_mpm/scripts/start_activity_logging.py +0 -0
- claude_mpm/services/agents/deployment/agent_template_builder.py +8 -0
- claude_mpm/services/agents/deployment/deployment_reconciler.py +577 -0
- claude_mpm/services/agents/deployment/startup_reconciliation.py +138 -0
- claude_mpm/services/agents/sources/git_source_sync_service.py +7 -4
- claude_mpm/services/agents/startup_sync.py +5 -2
- claude_mpm/services/pm_skills_deployer.py +4 -0
- claude_mpm/services/skills/git_skill_source_manager.py +24 -8
- claude_mpm/services/skills/selective_skill_deployer.py +82 -83
- claude_mpm/skills/bundled/collaboration/brainstorming/SKILL.md +79 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/SKILL.md +178 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/agent-prompts.md +577 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/coordination-patterns.md +467 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/examples.md +537 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/troubleshooting.md +730 -0
- claude_mpm/skills/bundled/collaboration/git-worktrees.md +317 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/SKILL.md +112 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/references/code-reviewer-template.md +146 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/references/review-examples.md +412 -0
- claude_mpm/skills/bundled/collaboration/stacked-prs.md +251 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/SKILL.md +81 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/references/best-practices.md +362 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/references/plan-structure-templates.md +312 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/SKILL.md +152 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/advanced-techniques.md +668 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/examples.md +587 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/integration.md +438 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/tracing-techniques.md +391 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/CREATION-LOG.md +119 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/SKILL.md +148 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/anti-patterns.md +483 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/examples.md +452 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/troubleshooting.md +449 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/workflow.md +411 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-academic.md +14 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-1.md +58 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-2.md +68 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-3.md +69 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/SKILL.md +131 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/gate-function.md +325 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/integration-and-workflows.md +490 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/red-flags-and-failures.md +425 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/verification-patterns.md +499 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/INTEGRATION.md +611 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/README.md +596 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/SKILL.md +260 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/examples/nextjs-env-structure.md +315 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/frameworks.md +436 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/security.md +433 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/synchronization.md +452 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/troubleshooting.md +404 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/validation.md +420 -0
- claude_mpm/skills/bundled/main/artifacts-builder/SKILL.md +86 -0
- claude_mpm/skills/bundled/main/internal-comms/SKILL.md +43 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/3p-updates.md +47 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/company-newsletter.md +65 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/faq-answers.md +30 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/general-comms.md +16 -0
- claude_mpm/skills/bundled/main/mcp-builder/SKILL.md +160 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/design_principles.md +412 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/evaluation.md +602 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/mcp_best_practices.md +915 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/node_mcp_server.md +916 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/python_mcp_server.md +752 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/workflow.md +1237 -0
- claude_mpm/skills/bundled/main/skill-creator/SKILL.md +189 -0
- claude_mpm/skills/bundled/main/skill-creator/references/best-practices.md +500 -0
- claude_mpm/skills/bundled/main/skill-creator/references/creation-workflow.md +464 -0
- claude_mpm/skills/bundled/main/skill-creator/references/examples.md +619 -0
- claude_mpm/skills/bundled/main/skill-creator/references/progressive-disclosure.md +437 -0
- claude_mpm/skills/bundled/main/skill-creator/references/skill-structure.md +231 -0
- claude_mpm/skills/bundled/php/espocrm-development/SKILL.md +170 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/architecture.md +602 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/common-tasks.md +821 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/development-workflow.md +742 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/frontend-customization.md +726 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/hooks-and-services.md +764 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/testing-debugging.md +831 -0
- claude_mpm/skills/bundled/pm/pm-bug-reporting/pm-bug-reporting.md +248 -0
- claude_mpm/skills/bundled/pm/pm-delegation-patterns/SKILL.md +167 -0
- claude_mpm/skills/bundled/pm/pm-git-file-tracking/SKILL.md +113 -0
- claude_mpm/skills/bundled/pm/pm-pr-workflow/SKILL.md +124 -0
- claude_mpm/skills/bundled/pm/pm-teaching-mode/SKILL.md +657 -0
- claude_mpm/skills/bundled/pm/pm-ticketing-integration/SKILL.md +154 -0
- claude_mpm/skills/bundled/pm/pm-verification-protocols/SKILL.md +198 -0
- claude_mpm/skills/bundled/react/flexlayout-react.md +742 -0
- claude_mpm/skills/bundled/rust/desktop-applications/SKILL.md +226 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/architecture-patterns.md +901 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/native-gui-frameworks.md +901 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/platform-integration.md +775 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/state-management.md +937 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/tauri-framework.md +770 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/testing-deployment.md +961 -0
- claude_mpm/skills/bundled/tauri/tauri-async-patterns.md +495 -0
- claude_mpm/skills/bundled/tauri/tauri-build-deploy.md +599 -0
- claude_mpm/skills/bundled/tauri/tauri-command-patterns.md +535 -0
- claude_mpm/skills/bundled/tauri/tauri-error-handling.md +613 -0
- claude_mpm/skills/bundled/tauri/tauri-event-system.md +648 -0
- claude_mpm/skills/bundled/tauri/tauri-file-system.md +673 -0
- claude_mpm/skills/bundled/tauri/tauri-frontend-integration.md +767 -0
- claude_mpm/skills/bundled/tauri/tauri-performance.md +669 -0
- claude_mpm/skills/bundled/tauri/tauri-state-management.md +573 -0
- claude_mpm/skills/bundled/tauri/tauri-testing.md +384 -0
- claude_mpm/skills/bundled/tauri/tauri-window-management.md +628 -0
- claude_mpm/skills/bundled/testing/condition-based-waiting/SKILL.md +119 -0
- claude_mpm/skills/bundled/testing/condition-based-waiting/references/patterns-and-implementation.md +253 -0
- claude_mpm/skills/bundled/testing/test-driven-development/SKILL.md +145 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/anti-patterns.md +543 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/examples.md +741 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/integration.md +470 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/philosophy.md +458 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/workflow.md +639 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/SKILL.md +458 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/examples/example-inspection-report.md +411 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/references/assertion-quality.md +317 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/references/inspection-checklist.md +270 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/references/red-flags.md +436 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/SKILL.md +140 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/completeness-anti-patterns.md +572 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/core-anti-patterns.md +411 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/detection-guide.md +569 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/tdd-connection.md +695 -0
- claude_mpm/skills/bundled/testing/webapp-testing/SKILL.md +184 -0
- claude_mpm/skills/bundled/testing/webapp-testing/decision-tree.md +459 -0
- claude_mpm/skills/bundled/testing/webapp-testing/playwright-patterns.md +479 -0
- claude_mpm/skills/bundled/testing/webapp-testing/reconnaissance-pattern.md +687 -0
- claude_mpm/skills/bundled/testing/webapp-testing/server-management.md +758 -0
- claude_mpm/skills/bundled/testing/webapp-testing/troubleshooting.md +868 -0
- claude_mpm/utils/agent_dependency_loader.py +103 -4
- claude_mpm/utils/robust_installer.py +45 -24
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/METADATA +47 -23
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/RECORD +159 -47
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/WHEEL +0 -0
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/entry_points.txt +0 -0
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tauri-error-handling
|
|
3
|
+
description: Comprehensive error handling in Tauri using custom error types, thiserror, structured errors, and frontend error management
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
category: development
|
|
6
|
+
author: Claude MPM Team
|
|
7
|
+
license: MIT
|
|
8
|
+
progressive_disclosure:
|
|
9
|
+
entry_point:
|
|
10
|
+
summary: "Production error handling: custom types with thiserror, structured errors, error context, frontend integration"
|
|
11
|
+
when_to_use: "Building production Tauri apps requiring robust error handling, debugging, and user-friendly error messages"
|
|
12
|
+
quick_start: "1. Define custom errors with thiserror 2. Convert to String for IPC 3. Handle in frontend 4. Log appropriately"
|
|
13
|
+
context_limit: 500
|
|
14
|
+
tags:
|
|
15
|
+
- tauri
|
|
16
|
+
- error-handling
|
|
17
|
+
- thiserror
|
|
18
|
+
- debugging
|
|
19
|
+
- production
|
|
20
|
+
requires_tools: []
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
# Tauri Error Handling
|
|
24
|
+
|
|
25
|
+
## Custom Error Types with thiserror
|
|
26
|
+
|
|
27
|
+
### Basic Error Definition
|
|
28
|
+
|
|
29
|
+
```rust
|
|
30
|
+
// src-tauri/src/error.rs
|
|
31
|
+
use thiserror::Error;
|
|
32
|
+
|
|
33
|
+
#[derive(Error, Debug)]
|
|
34
|
+
pub enum AppError {
|
|
35
|
+
#[error("File not found: {0}")]
|
|
36
|
+
FileNotFound(String),
|
|
37
|
+
|
|
38
|
+
#[error("Invalid input: {0}")]
|
|
39
|
+
InvalidInput(String),
|
|
40
|
+
|
|
41
|
+
#[error("Database error: {0}")]
|
|
42
|
+
DatabaseError(#[from] sqlx::Error),
|
|
43
|
+
|
|
44
|
+
#[error("IO error: {0}")]
|
|
45
|
+
IoError(#[from] std::io::Error),
|
|
46
|
+
|
|
47
|
+
#[error("Serialization error: {0}")]
|
|
48
|
+
SerdeError(#[from] serde_json::Error),
|
|
49
|
+
|
|
50
|
+
#[error("Permission denied: {0}")]
|
|
51
|
+
PermissionDenied(String),
|
|
52
|
+
|
|
53
|
+
#[error("Operation failed: {0}")]
|
|
54
|
+
OperationFailed(String),
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Convert to String for Tauri commands
|
|
58
|
+
impl From<AppError> for String {
|
|
59
|
+
fn from(error: AppError) -> Self {
|
|
60
|
+
error.to_string()
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Usage in Commands
|
|
66
|
+
|
|
67
|
+
```rust
|
|
68
|
+
#[tauri::command]
|
|
69
|
+
async fn load_config(path: String) -> Result<Config, String> {
|
|
70
|
+
read_config(&path).await? // AppError auto-converts to String
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async fn read_config(path: &str) -> Result<Config, AppError> {
|
|
74
|
+
let content = tokio::fs::read_to_string(path)
|
|
75
|
+
.await
|
|
76
|
+
.map_err(|_| AppError::FileNotFound(path.to_string()))?;
|
|
77
|
+
|
|
78
|
+
let config: Config = serde_json::from_str(&content)?; // Auto-converts SerdeError
|
|
79
|
+
|
|
80
|
+
Ok(config)
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Structured Error Returns
|
|
85
|
+
|
|
86
|
+
### Error Response Type
|
|
87
|
+
|
|
88
|
+
```rust
|
|
89
|
+
#[derive(serde::Serialize)]
|
|
90
|
+
#[serde(tag = "type")]
|
|
91
|
+
pub enum CommandResult<T> {
|
|
92
|
+
#[serde(rename = "success")]
|
|
93
|
+
Success {
|
|
94
|
+
data: T,
|
|
95
|
+
},
|
|
96
|
+
#[serde(rename = "error")]
|
|
97
|
+
Error {
|
|
98
|
+
code: String,
|
|
99
|
+
message: String,
|
|
100
|
+
details: Option<serde_json::Value>,
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
#[derive(serde::Serialize)]
|
|
105
|
+
pub struct ErrorDetails {
|
|
106
|
+
timestamp: u64,
|
|
107
|
+
operation: String,
|
|
108
|
+
context: Option<String>,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
#[tauri::command]
|
|
112
|
+
async fn complex_operation() -> CommandResult<String> {
|
|
113
|
+
match perform_operation().await {
|
|
114
|
+
Ok(result) => CommandResult::Success { data: result },
|
|
115
|
+
Err(e) => CommandResult::Error {
|
|
116
|
+
code: "OPERATION_FAILED".to_string(),
|
|
117
|
+
message: e.to_string(),
|
|
118
|
+
details: Some(serde_json::json!({
|
|
119
|
+
"timestamp": std::time::SystemTime::now()
|
|
120
|
+
.duration_since(std::time::UNIX_EPOCH)
|
|
121
|
+
.unwrap()
|
|
122
|
+
.as_secs(),
|
|
123
|
+
"operation": "complex_operation"
|
|
124
|
+
})),
|
|
125
|
+
},
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Frontend handling**:
|
|
131
|
+
```typescript
|
|
132
|
+
type CommandResult<T> =
|
|
133
|
+
| { type: 'success'; data: T }
|
|
134
|
+
| { type: 'error'; code: string; message: string; details?: any };
|
|
135
|
+
|
|
136
|
+
const result = await invoke<CommandResult<string>>('complex_operation');
|
|
137
|
+
|
|
138
|
+
if (result.type === 'success') {
|
|
139
|
+
console.log('Data:', result.data);
|
|
140
|
+
} else {
|
|
141
|
+
console.error(`Error ${result.code}: ${result.message}`);
|
|
142
|
+
if (result.details) {
|
|
143
|
+
console.error('Details:', result.details);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Error Context and Chaining
|
|
149
|
+
|
|
150
|
+
### Adding Context to Errors
|
|
151
|
+
|
|
152
|
+
```rust
|
|
153
|
+
use thiserror::Error;
|
|
154
|
+
|
|
155
|
+
#[derive(Error, Debug)]
|
|
156
|
+
pub enum AppError {
|
|
157
|
+
#[error("Failed to load user data: {0}")]
|
|
158
|
+
UserLoadError(#[source] Box<dyn std::error::Error + Send + Sync>),
|
|
159
|
+
|
|
160
|
+
#[error("Database connection failed: {0}")]
|
|
161
|
+
DatabaseError(String),
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async fn load_user_with_context(id: u64) -> Result<User, AppError> {
|
|
165
|
+
let db = connect_db()
|
|
166
|
+
.await
|
|
167
|
+
.map_err(|e| AppError::DatabaseError(e.to_string()))?;
|
|
168
|
+
|
|
169
|
+
let user = db.query_user(id)
|
|
170
|
+
.await
|
|
171
|
+
.map_err(|e| AppError::UserLoadError(Box::new(e)))?;
|
|
172
|
+
|
|
173
|
+
Ok(user)
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Error Context Pattern
|
|
178
|
+
|
|
179
|
+
```rust
|
|
180
|
+
pub trait ErrorContext<T> {
|
|
181
|
+
fn context(self, ctx: &str) -> Result<T, AppError>;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
impl<T, E> ErrorContext<T> for Result<T, E>
|
|
185
|
+
where
|
|
186
|
+
E: std::error::Error + Send + Sync + 'static,
|
|
187
|
+
{
|
|
188
|
+
fn context(self, ctx: &str) -> Result<T, AppError> {
|
|
189
|
+
self.map_err(|e| AppError::OperationFailed(
|
|
190
|
+
format!("{}: {}", ctx, e)
|
|
191
|
+
))
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
#[tauri::command]
|
|
196
|
+
async fn save_with_context(data: String) -> Result<(), String> {
|
|
197
|
+
let parsed = parse_data(&data)
|
|
198
|
+
.context("Failed to parse data")?;
|
|
199
|
+
|
|
200
|
+
write_to_disk(&parsed)
|
|
201
|
+
.await
|
|
202
|
+
.context("Failed to write to disk")?;
|
|
203
|
+
|
|
204
|
+
Ok(())
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Domain-Specific Error Types
|
|
209
|
+
|
|
210
|
+
### Multiple Error Enums
|
|
211
|
+
|
|
212
|
+
```rust
|
|
213
|
+
// Auth errors
|
|
214
|
+
#[derive(Error, Debug)]
|
|
215
|
+
pub enum AuthError {
|
|
216
|
+
#[error("Invalid credentials")]
|
|
217
|
+
InvalidCredentials,
|
|
218
|
+
|
|
219
|
+
#[error("Token expired")]
|
|
220
|
+
TokenExpired,
|
|
221
|
+
|
|
222
|
+
#[error("Insufficient permissions")]
|
|
223
|
+
InsufficientPermissions,
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// File errors
|
|
227
|
+
#[derive(Error, Debug)]
|
|
228
|
+
pub enum FileError {
|
|
229
|
+
#[error("File not found: {0}")]
|
|
230
|
+
NotFound(String),
|
|
231
|
+
|
|
232
|
+
#[error("File already exists: {0}")]
|
|
233
|
+
AlreadyExists(String),
|
|
234
|
+
|
|
235
|
+
#[error("Invalid file format: {0}")]
|
|
236
|
+
InvalidFormat(String),
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Application error combines all
|
|
240
|
+
#[derive(Error, Debug)]
|
|
241
|
+
pub enum AppError {
|
|
242
|
+
#[error("Authentication error: {0}")]
|
|
243
|
+
Auth(#[from] AuthError),
|
|
244
|
+
|
|
245
|
+
#[error("File error: {0}")]
|
|
246
|
+
File(#[from] FileError),
|
|
247
|
+
|
|
248
|
+
#[error("IO error: {0}")]
|
|
249
|
+
Io(#[from] std::io::Error),
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
impl From<AppError> for String {
|
|
253
|
+
fn from(error: AppError) -> Self {
|
|
254
|
+
error.to_string()
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Using Domain Errors
|
|
260
|
+
|
|
261
|
+
```rust
|
|
262
|
+
#[tauri::command]
|
|
263
|
+
async fn login(username: String, password: String) -> Result<String, String> {
|
|
264
|
+
authenticate(&username, &password).await? // AuthError auto-converts
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async fn authenticate(username: &str, password: &str) -> Result<String, AuthError> {
|
|
268
|
+
if username.is_empty() || password.is_empty() {
|
|
269
|
+
return Err(AuthError::InvalidCredentials);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
let token = verify_credentials(username, password)
|
|
273
|
+
.await
|
|
274
|
+
.ok_or(AuthError::InvalidCredentials)?;
|
|
275
|
+
|
|
276
|
+
if is_token_expired(&token) {
|
|
277
|
+
return Err(AuthError::TokenExpired);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
Ok(token)
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Frontend Error Handling
|
|
285
|
+
|
|
286
|
+
### Type-Safe Error Handling
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
// Define error types matching backend
|
|
290
|
+
type TauriError = string;
|
|
291
|
+
|
|
292
|
+
interface ErrorInfo {
|
|
293
|
+
code: string;
|
|
294
|
+
message: string;
|
|
295
|
+
recoverable: boolean;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function parseError(error: TauriError): ErrorInfo {
|
|
299
|
+
if (error.includes('File not found')) {
|
|
300
|
+
return {
|
|
301
|
+
code: 'FILE_NOT_FOUND',
|
|
302
|
+
message: 'The requested file could not be found',
|
|
303
|
+
recoverable: true
|
|
304
|
+
};
|
|
305
|
+
} else if (error.includes('Permission denied')) {
|
|
306
|
+
return {
|
|
307
|
+
code: 'PERMISSION_DENIED',
|
|
308
|
+
message: 'You do not have permission to perform this action',
|
|
309
|
+
recoverable: false
|
|
310
|
+
};
|
|
311
|
+
} else if (error.includes('Invalid input')) {
|
|
312
|
+
return {
|
|
313
|
+
code: 'INVALID_INPUT',
|
|
314
|
+
message: 'The provided input is invalid',
|
|
315
|
+
recoverable: true
|
|
316
|
+
};
|
|
317
|
+
} else {
|
|
318
|
+
return {
|
|
319
|
+
code: 'UNKNOWN_ERROR',
|
|
320
|
+
message: error,
|
|
321
|
+
recoverable: false
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Usage
|
|
327
|
+
async function loadDocument(filename: string) {
|
|
328
|
+
try {
|
|
329
|
+
const content = await invoke<string>('read_document', { filename });
|
|
330
|
+
return content;
|
|
331
|
+
} catch (error) {
|
|
332
|
+
const errorInfo = parseError(error as TauriError);
|
|
333
|
+
|
|
334
|
+
if (errorInfo.recoverable) {
|
|
335
|
+
showNotification(errorInfo.message, 'warning');
|
|
336
|
+
} else {
|
|
337
|
+
showNotification(errorInfo.message, 'error');
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
throw errorInfo;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### React Error Boundary Integration
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
import { useEffect, useState } from 'react';
|
|
349
|
+
import { invoke } from '@tauri-apps/api/core';
|
|
350
|
+
|
|
351
|
+
interface ErrorState {
|
|
352
|
+
hasError: boolean;
|
|
353
|
+
error: string | null;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function useTauriCommand<T>(command: string, args?: any) {
|
|
357
|
+
const [data, setData] = useState<T | null>(null);
|
|
358
|
+
const [error, setError] = useState<ErrorState>({ hasError: false, error: null });
|
|
359
|
+
const [loading, setLoading] = useState(true);
|
|
360
|
+
|
|
361
|
+
useEffect(() => {
|
|
362
|
+
invoke<T>(command, args)
|
|
363
|
+
.then(setData)
|
|
364
|
+
.catch((err) => {
|
|
365
|
+
setError({
|
|
366
|
+
hasError: true,
|
|
367
|
+
error: err.toString()
|
|
368
|
+
});
|
|
369
|
+
})
|
|
370
|
+
.finally(() => setLoading(false));
|
|
371
|
+
}, [command, JSON.stringify(args)]);
|
|
372
|
+
|
|
373
|
+
return { data, error, loading };
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Usage
|
|
377
|
+
function DocumentView({ filename }: { filename: string }) {
|
|
378
|
+
const { data, error, loading } = useTauriCommand<string>(
|
|
379
|
+
'read_document',
|
|
380
|
+
{ filename }
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
if (loading) return <div>Loading...</div>;
|
|
384
|
+
if (error.hasError) return <div>Error: {error.error}</div>;
|
|
385
|
+
|
|
386
|
+
return <div>{data}</div>;
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
## Error Logging and Debugging
|
|
391
|
+
|
|
392
|
+
### Logging Errors
|
|
393
|
+
|
|
394
|
+
```rust
|
|
395
|
+
use log::{error, warn, info};
|
|
396
|
+
|
|
397
|
+
#[tauri::command]
|
|
398
|
+
async fn operation_with_logging() -> Result<(), String> {
|
|
399
|
+
info!("Starting operation");
|
|
400
|
+
|
|
401
|
+
match perform_operation().await {
|
|
402
|
+
Ok(result) => {
|
|
403
|
+
info!("Operation completed successfully");
|
|
404
|
+
Ok(result)
|
|
405
|
+
}
|
|
406
|
+
Err(e) => {
|
|
407
|
+
error!("Operation failed: {}", e);
|
|
408
|
+
Err(e.to_string())
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Error with context logging
|
|
414
|
+
async fn load_config() -> Result<Config, AppError> {
|
|
415
|
+
match tokio::fs::read_to_string("config.json").await {
|
|
416
|
+
Ok(content) => {
|
|
417
|
+
info!("Config file loaded");
|
|
418
|
+
Ok(serde_json::from_str(&content)?)
|
|
419
|
+
}
|
|
420
|
+
Err(e) => {
|
|
421
|
+
error!("Failed to load config: {}", e);
|
|
422
|
+
warn!("Using default configuration");
|
|
423
|
+
Ok(Config::default())
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### Error Reporting to Frontend
|
|
430
|
+
|
|
431
|
+
```rust
|
|
432
|
+
#[tauri::command]
|
|
433
|
+
async fn operation_with_reporting(
|
|
434
|
+
window: tauri::Window,
|
|
435
|
+
) -> Result<(), String> {
|
|
436
|
+
match risky_operation().await {
|
|
437
|
+
Ok(_) => Ok(()),
|
|
438
|
+
Err(e) => {
|
|
439
|
+
// Log error
|
|
440
|
+
log::error!("Operation failed: {}", e);
|
|
441
|
+
|
|
442
|
+
// Report to frontend
|
|
443
|
+
window.emit("error-occurred", serde_json::json!({
|
|
444
|
+
"message": e.to_string(),
|
|
445
|
+
"timestamp": chrono::Utc::now().to_rfc3339(),
|
|
446
|
+
"severity": "error"
|
|
447
|
+
})).ok();
|
|
448
|
+
|
|
449
|
+
Err(e.to_string())
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
**Frontend error listener**:
|
|
456
|
+
```typescript
|
|
457
|
+
import { listen } from '@tauri-apps/api/event';
|
|
458
|
+
|
|
459
|
+
listen('error-occurred', (event) => {
|
|
460
|
+
const { message, timestamp, severity } = event.payload;
|
|
461
|
+
|
|
462
|
+
console.error(`[${timestamp}] ${severity}: ${message}`);
|
|
463
|
+
|
|
464
|
+
// Show toast notification
|
|
465
|
+
showErrorToast(message);
|
|
466
|
+
|
|
467
|
+
// Log to error tracking service
|
|
468
|
+
trackError({ message, timestamp, severity });
|
|
469
|
+
});
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
## Retry and Recovery Patterns
|
|
473
|
+
|
|
474
|
+
### Retry with Exponential Backoff
|
|
475
|
+
|
|
476
|
+
```rust
|
|
477
|
+
use tokio::time::{sleep, Duration};
|
|
478
|
+
|
|
479
|
+
async fn retry_operation<F, T, E>(
|
|
480
|
+
mut operation: F,
|
|
481
|
+
max_attempts: u32,
|
|
482
|
+
) -> Result<T, E>
|
|
483
|
+
where
|
|
484
|
+
F: FnMut() -> std::pin::Pin<Box<dyn Future<Output = Result<T, E>> + Send>>,
|
|
485
|
+
{
|
|
486
|
+
let mut attempt = 0;
|
|
487
|
+
|
|
488
|
+
loop {
|
|
489
|
+
attempt += 1;
|
|
490
|
+
|
|
491
|
+
match operation().await {
|
|
492
|
+
Ok(result) => return Ok(result),
|
|
493
|
+
Err(e) if attempt >= max_attempts => return Err(e),
|
|
494
|
+
Err(_) => {
|
|
495
|
+
let delay = Duration::from_millis(100 * 2u64.pow(attempt));
|
|
496
|
+
sleep(delay).await;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
#[tauri::command]
|
|
503
|
+
async fn fetch_with_retry(url: String) -> Result<String, String> {
|
|
504
|
+
retry_operation(
|
|
505
|
+
|| Box::pin(fetch_data(&url)),
|
|
506
|
+
3
|
|
507
|
+
)
|
|
508
|
+
.await
|
|
509
|
+
.map_err(|e| e.to_string())
|
|
510
|
+
}
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### Graceful Degradation
|
|
514
|
+
|
|
515
|
+
```rust
|
|
516
|
+
#[tauri::command]
|
|
517
|
+
async fn load_data_with_fallback() -> Result<Data, String> {
|
|
518
|
+
// Try primary source
|
|
519
|
+
match load_from_primary().await {
|
|
520
|
+
Ok(data) => return Ok(data),
|
|
521
|
+
Err(e) => {
|
|
522
|
+
log::warn!("Primary source failed: {}, trying cache", e);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Try cache
|
|
527
|
+
match load_from_cache().await {
|
|
528
|
+
Ok(data) => return Ok(data),
|
|
529
|
+
Err(e) => {
|
|
530
|
+
log::warn!("Cache failed: {}, using defaults", e);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Use defaults
|
|
535
|
+
Ok(Data::default())
|
|
536
|
+
}
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
## Best Practices
|
|
540
|
+
|
|
541
|
+
1. **Use thiserror for custom errors** - Clean, maintainable error definitions
|
|
542
|
+
2. **Convert to String for IPC** - Tauri requires serializable errors
|
|
543
|
+
3. **Add context to errors** - Include what operation failed
|
|
544
|
+
4. **Log errors appropriately** - Use log levels (error, warn, info)
|
|
545
|
+
5. **Provide user-friendly messages** - Don't expose technical details
|
|
546
|
+
6. **Use domain-specific errors** - Organize by feature/module
|
|
547
|
+
7. **Implement retry logic** - For transient failures
|
|
548
|
+
8. **Report errors to frontend** - Via events for async operations
|
|
549
|
+
9. **Test error paths** - Ensure proper error handling
|
|
550
|
+
10. **Document error codes** - Help frontend developers
|
|
551
|
+
|
|
552
|
+
## Common Pitfalls
|
|
553
|
+
|
|
554
|
+
❌ **Using panic! instead of Result**:
|
|
555
|
+
```rust
|
|
556
|
+
// WRONG - panic in library code
|
|
557
|
+
#[tauri::command]
|
|
558
|
+
fn bad_command(input: String) -> String {
|
|
559
|
+
if input.is_empty() {
|
|
560
|
+
panic!("Empty input!"); // Crashes app!
|
|
561
|
+
}
|
|
562
|
+
process(input)
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// CORRECT - return Result
|
|
566
|
+
#[tauri::command]
|
|
567
|
+
fn good_command(input: String) -> Result<String, String> {
|
|
568
|
+
if input.is_empty() {
|
|
569
|
+
return Err("Empty input not allowed".to_string());
|
|
570
|
+
}
|
|
571
|
+
Ok(process(input))
|
|
572
|
+
}
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
❌ **Not implementing From<AppError> for String**:
|
|
576
|
+
```rust
|
|
577
|
+
// WRONG - can't use ? operator
|
|
578
|
+
#[tauri::command]
|
|
579
|
+
async fn command() -> Result<Data, String> {
|
|
580
|
+
let data = load().await?; // AppError doesn't convert to String
|
|
581
|
+
Ok(data)
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// CORRECT - implement conversion
|
|
585
|
+
impl From<AppError> for String {
|
|
586
|
+
fn from(error: AppError) -> Self {
|
|
587
|
+
error.to_string()
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
❌ **Exposing technical details in errors**:
|
|
593
|
+
```rust
|
|
594
|
+
// WRONG - leaks implementation details
|
|
595
|
+
Err(format!("Database error: {}", e)) // Shows SQL errors to user
|
|
596
|
+
|
|
597
|
+
// CORRECT - user-friendly message
|
|
598
|
+
Err("Failed to load data. Please try again.".to_string())
|
|
599
|
+
// Log technical details separately
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
## Summary
|
|
603
|
+
|
|
604
|
+
- **thiserror** for clean custom error types
|
|
605
|
+
- **Convert to String** for Tauri IPC compatibility
|
|
606
|
+
- **Add context** to errors for debugging
|
|
607
|
+
- **Domain-specific errors** for better organization
|
|
608
|
+
- **Structured errors** for complex error information
|
|
609
|
+
- **Log appropriately** with proper severity levels
|
|
610
|
+
- **Frontend error handling** with type-safe parsing
|
|
611
|
+
- **Retry logic** for transient failures
|
|
612
|
+
- **Graceful degradation** when possible
|
|
613
|
+
- **User-friendly messages** without technical jargon
|