swagger_generator_flutter/lib/validators/rules/structure_rules.dart

376 lines
11 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:swagger_generator_flutter/core/models.dart';
import 'package:swagger_generator_flutter/validators/core/validation_context.dart';
import 'package:swagger_generator_flutter/validators/core/validation_result.dart';
import 'package:swagger_generator_flutter/validators/core/validation_rule.dart';
/// 文档结构验证规则
class StructureValidationRule extends ValidationRule {
@override
String get id => 'structure_validation';
@override
String get name => '文档结构验证';
@override
ValidationResult validate(ValidationContext context) {
final document = context.document;
final errors = <ValidationError>[];
final warnings = <ValidationWarning>[];
_validatePathStructure(document, errors, warnings);
_validateComponentReferences(document, errors, warnings);
_validateSecurityReferences(document, errors, warnings);
if (context.options.validateExamples) {
_validateExampleConsistency(document, errors, warnings);
}
return ValidationResult(
isValid: errors.isEmpty,
errors: errors,
warnings: warnings,
);
}
void _validatePathStructure(
SwaggerDocument document,
List<ValidationError> errors,
List<ValidationWarning> warnings,
) {
final pathPatterns =
document.paths.keys.map((key) => key.pattern).toSet().toList();
// 检查路径冲突
for (var i = 0; i < pathPatterns.length; i++) {
for (var j = i + 1; j < pathPatterns.length; j++) {
if (_pathsConflict(pathPatterns[i], pathPatterns[j])) {
errors.add(
ValidationError(
path: 'paths',
message: '路径冲突: "${pathPatterns[i]}" 与 "${pathPatterns[j]}"',
type: ValidationErrorType.constraint,
suggestion: '确保路径模式不会产生歧义',
),
);
}
}
}
// 检查路径参数一致性
document.paths.forEach((routeKey, path) {
final pattern = routeKey.pattern;
final pathParams = _extractPathParameters(pattern);
final declaredParams = path.parameters
.where((p) => p.location == ParameterLocation.path)
.map((p) => p.name)
.toSet();
// 检查路径中的参数是否都有声明
for (final param in pathParams) {
if (!declaredParams.contains(param)) {
errors.add(
ValidationError(
path: 'paths["$pattern"][${path.method.value}].parameters',
message: '路径参数 "$param" 未在参数列表中声明',
type: ValidationErrorType.reference,
suggestion: '添加路径参数的声明',
),
);
}
}
// 检查声明的路径参数是否都在路径中使用
for (final param in declaredParams) {
if (!pathParams.contains(param)) {
warnings.add(
ValidationWarning(
path: 'paths["$pattern"][${path.method.value}].parameters',
message: '声明的路径参数 "$param" 未在路径中使用',
suggestion: '移除未使用的参数声明或修正路径',
),
);
}
}
});
}
void _validateComponentReferences(
SwaggerDocument document,
List<ValidationError> errors,
List<ValidationWarning> warnings,
) {
final schemas = document.components.schemas.keys.toSet();
final securitySchemes = document.components.securitySchemes.keys.toSet();
// 收集所有引用
final schemaRefs = <String>{};
final securityRefs = <String>{};
_collectReferences(document, schemaRefs, securityRefs);
// 检查未定义的引用
for (final ref in schemaRefs) {
if (!schemas.contains(ref)) {
errors.add(
ValidationError(
path: 'components.schemas',
message: '引用的 schema "$ref" 未定义',
type: ValidationErrorType.reference,
suggestion: '定义缺失的 schema 或修正引用',
),
);
}
}
for (final ref in securityRefs) {
if (!securitySchemes.contains(ref)) {
errors.add(
ValidationError(
path: 'components.securitySchemes',
message: '引用的安全方案 "$ref" 未定义',
type: ValidationErrorType.reference,
suggestion: '定义缺失的安全方案或修正引用',
),
);
}
}
// 检查未使用的组件
for (final schema in schemas) {
if (!schemaRefs.contains(schema)) {
warnings.add(
ValidationWarning(
path: 'components.schemas["$schema"]',
message: 'Schema "$schema" 已定义但未被使用',
suggestion: '移除未使用的 schema 或添加引用',
),
);
}
}
}
void _validateSecurityReferences(
SwaggerDocument document,
List<ValidationError> errors,
List<ValidationWarning> warnings,
) {
final definedSchemes = document.components.securitySchemes.keys.toSet();
// 检查全局安全要求
for (var i = 0; i < document.security.length; i++) {
final requirement = document.security[i];
for (final schemeName in requirement.schemeNames) {
if (!definedSchemes.contains(schemeName)) {
errors.add(
ValidationError(
path: 'security[$i]',
message: '引用的安全方案 "$schemeName" 未定义',
type: ValidationErrorType.reference,
suggestion: '在 components.securitySchemes 中定义该安全方案',
),
);
}
}
}
// 检查操作级别的安全要求
document.paths.forEach((routeKey, path) {
final pattern = routeKey.pattern;
for (var i = 0; i < path.security.length; i++) {
final requirement = path.security[i];
for (final schemeName in requirement.schemeNames) {
if (!definedSchemes.contains(schemeName)) {
errors.add(
ValidationError(
path: 'paths["$pattern"][${path.method.value}].security[$i]',
message: '引用的安全方案 "$schemeName" 未定义',
type: ValidationErrorType.reference,
suggestion: '在 components.securitySchemes 中定义该安全方案',
),
);
}
}
}
});
}
void _validateExampleConsistency(
SwaggerDocument document,
List<ValidationError> errors,
List<ValidationWarning> warnings,
) {
document.paths.forEach((routeKey, path) {
final pattern = routeKey.pattern;
// 验证请求体示例
if (path.requestBody != null) {
for (final entry in path.requestBody!.content.entries) {
_validateMediaTypeExamples(
entry.value,
'$pattern[${path.method.value}]'
'.requestBody.content["${entry.key}"]',
errors,
warnings,
);
}
}
// 验证响应示例
for (final responseEntry in path.responses.entries) {
for (final contentEntry in responseEntry.value.content.entries) {
_validateMediaTypeExamples(
contentEntry.value,
'$pattern[${path.method.value}]'
'.responses["${responseEntry.key}"].content["${contentEntry.key}"]',
errors,
warnings,
);
}
}
});
}
void _validateMediaTypeExamples(
ApiMediaType mediaType,
String path,
List<ValidationError> errors,
List<ValidationWarning> warnings,
) {
// 检查 example 和 examples 不能同时存在
if (mediaType.example != null && mediaType.examples.isNotEmpty) {
warnings.add(
ValidationWarning(
path: path,
message: 'example 和 examples 不应同时存在',
suggestion: '使用 examples 对象来提供多个示例',
),
);
}
// 验证示例格式
if (mediaType.example != null && mediaType.schema != null) {
_validateExampleAgainstSchema(
mediaType.example,
mediaType.schema!,
'$path.example',
errors,
warnings,
);
}
}
void _validateExampleAgainstSchema(
dynamic example,
Map<String, dynamic> schema,
String path,
List<ValidationError> errors,
List<ValidationWarning> warnings,
) {
if (schema.containsKey(r'$ref')) {
// 引用在此处无法解析,跳过验证
return;
}
final nullable = schema['nullable'] == true;
if (example == null) {
if (!nullable) {
errors.add(
ValidationError(
path: path,
message: '示例值为 null但 schema 不允许 null',
type: ValidationErrorType.type,
suggestion: '更新 example 或在 schema 中标记 nullable',
),
);
}
return;
}
// TODO: 实现更完整的示例验证逻辑
}
bool _pathsConflict(String path1, String path2) {
// 简单的冲突检测:将参数替换为 {} 后比较
final p1 = path1.replaceAll(RegExp(r'\{[^}]+\}'), '{}');
final p2 = path2.replaceAll(RegExp(r'\{[^}]+\}'), '{}');
return p1 == p2;
}
Set<String> _extractPathParameters(String path) {
final regex = RegExp(r'\{(\w+)\}');
final matches = regex.allMatches(path);
return matches.map((match) => match.group(1)!).toSet();
}
void _collectReferences(
SwaggerDocument document,
Set<String> schemaRefs,
Set<String> securityRefs,
) {
// 收集路径中的引用
for (final path in document.paths.values) {
// ApiParameter 目前没有暴露 schema 属性,暂时无法收集参数中的引用
// for (final param in path.parameters) {
// if (param.schema != null) {
// _collectSchemaRefs(param.schema!, schemaRefs);
// }
// }
if (path.requestBody != null) {
for (final mediaType in path.requestBody!.content.values) {
if (mediaType.schema != null) {
_collectSchemaRefs(mediaType.schema!, schemaRefs);
}
}
}
for (final response in path.responses.values) {
for (final mediaType in response.content.values) {
if (mediaType.schema != null) {
_collectSchemaRefs(mediaType.schema!, schemaRefs);
}
}
}
}
// 收集组件中的引用
for (final model in document.components.schemas.values) {
for (final property in model.properties.values) {
if (property.type == PropertyType.reference &&
property.reference != null) {
schemaRefs.add(property.reference!);
}
}
}
}
void _collectSchemaRefs(
Map<String, dynamic> schema,
Set<String> refs,
) {
if (schema.containsKey(r'$ref')) {
final ref = schema[r'$ref'] as String;
// 假设引用格式为 #/components/schemas/Name
final parts = ref.split('/');
if (parts.length >= 4 &&
parts[1] == 'components' &&
parts[2] == 'schemas') {
refs.add(parts.last);
}
}
// 递归检查 items, properties 等
if (schema.containsKey('items')) {
_collectSchemaRefs(schema['items'] as Map<String, dynamic>, refs);
}
if (schema.containsKey('properties')) {
final props = schema['properties'] as Map<String, dynamic>;
for (final value in props.values) {
if (value is Map<String, dynamic>) {
_collectSchemaRefs(value, refs);
}
}
}
}
}