import 'package:swagger_generator_flutter/core/models.dart'; import 'package:swagger_generator_flutter/pipeline/validate/core/validation_context.dart'; import 'package:swagger_generator_flutter/pipeline/validate/core/validation_result.dart'; import 'package:swagger_generator_flutter/pipeline/validate/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 = []; final warnings = []; _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 errors, List 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 errors, List warnings, ) { final schemas = document.components.schemas.keys.toSet(); final securitySchemes = document.components.securitySchemes.keys.toSet(); // 收集所有引用 final schemaRefs = {}; final securityRefs = {}; _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 errors, List 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 errors, List 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 errors, List 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 schema, String path, List errors, List 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 _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 schemaRefs, Set 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 schema, Set 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, refs); } if (schema.containsKey('properties')) { final props = schema['properties'] as Map; for (final value in props.values) { if (value is Map) { _collectSchemaRefs(value, refs); } } } } }