/// Result type for handling success and failure cases /// Uses sealed classes for exhaustive pattern matching sealed class Result { const Result(); /// Check if result is success bool get isSuccess => this is Success; /// Check if result is failure bool get isFailure => this is Failure; /// Get data if success, null otherwise T? get dataOrNull => switch (this) { Success(data: final data) => data, Failure() => null, }; /// Get error if failure, null otherwise String? get errorOrNull => switch (this) { Success() => null, Failure(message: final message) => message, }; /// Transform success value Result map(R Function(T data) transform) { return switch (this) { Success(data: final data) => Success(transform(data)), Failure(message: final msg, exception: final ex) => Failure(msg, ex), }; } /// Execute callback on success Result onSuccess(void Function(T data) callback) { if (this case Success(data: final data)) { callback(data); } return this; } /// Execute callback on failure Result onFailure(void Function(String message) callback) { if (this case Failure(message: final message)) { callback(message); } return this; } /// Get data or throw exception T getOrThrow() { return switch (this) { Success(data: final data) => data, Failure(message: final msg, exception: final ex) => throw ex ?? Exception(msg), }; } /// Get data or return default value T getOrDefault(T defaultValue) { return switch (this) { Success(data: final data) => data, Failure() => defaultValue, }; } /// Get data or compute from error T getOrElse(T Function(String error) onError) { return switch (this) { Success(data: final data) => data, Failure(message: final msg) => onError(msg), }; } } /// Success result containing data class Success extends Result { final T data; const Success(this.data); @override String toString() => 'Success($data)'; @override bool operator ==(Object other) => identical(this, other) || other is Success && runtimeType == other.runtimeType && data == other.data; @override int get hashCode => data.hashCode; } /// Failure result containing error message and optional exception class Failure extends Result { final String message; final Exception? exception; const Failure(this.message, [this.exception]); @override String toString() => 'Failure($message${exception != null ? ', $exception' : ''})'; @override bool operator ==(Object other) => identical(this, other) || other is Failure && runtimeType == other.runtimeType && message == other.message && exception == other.exception; @override int get hashCode => message.hashCode ^ exception.hashCode; } /// Extension for Future> extension FutureResultExtension on Future> { /// Map async result Future> mapAsync(R Function(T data) transform) async { final result = await this; return result.map(transform); } /// Execute callback on success Future> onSuccessAsync( Future Function(T data) callback, ) async { final result = await this; if (result case Success(data: final data)) { await callback(data); } return result; } /// Execute callback on failure Future> onFailureAsync( Future Function(String message) callback, ) async { final result = await this; if (result case Failure(message: final message)) { await callback(message); } return result; } } /// Helper to wrap try-catch blocks Result resultOf(T Function() computation) { try { return Success(computation()); } catch (e) { return Failure(e.toString(), e is Exception ? e : Exception(e.toString())); } } /// Helper to wrap async try-catch blocks Future> asyncResultOf(Future Function() computation) async { try { final data = await computation(); return Success(data); } catch (e) { return Failure(e.toString(), e is Exception ? e : Exception(e.toString())); } }