home / skills / yelmuratoff / agent_sync / error-handling

error-handling skill

/.ai/src/skills/error-handling

This skill helps you implement typed, layered error handling in Dart/Flutter projects, mapping repository failures to UI states and safe logging.

npx playbooks add skill yelmuratoff/agent_sync --skill error-handling

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
3.1 KB
---
name: error-handling
description: When designing typed exceptions, mapping low-level failures in repositories, and converting errors to UI states via `handleException` and `ISpect.logger.handle`.
---

# Error Handling (Typed, Layered, Testable)

## When to use

- Introducing a new network/storage flow.
- Handling backend error responses and mapping them to domain-level failures.
- Ensuring BLoC error states are consistent and user-safe.

## Steps

### 1) Define explicit exception types

Keep them small and descriptive:

```dart
final class NetworkException implements Exception {
  const NetworkException(this.message, {this.cause});
  final String message;
  final Object? cause;
}

final class TimeoutAppException implements Exception {
  const TimeoutAppException(this.message, {this.cause});
  final String message;
  final Object? cause;
}

final class ParseException implements Exception {
  const ParseException(this.message, {this.cause});
  final String message;
  final Object? cause;
}
```

### 2) Translate low-level failures in the repository

Repository is the boundary that converts transport/storage details into app-level meaning:

```dart
final class OrdersRepository implements IOrdersRepository {
  OrdersRepository({required this.remote});
  final IOrdersRemoteDataSource remote;

  @override
  Future<List<OrderDto>> getOrders() async {
    try {
      final maps = await remote.fetchOrders();
      return maps.map(OrderDto.fromMap).toList();
    } on FormatException catch (e) {
      throw ParseException('Invalid orders payload', cause: e);
    } on TimeoutException catch (e) {
      throw TimeoutAppException('Orders request timed out', cause: e);
    } catch (e) {
      throw NetworkException('Failed to fetch orders', cause: e);
    }
  }
}
```

### 3) Handle exceptions at async boundaries and log safely

When catching, log via ISpect and include stack trace; never log PII or secrets:

```dart
try {
  await repo.getOrders();
} catch (e, st) {
  ISpect.logger.handle(
    exception: e,
    stackTrace: st,
    message: 'Orders load failed',
  );
  rethrow;
}
```

### 4) Convert exceptions into UI state via handleException in BLoC

Use the project’s `handleException` helper to map exceptions to user-facing messages consistently:

```dart
try {
  final orders = await repository.getOrders();
  emit(OrdersLoadedState(orders: orders));
} catch (e, st) {
  handleException(
    exception: e,
    stackTrace: st,
    onError: (message, _, __, ___) => emit(
      OrdersErrorState(message: message, error: e, stackTrace: st),
    ),
  );
}
```

### 5) Test error mapping

Unit test that repositories map failures deterministically:

```dart
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';

class _RemoteMock extends Mock implements IOrdersRemoteDataSource {}

void main() {
  test('maps unknown errors to NetworkException', () async {
    final remote = _RemoteMock();
    when(() => remote.fetchOrders()).thenThrow(Exception('boom'));
    final repo = OrdersRepository(remote: remote);
    expect(repo.getOrders(), throwsA(isA<NetworkException>()));
  });
}
```

Overview

This skill codifies a typed, layered error-handling approach for Dart/Flutter agents. It focuses on defining explicit exception types, translating low-level failures at repository boundaries, safely logging errors, and converting exceptions into consistent UI states via handleException and ISpect.logger.handle. The result is predictable, testable error flows across network, storage, and presentation layers.

How this skill works

Define small, descriptive exception classes that carry a message and optional cause. Repositories catch transport or parsing errors and rethrow domain-level exceptions. Async boundaries log via ISpect.logger.handle with stack traces while avoiding PII, then either rethrow or map errors into UI states using handleException in BLoC. Unit tests assert deterministic mapping from low-level failures to typed exceptions.

When to use it

  • When adding a new network or storage flow and you need clear error semantics.
  • When backend responses or parsing failures must map to domain-level failures.
  • When BLoC or other state managers must present safe, consistent UI error states.
  • When you need deterministic, testable error translation across layers.

Best practices

  • Create small, explicit exception classes (e.g., NetworkException, ParseException).
  • Translate low-level errors inside repositories; do not leak transport-specific errors upward.
  • Log exceptions at async boundaries with ISpect.logger.handle and include stack traces; never log PII or secrets.
  • Use handleException to map exceptions to user-facing messages consistently in BLoC.
  • Write unit tests that assert repository error mappings and BLoC state conversions.

Example use cases

  • Repository converting FormatException to ParseException before returning to domain logic.
  • Catching a TimeoutException in a repository and throwing a TimeoutAppException for upstream handling.
  • BLoC using handleException to emit an OrdersErrorState with a user-safe message.
  • Centralized logging of unexpected failures via ISpect.logger.handle while rethrowing for higher-level handling.
  • Unit tests validating that unknown remote errors map to NetworkException deterministically.

FAQ

Why use typed exceptions instead of plain Exception?

Typed exceptions make intent explicit, enable precise handling and testing, and prevent coupling to transport-specific error types.

Where should I log errors?

Log at async boundaries using ISpect.logger.handle with the exception and stack trace. Avoid logging PII and keep messages contextual but generic for users.