Exception Converter Suite

An ExceptionConverterSuite allows multiple Exception Converters to be grouped together, so that when a task is executed and an exception is thrown, it can be converted into the appropriate Failure object.

If an Exception does not have an ideal ExceptionConverter, then it will be converted to the base Failure object using the FallbackExceptionConverter.

This class exposes three methods:

Method
Description

observe

Executes an asynchronous task and returns a Failure when an Exception is thrown.

observeSync

Executes a synchronous task and returns a Failure when an Exception is thrown.

convert

Directly converts a passed Exception into a Failure object.

Observing a task and converting Exceptions

Using the observe or observeSync method of the ExceptionConverterSuite, we can run a task that returns an Either monad containing a Left (failed) or Right (success) value. If any exception occurs while running the task, then a Left object will be returned containing the respective Failure object of the exception.

There are various approaches to converting an Exception into a Failure object, each with its own advantages:

Method A: Using the default exception converters

Default exception converters can be configured when instantiating a ExceptionConverterSuite object:

This is suitable if we want a group of exception converters to take effect on any observe, observeSync and convert method calls of the class.

// Create an exception converter suite that converts a `SocketException` 
// into a `NetworkFailure`
final exceptionConverterSuite = ExceptionConverterSuite(
  exceptionConverters: [SocketExceptionConverter.new],
);

final result = await exceptionConverterSuite.observe<void>(
  task: (messageLog) async {
    // Some code to execute
    ...

    // Simulate exception
    throw const SocketException('test');
  },
);

// A `Left` object containing a `NetworkFailure`
print('result: $result');

Method B: Using the argument exception converters

Exceptions converters can also be passed to the observe, observeSync and convert methods when calling them:

This is ideal if we want to use a custom exception converter to be used for a particular method call, taking precedence over the default exception converters.

final exceptionConverterSuite = ExceptionConverterSuite();

final result = exceptionConverterSuite.observeSync<void>(
  // Provide an exception converter as an argument
  exceptionConverters: [const SocketExceptionConverter()],
  task: (messageLog) {
    // Some code to execute
    ...  
   
    // Simulate exception
    throw const SocketException('test');
  },
);

// A `Left` object containing a `NetworkFailure`
print('result: $result');

Method C: Not using exception converters

In some cases, creating a custom exception converter may be unnecessary, particularly if an exception only occurs in a specific location. In these situations, you can manually convert an exception into a failure instead:

This is ideal if we want to manually handle a specific exception.

final exceptionConverterSuite = ExceptionConverterSuite();

final result = exceptionConverterSuite.observeSync<void>(
  task: (messageLog) {
    try {
      // Simulate exception
      throw const SocketException('test');
    } on SocketException {
      return const Left(NetworkFailure());
    }
  },
);

// A `Left` object containing a `NetworkFailure`
print('Convert result: $result');

Logging a Task

A task can automatically be logged by providing a MessageLog (from the Codenic Logger) to the observe and observeSync method calls. This allows you to construct log messages by passing it data and assigning a message which will automatically be displayed at the end of the task's execution.

For more info about the logger, visit the Codenic Logger documentation.

Automatic logging is done when any of the following events occur:

Event A: Directly returning a Left value

When a Left value is returned, then a logger.warn will be called to print the messageLog:

exceptionConverterSuite.observeSync<void>(
    messageLog: MessageLog(id: 'test-message-log'),
    task: (messageLog) {
      messageLog?.data.addAll({'lorep': 2});
      messageLog?.message = 'Test warning message';

      return const Left(NetworkFailure());
    },
  );

Log output:

Event B: Directly returning a Right value

When a Right value is returned, then a logger.info will be called to print the messageLog:

exceptionConverterSuite.observeSync<void>(
    messageLog: MessageLog(id: 'test-message-log'),
    task: (messageLog) {
      messageLog?.data.addAll({'lorep': 2});
      messageLog?.message = 'Test info message';

      return const Right(null);
    },
  );

Log output:

Event C: catching an exception via a Exception Converter

When an exception is thrown, an ExceptionConverter can catch that exception and decide whether to print the logs.

Using our SocketExceptionConverter example, this calls logger.wtf to print the messageLog when a SocketException is thrown:

exceptionConverterSuite.observeSync<void>(
    exceptionConverters: const [SocketExceptionConverter()],
    messageLog: MessageLog(id: 'test-message-log'),
    task: (messageLog) {
      messageLog?.data.addAll({'lorep': 2});
      messageLog?.message = 'Test exception converter message';

      throw SocketException('test');
    },
  );

Log output:

Last updated