159 lines
4.8 KiB
Dart
159 lines
4.8 KiB
Dart
/// 增强的 OpenAPI 验证器
|
|
/// 集成详细的错误报告和修复建议
|
|
library;
|
|
|
|
import 'package:swagger_generator_flutter/core/error_reporter.dart';
|
|
import 'package:swagger_generator_flutter/core/models.dart';
|
|
import 'package:swagger_generator_flutter/validators/schema_validator.dart';
|
|
|
|
/// 增强的 OpenAPI 验证器
|
|
class EnhancedValidator {
|
|
EnhancedValidator({
|
|
SchemaValidator? schemaValidator,
|
|
bool includeWarnings = true,
|
|
}) : _schemaValidator = schemaValidator ?? SchemaValidator(),
|
|
_errorReporter = ErrorReporter(),
|
|
_includeWarnings = includeWarnings;
|
|
|
|
final SchemaValidator _schemaValidator;
|
|
final ErrorReporter _errorReporter;
|
|
final bool _includeWarnings;
|
|
|
|
/// 获取错误报告器
|
|
ErrorReporter get errorReporter => _errorReporter;
|
|
|
|
/// 验证 OpenAPI 文档
|
|
bool validateDocument(SwaggerDocument document) {
|
|
_errorReporter.clear();
|
|
|
|
// 使用基础验证器进行验证
|
|
final result = _schemaValidator.validateDocument(document);
|
|
|
|
// 转换错误
|
|
for (final error in result.errors) {
|
|
_errorReporter.reportError(
|
|
id: 'VALIDATION_ERROR',
|
|
title: 'Validation Error',
|
|
description: error.message,
|
|
severity: _mapSeverity(error.type),
|
|
category: ErrorCategory.validation,
|
|
jsonPath: error.path,
|
|
suggestions: error.suggestion != null
|
|
? [FixSuggestion(description: error.suggestion!)]
|
|
: [],
|
|
);
|
|
}
|
|
|
|
// 转换警告
|
|
if (_includeWarnings) {
|
|
for (final warning in result.warnings) {
|
|
_errorReporter.reportError(
|
|
id: 'VALIDATION_WARNING',
|
|
title: 'Validation Warning',
|
|
description: warning.message,
|
|
severity: ErrorSeverity.warning,
|
|
category: ErrorCategory.bestPractice,
|
|
jsonPath: warning.path,
|
|
suggestions: warning.suggestion != null
|
|
? [FixSuggestion(description: warning.suggestion!)]
|
|
: [],
|
|
);
|
|
}
|
|
|
|
// 额外的最佳实践检查
|
|
_checkBestPractices(document);
|
|
}
|
|
|
|
return !_errorReporter.hasErrorsOrCritical;
|
|
}
|
|
|
|
ErrorSeverity _mapSeverity(ValidationErrorType type) {
|
|
switch (type) {
|
|
case ValidationErrorType.required:
|
|
case ValidationErrorType.type:
|
|
case ValidationErrorType.format:
|
|
case ValidationErrorType.reference:
|
|
case ValidationErrorType.constraint:
|
|
case ValidationErrorType.security:
|
|
case ValidationErrorType.compatibility:
|
|
return ErrorSeverity.error;
|
|
}
|
|
}
|
|
|
|
/// 检查最佳实践
|
|
void _checkBestPractices(SwaggerDocument document) {
|
|
// 检查是否使用了标签
|
|
final hasTaggedOperations =
|
|
document.paths.values.any((path) => path.tags.isNotEmpty);
|
|
if (!hasTaggedOperations) {
|
|
_errorReporter.reportError(
|
|
id: 'NO_OPERATION_TAGS',
|
|
title: 'No Operation Tags',
|
|
description: 'Consider using tags to organize your API operations.',
|
|
severity: ErrorSeverity.info,
|
|
category: ErrorCategory.bestPractice,
|
|
jsonPath: 'paths',
|
|
suggestions: [
|
|
const FixSuggestion(
|
|
description: 'Add tags to operations',
|
|
codeExample: '"tags": ["users"]',
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
// 检查操作 ID
|
|
document.paths.forEach((routeKey, path) {
|
|
if (path.operationId.isEmpty) {
|
|
final pathPattern = routeKey.pattern;
|
|
final method = path.method;
|
|
_errorReporter.reportError(
|
|
id: 'MISSING_OPERATION_ID',
|
|
title: 'Missing Operation ID',
|
|
description: 'Operation should have an operationId for '
|
|
'better code generation.',
|
|
severity: ErrorSeverity.warning,
|
|
category: ErrorCategory.bestPractice,
|
|
jsonPath: 'paths["$pathPattern"][${method.value}].operationId',
|
|
suggestions: [
|
|
FixSuggestion(
|
|
description: 'Add a unique operationId',
|
|
codeExample: '"operationId": '
|
|
'"${_generateOperationId(pathPattern, method)}"',
|
|
),
|
|
],
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
/// 生成操作 ID
|
|
String _generateOperationId(String path, HttpMethod method) {
|
|
final pathParts = path
|
|
.split('/')
|
|
.where((part) => part.isNotEmpty && !part.startsWith('{'))
|
|
.toList();
|
|
final methodPrefix = method.value.toLowerCase();
|
|
|
|
if (pathParts.length >= 3) {
|
|
// 移除 api/v1 前缀
|
|
pathParts.removeRange(0, 2);
|
|
}
|
|
|
|
final nameParts = pathParts.map(_toPascalCase).join();
|
|
return '$methodPrefix$nameParts';
|
|
}
|
|
|
|
/// 转换为 PascalCase
|
|
String _toPascalCase(String input) {
|
|
return input
|
|
.split('_')
|
|
.map(
|
|
(word) => word.isEmpty
|
|
? ''
|
|
: word[0].toUpperCase() + word.substring(1).toLowerCase(),
|
|
)
|
|
.join();
|
|
}
|
|
}
|