Files
iiEsaywebUIapp/lib/core/validation/validation_interceptor.dart

218 lines
6.4 KiB
Dart
Raw Normal View History

2025-08-10 01:20:45 +05:30
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'api_validator.dart';
import 'validation_result.dart';
2025-08-20 22:15:26 +05:30
import '../utils/debug_logger.dart';
2025-08-10 01:20:45 +05:30
/// Dio interceptor for automatic API validation
/// Validates requests and responses against OpenAPI schemas
class ValidationInterceptor extends Interceptor {
final ApiValidator _validator = ApiValidator();
final bool enableRequestValidation;
final bool enableResponseValidation;
final bool throwOnValidationError;
final bool logValidationResults;
ValidationInterceptor({
this.enableRequestValidation = true,
this.enableResponseValidation = true,
this.throwOnValidationError = false,
this.logValidationResults = true,
});
/// Initialize the validator
Future<void> initialize() async {
await _validator.initialize();
}
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
if (enableRequestValidation && options.data != null) {
try {
final result = _validator.validateRequest(
options.data,
options.path,
method: options.method,
);
if (logValidationResults) {
_logValidationResult(result, 'REQUEST', options.path, options.method);
}
if (!result.isValid && throwOnValidationError) {
throw ValidationException(result);
}
// Transform data if validation succeeded
2025-08-16 17:36:02 +05:30
// Temporarily disabled to preserve background_tasks and session_id parameters
// if (result.isValid && options.data is Map<String, dynamic>) {
// options.data = _validator.transformForApi(
// options.data as Map<String, dynamic>,
// );
// }
2025-08-10 01:20:45 +05:30
} catch (e) {
if (e is ValidationException) {
handler.reject(
DioException(
requestOptions: options,
error: e,
type: DioExceptionType.unknown,
message: 'Request validation failed: ${e.result.message}',
),
);
return;
} else {
2025-08-20 22:15:26 +05:30
DebugLogger.error('Request validation error', e);
2025-08-10 01:20:45 +05:30
}
}
}
handler.next(options);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
if (enableResponseValidation && response.data != null) {
try {
final result = _validator.validateResponse(
response.data,
response.requestOptions.path,
method: response.requestOptions.method,
statusCode: response.statusCode,
);
if (logValidationResults) {
_logValidationResult(
result,
'RESPONSE',
response.requestOptions.path,
response.requestOptions.method,
statusCode: response.statusCode,
);
}
if (!result.isValid && throwOnValidationError) {
throw ValidationException(result);
}
// Transform data if validation succeeded and data is available
if (result.isValid && result.data != null) {
response.data = result.data;
} else if (result.isValid && response.data is Map<String, dynamic>) {
response.data = _validator.transformFromApi(
response.data as Map<String, dynamic>,
);
}
// Store validation result in response for debugging
if (kDebugMode) {
response.extra['validationResult'] = result;
}
} catch (e) {
if (e is ValidationException) {
handler.reject(
DioException(
requestOptions: response.requestOptions,
response: response,
error: e,
type: DioExceptionType.unknown,
message: 'Response validation failed: ${e.result.message}',
),
);
return;
} else {
2025-08-20 22:15:26 +05:30
DebugLogger.error('Response validation error', e);
2025-08-10 01:20:45 +05:30
}
}
}
handler.next(response);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
// Try to validate error responses too
if (enableResponseValidation && err.response?.data != null) {
try {
final result = _validator.validateResponse(
err.response!.data,
err.requestOptions.path,
method: err.requestOptions.method,
statusCode: err.response!.statusCode,
);
if (logValidationResults) {
_logValidationResult(
result,
'ERROR_RESPONSE',
err.requestOptions.path,
err.requestOptions.method,
statusCode: err.response!.statusCode,
);
}
// Transform error response data
if (result.isValid && result.data != null) {
err.response!.data = result.data;
} else if (result.isValid &&
err.response!.data is Map<String, dynamic>) {
err.response!.data = _validator.transformFromApi(
err.response!.data as Map<String, dynamic>,
);
}
// Store validation result for debugging
if (kDebugMode) {
err.response!.extra['validationResult'] = result;
}
} catch (e) {
2025-08-20 22:15:26 +05:30
DebugLogger.error('Error response validation failed', e);
2025-08-10 01:20:45 +05:30
}
}
handler.next(err);
}
/// Log validation results in a structured format
void _logValidationResult(
ValidationResult result,
String type,
String path,
String method, {
int? statusCode,
}) {
if (!logValidationResults) return;
final statusText = statusCode != null ? ' ($statusCode)' : '';
final icon = result.isValid ? '' : '';
2025-08-20 22:15:26 +05:30
DebugLogger.validation(
2025-08-10 01:20:45 +05:30
'$icon Validation $type: ${method.toUpperCase()} $path$statusText - ${result.status.name}',
);
if (result.hasErrors) {
2025-08-20 22:15:26 +05:30
DebugLogger.error(' Errors: ${result.errors.join(', ')}');
2025-08-10 01:20:45 +05:30
}
if (result.hasWarnings) {
2025-08-20 22:15:26 +05:30
DebugLogger.warning(' Warnings: ${result.warnings.join(', ')}');
2025-08-10 01:20:45 +05:30
}
if (result.message.isNotEmpty &&
result.status != ValidationStatus.success) {
2025-08-20 22:15:26 +05:30
DebugLogger.info(' Message: ${result.message}');
2025-08-10 01:20:45 +05:30
}
}
/// Get validation statistics
Map<String, dynamic> getStats() {
return {
'requestValidationEnabled': enableRequestValidation,
'responseValidationEnabled': enableResponseValidation,
'throwOnError': throwOnValidationError,
'loggingEnabled': logValidationResults,
'validatorInitialized': _validator.isInitialized,
};
}
}