diff --git a/lib/parsers/swagger_data_parser.dart b/lib/parsers/swagger_data_parser.dart index e35ec09..b75efa7 100644 --- a/lib/parsers/swagger_data_parser.dart +++ b/lib/parsers/swagger_data_parser.dart @@ -1,567 +1,5 @@ -import 'dart:convert'; +/// Backward-compat shim for SwaggerDataParser +library; -import 'package:swagger_generator_flutter/core/config.dart'; -import 'package:swagger_generator_flutter/core/exceptions.dart'; -import 'package:swagger_generator_flutter/core/models.dart'; -import 'package:swagger_generator_flutter/parsers/swagger_fetcher.dart'; -import 'package:swagger_generator_flutter/utils/cache_manager.dart'; -import 'package:swagger_generator_flutter/utils/logger.dart'; -import 'package:swagger_generator_flutter/utils/performance_monitor.dart'; -import 'package:swagger_generator_flutter/utils/reference_resolver.dart'; -import 'package:swagger_generator_flutter/utils/string_utils.dart'; +export 'package:swagger_generator_flutter/pipeline/parse/impl/swagger_data_parser.dart'; -/// Swagger数据解析器 -/// 负责解析Swagger JSON文档并提取相关信息 -class SwaggerDataParser { - SwaggerDataParser({SwaggerFetcher? fetcher}) - : _fetcher = fetcher ?? SwaggerFetcher(), - _cacheManager = CacheManager(), - _performanceMonitor = PerformanceMonitor(); - - final SwaggerFetcher _fetcher; - final CacheManager _cacheManager; - final PerformanceMonitor _performanceMonitor; - - // 缓存解析结果 - final Map _cachedDocuments = {}; - - /// 获取并解析Swagger JSON文档 - /// [url] 可选参数,如果不传则使用配置中的第一个 URL - Future fetchAndParseSwaggerDocument([String? url]) async { - final swaggerUrl = url ?? SwaggerConfig.swaggerJsonUrls.first; - - // 如果有缓存,直接返回缓存结果 - if (_cachedDocuments.containsKey(swaggerUrl)) { - appLogger.info('📦 使用缓存的文档: $swaggerUrl'); - return _cachedDocuments[swaggerUrl]!; - } - - return _performanceMonitor.measure( - 'fetchAndParseSwaggerDocument', - () async { - try { - // 使用 Fetcher 获取内容 - final content = await _fetcher.fetch(swaggerUrl); - - // 解析 JSON - final jsonData = json.decode(content) as Map; - - // 解析文档 - final document = await parseSwaggerDocument(jsonData, swaggerUrl); - - // 更新缓存 - _cachedDocuments[swaggerUrl] = document; - appLogger.info('✅ Swagger文档解析完成'); - - return document; - } on Object catch (e) { - if (e is SwaggerParseException) { - rethrow; - } - throw SwaggerParseException( - '获取Swagger文档失败', - url: swaggerUrl, - details: e.toString(), - ); - } - }, - ); - } - - /// 解析Swagger JSON文档 - Future parseSwaggerDocument( - Map jsonData, [ - String? sourceUrl, - ]) async { - return _performanceMonitor.measure('parseSwaggerDocument', () async { - // 计算内容哈希作为缓存键 - final contentHash = json.encode(jsonData).hashCode.toString(); - final cacheKey = 'swagger_doc_$contentHash'; - - // 尝试从缓存获取 - final cachedResult = _cacheManager.get(cacheKey); - if (cachedResult != null) { - if (sourceUrl != null) { - _cachedDocuments[sourceUrl] = cachedResult; - } - return cachedResult; - } - - // 解析文档基本信息 - final info = jsonData['info'] as Map? ?? {}; - final title = info['title'] as String? ?? 'API Documentation'; - final version = info['version'] as String? ?? '1.0.0'; - final description = info['description'] as String? ?? ''; - - // ✨ 分析 schema 使用情况(在解析 components 之前) - final schemaUsage = _analyzeSchemaUsage(jsonData); - - // 解析 servers (OpenAPI 3.0) - final servers = _parseServers(jsonData); - - // 解析 components (OpenAPI 3.0),传入使用情况分析结果 - final components = _parseComponents(jsonData, schemaUsage); - - // 解析tags信息 (用于获取控制器描述) - final tagsInfo = _parseTagsInfo(jsonData); - - // 解析API路径 - final paths = _parseApiPaths(jsonData); - - // 解析API模型 (从 components 中提取) - final models = components.schemas; - - // 解析API控制器 (传入tags信息) - final controllers = _parseApiControllers(paths, tagsInfo); - - final document = SwaggerDocument( - title: title, - version: version, - description: description, - servers: servers, - components: components, - paths: paths, - models: models, - controllers: controllers, - ); - - // 缓存结果 - _cacheManager.put(cacheKey, document); - if (sourceUrl != null) { - _cachedDocuments[sourceUrl] = document; - } - - return document; - }); - } - - /// 解析 servers 配置 (OpenAPI 3.0) - List _parseServers(Map jsonData) { - final servers = []; - - try { - final serversJson = jsonData['servers'] as List?; - if (serversJson != null) { - for (final serverJson in serversJson) { - if (serverJson is Map) { - final server = ApiServer.fromJson(serverJson); - servers.add(server); - } - } - } - - // 如果没有 servers 配置,提供默认值 - if (servers.isEmpty) { - servers.add(const ApiServer(url: '/')); - } - } on Object catch (e) { - appLogger.warning('⚠️ 解析servers配置时发生错误: $e'); - // 提供默认服务器配置 - servers.add(const ApiServer(url: '/')); - } - - return servers; - } - - /// 解析 components 配置 (OpenAPI 3.0) - ApiComponents _parseComponents( - Map jsonData, - Map> schemaUsage, - ) { - try { - final componentsJson = jsonData['components'] as Map?; - if (componentsJson != null) { - // 使用引用解析器处理复杂嵌套和循环引用 - final resolver = ReferenceResolver(); - final resolvedSchemas = resolver.resolveModels(componentsJson); - - // ✨ 根据使用情况更新模型的 usageType - final schemasWithUsageType = {}; - resolvedSchemas.forEach((name, model) { - final usages = schemaUsage[name] ?? {}; - final ModelUsageType usageType; - - if (usages.isEmpty) { - usageType = ModelUsageType.unknown; - } else if (usages.length == 1) { - usageType = usages.first; - } else { - // 既用于请求又用于响应 - usageType = ModelUsageType.common; - } - - schemasWithUsageType[name] = model.copyWithUsageType(usageType); - }); - - // 创建 ApiComponents,但使用解析后的 schemas - final components = ApiComponents.fromJson(componentsJson); - return ApiComponents( - schemas: schemasWithUsageType, - responses: components.responses, - parameters: components.parameters, - examples: components.examples, - requestBodies: components.requestBodies, - headers: components.headers, - securitySchemes: components.securitySchemes, - links: components.links, - callbacks: components.callbacks, - ); - } - } on Object catch (e) { - appLogger.warning('⚠️ 解析components配置时发生错误: $e'); - } - - return const ApiComponents(); - } - - /// 解析tags信息 - Map _parseTagsInfo(Map jsonData) { - final tagsInfo = {}; - - try { - final tags = jsonData['tags'] as List?; - if (tags != null) { - for (final tag in tags) { - if (tag is Map) { - final name = tag['name'] as String?; - final description = tag['description'] as String?; - if (name != null && description != null) { - tagsInfo[name] = description; - } - } - } - } - } on Object catch (e) { - appLogger.warning('⚠️ 解析tags信息时发生错误: $e'); - } - - return tagsInfo; - } - - /// 解析API路径 - Map _parseApiPaths(Map jsonData) { - final paths = {}; - final pathsData = jsonData['paths'] as Map?; - - if (pathsData == null) { - throw SwaggerParseException('未发现API路径定义'); - } - - try { - pathsData.forEach((pathKey, pathValue) { - if (pathValue is Map) { - pathValue.forEach((methodKey, methodValue) { - if (methodValue is Map) { - final method = HttpMethod.fromString(methodKey); - final apiPath = ApiPath.fromJson( - pathKey, - method, - methodValue, - ); - final key = ApiPathKey.from(pathKey, method); - paths[key] = apiPath; - } - }); - } - }); - } catch (e) { - throw SwaggerParseException('解析API路径失败', details: e.toString()); - } - - return paths; - } - - /// 解析API控制器 - Map _parseApiControllers( - Map paths, - Map tagsInfo, - ) { - final controllers = >{}; - - try { - // 根据tags分组API路径 - for (final apiPath in paths.values) { - for (final tag in apiPath.tags) { - controllers.putIfAbsent(tag, () => []).add(apiPath); - } - } - - // 创建控制器对象 - final result = {}; - controllers.forEach((name, pathList) { - // 从tags信息中获取描述 - final swaggerDescription = tagsInfo[name]; - - // 使用通用的描述获取方法 - final description = SwaggerConfig.getControllerDescription( - name, - swaggerDescription: swaggerDescription, - ); - - result[name] = ApiController( - name: name, - description: description, - paths: pathList, - ); - }); - - return result; - } catch (e) { - throw SwaggerParseException('解析API控制器失败', details: e.toString()); - } - } - - /// 解析属性类型 - String parsePropertyType(Map propData) { - try { - // 直接类型 - if (propData['type'] != null) { - return propData['type'] as String; - } - - // 引用类型 ($ref) - if (propData[r'$ref'] != null) { - final ref = propData[r'$ref'] as String; - // 从 #/components/schemas/ModelName 或 #/definitions/ModelName 中提取类型名 - final parts = ref.split('/'); - if (parts.isNotEmpty) { - return parts.last; - } - } - - // 数组类型 - if (propData['items'] != null) { - final items = propData['items'] as Map; - final itemType = parsePropertyType(items); - return 'array<$itemType>'; - } - - // 默认类型 - return 'string'; - } catch (e) { - throw SwaggerParseException('解析属性类型失败', details: e.toString()); - } - } - - /// 获取Dart类型映射 - String getDartType(String swaggerType, String? format) { - switch (swaggerType.toLowerCase()) { - case 'string': - switch (format?.toLowerCase()) { - case 'date': - case 'date-time': - return 'DateTime'; - case 'byte': - case 'binary': - return 'String'; - default: - return 'String'; - } - case 'integer': - switch (format?.toLowerCase()) { - case 'int64': - return 'int'; - case 'int32': - default: - return 'int'; - } - case 'number': - switch (format?.toLowerCase()) { - case 'float': - case 'double': - return 'double'; - default: - return 'double'; - } - case 'boolean': - return 'bool'; - case 'array': - return 'List'; - case 'object': - return 'Map'; - case 'file': - return 'String'; - default: - // 检查是否为数组类型 - if (swaggerType.startsWith('array<') && swaggerType.endsWith('>')) { - final itemType = swaggerType.substring(6, swaggerType.length - 1); - final dartItemType = getDartType(itemType, null); - return 'List<$dartItemType>'; - } - - // 默认为自定义类型 - return StringUtils.generateClassName(swaggerType); - } - } - - /// 清除缓存 - void clearCache() { - _cachedDocuments.clear(); - _cacheManager.clear(); - } - - /// 获取文档统计信息 - Map getDocumentStats() { - if (_cachedDocuments.isEmpty) { - return {}; - } - - // 使用第一个缓存的文档 - final doc = _cachedDocuments.values.first; - - // 统计HTTP方法 - final methodStats = {}; - for (final path in doc.paths.values) { - final method = path.method.value; - methodStats[method] = (methodStats[method] ?? 0) + 1; - } - - // 统计模型类型 - final modelStats = {}; - for (final model in doc.models.values) { - if (model.isEnum) { - modelStats['enum'] = (modelStats['enum'] ?? 0) + 1; - } else { - modelStats['class'] = (modelStats['class'] ?? 0) + 1; - } - } - - return { - 'title': doc.title, - 'version': doc.version, - 'paths': doc.paths.length, - 'models': doc.models.length, - 'controllers': doc.controllers.length, - 'methods': methodStats, - 'modelTypes': modelStats, - }; - } - - /// 分析 schemas 在 API 中的使用情况 - /// 返回每个 schema 的用途类型集合 - Map> _analyzeSchemaUsage( - Map jsonData, - ) { - final schemaUsage = >{}; - - final pathsData = jsonData['paths'] as Map?; - if (pathsData == null) return schemaUsage; - - pathsData.forEach((pathKey, pathValue) { - if (pathValue is! Map) return; - - pathValue.forEach((methodKey, methodValue) { - if (methodValue is! Map) return; - - // 分析 requestBody 中使用的 schemas - final requestBody = methodValue['requestBody'] as Map?; - if (requestBody != null) { - _extractSchemasFromContent( - requestBody['content'] as Map?, - schemaUsage, - ModelUsageType.request, - ); - } - - // 分析 parameters 中使用的 schemas - final parameters = methodValue['parameters'] as List?; - if (parameters != null) { - for (final param in parameters) { - if (param is Map) { - final schema = param['schema'] as Map?; - if (schema != null) { - _extractSchemaRef(schema, schemaUsage, ModelUsageType.request); - } - } - } - } - - // 分析 responses 中使用的 schemas - final responses = methodValue['responses'] as Map?; - if (responses != null) { - responses.forEach((statusCode, responseValue) { - if (responseValue is Map) { - _extractSchemasFromContent( - responseValue['content'] as Map?, - schemaUsage, - ModelUsageType.response, - ); - } - }); - } - }); - }); - - return schemaUsage; - } - - /// 从 content 中提取 schema 引用 - void _extractSchemasFromContent( - Map? content, - Map> schemaUsage, - ModelUsageType usageType, - ) { - if (content == null) return; - - content.forEach((mediaType, mediaTypeValue) { - if (mediaTypeValue is Map) { - final schema = mediaTypeValue['schema'] as Map?; - if (schema != null) { - _extractSchemaRef(schema, schemaUsage, usageType); - } - } - }); - } - - /// 递归提取 schema $ref 并记录用途 - void _extractSchemaRef( - Map schema, - Map> schemaUsage, - ModelUsageType usageType, - ) { - // 处理 $ref - final ref = schema[r'$ref'] as String?; - if (ref != null) { - // 从 #/components/schemas/ModelName 提取 ModelName - final schemaName = ref.split('/').last; - schemaUsage.putIfAbsent(schemaName, () => {}).add(usageType); - return; // $ref 存在时,不再处理其他字段 - } - - // 处理数组类型 - if (schema['type'] == 'array') { - final items = schema['items'] as Map?; - if (items != null) { - _extractSchemaRef(items, schemaUsage, usageType); - } - } - - // 处理 allOf/oneOf/anyOf - for (final key in ['allOf', 'oneOf', 'anyOf']) { - final schemas = schema[key] as List?; - if (schemas != null) { - for (final s in schemas) { - if (s is Map) { - _extractSchemaRef(s, schemaUsage, usageType); - } - } - } - } - - // 处理对象属性 - final properties = schema['properties'] as Map?; - if (properties != null) { - properties.forEach((propName, propSchema) { - if (propSchema is Map) { - _extractSchemaRef(propSchema, schemaUsage, usageType); - } - }); - } - - // 处理 additionalProperties - final additionalProperties = schema['additionalProperties']; - if (additionalProperties is Map) { - _extractSchemaRef(additionalProperties, schemaUsage, usageType); - } - } -} diff --git a/lib/parsers/swagger_fetcher.dart b/lib/parsers/swagger_fetcher.dart index 882005c..cef1bb1 100644 --- a/lib/parsers/swagger_fetcher.dart +++ b/lib/parsers/swagger_fetcher.dart @@ -1,83 +1,4 @@ -import 'dart:io'; +/// Backward-compat shim for parser fetcher +library; -import 'package:http/http.dart' as http; -import 'package:swagger_generator_flutter/core/config.dart'; -import 'package:swagger_generator_flutter/core/exceptions.dart'; -import 'package:swagger_generator_flutter/utils/logger.dart'; -import 'package:swagger_generator_flutter/utils/path_resolver.dart'; - -/// Swagger 数据获取器 -/// 负责从本地文件或远程 URL 获取 Swagger 文档内容 -class SwaggerFetcher { - /// 获取 Swagger 文档内容 - /// [url] 可以是本地文件路径 (file://) 或远程 URL (http://, https://) - Future fetch(String url) async { - appLogger.info('🔄 正在获取Swagger JSON文档: $url'); - - if (url.startsWith('http://') || url.startsWith('https://')) { - return _fetchFromUrl(url); - } else { - return _fetchFromFile(url); - } - } - - /// 从远程 URL 获取 - Future _fetchFromUrl(String url) async { - try { - final response = await http.get( - Uri.parse(url), - headers: SwaggerConfig.httpHeaders, - ); - - if (response.statusCode == 200) { - return response.body; - } else { - throw SwaggerParseException( - 'HTTP请求失败', - url: url, - statusCode: response.statusCode, - details: 'HTTP响应状态码: ${response.statusCode}', - ); - } - } catch (e) { - if (e is SwaggerParseException) rethrow; - throw SwaggerParseException( - '获取远程Swagger文档失败', - url: url, - details: e.toString(), - ); - } - } - - /// 从本地文件获取 - Future _fetchFromFile(String url) async { - try { - // 移除 file:// 前缀(如果有) - var filePath = url; - if (filePath.startsWith('file://')) { - filePath = filePath.substring(7); - } - - // 使用 PathResolver 解析路径 - final resolvedPath = PathResolver.resolvePath(filePath); - final file = File(resolvedPath); - - if (!file.existsSync()) { - throw SwaggerParseException( - '本地文件不存在', - url: url, - details: '文件路径: $resolvedPath', - ); - } - - return await file.readAsString(); - } catch (e) { - if (e is SwaggerParseException) rethrow; - throw SwaggerParseException( - '读取本地Swagger文件失败', - url: url, - details: e.toString(), - ); - } - } -} +export 'package:swagger_generator_flutter/pipeline/parse/impl/swagger_fetcher.dart'; diff --git a/lib/pipeline/parse/impl/swagger_data_parser.dart b/lib/pipeline/parse/impl/swagger_data_parser.dart new file mode 100644 index 0000000..e35ec09 --- /dev/null +++ b/lib/pipeline/parse/impl/swagger_data_parser.dart @@ -0,0 +1,567 @@ +import 'dart:convert'; + +import 'package:swagger_generator_flutter/core/config.dart'; +import 'package:swagger_generator_flutter/core/exceptions.dart'; +import 'package:swagger_generator_flutter/core/models.dart'; +import 'package:swagger_generator_flutter/parsers/swagger_fetcher.dart'; +import 'package:swagger_generator_flutter/utils/cache_manager.dart'; +import 'package:swagger_generator_flutter/utils/logger.dart'; +import 'package:swagger_generator_flutter/utils/performance_monitor.dart'; +import 'package:swagger_generator_flutter/utils/reference_resolver.dart'; +import 'package:swagger_generator_flutter/utils/string_utils.dart'; + +/// Swagger数据解析器 +/// 负责解析Swagger JSON文档并提取相关信息 +class SwaggerDataParser { + SwaggerDataParser({SwaggerFetcher? fetcher}) + : _fetcher = fetcher ?? SwaggerFetcher(), + _cacheManager = CacheManager(), + _performanceMonitor = PerformanceMonitor(); + + final SwaggerFetcher _fetcher; + final CacheManager _cacheManager; + final PerformanceMonitor _performanceMonitor; + + // 缓存解析结果 + final Map _cachedDocuments = {}; + + /// 获取并解析Swagger JSON文档 + /// [url] 可选参数,如果不传则使用配置中的第一个 URL + Future fetchAndParseSwaggerDocument([String? url]) async { + final swaggerUrl = url ?? SwaggerConfig.swaggerJsonUrls.first; + + // 如果有缓存,直接返回缓存结果 + if (_cachedDocuments.containsKey(swaggerUrl)) { + appLogger.info('📦 使用缓存的文档: $swaggerUrl'); + return _cachedDocuments[swaggerUrl]!; + } + + return _performanceMonitor.measure( + 'fetchAndParseSwaggerDocument', + () async { + try { + // 使用 Fetcher 获取内容 + final content = await _fetcher.fetch(swaggerUrl); + + // 解析 JSON + final jsonData = json.decode(content) as Map; + + // 解析文档 + final document = await parseSwaggerDocument(jsonData, swaggerUrl); + + // 更新缓存 + _cachedDocuments[swaggerUrl] = document; + appLogger.info('✅ Swagger文档解析完成'); + + return document; + } on Object catch (e) { + if (e is SwaggerParseException) { + rethrow; + } + throw SwaggerParseException( + '获取Swagger文档失败', + url: swaggerUrl, + details: e.toString(), + ); + } + }, + ); + } + + /// 解析Swagger JSON文档 + Future parseSwaggerDocument( + Map jsonData, [ + String? sourceUrl, + ]) async { + return _performanceMonitor.measure('parseSwaggerDocument', () async { + // 计算内容哈希作为缓存键 + final contentHash = json.encode(jsonData).hashCode.toString(); + final cacheKey = 'swagger_doc_$contentHash'; + + // 尝试从缓存获取 + final cachedResult = _cacheManager.get(cacheKey); + if (cachedResult != null) { + if (sourceUrl != null) { + _cachedDocuments[sourceUrl] = cachedResult; + } + return cachedResult; + } + + // 解析文档基本信息 + final info = jsonData['info'] as Map? ?? {}; + final title = info['title'] as String? ?? 'API Documentation'; + final version = info['version'] as String? ?? '1.0.0'; + final description = info['description'] as String? ?? ''; + + // ✨ 分析 schema 使用情况(在解析 components 之前) + final schemaUsage = _analyzeSchemaUsage(jsonData); + + // 解析 servers (OpenAPI 3.0) + final servers = _parseServers(jsonData); + + // 解析 components (OpenAPI 3.0),传入使用情况分析结果 + final components = _parseComponents(jsonData, schemaUsage); + + // 解析tags信息 (用于获取控制器描述) + final tagsInfo = _parseTagsInfo(jsonData); + + // 解析API路径 + final paths = _parseApiPaths(jsonData); + + // 解析API模型 (从 components 中提取) + final models = components.schemas; + + // 解析API控制器 (传入tags信息) + final controllers = _parseApiControllers(paths, tagsInfo); + + final document = SwaggerDocument( + title: title, + version: version, + description: description, + servers: servers, + components: components, + paths: paths, + models: models, + controllers: controllers, + ); + + // 缓存结果 + _cacheManager.put(cacheKey, document); + if (sourceUrl != null) { + _cachedDocuments[sourceUrl] = document; + } + + return document; + }); + } + + /// 解析 servers 配置 (OpenAPI 3.0) + List _parseServers(Map jsonData) { + final servers = []; + + try { + final serversJson = jsonData['servers'] as List?; + if (serversJson != null) { + for (final serverJson in serversJson) { + if (serverJson is Map) { + final server = ApiServer.fromJson(serverJson); + servers.add(server); + } + } + } + + // 如果没有 servers 配置,提供默认值 + if (servers.isEmpty) { + servers.add(const ApiServer(url: '/')); + } + } on Object catch (e) { + appLogger.warning('⚠️ 解析servers配置时发生错误: $e'); + // 提供默认服务器配置 + servers.add(const ApiServer(url: '/')); + } + + return servers; + } + + /// 解析 components 配置 (OpenAPI 3.0) + ApiComponents _parseComponents( + Map jsonData, + Map> schemaUsage, + ) { + try { + final componentsJson = jsonData['components'] as Map?; + if (componentsJson != null) { + // 使用引用解析器处理复杂嵌套和循环引用 + final resolver = ReferenceResolver(); + final resolvedSchemas = resolver.resolveModels(componentsJson); + + // ✨ 根据使用情况更新模型的 usageType + final schemasWithUsageType = {}; + resolvedSchemas.forEach((name, model) { + final usages = schemaUsage[name] ?? {}; + final ModelUsageType usageType; + + if (usages.isEmpty) { + usageType = ModelUsageType.unknown; + } else if (usages.length == 1) { + usageType = usages.first; + } else { + // 既用于请求又用于响应 + usageType = ModelUsageType.common; + } + + schemasWithUsageType[name] = model.copyWithUsageType(usageType); + }); + + // 创建 ApiComponents,但使用解析后的 schemas + final components = ApiComponents.fromJson(componentsJson); + return ApiComponents( + schemas: schemasWithUsageType, + responses: components.responses, + parameters: components.parameters, + examples: components.examples, + requestBodies: components.requestBodies, + headers: components.headers, + securitySchemes: components.securitySchemes, + links: components.links, + callbacks: components.callbacks, + ); + } + } on Object catch (e) { + appLogger.warning('⚠️ 解析components配置时发生错误: $e'); + } + + return const ApiComponents(); + } + + /// 解析tags信息 + Map _parseTagsInfo(Map jsonData) { + final tagsInfo = {}; + + try { + final tags = jsonData['tags'] as List?; + if (tags != null) { + for (final tag in tags) { + if (tag is Map) { + final name = tag['name'] as String?; + final description = tag['description'] as String?; + if (name != null && description != null) { + tagsInfo[name] = description; + } + } + } + } + } on Object catch (e) { + appLogger.warning('⚠️ 解析tags信息时发生错误: $e'); + } + + return tagsInfo; + } + + /// 解析API路径 + Map _parseApiPaths(Map jsonData) { + final paths = {}; + final pathsData = jsonData['paths'] as Map?; + + if (pathsData == null) { + throw SwaggerParseException('未发现API路径定义'); + } + + try { + pathsData.forEach((pathKey, pathValue) { + if (pathValue is Map) { + pathValue.forEach((methodKey, methodValue) { + if (methodValue is Map) { + final method = HttpMethod.fromString(methodKey); + final apiPath = ApiPath.fromJson( + pathKey, + method, + methodValue, + ); + final key = ApiPathKey.from(pathKey, method); + paths[key] = apiPath; + } + }); + } + }); + } catch (e) { + throw SwaggerParseException('解析API路径失败', details: e.toString()); + } + + return paths; + } + + /// 解析API控制器 + Map _parseApiControllers( + Map paths, + Map tagsInfo, + ) { + final controllers = >{}; + + try { + // 根据tags分组API路径 + for (final apiPath in paths.values) { + for (final tag in apiPath.tags) { + controllers.putIfAbsent(tag, () => []).add(apiPath); + } + } + + // 创建控制器对象 + final result = {}; + controllers.forEach((name, pathList) { + // 从tags信息中获取描述 + final swaggerDescription = tagsInfo[name]; + + // 使用通用的描述获取方法 + final description = SwaggerConfig.getControllerDescription( + name, + swaggerDescription: swaggerDescription, + ); + + result[name] = ApiController( + name: name, + description: description, + paths: pathList, + ); + }); + + return result; + } catch (e) { + throw SwaggerParseException('解析API控制器失败', details: e.toString()); + } + } + + /// 解析属性类型 + String parsePropertyType(Map propData) { + try { + // 直接类型 + if (propData['type'] != null) { + return propData['type'] as String; + } + + // 引用类型 ($ref) + if (propData[r'$ref'] != null) { + final ref = propData[r'$ref'] as String; + // 从 #/components/schemas/ModelName 或 #/definitions/ModelName 中提取类型名 + final parts = ref.split('/'); + if (parts.isNotEmpty) { + return parts.last; + } + } + + // 数组类型 + if (propData['items'] != null) { + final items = propData['items'] as Map; + final itemType = parsePropertyType(items); + return 'array<$itemType>'; + } + + // 默认类型 + return 'string'; + } catch (e) { + throw SwaggerParseException('解析属性类型失败', details: e.toString()); + } + } + + /// 获取Dart类型映射 + String getDartType(String swaggerType, String? format) { + switch (swaggerType.toLowerCase()) { + case 'string': + switch (format?.toLowerCase()) { + case 'date': + case 'date-time': + return 'DateTime'; + case 'byte': + case 'binary': + return 'String'; + default: + return 'String'; + } + case 'integer': + switch (format?.toLowerCase()) { + case 'int64': + return 'int'; + case 'int32': + default: + return 'int'; + } + case 'number': + switch (format?.toLowerCase()) { + case 'float': + case 'double': + return 'double'; + default: + return 'double'; + } + case 'boolean': + return 'bool'; + case 'array': + return 'List'; + case 'object': + return 'Map'; + case 'file': + return 'String'; + default: + // 检查是否为数组类型 + if (swaggerType.startsWith('array<') && swaggerType.endsWith('>')) { + final itemType = swaggerType.substring(6, swaggerType.length - 1); + final dartItemType = getDartType(itemType, null); + return 'List<$dartItemType>'; + } + + // 默认为自定义类型 + return StringUtils.generateClassName(swaggerType); + } + } + + /// 清除缓存 + void clearCache() { + _cachedDocuments.clear(); + _cacheManager.clear(); + } + + /// 获取文档统计信息 + Map getDocumentStats() { + if (_cachedDocuments.isEmpty) { + return {}; + } + + // 使用第一个缓存的文档 + final doc = _cachedDocuments.values.first; + + // 统计HTTP方法 + final methodStats = {}; + for (final path in doc.paths.values) { + final method = path.method.value; + methodStats[method] = (methodStats[method] ?? 0) + 1; + } + + // 统计模型类型 + final modelStats = {}; + for (final model in doc.models.values) { + if (model.isEnum) { + modelStats['enum'] = (modelStats['enum'] ?? 0) + 1; + } else { + modelStats['class'] = (modelStats['class'] ?? 0) + 1; + } + } + + return { + 'title': doc.title, + 'version': doc.version, + 'paths': doc.paths.length, + 'models': doc.models.length, + 'controllers': doc.controllers.length, + 'methods': methodStats, + 'modelTypes': modelStats, + }; + } + + /// 分析 schemas 在 API 中的使用情况 + /// 返回每个 schema 的用途类型集合 + Map> _analyzeSchemaUsage( + Map jsonData, + ) { + final schemaUsage = >{}; + + final pathsData = jsonData['paths'] as Map?; + if (pathsData == null) return schemaUsage; + + pathsData.forEach((pathKey, pathValue) { + if (pathValue is! Map) return; + + pathValue.forEach((methodKey, methodValue) { + if (methodValue is! Map) return; + + // 分析 requestBody 中使用的 schemas + final requestBody = methodValue['requestBody'] as Map?; + if (requestBody != null) { + _extractSchemasFromContent( + requestBody['content'] as Map?, + schemaUsage, + ModelUsageType.request, + ); + } + + // 分析 parameters 中使用的 schemas + final parameters = methodValue['parameters'] as List?; + if (parameters != null) { + for (final param in parameters) { + if (param is Map) { + final schema = param['schema'] as Map?; + if (schema != null) { + _extractSchemaRef(schema, schemaUsage, ModelUsageType.request); + } + } + } + } + + // 分析 responses 中使用的 schemas + final responses = methodValue['responses'] as Map?; + if (responses != null) { + responses.forEach((statusCode, responseValue) { + if (responseValue is Map) { + _extractSchemasFromContent( + responseValue['content'] as Map?, + schemaUsage, + ModelUsageType.response, + ); + } + }); + } + }); + }); + + return schemaUsage; + } + + /// 从 content 中提取 schema 引用 + void _extractSchemasFromContent( + Map? content, + Map> schemaUsage, + ModelUsageType usageType, + ) { + if (content == null) return; + + content.forEach((mediaType, mediaTypeValue) { + if (mediaTypeValue is Map) { + final schema = mediaTypeValue['schema'] as Map?; + if (schema != null) { + _extractSchemaRef(schema, schemaUsage, usageType); + } + } + }); + } + + /// 递归提取 schema $ref 并记录用途 + void _extractSchemaRef( + Map schema, + Map> schemaUsage, + ModelUsageType usageType, + ) { + // 处理 $ref + final ref = schema[r'$ref'] as String?; + if (ref != null) { + // 从 #/components/schemas/ModelName 提取 ModelName + final schemaName = ref.split('/').last; + schemaUsage.putIfAbsent(schemaName, () => {}).add(usageType); + return; // $ref 存在时,不再处理其他字段 + } + + // 处理数组类型 + if (schema['type'] == 'array') { + final items = schema['items'] as Map?; + if (items != null) { + _extractSchemaRef(items, schemaUsage, usageType); + } + } + + // 处理 allOf/oneOf/anyOf + for (final key in ['allOf', 'oneOf', 'anyOf']) { + final schemas = schema[key] as List?; + if (schemas != null) { + for (final s in schemas) { + if (s is Map) { + _extractSchemaRef(s, schemaUsage, usageType); + } + } + } + } + + // 处理对象属性 + final properties = schema['properties'] as Map?; + if (properties != null) { + properties.forEach((propName, propSchema) { + if (propSchema is Map) { + _extractSchemaRef(propSchema, schemaUsage, usageType); + } + }); + } + + // 处理 additionalProperties + final additionalProperties = schema['additionalProperties']; + if (additionalProperties is Map) { + _extractSchemaRef(additionalProperties, schemaUsage, usageType); + } + } +} diff --git a/lib/pipeline/parse/impl/swagger_fetcher.dart b/lib/pipeline/parse/impl/swagger_fetcher.dart new file mode 100644 index 0000000..01ef8fa --- /dev/null +++ b/lib/pipeline/parse/impl/swagger_fetcher.dart @@ -0,0 +1,84 @@ +import 'dart:io'; + +import 'package:http/http.dart' as http; +import 'package:swagger_generator_flutter/core/config.dart'; +import 'package:swagger_generator_flutter/core/exceptions.dart'; +import 'package:swagger_generator_flutter/utils/logger.dart'; +import 'package:swagger_generator_flutter/utils/path_resolver.dart'; + +/// Swagger 数据获取器 +/// 负责从本地文件或远程 URL 获取 Swagger 文档内容 +class SwaggerFetcher { + /// 获取 Swagger 文档内容 + /// [url] 可以是本地文件路径 (file://) 或远程 URL (http://, https://) + Future fetch(String url) async { + appLogger.info('🔄 正在获取Swagger JSON文档: $url'); + + if (url.startsWith('http://') || url.startsWith('https://')) { + return _fetchFromUrl(url); + } else { + return _fetchFromFile(url); + } + } + + /// 从远程 URL 获取 + Future _fetchFromUrl(String url) async { + try { + final response = await http.get( + Uri.parse(url), + headers: SwaggerConfig.httpHeaders, + ); + + if (response.statusCode == 200) { + return response.body; + } else { + throw SwaggerParseException( + 'HTTP请求失败', + url: url, + statusCode: response.statusCode, + details: 'HTTP响应状态码: ${response.statusCode}', + ); + } + } catch (e) { + if (e is SwaggerParseException) rethrow; + throw SwaggerParseException( + '获取远程Swagger文档失败', + url: url, + details: e.toString(), + ); + } + } + + /// 从本地文件获取 + Future _fetchFromFile(String url) async { + try { + // 移除 file:// 前缀(如果有) + var filePath = url; + if (filePath.startsWith('file://')) { + filePath = filePath.substring(7); + } + + // 使用 PathResolver 解析路径 + final resolvedPath = PathResolver.resolvePath(filePath); + final file = File(resolvedPath); + + if (!file.existsSync()) { + throw SwaggerParseException( + '本地文件不存在', + url: url, + details: '文件路径: $resolvedPath', + ); + } + + return await file.readAsString(); + } catch (e) { + if (e is SwaggerParseException) rethrow; + throw SwaggerParseException( + '读取本地Swagger文件失败', + url: url, + details: e.toString(), + ); + } + } +} + diff --git a/lib/pipeline/validate/impl/enhanced_validator.dart b/lib/pipeline/validate/impl/enhanced_validator.dart new file mode 100644 index 0000000..187db3e --- /dev/null +++ b/lib/pipeline/validate/impl/enhanced_validator.dart @@ -0,0 +1,148 @@ +/// 增强的 OpenAPI 验证器(Pipeline Impl) +/// 集成详细的错误报告和修复建议 +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(); + } +} + diff --git a/lib/pipeline/validate/impl/schema_validator.dart b/lib/pipeline/validate/impl/schema_validator.dart new file mode 100644 index 0000000..b0b284d --- /dev/null +++ b/lib/pipeline/validate/impl/schema_validator.dart @@ -0,0 +1,55 @@ +/// Schema 验证器 +/// 验证 OpenAPI 3.0 文档的完整性和正确性 +library; + +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'; +import 'package:swagger_generator_flutter/validators/rules/component_rules.dart'; +import 'package:swagger_generator_flutter/validators/rules/info_rules.dart'; +import 'package:swagger_generator_flutter/validators/rules/path_rules.dart'; +import 'package:swagger_generator_flutter/validators/rules/security_rules.dart'; +import 'package:swagger_generator_flutter/validators/rules/server_rules.dart'; +import 'package:swagger_generator_flutter/validators/rules/structure_rules.dart'; + +export 'package:swagger_generator_flutter/validators/core/validation_context.dart'; +export 'package:swagger_generator_flutter/validators/core/validation_result.dart'; +export 'package:swagger_generator_flutter/validators/core/validation_rule.dart'; + +/// Schema 验证器 +class SchemaValidator { + SchemaValidator({ + List? rules, + }) : _rules = rules ?? _defaultRules; + + final List _rules; + + static final List _defaultRules = [ + InfoValidationRule(), + ServerValidationRule(), + PathValidationRule(), + ComponentValidationRule(), + SecurityValidationRule(), + StructureValidationRule(), + ]; + + /// 验证 OpenAPI 文档 + ValidationResult validateDocument( + SwaggerDocument document, { + ValidationOptions options = const ValidationOptions(), + }) { + final context = ValidationContext( + document: document, + options: options, + ); + + final results = []; + for (final rule in _rules) { + results.add(rule.validate(context)); + } + + return results.merge(); + } +} + diff --git a/lib/pipeline/validate/schema_validator.dart b/lib/pipeline/validate/schema_validator.dart index cfe7e82..b273080 100644 --- a/lib/pipeline/validate/schema_validator.dart +++ b/lib/pipeline/validate/schema_validator.dart @@ -1,6 +1,5 @@ /// Pipeline: validate -/// Re-export schema validator for pipeline-oriented imports. +/// Re-export schema validator impl during Phase 2. library; -export 'package:swagger_generator_flutter/validators/schema_validator.dart'; - +export 'package:swagger_generator_flutter/pipeline/validate/impl/schema_validator.dart'; diff --git a/lib/validators/enhanced_validator.dart b/lib/validators/enhanced_validator.dart index 33c0954..b775ec3 100644 --- a/lib/validators/enhanced_validator.dart +++ b/lib/validators/enhanced_validator.dart @@ -1,158 +1,4 @@ -/// 增强的 OpenAPI 验证器 -/// 集成详细的错误报告和修复建议 +/// Backward-compat shim for EnhancedValidator 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(); - } -} +export 'package:swagger_generator_flutter/pipeline/validate/impl/enhanced_validator.dart'; diff --git a/lib/validators/schema_validator.dart b/lib/validators/schema_validator.dart index c455d8d..775e4ec 100644 --- a/lib/validators/schema_validator.dart +++ b/lib/validators/schema_validator.dart @@ -1,54 +1,5 @@ -/// Schema 验证器 -/// 验证 OpenAPI 3.0 文档的完整性和正确性 +/// Backward-compat shim for schema validator library; -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'; -import 'package:swagger_generator_flutter/validators/rules/component_rules.dart'; -import 'package:swagger_generator_flutter/validators/rules/info_rules.dart'; -import 'package:swagger_generator_flutter/validators/rules/path_rules.dart'; -import 'package:swagger_generator_flutter/validators/rules/security_rules.dart'; -import 'package:swagger_generator_flutter/validators/rules/server_rules.dart'; -import 'package:swagger_generator_flutter/validators/rules/structure_rules.dart'; +export 'package:swagger_generator_flutter/pipeline/validate/impl/schema_validator.dart'; -export 'package:swagger_generator_flutter/validators/core/validation_context.dart'; -export 'package:swagger_generator_flutter/validators/core/validation_result.dart'; -export 'package:swagger_generator_flutter/validators/core/validation_rule.dart'; - -/// Schema 验证器 -class SchemaValidator { - SchemaValidator({ - List? rules, - }) : _rules = rules ?? _defaultRules; - - final List _rules; - - static final List _defaultRules = [ - InfoValidationRule(), - ServerValidationRule(), - PathValidationRule(), - ComponentValidationRule(), - SecurityValidationRule(), - StructureValidationRule(), - ]; - - /// 验证 OpenAPI 文档 - ValidationResult validateDocument( - SwaggerDocument document, { - ValidationOptions options = const ValidationOptions(), - }) { - final context = ValidationContext( - document: document, - options: options, - ); - - final results = []; - for (final rule in _rules) { - results.add(rule.validate(context)); - } - - return results.merge(); - } -}