abstract interface class Serializable { Map toJson(); } enum ApiErrorType { network, timeout, validation, http, unknown, } class ApiError { final ApiErrorType type; final String message; final int? statusCode; final Map>? details; final Exception? originalException; const ApiError({ required this.type, required this.message, this.statusCode, this.details, this.originalException, }); factory ApiError.network(String message) => ApiError( type: ApiErrorType.network, message: message, ); factory ApiError.timeout() => const ApiError( type: ApiErrorType.timeout, message: 'Request timeout', ); factory ApiError.validation(String message, Map>? details) => ApiError( type: ApiErrorType.validation, message: message, details: details, ); factory ApiError.http({ required int statusCode, required String message, }) => ApiError( type: ApiErrorType.http, message: message, statusCode: statusCode, ); factory ApiError.unknown(String message, {Exception? exception}) => ApiError( type: ApiErrorType.unknown, message: message, originalException: exception, ); } sealed class Result { const Result(); factory Result.success(T data) => Success(data); factory Result.error(ApiError error) => Error(error); R when({ required R Function(T data) success, required R Function(ApiError error) onError, }) { return switch (this) { Success(:final data) => success(data), Error(:final error) => onError(error), }; } R? whenSuccess(R Function(T data) fn) { return switch (this) { Success(:final data) => fn(data), Error() => null, }; } R? whenError(R Function(ApiError error) fn) { return switch (this) { Success() => null, Error(:final error) => fn(error), }; } bool get isSuccess => this is Success; bool get isError => this is Error; T? getOrNull() => whenSuccess((data) => data); ApiError? getErrorOrNull() => whenError((error) => error); } final class Success extends Result { final T data; const Success(this.data); } final class Error extends Result { final ApiError error; const Error(this.error); } class PaginatedResult { final List items; final int page; final int pageSize; final int totalCount; const PaginatedResult({ required this.items, required this.page, required this.pageSize, required this.totalCount, }); int get totalPages => (totalCount / pageSize).ceil(); bool get hasNextPage => page < totalPages; } enum FilterOperator { equals('eq'), notEquals('neq'), greaterThan('gt'), greaterThanOrEqual('gte'), lessThan('lt'), lessThanOrEqual('lte'), contains('contains'), startsWith('startsWith'), endsWith('endsWith'), in_('in'); final String operator; const FilterOperator(this.operator); } class FilterCriteria implements Serializable { final String field; final FilterOperator operator; final Object? value; FilterCriteria({ required this.field, required this.operator, required this.value, }); @override Map toJson() => { 'field': field, 'operator': operator.operator, 'value': value, }; }