Compare commits
No commits in common. "d6a31d5a24a2645b90df7610c4575b31766f158f" and "fd7975c1c4ad4b3de57eb091703a5d60438d1c38" have entirely different histories.
d6a31d5a24
...
fd7975c1c4
|
|
@ -118,8 +118,8 @@ dart run build_runner build --delete-conflicting-outputs
|
||||||
### 🧩 编程式用法
|
### 🧩 编程式用法
|
||||||
```dart
|
```dart
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:swagger_generator_flutter/pipeline/parse/swagger_data_parser.dart';
|
import 'package:swagger_generator_flutter/parsers/swagger_data_parser.dart';
|
||||||
import 'package:swagger_generator_flutter/pipeline/generate/impl/retrofit_api_generator.dart';
|
import 'package:swagger_generator_flutter/generators/retrofit_api_generator.dart';
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
// 解析本地或远程的 Swagger 文档(支持 file:// 与 http(s)://)
|
// 解析本地或远程的 Swagger 文档(支持 file:// 与 http(s)://)
|
||||||
|
|
@ -321,8 +321,10 @@ swagger_generator_flutter/
|
||||||
lib/ # 核心代码
|
lib/ # 核心代码
|
||||||
commands/ # CLI 命令(GenerateCommand 等)
|
commands/ # CLI 命令(GenerateCommand 等)
|
||||||
core/ # 配置、模型、异常
|
core/ # 配置、模型、异常
|
||||||
pipeline/ # 核心处理流程 (Parse -> Validate -> Generate -> Render -> Output)
|
generators/ # 模型/API/文档生成器
|
||||||
|
parsers/ # SwaggerDataParser
|
||||||
utils/ # FileUtils、StringUtils 等
|
utils/ # FileUtils、StringUtils 等
|
||||||
|
validators/ # Schema/Enhanced 校验器
|
||||||
tests/ # 基础测试示例
|
tests/ # 基础测试示例
|
||||||
generator_config.template.yaml
|
generator_config.template.yaml
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -18,18 +18,19 @@ XY Swagger Generator 是一个专为 Flutter 开发优化的 OpenAPI 3.0 代码
|
||||||
```
|
```
|
||||||
命令行输入
|
命令行输入
|
||||||
↓
|
↓
|
||||||
SwaggerCLI / GenerateCommand(编排流程)
|
SwaggerCLI / GenerateCommand(合并多 Swagger、处理版本与 Tag 过滤)
|
||||||
↓
|
↓
|
||||||
Pipeline (按职责分层)
|
配置层(ConfigRepository 为主,SwaggerConfig 静态访问保持兼容)
|
||||||
- Parse: SwaggerDataParser (获取与解析)
|
|
||||||
- Validate: EnhancedValidator (校验)
|
|
||||||
- Generate: ModelCodeGenerator, RetrofitApiGenerator (生成)
|
|
||||||
- Render: TemplateRenderer (渲染)
|
|
||||||
- Output: GenerationOutputService (落盘)
|
|
||||||
↓
|
↓
|
||||||
Core (核心模型、配置、异常)
|
获取与解析(SwaggerFetcher / SwaggerDataParser,带缓存与性能监控)
|
||||||
↓
|
↓
|
||||||
Utils (通用工具)
|
验证层(SchemaValidator 基础规则 → EnhancedValidator 装饰增强 + ErrorReporter 渲染)
|
||||||
|
↓
|
||||||
|
生成器层(ModelCodeGenerator / RetrofitApiGenerator + TemplateRenderer/TemplateService)
|
||||||
|
↓
|
||||||
|
工具层(FileUtils / PathResolver / ReferenceResolver / StringUtils 模块化)
|
||||||
|
↓
|
||||||
|
落盘输出(按版本与模型类别组织)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -38,40 +39,48 @@ Utils (通用工具)
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart TD
|
flowchart TD
|
||||||
CLI[CLI / main] --> GC[GenerateCommand]
|
CLI[CLI / main] --> GC[GenerateCommand]
|
||||||
GC --> Pipeline
|
GC --> CFG[ConfigRepository]
|
||||||
|
GC --> PF[SwaggerFetcher]
|
||||||
subgraph Pipeline
|
PF --> SDP[SwaggerDataParser]
|
||||||
direction LR
|
SDP --> VAL[SchemaValidator]
|
||||||
Parse[Parse] --> Validate[Validate]
|
VAL -->|decorated by| EV[EnhancedValidator]
|
||||||
Validate --> Generate[Generate]
|
SDP --> MODELS[Core Models]
|
||||||
Generate --> Render[Render]
|
EV --> ER[ErrorReporter]
|
||||||
Render --> Output[Output]
|
GC --> GEN[Generators]
|
||||||
|
GEN --> MC[ModelCodeGenerator]
|
||||||
|
GEN --> RG[RetrofitApiGenerator]
|
||||||
|
RG --> TR[TemplateRenderer]
|
||||||
|
TR --> TS[TemplateService]
|
||||||
|
GC --> OUT[File Output / Writer]
|
||||||
|
subgraph Utils
|
||||||
|
FU[FileUtils]
|
||||||
|
PR[PathResolver]
|
||||||
|
RR[ReferenceResolver]
|
||||||
|
SU[StringUtils]
|
||||||
end
|
end
|
||||||
|
GEN -.-> Utils
|
||||||
GC --> Core[Core Models, Config, Exceptions]
|
SDP -.-> Utils
|
||||||
Pipeline --> Core
|
GC -.-> Utils
|
||||||
Pipeline --> Utils
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 模块职责与核心类
|
## 模块职责与核心类
|
||||||
|
|
||||||
- Commands
|
- Commands
|
||||||
- GenerateCommand: 解析参数、编排流程(解析→验证→生成→落盘)
|
- GenerateCommand: 解析参数、编排流程(解析→验证→生成→落盘)
|
||||||
- Pipeline
|
- Config
|
||||||
- Parse: `SwaggerDataParser` (获取与解析)
|
- ConfigRepository: 主配置入口,提供只读配置访问和缓存
|
||||||
- Validate: `EnhancedValidator` (校验)
|
- ConfigLoader: 向后兼容的静态加载器(保留,推荐迁移到 ConfigRepository)
|
||||||
- Generate: `ModelCodeGenerator`, `RetrofitApiGenerator` (生成)
|
- Parsers
|
||||||
- Render: `TemplateRenderer` (渲染)
|
- SwaggerFetcher: 统一 http/file 源获取、缓存与错误处理
|
||||||
- Output: `GenerationOutputService` (落盘)
|
- SwaggerDataParser: OpenAPI 3.0 解析为内部模型
|
||||||
- Core
|
- Validators
|
||||||
- `ConfigRepository`: 主配置入口
|
- SchemaValidator: 基础规则验证器(必留)
|
||||||
- `models`: 核心数据模型
|
- EnhancedValidator: 装饰器,复用 SchemaValidator 结果并通过 ErrorReporter 渲染
|
||||||
- `exceptions`: 自定义异常
|
- ErrorReporter: 可插拔渲染(文本/JSON/CI)
|
||||||
- `error_reporter`: 错误报告
|
- Generators
|
||||||
- Utils
|
- ModelCodeGenerator: 生成模型(Freezed/json_serializable)
|
||||||
- `FileUtils`/`PathResolver`: 异步 IO、路径解析
|
- RetrofitApiGenerator: 生成 API(Mustache 模板、按 tag 拆分、版本化)
|
||||||
- `ReferenceResolver`: $ref 解析与去循环
|
- TemplateRenderer/TemplateService: 模板加载与注释/文件头生成
|
||||||
- `StringUtils`: 统一导出(NamingConverter/TextCleaner/TemplateService)
|
|
||||||
- Utils
|
- Utils
|
||||||
- FileUtils/PathResolver: 异步 IO、路径解析
|
- FileUtils/PathResolver: 异步 IO、路径解析
|
||||||
- ReferenceResolver: $ref 解析与去循环
|
- ReferenceResolver: $ref 解析与去循环
|
||||||
|
|
@ -117,12 +126,16 @@ flowchart TD
|
||||||
- **SwaggerCLI / GenerateCommand**: 注册命令、展示帮助、执行生成,支持多 Swagger 合并、版本化输出、Tag 过滤和忽略列表
|
- **SwaggerCLI / GenerateCommand**: 注册命令、展示帮助、执行生成,支持多 Swagger 合并、版本化输出、Tag 过滤和忽略列表
|
||||||
- **ConfigLoader / SwaggerConfig**: 解析 `generator_config.yaml`,提供 swagger_urls 顺序合并、输出目录、版本提取正则、ApiClient 命名、BaseResult 导入等配置
|
- **ConfigLoader / SwaggerConfig**: 解析 `generator_config.yaml`,提供 swagger_urls 顺序合并、输出目录、版本提取正则、ApiClient 命名、BaseResult 导入等配置
|
||||||
|
|
||||||
#### 2. Pipeline (核心流程)
|
#### 2. 解析器 (Parsers)
|
||||||
- **Parse**: `SwaggerDataParser` 支持 http(s) 与 file:// 源的 OpenAPI 解析,内置缓存与性能监测。
|
- **SwaggerDataParser**: 支持 http(s) 与 file:// 源的 OpenAPI 解析,内置缓存与性能监测,解析 controllers/tags/schema 依赖
|
||||||
- **Validate**: `SchemaValidator` 与 `EnhancedValidator` 用于在生成前验证文档一致性。
|
|
||||||
- **Generate**: `ModelCodeGenerator` 和 `RetrofitApiGenerator` 负责生成数据模型和 API 代码。
|
#### 3. 验证器 (Validators)
|
||||||
- **Render**: `TemplateRenderer` (内部使用) 负责模板渲染。
|
- **SchemaValidator / EnhancedValidator**: 基础与增强校验器,用于在生成前验证文档一致性
|
||||||
- **Output**: `GenerationOutputService` 负责将生成的文件写入磁盘。
|
|
||||||
|
#### 4. 生成器 (Generators)
|
||||||
|
- **ModelCodeGenerator**: 生成基于 Freezed 的不可变数据模型,自动获得 `copyWith`、`toString`、`==/hashCode` 等功能,并与 `json_serializable` 无缝集成。
|
||||||
|
- **RetrofitApiGenerator**: 支持按 tag 拆分、多版本目录与统一 ApiClient,自动生成查询参数实体并处理版本化类名
|
||||||
|
- **DocumentationGenerator**: 输出 Markdown API 文档与统计摘要
|
||||||
|
|
||||||
#### 5. 工具类 (Utils)
|
#### 5. 工具类 (Utils)
|
||||||
- **CacheManager / PerformanceMonitor**: 缓存解析结果并记录耗时
|
- **CacheManager / PerformanceMonitor**: 缓存解析结果并记录耗时
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
import 'package:swagger_generator_flutter/commands/base_command.dart';
|
import 'package:swagger_generator_flutter/commands/base_command.dart';
|
||||||
import 'package:swagger_generator_flutter/commands/services/document_filter_service.dart';
|
import 'package:swagger_generator_flutter/commands/services/document_filter_service.dart';
|
||||||
import 'package:swagger_generator_flutter/commands/services/document_merge_service.dart';
|
import 'package:swagger_generator_flutter/commands/services/document_merge_service.dart';
|
||||||
import 'package:swagger_generator_flutter/index.dart';
|
import 'package:swagger_generator_flutter/commands/services/generation_output_service.dart';
|
||||||
|
import 'package:swagger_generator_flutter/core/config.dart';
|
||||||
|
import 'package:swagger_generator_flutter/core/config_repository.dart';
|
||||||
|
import 'package:swagger_generator_flutter/utils/logger.dart';
|
||||||
|
|
||||||
/// Generate命令
|
/// Generate命令
|
||||||
/// 用于生成各种代码文件
|
/// 用于生成各种代码文件
|
||||||
|
|
@ -78,10 +81,9 @@ class GenerateCommand extends BaseCommand {
|
||||||
validateArguments(parsedArgs);
|
validateArguments(parsedArgs);
|
||||||
|
|
||||||
await prepare(parsedArgs);
|
await prepare(parsedArgs);
|
||||||
final config = ConfigRepository.loadSync();
|
|
||||||
|
|
||||||
progress('正在解析 ${config.swaggerUrls.length} 个 Swagger 文档...');
|
progress('正在解析 ${SwaggerConfig.swaggerJsonUrls.length} 个 Swagger 文档...');
|
||||||
final urls = config.swaggerUrls;
|
final urls = SwaggerConfig.swaggerJsonUrls;
|
||||||
progress('URL 处理顺序: ${urls.join(" -> ")}');
|
progress('URL 处理顺序: ${urls.join(" -> ")}');
|
||||||
|
|
||||||
final mergedDocument = await _documentMergeService.fetchAndMerge(
|
final mergedDocument = await _documentMergeService.fetchAndMerge(
|
||||||
|
|
@ -94,7 +96,7 @@ class GenerateCommand extends BaseCommand {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
success('成功合并 ${config.swaggerUrls.length} 个 Swagger 文档');
|
success('成功合并 ${SwaggerConfig.swaggerJsonUrls.length} 个 Swagger 文档');
|
||||||
|
|
||||||
final options = _parseGenerateOptions(parsedArgs);
|
final options = _parseGenerateOptions(parsedArgs);
|
||||||
|
|
||||||
|
|
@ -106,9 +108,9 @@ class GenerateCommand extends BaseCommand {
|
||||||
);
|
);
|
||||||
|
|
||||||
// 使用配置的输出目录
|
// 使用配置的输出目录
|
||||||
final baseDir = config.baseDir;
|
final baseDir = SwaggerConfig.generatorDir;
|
||||||
final apiDir = config.apiDir;
|
final apiDir = SwaggerConfig.apiDir;
|
||||||
final modelsDir = config.modelsDir;
|
final modelsDir = SwaggerConfig.modelsDir;
|
||||||
|
|
||||||
progress('输出目录: $baseDir');
|
progress('输出目录: $baseDir');
|
||||||
progress('API 目录: $apiDir');
|
progress('API 目录: $apiDir');
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:swagger_generator_flutter/commands/services/service_typedefs.dart';
|
import 'package:swagger_generator_flutter/commands/services/service_typedefs.dart';
|
||||||
import 'package:swagger_generator_flutter/core/models.dart';
|
import 'package:swagger_generator_flutter/core/models.dart';
|
||||||
import 'package:swagger_generator_flutter/pipeline/parse/impl/swagger_data_parser.dart';
|
import 'package:swagger_generator_flutter/parsers/swagger_data_parser.dart';
|
||||||
|
|
||||||
class DocumentMergeService {
|
class DocumentMergeService {
|
||||||
DocumentMergeService({SwaggerDataParser? parser})
|
DocumentMergeService({SwaggerDataParser? parser})
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,657 @@
|
||||||
/// Backward-compat shim for GenerationOutputService
|
import 'dart:io';
|
||||||
library;
|
|
||||||
|
|
||||||
export 'package:swagger_generator_flutter/pipeline/output/impl/generation_output_service.dart';
|
import 'package:path/path.dart' as path;
|
||||||
|
import 'package:swagger_generator_flutter/commands/generate_command.dart'
|
||||||
|
show GenerateOptions;
|
||||||
|
import 'package:swagger_generator_flutter/commands/services/service_typedefs.dart';
|
||||||
|
import 'package:swagger_generator_flutter/core/config.dart';
|
||||||
|
import 'package:swagger_generator_flutter/core/config_repository.dart';
|
||||||
|
import 'package:swagger_generator_flutter/core/models.dart';
|
||||||
|
import 'package:swagger_generator_flutter/generators/model_code_generator.dart';
|
||||||
|
import 'package:swagger_generator_flutter/generators/retrofit_api_generator.dart';
|
||||||
|
import 'package:swagger_generator_flutter/utils/file_utils.dart';
|
||||||
|
import 'package:swagger_generator_flutter/utils/logger.dart';
|
||||||
|
|
||||||
|
class GenerationOutputService {
|
||||||
|
const GenerationOutputService();
|
||||||
|
static ConfigRepository? _cachedConfig;
|
||||||
|
ConfigRepository get _config => _cachedConfig ??= ConfigRepository.loadSync();
|
||||||
|
|
||||||
|
Future<int> generateOutputs({
|
||||||
|
required SwaggerDocument document,
|
||||||
|
required GenerateOptions options,
|
||||||
|
required String baseDir,
|
||||||
|
required String apiDir,
|
||||||
|
required String modelsDir,
|
||||||
|
required LogCallback progress,
|
||||||
|
required LogCallback success,
|
||||||
|
}) async {
|
||||||
|
await FileUtils.ensureDirectoryExists(baseDir);
|
||||||
|
await FileUtils.ensureDirectoryExists(apiDir);
|
||||||
|
await FileUtils.ensureDirectoryExists(modelsDir);
|
||||||
|
|
||||||
|
var generatedFiles = 0;
|
||||||
|
|
||||||
|
if (options.generateModels) {
|
||||||
|
generatedFiles += await _generateModels(
|
||||||
|
document,
|
||||||
|
modelsDir,
|
||||||
|
progress,
|
||||||
|
success,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.generateApi) {
|
||||||
|
generatedFiles += await _generateApis(
|
||||||
|
document,
|
||||||
|
apiDir,
|
||||||
|
modelsDir,
|
||||||
|
progress,
|
||||||
|
success,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.generateModels || options.generateApi) {
|
||||||
|
await _regenerateModelsIndex(modelsDir, success);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _generateSummary(document, baseDir);
|
||||||
|
return generatedFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> _generateModels(
|
||||||
|
SwaggerDocument document,
|
||||||
|
String modelsDir,
|
||||||
|
LogCallback progress,
|
||||||
|
LogCallback success,
|
||||||
|
) async {
|
||||||
|
progress('正在生成数据模型...');
|
||||||
|
final generator = ModelCodeGenerator(document);
|
||||||
|
final modelFiles = generator.generateSeparateModelFiles();
|
||||||
|
var generatedFiles = 0;
|
||||||
|
|
||||||
|
for (final entry in modelFiles.entries) {
|
||||||
|
final filePath = '$modelsDir/${entry.key}';
|
||||||
|
if (_config.shouldSkipFile(filePath)) {
|
||||||
|
progress('跳过文件: $filePath');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
await FileUtils.writeFile(filePath, entry.value);
|
||||||
|
success('模型文件已保存到: $filePath');
|
||||||
|
generatedFiles++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return generatedFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> _generateApis(
|
||||||
|
SwaggerDocument document,
|
||||||
|
String apiDir,
|
||||||
|
String modelsDir,
|
||||||
|
LogCallback progress,
|
||||||
|
LogCallback success,
|
||||||
|
) async {
|
||||||
|
progress('正在按版本和tags分组生成Retrofit风格API接口...');
|
||||||
|
|
||||||
|
await FileUtils.ensureDirectoryExists(apiDir);
|
||||||
|
final pathsByVersion = _groupPathsByVersion(document);
|
||||||
|
|
||||||
|
progress(
|
||||||
|
'检测到 ${pathsByVersion.keys.length} 个版本: '
|
||||||
|
'${pathsByVersion.keys.join(", ")}',
|
||||||
|
);
|
||||||
|
|
||||||
|
final versionedFiles = await _buildVersionedApis(
|
||||||
|
document,
|
||||||
|
pathsByVersion,
|
||||||
|
progress,
|
||||||
|
);
|
||||||
|
|
||||||
|
var generatedFiles = 0;
|
||||||
|
generatedFiles += await _writeVersionedApis(
|
||||||
|
apiDir,
|
||||||
|
versionedFiles,
|
||||||
|
progress,
|
||||||
|
success,
|
||||||
|
);
|
||||||
|
|
||||||
|
generatedFiles += await _writeMainApiFile(
|
||||||
|
apiDir,
|
||||||
|
versionedFiles,
|
||||||
|
success,
|
||||||
|
);
|
||||||
|
|
||||||
|
generatedFiles += await _writeParameterEntities(
|
||||||
|
document,
|
||||||
|
modelsDir,
|
||||||
|
success,
|
||||||
|
progress,
|
||||||
|
);
|
||||||
|
|
||||||
|
return generatedFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, List<ApiPath>> _groupPathsByVersion(SwaggerDocument document) {
|
||||||
|
final pathsByVersion = <String, List<ApiPath>>{};
|
||||||
|
for (final path in document.paths.values) {
|
||||||
|
final version = _extractVersionFromPath(path.path);
|
||||||
|
pathsByVersion.putIfAbsent(version, () => []).add(path);
|
||||||
|
}
|
||||||
|
return pathsByVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, Map<String, String>>> _buildVersionedApis(
|
||||||
|
SwaggerDocument document,
|
||||||
|
Map<String, List<ApiPath>> pathsByVersion,
|
||||||
|
LogCallback progress,
|
||||||
|
) async {
|
||||||
|
final versionedFiles = <String, Map<String, String>>{};
|
||||||
|
final apiClientClassName = _config.apiClientClassName;
|
||||||
|
|
||||||
|
for (final versionEntry in pathsByVersion.entries) {
|
||||||
|
final version = versionEntry.key;
|
||||||
|
final versionPaths = versionEntry.value;
|
||||||
|
|
||||||
|
progress(' 正在生成 $version 版本 API(${versionPaths.length} 个接口)...');
|
||||||
|
|
||||||
|
final versionTags = versionPaths.expand((p) => p.tags).toSet();
|
||||||
|
final versionControllers = {
|
||||||
|
for (final tag in versionTags)
|
||||||
|
if (document.controllers.containsKey(tag))
|
||||||
|
tag: document.controllers[tag]!,
|
||||||
|
};
|
||||||
|
|
||||||
|
final versionDocument = SwaggerDocument(
|
||||||
|
title: document.title,
|
||||||
|
description: document.description,
|
||||||
|
version: document.version,
|
||||||
|
paths: {
|
||||||
|
for (final p in versionPaths)
|
||||||
|
SwaggerDocument.buildPathKey(p.path, p.method): p,
|
||||||
|
},
|
||||||
|
models: document.models,
|
||||||
|
controllers: versionControllers,
|
||||||
|
);
|
||||||
|
|
||||||
|
final generator = RetrofitApiGenerator(
|
||||||
|
className: apiClientClassName,
|
||||||
|
)
|
||||||
|
..document = versionDocument
|
||||||
|
..ensureParameterEntitiesGenerated();
|
||||||
|
|
||||||
|
final tagApiFiles = generator.generateApiFilesByTags();
|
||||||
|
versionedFiles[version] = {};
|
||||||
|
|
||||||
|
for (final entry in tagApiFiles.entries) {
|
||||||
|
final fileName = entry.key;
|
||||||
|
var code = entry.value;
|
||||||
|
code = _addVersionSuffixToCode(code, version);
|
||||||
|
versionedFiles[version]![fileName] = code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return versionedFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> _writeVersionedApis(
|
||||||
|
String apiDir,
|
||||||
|
Map<String, Map<String, String>> versionedFiles,
|
||||||
|
LogCallback progress,
|
||||||
|
LogCallback success,
|
||||||
|
) async {
|
||||||
|
var generatedFiles = 0;
|
||||||
|
|
||||||
|
for (final versionEntry in versionedFiles.entries) {
|
||||||
|
final version = versionEntry.key;
|
||||||
|
final files = versionEntry.value;
|
||||||
|
final versionDir = '$apiDir/$version';
|
||||||
|
|
||||||
|
if (_config.shouldSkipFile(versionDir)) {
|
||||||
|
progress('跳过版本目录: $versionDir');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await FileUtils.ensureDirectoryExists(versionDir);
|
||||||
|
|
||||||
|
for (final fileEntry in files.entries) {
|
||||||
|
final fileName = fileEntry.key;
|
||||||
|
final code = fileEntry.value;
|
||||||
|
final filePath = '$versionDir/$fileName';
|
||||||
|
|
||||||
|
if (_config.shouldSkipFile(filePath)) {
|
||||||
|
progress('跳过文件: $filePath');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await FileUtils.writeFile(filePath, code);
|
||||||
|
success('API接口文件已保存到: $filePath');
|
||||||
|
generatedFiles++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_config.shouldSkipFile(versionDir)) {
|
||||||
|
await _generateVersionIndexFile(versionDir, files.keys.toList());
|
||||||
|
success('$version/index.dart 已生成');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return generatedFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> _writeMainApiFile(
|
||||||
|
String apiDir,
|
||||||
|
Map<String, Map<String, String>> versionedFiles,
|
||||||
|
LogCallback success,
|
||||||
|
) async {
|
||||||
|
final apiClientFileName = _config.apiClientFileName;
|
||||||
|
final mainCode = _generateVersionedApiClient(versionedFiles);
|
||||||
|
final mainFilePath = '$apiDir/$apiClientFileName.dart';
|
||||||
|
|
||||||
|
if (!_config.shouldSkipFile(mainFilePath)) {
|
||||||
|
await FileUtils.writeFile(mainFilePath, mainCode);
|
||||||
|
success('主API接口文件已保存到: $mainFilePath');
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> _writeParameterEntities(
|
||||||
|
SwaggerDocument document,
|
||||||
|
String modelsDir,
|
||||||
|
LogCallback success,
|
||||||
|
LogCallback progress,
|
||||||
|
) async {
|
||||||
|
final apiClientClassName = _config.apiClientClassName;
|
||||||
|
final lastGenerator = RetrofitApiGenerator(
|
||||||
|
className: apiClientClassName,
|
||||||
|
)
|
||||||
|
..document = document
|
||||||
|
..ensureParameterEntitiesGenerated();
|
||||||
|
|
||||||
|
final parameterEntityFiles = lastGenerator.generateParameterEntityFiles();
|
||||||
|
if (parameterEntityFiles.isEmpty) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
final parametersDir = '$modelsDir/parameters';
|
||||||
|
await FileUtils.ensureDirectoryExists(parametersDir);
|
||||||
|
var generatedFiles = 0;
|
||||||
|
|
||||||
|
for (final entry in parameterEntityFiles.entries) {
|
||||||
|
final filePath = '$parametersDir/${entry.key}';
|
||||||
|
|
||||||
|
if (_config.shouldSkipFile(filePath)) {
|
||||||
|
progress('跳过文件: $filePath');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await FileUtils.writeFile(filePath, entry.value);
|
||||||
|
success('参数实体类文件已保存到: $filePath');
|
||||||
|
generatedFiles++;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _generateSubDirectoryIndexFile(parametersDir, success);
|
||||||
|
return generatedFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _regenerateModelsIndex(
|
||||||
|
String modelsDir,
|
||||||
|
LogCallback success,
|
||||||
|
) async {
|
||||||
|
final allFiles = await _getAllModelFiles(modelsDir);
|
||||||
|
final indexContent = _generateUpdatedIndexFile(allFiles);
|
||||||
|
final indexPath = '$modelsDir/index.dart';
|
||||||
|
await FileUtils.writeFile(indexPath, indexContent);
|
||||||
|
success('index.dart 文件已更新');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<String>> _getAllModelFiles(String modelsDir) async {
|
||||||
|
try {
|
||||||
|
final directory = Directory(modelsDir);
|
||||||
|
if (!directory.existsSync()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final files = directory.listSync();
|
||||||
|
final exportPaths = <String>[];
|
||||||
|
|
||||||
|
for (final entity in files) {
|
||||||
|
if (entity is Directory) {
|
||||||
|
final dirName = path.basename(entity.path);
|
||||||
|
final subIndexPath = path.join(entity.path, 'index.dart');
|
||||||
|
if (File(subIndexPath).existsSync()) {
|
||||||
|
exportPaths.add('$dirName/index.dart');
|
||||||
|
}
|
||||||
|
} else if (entity is File && entity.path.endsWith('.dart')) {
|
||||||
|
final fileName = path.basename(entity.path);
|
||||||
|
if (fileName != 'index.dart' && !fileName.endsWith('.g.dart')) {
|
||||||
|
exportPaths.add(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exportPaths.sort((a, b) {
|
||||||
|
final aIsDir = a.contains('/');
|
||||||
|
final bIsDir = b.contains('/');
|
||||||
|
if (aIsDir && !bIsDir) return -1;
|
||||||
|
if (!aIsDir && bIsDir) return 1;
|
||||||
|
return a.compareTo(b);
|
||||||
|
});
|
||||||
|
|
||||||
|
return exportPaths;
|
||||||
|
} on Exception catch (e, stackTrace) {
|
||||||
|
appLogger.severe('获取模型文件列表失败', e, stackTrace);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _generateSubDirectoryIndexFile(
|
||||||
|
String subDir,
|
||||||
|
LogCallback success,
|
||||||
|
) async {
|
||||||
|
final directory = Directory(subDir);
|
||||||
|
if (!directory.existsSync()) return;
|
||||||
|
|
||||||
|
final dirName = path.basename(subDir);
|
||||||
|
final files = directory.listSync();
|
||||||
|
final dartFiles = <String>[];
|
||||||
|
|
||||||
|
for (final entity in files) {
|
||||||
|
if (entity is File && entity.path.endsWith('.dart')) {
|
||||||
|
final fileName = path.basename(entity.path);
|
||||||
|
if (fileName != 'index.dart' && !fileName.endsWith('.g.dart')) {
|
||||||
|
final filePath = path.join(subDir, fileName);
|
||||||
|
if (!_config.shouldSkipFile(filePath)) {
|
||||||
|
dartFiles.add(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dartFiles.sort();
|
||||||
|
|
||||||
|
final buffer = StringBuffer()
|
||||||
|
..writeln('// 模型导出文件')
|
||||||
|
..writeln('// 基于 Swagger API 文档: ')
|
||||||
|
..writeln('// 由 xy_swagger_generator by max 生成')
|
||||||
|
..writeln('// Copyright (C) 2025 YuanXuan. All rights reserved.')
|
||||||
|
..writeln()
|
||||||
|
..writeln()
|
||||||
|
..writeln('library;')
|
||||||
|
..writeln();
|
||||||
|
|
||||||
|
for (final fileName in dartFiles) {
|
||||||
|
buffer.writeln("export '$fileName';");
|
||||||
|
}
|
||||||
|
|
||||||
|
final indexPath = path.join(subDir, 'index.dart');
|
||||||
|
await FileUtils.writeFile(indexPath, buffer.toString());
|
||||||
|
success('$dirName/index.dart 已生成,包含 ${dartFiles.length} 个文件');
|
||||||
|
}
|
||||||
|
|
||||||
|
String _generateUpdatedIndexFile(List<String> fileNames) {
|
||||||
|
final buffer = StringBuffer()
|
||||||
|
..writeln('// API 模型导出文件')
|
||||||
|
..writeln('// 基于 Swagger API 文档: ')
|
||||||
|
..writeln('// 由 xy_swagger_generator by max 生成')
|
||||||
|
..writeln('// Copyright (C) 2025 YuanXuan. All rights reserved.')
|
||||||
|
..writeln()
|
||||||
|
..writeln('library;')
|
||||||
|
..writeln();
|
||||||
|
|
||||||
|
final baseResultImport = SwaggerConfig.baseResultImport;
|
||||||
|
final basePageResultImport = SwaggerConfig.basePageResultImport;
|
||||||
|
|
||||||
|
if (baseResultImport.isNotEmpty) {
|
||||||
|
buffer.writeln("export '$baseResultImport';");
|
||||||
|
}
|
||||||
|
if (basePageResultImport.isNotEmpty) {
|
||||||
|
buffer.writeln("export '$basePageResultImport';");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((baseResultImport.isNotEmpty || basePageResultImport.isNotEmpty) &&
|
||||||
|
fileNames.isNotEmpty) {
|
||||||
|
buffer.writeln();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final fileName in fileNames) {
|
||||||
|
buffer.writeln("export '$fileName';");
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _generateSummary(
|
||||||
|
SwaggerDocument document,
|
||||||
|
String outputDir,
|
||||||
|
) async {
|
||||||
|
final summary = StringBuffer()
|
||||||
|
..writeln('# 代码生成摘要')
|
||||||
|
..writeln()
|
||||||
|
..writeln('**API标题**: ${document.title}')
|
||||||
|
..writeln('**API版本**: ${document.version}')
|
||||||
|
..writeln('**生成时间**: ${DateTime.now().toIso8601String()}')
|
||||||
|
..writeln()
|
||||||
|
..writeln('## 统计信息')
|
||||||
|
..writeln('- 控制器数量: ${document.controllers.length}')
|
||||||
|
..writeln('- API路径数量: ${document.paths.length}')
|
||||||
|
..writeln('- 数据模型数量: ${document.models.length}')
|
||||||
|
..writeln()
|
||||||
|
..writeln('## 控制器列表');
|
||||||
|
document.controllers.forEach((name, controller) {
|
||||||
|
summary.writeln(
|
||||||
|
'- **$name**: ${controller.description} '
|
||||||
|
'(${controller.paths.length} 个路径)',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await FileUtils.writeFile('$outputDir/SUMMARY.md', summary.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
String _extractVersionFromPath(String path) {
|
||||||
|
final pattern = _config.versionExtractionPattern;
|
||||||
|
final defaultVersion = _config.defaultVersion;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final versionMatch = RegExp(pattern).firstMatch(path);
|
||||||
|
if (versionMatch != null && versionMatch.groupCount > 0) {
|
||||||
|
return 'v${versionMatch.group(1)}';
|
||||||
|
}
|
||||||
|
} on FormatException {
|
||||||
|
const defaultPattern = r'/api/v(\d+)/';
|
||||||
|
final versionMatch = RegExp(defaultPattern).firstMatch(path);
|
||||||
|
if (versionMatch != null) {
|
||||||
|
return 'v${versionMatch.group(1)}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _addVersionSuffixToCode(String code, String version) {
|
||||||
|
if (version == 'v1') {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
final versionUpper = version.toUpperCase();
|
||||||
|
var updatedCode = code;
|
||||||
|
|
||||||
|
updatedCode = updatedCode.replaceAllMapped(
|
||||||
|
RegExp(r'abstract class (\w+Api)\b'),
|
||||||
|
(match) => 'abstract class ${match.group(1)}$versionUpper',
|
||||||
|
);
|
||||||
|
|
||||||
|
updatedCode = updatedCode.replaceAllMapped(
|
||||||
|
RegExp(r'factory (\w+Api)\('),
|
||||||
|
(match) => 'factory ${match.group(1)}$versionUpper(',
|
||||||
|
);
|
||||||
|
|
||||||
|
updatedCode = updatedCode.replaceAllMapped(
|
||||||
|
RegExp(r'= _(\w+Api);'),
|
||||||
|
(match) => '= _${match.group(1)}$versionUpper;',
|
||||||
|
);
|
||||||
|
|
||||||
|
updatedCode = updatedCode.replaceAllMapped(
|
||||||
|
RegExp(r"part '(\w+)\.g\.dart';"),
|
||||||
|
(match) => "part '${match.group(1)}.g.dart';",
|
||||||
|
);
|
||||||
|
|
||||||
|
updatedCode = updatedCode.replaceAllMapped(
|
||||||
|
RegExp(r"import '../(\w+_api)\.dart';"),
|
||||||
|
(match) => "import '../$version/${match.group(1)}.dart';",
|
||||||
|
);
|
||||||
|
|
||||||
|
return updatedCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _generateVersionedApiClient(
|
||||||
|
Map<String, Map<String, String>> versionedFiles,
|
||||||
|
) {
|
||||||
|
final buffer = StringBuffer()
|
||||||
|
..writeln('// 统一 API 客户端')
|
||||||
|
..writeln('// 支持多版本 API 管理')
|
||||||
|
..writeln('// 由 xy_swagger_generator by max 生成')
|
||||||
|
..writeln('// Copyright (C) 2025 YuanXuan. All rights reserved.')
|
||||||
|
..writeln()
|
||||||
|
..writeln("import 'package:dio/dio.dart';")
|
||||||
|
..writeln();
|
||||||
|
|
||||||
|
final apiClasses = <String, Set<String>>{};
|
||||||
|
|
||||||
|
for (final versionEntry in versionedFiles.entries) {
|
||||||
|
final version = versionEntry.key;
|
||||||
|
final files = versionEntry.value;
|
||||||
|
apiClasses[version] = {};
|
||||||
|
|
||||||
|
for (final entry in files.entries) {
|
||||||
|
final code = entry.value;
|
||||||
|
final extracted = _extractApiClassNamesFromCode(code);
|
||||||
|
if (extracted.isNotEmpty) {
|
||||||
|
apiClasses[version]!.addAll(extracted.toSet());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final fileName = entry.key;
|
||||||
|
final className = fileName
|
||||||
|
.replaceAll('.dart', '')
|
||||||
|
.split('_')
|
||||||
|
.map(
|
||||||
|
(word) => word.isEmpty
|
||||||
|
? ''
|
||||||
|
: (word[0].toUpperCase() + word.substring(1)),
|
||||||
|
)
|
||||||
|
.join();
|
||||||
|
apiClasses[version]!.add(className);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final versions = apiClasses.keys.toList()..sort();
|
||||||
|
for (final version in versions) {
|
||||||
|
buffer.writeln("import '$version/index.dart';");
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer
|
||||||
|
..writeln()
|
||||||
|
..writeln('/// 统一 API 客户端');
|
||||||
|
|
||||||
|
final apiClientClassName = _config.apiClientClassName;
|
||||||
|
buffer
|
||||||
|
..writeln('/// 支持多版本 API 访问')
|
||||||
|
..writeln('class $apiClientClassName {')
|
||||||
|
..writeln(' final Dio _dio;')
|
||||||
|
..writeln();
|
||||||
|
|
||||||
|
for (final versionEntry in apiClasses.entries) {
|
||||||
|
final version = versionEntry.key;
|
||||||
|
final versionUpper = version == 'v1' ? '' : version.toUpperCase();
|
||||||
|
|
||||||
|
for (final className in versionEntry.value) {
|
||||||
|
final suffix = version == 'v1' ? '' : versionUpper;
|
||||||
|
buffer.writeln(
|
||||||
|
' late final $className$suffix '
|
||||||
|
'_${_toLowerCamelCase(className)}$suffix;',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer
|
||||||
|
..writeln()
|
||||||
|
..writeln(' $apiClientClassName(this._dio) {')
|
||||||
|
..writeln(' _initApis();')
|
||||||
|
..writeln(' }')
|
||||||
|
..writeln()
|
||||||
|
..writeln(' void _initApis() {');
|
||||||
|
|
||||||
|
for (final versionEntry in apiClasses.entries) {
|
||||||
|
final version = versionEntry.key;
|
||||||
|
final versionUpper = version == 'v1' ? '' : version.toUpperCase();
|
||||||
|
|
||||||
|
for (final className in versionEntry.value) {
|
||||||
|
final fieldName = _toLowerCamelCase(className);
|
||||||
|
final suffix = version == 'v1' ? '' : versionUpper;
|
||||||
|
buffer.writeln(' _$fieldName$suffix = $className$suffix(_dio);');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer
|
||||||
|
..writeln(' }')
|
||||||
|
..writeln()
|
||||||
|
..writeln(' // ========== 版本化 API 访问 ==========')
|
||||||
|
..writeln();
|
||||||
|
|
||||||
|
for (final versionEntry in apiClasses.entries) {
|
||||||
|
final version = versionEntry.key;
|
||||||
|
final versionUpper = version == 'v1' ? '' : version.toUpperCase();
|
||||||
|
final versionLabel =
|
||||||
|
version == 'v1' ? 'V1(默认版本)' : '${version.toUpperCase()} 版本';
|
||||||
|
|
||||||
|
buffer.writeln(' /// $versionLabel API');
|
||||||
|
for (final className in versionEntry.value) {
|
||||||
|
final fieldName = _toLowerCamelCase(className);
|
||||||
|
final suffix = version == 'v1' ? '' : versionUpper;
|
||||||
|
buffer.writeln(
|
||||||
|
' $className$suffix get $fieldName$suffix => _$fieldName$suffix;',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
buffer.writeln();
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.writeln('}');
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> _extractApiClassNamesFromCode(String code) {
|
||||||
|
try {
|
||||||
|
final regex = RegExp(r'abstract\s+class\s+(\w+Api)\b');
|
||||||
|
final matches = regex.allMatches(code);
|
||||||
|
if (matches.isEmpty) return const [];
|
||||||
|
return matches.map((m) => m.group(1)!).toList();
|
||||||
|
} on FormatException {
|
||||||
|
return const [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _toLowerCamelCase(String className) {
|
||||||
|
final name = className.replaceAll('Api', '');
|
||||||
|
return name[0].toLowerCase() + name.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _generateVersionIndexFile(
|
||||||
|
String versionDir,
|
||||||
|
List<String> fileNames,
|
||||||
|
) async {
|
||||||
|
final buffer = StringBuffer()
|
||||||
|
..writeln('// API 接口导出文件')
|
||||||
|
..writeln('// 由 xy_swagger_generator by max 生成')
|
||||||
|
..writeln('// Copyright (C) 2025 YuanXuan. All rights reserved.')
|
||||||
|
..writeln();
|
||||||
|
|
||||||
|
final sortedFiles = fileNames.toList()..sort();
|
||||||
|
for (final fileName in sortedFiles) {
|
||||||
|
buffer.writeln("export '$fileName';");
|
||||||
|
}
|
||||||
|
|
||||||
|
final indexPath = '$versionDir/index.dart';
|
||||||
|
await FileUtils.writeFile(indexPath, buffer.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,12 +96,6 @@ class ApiPath {
|
||||||
|
|
||||||
/// 安全要求
|
/// 安全要求
|
||||||
final List<ApiSecurityRequirement> security;
|
final List<ApiSecurityRequirement> security;
|
||||||
|
|
||||||
bool get isMultipart =>
|
|
||||||
parameters.any((p) => p.type == PropertyType.file) ||
|
|
||||||
(requestBody?.content.keys
|
|
||||||
.any((k) => k.contains('multipart/form-data')) ??
|
|
||||||
false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// API参数信息
|
/// API参数信息
|
||||||
|
|
|
||||||
|
|
@ -357,7 +357,6 @@ class ApiModel {
|
||||||
this.not,
|
this.not,
|
||||||
this.discriminator,
|
this.discriminator,
|
||||||
this.usageType = ModelUsageType.unknown,
|
this.usageType = ModelUsageType.unknown,
|
||||||
this.type,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/// 从JSON创建ApiModel
|
/// 从JSON创建ApiModel
|
||||||
|
|
@ -416,7 +415,6 @@ class ApiModel {
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
return ApiModel(
|
return ApiModel(
|
||||||
type: json['type'] as String?,
|
|
||||||
name: name,
|
name: name,
|
||||||
description: json['description'] as String? ?? '',
|
description: json['description'] as String? ?? '',
|
||||||
required: required,
|
required: required,
|
||||||
|
|
@ -463,7 +461,6 @@ class ApiModel {
|
||||||
/// 模型用途类型
|
/// 模型用途类型
|
||||||
/// 标识该模型在 API 中的实际用途(请求/响应/通用/未知)
|
/// 标识该模型在 API 中的实际用途(请求/响应/通用/未知)
|
||||||
final ModelUsageType usageType;
|
final ModelUsageType usageType;
|
||||||
final String? type;
|
|
||||||
|
|
||||||
/// 检查是否使用了组合模式
|
/// 检查是否使用了组合模式
|
||||||
bool get isComposition =>
|
bool get isComposition =>
|
||||||
|
|
@ -501,7 +498,6 @@ class ApiModel {
|
||||||
not: not,
|
not: not,
|
||||||
discriminator: discriminator,
|
discriminator: discriminator,
|
||||||
usageType: newUsageType,
|
usageType: newUsageType,
|
||||||
type: type,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,92 @@
|
||||||
/// Backward-compat shim for TemplateRenderer
|
import 'dart:io';
|
||||||
library;
|
|
||||||
|
|
||||||
export 'package:swagger_generator_flutter/pipeline/render/impl/template_renderer.dart';
|
import 'package:mustache_template/mustache_template.dart';
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
import 'package:swagger_generator_flutter/core/config_repository.dart';
|
||||||
|
import 'package:swagger_generator_flutter/utils/path_resolver.dart';
|
||||||
|
|
||||||
|
part 'template/template_loader.dart';
|
||||||
|
|
||||||
|
/// 模板渲染器
|
||||||
|
/// 负责加载和渲染 Mustache 模板,支持文件覆盖与内置模板
|
||||||
|
class TemplateRenderer {
|
||||||
|
TemplateRenderer({
|
||||||
|
String? templateRoot,
|
||||||
|
List<String>? extraTemplateRoots,
|
||||||
|
}) : _loader = TemplateLoader(
|
||||||
|
customRoot: templateRoot,
|
||||||
|
extraRoots: extraTemplateRoots,
|
||||||
|
),
|
||||||
|
_baseContext = _buildBaseContext();
|
||||||
|
|
||||||
|
final TemplateLoader _loader;
|
||||||
|
final Map<String, Template> _templateCache = {};
|
||||||
|
final Map<String, dynamic> _baseContext;
|
||||||
|
|
||||||
|
/// 渲染模板
|
||||||
|
///
|
||||||
|
/// [templateName] 模板名称(不含 .mustache 扩展名)
|
||||||
|
/// [data] 模板数据
|
||||||
|
String render(
|
||||||
|
String templateName,
|
||||||
|
Map<String, dynamic> data, {
|
||||||
|
Map<String, String>? partials,
|
||||||
|
}) {
|
||||||
|
final template = _getTemplate(templateName);
|
||||||
|
final context = {..._baseContext, ...data};
|
||||||
|
return template.renderString(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 部分模板解析器
|
||||||
|
Template? _partialResolver(String name) {
|
||||||
|
try {
|
||||||
|
return _getTemplate(name);
|
||||||
|
} on Exception {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取模板(带缓存)
|
||||||
|
Template _getTemplate(String templateName) {
|
||||||
|
if (_templateCache.containsKey(templateName)) {
|
||||||
|
return _templateCache[templateName]!;
|
||||||
|
}
|
||||||
|
|
||||||
|
final source = _getTemplateSource(templateName);
|
||||||
|
final template = Template(
|
||||||
|
source,
|
||||||
|
name: templateName,
|
||||||
|
lenient: true,
|
||||||
|
htmlEscapeValues: false,
|
||||||
|
partialResolver: _partialResolver,
|
||||||
|
);
|
||||||
|
_templateCache[templateName] = template;
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取模板源码:优先文件,其次内嵌
|
||||||
|
String _getTemplateSource(String templateName) {
|
||||||
|
final fileTemplate = _loader.load(templateName);
|
||||||
|
if (fileTemplate != null) {
|
||||||
|
return fileTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw Exception('Template not found in file system: $templateName');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 清除模板缓存
|
||||||
|
void clearCache() {
|
||||||
|
_templateCache.clear();
|
||||||
|
_loader.clearCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, dynamic> _buildBaseContext() {
|
||||||
|
// Load once synchronously to avoid repeated disk IO
|
||||||
|
final config = ConfigRepository.loadSync();
|
||||||
|
return {
|
||||||
|
'generatorName': config.generatorName,
|
||||||
|
'author': config.author,
|
||||||
|
'copyright': config.copyright,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:swagger_generator_flutter/core/config.dart';
|
import 'package:swagger_generator_flutter/core/config.dart';
|
||||||
import 'package:swagger_generator_flutter/core/exceptions.dart';
|
import 'package:swagger_generator_flutter/core/exceptions.dart';
|
||||||
import 'package:swagger_generator_flutter/core/models.dart';
|
import 'package:swagger_generator_flutter/core/models.dart';
|
||||||
import 'package:swagger_generator_flutter/utils/string_helper.dart';
|
import 'package:swagger_generator_flutter/utils/string_utils.dart';
|
||||||
|
|
||||||
/// 代码生成器基类
|
/// 代码生成器基类
|
||||||
/// 定义通用的接口和功能
|
/// 定义通用的接口和功能
|
||||||
|
|
@ -16,7 +16,7 @@ abstract class BaseGenerator {
|
||||||
/// [description] 文件描述
|
/// [description] 文件描述
|
||||||
/// [fileName] 文件名(可选)
|
/// [fileName] 文件名(可选)
|
||||||
String generateFileHeader(String description, {String? fileName}) {
|
String generateFileHeader(String description, {String? fileName}) {
|
||||||
final header = StringHelper.generateFileHeader(
|
final header = StringUtils.generateFileHeader(
|
||||||
description,
|
description,
|
||||||
SwaggerConfig.swaggerJsonUrls.isNotEmpty
|
SwaggerConfig.swaggerJsonUrls.isNotEmpty
|
||||||
? SwaggerConfig.swaggerJsonUrls.first
|
? SwaggerConfig.swaggerJsonUrls.first
|
||||||
|
|
@ -105,7 +105,7 @@ abstract class ModelGenerator extends BaseGenerator {
|
||||||
throw CodeGenerationException('模型不是枚举类型', generatorType: generatorType);
|
throw CodeGenerationException('模型不是枚举类型', generatorType: generatorType);
|
||||||
}
|
}
|
||||||
|
|
||||||
final className = StringHelper.generateClassName(model.name);
|
final className = StringUtils.generateClassName(model.name);
|
||||||
final enumType = model.enumType?.value ?? 'string';
|
final enumType = model.enumType?.value ?? 'string';
|
||||||
final valueType =
|
final valueType =
|
||||||
enumType == 'integer' || enumType == 'number' ? 'int' : 'String';
|
enumType == 'integer' || enumType == 'number' ? 'int' : 'String';
|
||||||
|
|
@ -116,7 +116,7 @@ abstract class ModelGenerator extends BaseGenerator {
|
||||||
|
|
||||||
// 生成枚举类
|
// 生成枚举类
|
||||||
if (model.description.isNotEmpty) {
|
if (model.description.isNotEmpty) {
|
||||||
buffer.writeln(StringHelper.generateComment(model.description));
|
buffer.writeln(StringUtils.generateComment(model.description));
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.writeln('enum $className {');
|
buffer.writeln('enum $className {');
|
||||||
|
|
@ -124,7 +124,7 @@ abstract class ModelGenerator extends BaseGenerator {
|
||||||
// 生成枚举值
|
// 生成枚举值
|
||||||
for (var i = 0; i < model.enumValues.length; i++) {
|
for (var i = 0; i < model.enumValues.length; i++) {
|
||||||
final value = model.enumValues[i];
|
final value = model.enumValues[i];
|
||||||
final enumName = StringHelper.generateEnumValueName(value, i);
|
final enumName = StringUtils.generateEnumValueName(value, i);
|
||||||
final enumLine = enumType == 'integer' || enumType == 'number'
|
final enumLine = enumType == 'integer' || enumType == 'number'
|
||||||
? ' $enumName($value),'
|
? ' $enumName($value),'
|
||||||
: " $enumName('$value'),";
|
: " $enumName('$value'),";
|
||||||
|
|
@ -226,7 +226,7 @@ abstract class ModelGenerator extends BaseGenerator {
|
||||||
return 'Map<String, dynamic>';
|
return 'Map<String, dynamic>';
|
||||||
case PropertyType.reference:
|
case PropertyType.reference:
|
||||||
return property.reference != null
|
return property.reference != null
|
||||||
? StringHelper.generateClassName(property.reference!)
|
? StringUtils.generateClassName(property.reference!)
|
||||||
: 'dynamic';
|
: 'dynamic';
|
||||||
case PropertyType.file:
|
case PropertyType.file:
|
||||||
return 'dynamic';
|
return 'dynamic';
|
||||||
|
|
@ -248,7 +248,7 @@ abstract class ModelGenerator extends BaseGenerator {
|
||||||
items.name != 'integer' &&
|
items.name != 'integer' &&
|
||||||
items.name != 'number' &&
|
items.name != 'number' &&
|
||||||
items.name != 'boolean') {
|
items.name != 'boolean') {
|
||||||
return StringHelper.generateClassName(items.name);
|
return StringUtils.generateClassName(items.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果是基本类型,转换为对应的Dart类型
|
// 如果是基本类型,转换为对应的Dart类型
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
part of '../model_code_generator.dart';
|
part of 'package:swagger_generator_flutter/generators/model_code_generator.dart';
|
||||||
|
|
||||||
String _generateModelCodeWithoutImports(
|
String _generateModelCodeWithoutImports(
|
||||||
ModelCodeGenerator generator,
|
ModelCodeGenerator generator,
|
||||||
|
|
@ -11,14 +11,14 @@ String _generateModelCodeWithoutImports(
|
||||||
}
|
}
|
||||||
|
|
||||||
String _generateEnumCodeWithoutImports(ApiModel model) {
|
String _generateEnumCodeWithoutImports(ApiModel model) {
|
||||||
final className = StringHelper.generateClassName(model.name);
|
final className = StringUtils.generateClassName(model.name);
|
||||||
final enumType = model.enumType?.value ?? 'string';
|
final enumType = model.enumType?.value ?? 'string';
|
||||||
final valueType =
|
final valueType =
|
||||||
enumType == 'integer' || enumType == 'number' ? 'int' : 'String';
|
enumType == 'integer' || enumType == 'number' ? 'int' : 'String';
|
||||||
final buffer = StringBuffer();
|
final buffer = StringBuffer();
|
||||||
|
|
||||||
if (model.description.isNotEmpty) {
|
if (model.description.isNotEmpty) {
|
||||||
buffer.writeln(StringHelper.generateComment(model.description));
|
buffer.writeln(StringUtils.generateComment(model.description));
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer
|
buffer
|
||||||
|
|
@ -27,7 +27,7 @@ String _generateEnumCodeWithoutImports(ApiModel model) {
|
||||||
|
|
||||||
for (var i = 0; i < model.enumValues.length; i++) {
|
for (var i = 0; i < model.enumValues.length; i++) {
|
||||||
final value = model.enumValues[i];
|
final value = model.enumValues[i];
|
||||||
final enumName = StringHelper.generateEnumValueName(value, i);
|
final enumName = StringUtils.generateEnumValueName(value, i);
|
||||||
final enumLine = enumType == 'integer' || enumType == 'number'
|
final enumLine = enumType == 'integer' || enumType == 'number'
|
||||||
? ' $enumName($value),'
|
? ' $enumName($value),'
|
||||||
: " $enumName('$value'),";
|
: " $enumName('$value'),";
|
||||||
|
|
@ -73,10 +73,10 @@ String _generateAnnotatedModelCodeWithoutImports(
|
||||||
ModelCodeGenerator generator,
|
ModelCodeGenerator generator,
|
||||||
ApiModel model,
|
ApiModel model,
|
||||||
) {
|
) {
|
||||||
final className = StringHelper.generateClassName(model.name);
|
final className = StringUtils.generateClassName(model.name);
|
||||||
final buffer = StringBuffer();
|
final buffer = StringBuffer();
|
||||||
|
|
||||||
final partFileName = StringHelper.generateFileName(model.name);
|
final partFileName = StringUtils.generateFileName(model.name);
|
||||||
final freezedPart = partFileName.replaceAll('.dart', '.freezed.dart');
|
final freezedPart = partFileName.replaceAll('.dart', '.freezed.dart');
|
||||||
final generatedPart = partFileName.replaceAll('.dart', '.g.dart');
|
final generatedPart = partFileName.replaceAll('.dart', '.g.dart');
|
||||||
buffer
|
buffer
|
||||||
|
|
@ -85,7 +85,7 @@ String _generateAnnotatedModelCodeWithoutImports(
|
||||||
..writeln();
|
..writeln();
|
||||||
|
|
||||||
if (model.description.isNotEmpty) {
|
if (model.description.isNotEmpty) {
|
||||||
buffer.writeln(StringHelper.generateComment(model.description));
|
buffer.writeln(StringUtils.generateComment(model.description));
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer
|
buffer
|
||||||
|
|
@ -100,11 +100,11 @@ String _generateAnnotatedModelCodeWithoutImports(
|
||||||
property.format != 'date';
|
property.format != 'date';
|
||||||
final hasDefaultValue = property.defaultValue != null || isNormalString;
|
final hasDefaultValue = property.defaultValue != null || isNormalString;
|
||||||
final nullable = hasDefaultValue ? '' : (property.nullable ? '?' : '');
|
final nullable = hasDefaultValue ? '' : (property.nullable ? '?' : '');
|
||||||
final dartPropName = StringHelper.toDartPropertyName(propName);
|
final dartPropName = StringUtils.toDartPropertyName(propName);
|
||||||
|
|
||||||
if (property.description.isNotEmpty) {
|
if (property.description.isNotEmpty) {
|
||||||
buffer.writeln(
|
buffer.writeln(
|
||||||
' ${StringHelper.generateComment(property.description)}',
|
' ${StringUtils.generateComment(property.description)}',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,7 +179,7 @@ String _generateSubDirectoryIndexFile(
|
||||||
..sort((a, b) => a.name.compareTo(b.name));
|
..sort((a, b) => a.name.compareTo(b.name));
|
||||||
|
|
||||||
for (final model in sortedModels) {
|
for (final model in sortedModels) {
|
||||||
final fileName = StringHelper.generateFileName(model.name);
|
final fileName = StringUtils.generateFileName(model.name);
|
||||||
buffer.writeln("export '$fileName';");
|
buffer.writeln("export '$fileName';");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
part of '../model_code_generator.dart';
|
part of 'package:swagger_generator_flutter/generators/model_code_generator.dart';
|
||||||
|
|
||||||
Map<String, String> buildSeparateModelFiles(ModelCodeGenerator generator) {
|
Map<String, String> buildSeparateModelFiles(ModelCodeGenerator generator) {
|
||||||
final files = <String, String>{};
|
final files = <String, String>{};
|
||||||
|
|
@ -12,7 +12,7 @@ Map<String, String> buildSeparateModelFiles(ModelCodeGenerator generator) {
|
||||||
final subDir = _getModelSubDirectory(model);
|
final subDir = _getModelSubDirectory(model);
|
||||||
modelsByDirectory.putIfAbsent(subDir, () => []).add(model);
|
modelsByDirectory.putIfAbsent(subDir, () => []).add(model);
|
||||||
|
|
||||||
final fileName = StringHelper.generateFileName(model.name);
|
final fileName = StringUtils.generateFileName(model.name);
|
||||||
final filePath = '$subDir/$fileName';
|
final filePath = '$subDir/$fileName';
|
||||||
final content = buildSingleModelFile(generator, model, fileName: fileName);
|
final content = buildSingleModelFile(generator, model, fileName: fileName);
|
||||||
files[filePath] = content;
|
files[filePath] = content;
|
||||||
|
|
@ -38,7 +38,7 @@ String buildSingleModelFile(
|
||||||
..writeln(
|
..writeln(
|
||||||
generator.generateFileHeader(
|
generator.generateFileHeader(
|
||||||
'${model.name} 模型定义',
|
'${model.name} 模型定义',
|
||||||
fileName: fileName ?? StringHelper.generateFileName(model.name),
|
fileName: fileName ?? StringUtils.generateFileName(model.name),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
..writeln();
|
..writeln();
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
part of '../model_code_generator.dart';
|
part of 'package:swagger_generator_flutter/generators/model_code_generator.dart';
|
||||||
|
|
||||||
String getDartPropertyTypeWithPagination(
|
String getDartPropertyTypeWithPagination(
|
||||||
ModelCodeGenerator generator,
|
ModelCodeGenerator generator,
|
||||||
|
|
@ -24,7 +24,7 @@ String _getPaginationItemType(ApiModel items) {
|
||||||
items.name != 'integer' &&
|
items.name != 'integer' &&
|
||||||
items.name != 'number' &&
|
items.name != 'number' &&
|
||||||
items.name != 'boolean') {
|
items.name != 'boolean') {
|
||||||
return StringHelper.generateClassName(items.name);
|
return StringUtils.generateClassName(items.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (items.name) {
|
switch (items.name) {
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import 'package:swagger_generator_flutter/core/config.dart';
|
import 'package:swagger_generator_flutter/core/config.dart';
|
||||||
import 'package:swagger_generator_flutter/core/models.dart';
|
import 'package:swagger_generator_flutter/core/models.dart';
|
||||||
import 'package:swagger_generator_flutter/pipeline/generate/impl/base_generator.dart';
|
import 'package:swagger_generator_flutter/generators/base_generator.dart';
|
||||||
import 'package:swagger_generator_flutter/utils/string_helper.dart';
|
import 'package:swagger_generator_flutter/utils/string_utils.dart';
|
||||||
|
|
||||||
part 'model/model_content_builders.dart';
|
|
||||||
part 'model/model_file_writers.dart';
|
|
||||||
part 'model/model_pagination_helpers.dart';
|
part 'model/model_pagination_helpers.dart';
|
||||||
|
part 'model/model_file_writers.dart';
|
||||||
|
part 'model/model_content_builders.dart';
|
||||||
|
|
||||||
/// 模型代码生成器
|
/// 模型代码生成器
|
||||||
/// 负责生成Dart模型类代码
|
/// 负责生成Dart模型类代码
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
part of '../retrofit_api_generator.dart';
|
part of 'package:swagger_generator_flutter/generators/retrofit_api_generator.dart';
|
||||||
|
|
||||||
mixin RetrofitApiGrouping {
|
mixin RetrofitApiGrouping {
|
||||||
RetrofitApiGenerator get _g => this as RetrofitApiGenerator;
|
RetrofitApiGenerator get _g => this as RetrofitApiGenerator;
|
||||||
|
|
@ -16,7 +16,7 @@ mixin RetrofitApiGrouping {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 按版本分组 API paths
|
/// 按版本分组 API paths
|
||||||
/// 返回: `Map<版本, Map<Tag, List<ApiPath>>>`
|
/// 返回: Map<版本, Map<Tag, List<ApiPath>>>
|
||||||
Map<String, Map<String, List<ApiPath>>> groupApisByVersion(
|
Map<String, Map<String, List<ApiPath>>> groupApisByVersion(
|
||||||
List<ApiPath> paths,
|
List<ApiPath> paths,
|
||||||
) {
|
) {
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
part of '../retrofit_api_generator.dart';
|
part of 'package:swagger_generator_flutter/generators/retrofit_api_generator.dart';
|
||||||
|
|
||||||
/// API 方法参数
|
/// API 方法参数
|
||||||
class ApiMethodParameter {
|
class ApiMethodParameter {
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
part of '../retrofit_api_generator.dart';
|
part of 'package:swagger_generator_flutter/generators/retrofit_api_generator.dart';
|
||||||
|
|
||||||
mixin RetrofitApiParameterEntities {
|
mixin RetrofitApiParameterEntities {
|
||||||
RetrofitApiGenerator get _g => this as RetrofitApiGenerator;
|
RetrofitApiGenerator get _g => this as RetrofitApiGenerator;
|
||||||
|
|
@ -6,7 +6,7 @@ mixin RetrofitApiParameterEntities {
|
||||||
/// 生成参数实体类的类名
|
/// 生成参数实体类的类名
|
||||||
String _generateParameterEntityClassName(ApiPath path) {
|
String _generateParameterEntityClassName(ApiPath path) {
|
||||||
final methodName = _g._generateSimpleMethodName(path);
|
final methodName = _g._generateSimpleMethodName(path);
|
||||||
return '${StringHelper.toPascalCase(methodName)}Parameters';
|
return '${StringUtils.toPascalCase(methodName)}Parameters';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 生成参数实体类
|
/// 生成参数实体类
|
||||||
|
|
@ -19,7 +19,7 @@ mixin RetrofitApiParameterEntities {
|
||||||
..writeln(
|
..writeln(
|
||||||
_g.generateFileHeader(
|
_g.generateFileHeader(
|
||||||
'参数实体类 - $className',
|
'参数实体类 - $className',
|
||||||
fileName: '${StringHelper.toSnakeCase(className)}.dart',
|
fileName: '${StringUtils.toSnakeCase(className)}.dart',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
..writeln(
|
..writeln(
|
||||||
|
|
@ -28,12 +28,12 @@ mixin RetrofitApiParameterEntities {
|
||||||
..writeln()
|
..writeln()
|
||||||
..writeln("import 'package:json_annotation/json_annotation.dart';")
|
..writeln("import 'package:json_annotation/json_annotation.dart';")
|
||||||
..writeln()
|
..writeln()
|
||||||
..writeln("part '${StringHelper.toSnakeCase(className)}.g.dart';")
|
..writeln("part '${StringUtils.toSnakeCase(className)}.g.dart';")
|
||||||
..writeln()
|
..writeln()
|
||||||
..writeln('@JsonSerializable(checked: true, includeIfNull: false)')
|
..writeln('@JsonSerializable(checked: true, includeIfNull: false)')
|
||||||
..writeln('class $className {');
|
..writeln('class $className {');
|
||||||
for (final param in queryParams) {
|
for (final param in queryParams) {
|
||||||
final dartName = StringHelper.toDartPropertyName(param.name);
|
final dartName = StringUtils.toDartPropertyName(param.name);
|
||||||
final dartType = _g._getDartType(param.type);
|
final dartType = _g._getDartType(param.type);
|
||||||
final nullable = param.required ? '' : '?';
|
final nullable = param.required ? '' : '?';
|
||||||
|
|
||||||
|
|
@ -53,7 +53,7 @@ mixin RetrofitApiParameterEntities {
|
||||||
|
|
||||||
buffer.writeln(' const $className({');
|
buffer.writeln(' const $className({');
|
||||||
for (final param in queryParams) {
|
for (final param in queryParams) {
|
||||||
final dartName = StringHelper.toDartPropertyName(param.name);
|
final dartName = StringUtils.toDartPropertyName(param.name);
|
||||||
final required = param.required ? 'required ' : '';
|
final required = param.required ? 'required ' : '';
|
||||||
buffer.writeln(' ${required}this.$dartName,');
|
buffer.writeln(' ${required}this.$dartName,');
|
||||||
}
|
}
|
||||||
|
|
@ -73,7 +73,7 @@ mixin RetrofitApiParameterEntities {
|
||||||
..writeln(' Map<String, dynamic> toQueryMap() {')
|
..writeln(' Map<String, dynamic> toQueryMap() {')
|
||||||
..writeln(' final map = <String, dynamic>{};');
|
..writeln(' final map = <String, dynamic>{};');
|
||||||
for (final param in queryParams) {
|
for (final param in queryParams) {
|
||||||
final dartName = StringHelper.toDartPropertyName(param.name);
|
final dartName = StringUtils.toDartPropertyName(param.name);
|
||||||
buffer.writeln(
|
buffer.writeln(
|
||||||
" if ($dartName != null) map['${param.name}'] = $dartName;",
|
" if ($dartName != null) map['${param.name}'] = $dartName;",
|
||||||
);
|
);
|
||||||
|
|
@ -96,7 +96,7 @@ mixin RetrofitApiParameterEntities {
|
||||||
for (final entry in _generatedParameterEntities.entries) {
|
for (final entry in _generatedParameterEntities.entries) {
|
||||||
final className = entry.key;
|
final className = entry.key;
|
||||||
final content = entry.value;
|
final content = entry.value;
|
||||||
final fileName = StringHelper.generateFileName(className);
|
final fileName = StringUtils.generateFileName(className);
|
||||||
files[fileName] = content;
|
files[fileName] = content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
part of '../retrofit_api_generator.dart';
|
part of 'package:swagger_generator_flutter/generators/retrofit_api_generator.dart';
|
||||||
|
|
||||||
mixin RetrofitApiParameters {
|
mixin RetrofitApiParameters {
|
||||||
RetrofitApiGenerator get _g => this as RetrofitApiGenerator;
|
RetrofitApiGenerator get _g => this as RetrofitApiGenerator;
|
||||||
|
|
@ -13,7 +13,7 @@ mixin RetrofitApiParameters {
|
||||||
for (final param in pathParams) {
|
for (final param in pathParams) {
|
||||||
parameters.add(
|
parameters.add(
|
||||||
ApiMethodParameter(
|
ApiMethodParameter(
|
||||||
name: StringHelper.toDartPropertyName(param.name),
|
name: StringUtils.toDartPropertyName(param.name),
|
||||||
type: _getDartType(param.type),
|
type: _getDartType(param.type),
|
||||||
annotation: _g.useRetrofit ? "@Path('${param.name}')" : '',
|
annotation: _g.useRetrofit ? "@Path('${param.name}')" : '',
|
||||||
required: param.required,
|
required: param.required,
|
||||||
|
|
@ -46,7 +46,7 @@ mixin RetrofitApiParameters {
|
||||||
final nullable = param.required ? '' : '?';
|
final nullable = param.required ? '' : '?';
|
||||||
parameters.add(
|
parameters.add(
|
||||||
ApiMethodParameter(
|
ApiMethodParameter(
|
||||||
name: StringHelper.toDartPropertyName(param.name),
|
name: StringUtils.toDartPropertyName(param.name),
|
||||||
type: '${_getDartType(param.type)}$nullable',
|
type: '${_getDartType(param.type)}$nullable',
|
||||||
annotation: _g.useRetrofit ? "@Query('${param.name}')" : '',
|
annotation: _g.useRetrofit ? "@Query('${param.name}')" : '',
|
||||||
required: param.required,
|
required: param.required,
|
||||||
|
|
@ -64,7 +64,7 @@ mixin RetrofitApiParameters {
|
||||||
final bodyType = _inferRequestBodyType(path);
|
final bodyType = _inferRequestBodyType(path);
|
||||||
parameters.add(
|
parameters.add(
|
||||||
ApiMethodParameter(
|
ApiMethodParameter(
|
||||||
name: StringHelper.toDartPropertyName(
|
name: StringUtils.toDartPropertyName(
|
||||||
param.name.isNotEmpty ? param.name : 'request',
|
param.name.isNotEmpty ? param.name : 'request',
|
||||||
),
|
),
|
||||||
type: bodyType,
|
type: bodyType,
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
part of '../retrofit_api_generator.dart';
|
part of 'package:swagger_generator_flutter/generators/retrofit_api_generator.dart';
|
||||||
|
|
||||||
mixin RetrofitApiReturnTypes {
|
mixin RetrofitApiReturnTypes {
|
||||||
RetrofitApiGenerator get _g => this as RetrofitApiGenerator;
|
RetrofitApiGenerator get _g => this as RetrofitApiGenerator;
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
part of '../retrofit_api_generator.dart';
|
part of 'package:swagger_generator_flutter/generators/retrofit_api_generator.dart';
|
||||||
|
|
||||||
mixin RetrofitApiSchemaComposition {
|
mixin RetrofitApiSchemaComposition {
|
||||||
RetrofitApiGenerator get _g => this as RetrofitApiGenerator;
|
RetrofitApiGenerator get _g => this as RetrofitApiGenerator;
|
||||||
|
|
||||||
/// 组合模式 Schema 处理
|
/// 组合模式 Schema 处理
|
||||||
String? extractTypeFromCompositionSchema(Map<String, dynamic> schema) {
|
String? _extractTypeFromCompositionSchema(Map<String, dynamic> schema) {
|
||||||
if (schema['discriminator'] != null) {
|
if (schema['discriminator'] != null) {
|
||||||
final discriminatorType = _handleDiscriminatorSchema(schema);
|
final discriminatorType = _handleDiscriminatorSchema(schema);
|
||||||
if (discriminatorType != null) {
|
if (discriminatorType != null) {
|
||||||
|
|
@ -36,7 +36,7 @@ mixin RetrofitApiSchemaComposition {
|
||||||
if (schemaData[r'$ref'] != null) {
|
if (schemaData[r'$ref'] != null) {
|
||||||
final ref = schemaData[r'$ref'] as String;
|
final ref = schemaData[r'$ref'] as String;
|
||||||
final refName = ref.split('/').last;
|
final refName = ref.split('/').last;
|
||||||
return StringHelper.generateClassName(refName);
|
return StringUtils.generateClassName(refName);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (schemaData['type'] != null) {
|
if (schemaData['type'] != null) {
|
||||||
|
|
@ -52,7 +52,7 @@ mixin RetrofitApiSchemaComposition {
|
||||||
}
|
}
|
||||||
return 'List<dynamic>';
|
return 'List<dynamic>';
|
||||||
} else {
|
} else {
|
||||||
return mapJsonTypeToFlutterType(type);
|
return _mapJsonTypeToFlutterType(type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -67,7 +67,7 @@ mixin RetrofitApiSchemaComposition {
|
||||||
if (schemaData is Map<String, dynamic> && schemaData[r'$ref'] != null) {
|
if (schemaData is Map<String, dynamic> && schemaData[r'$ref'] != null) {
|
||||||
final ref = schemaData[r'$ref'] as String;
|
final ref = schemaData[r'$ref'] as String;
|
||||||
final refName = ref.split('/').last;
|
final refName = ref.split('/').last;
|
||||||
refTypes.add(StringHelper.generateClassName(refName));
|
refTypes.add(StringUtils.generateClassName(refName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,7 +104,7 @@ mixin RetrofitApiSchemaComposition {
|
||||||
for (final value in mapping.values) {
|
for (final value in mapping.values) {
|
||||||
if (value is String) {
|
if (value is String) {
|
||||||
final refName = value.split('/').last;
|
final refName = value.split('/').last;
|
||||||
mappedTypes.add(StringHelper.generateClassName(refName));
|
mappedTypes.add(StringUtils.generateClassName(refName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -124,7 +124,7 @@ mixin RetrofitApiSchemaComposition {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 将 JSON Schema 类型映射到 Flutter 类型
|
/// 将 JSON Schema 类型映射到 Flutter 类型
|
||||||
String mapJsonTypeToFlutterType(String jsonType) {
|
String _mapJsonTypeToFlutterType(String jsonType) {
|
||||||
switch (jsonType) {
|
switch (jsonType) {
|
||||||
case 'string':
|
case 'string':
|
||||||
return 'String';
|
return 'String';
|
||||||
|
|
@ -0,0 +1,272 @@
|
||||||
|
part of 'package:swagger_generator_flutter/generators/retrofit_api_generator.dart';
|
||||||
|
|
||||||
|
mixin RetrofitApiSchema {
|
||||||
|
RetrofitApiGenerator get _g => this as RetrofitApiGenerator;
|
||||||
|
|
||||||
|
/// 从路径的响应中提取返回类型
|
||||||
|
String? _extractResponseTypeFromPath(ApiPath path) {
|
||||||
|
final successResponses = ['200', '201', '202'];
|
||||||
|
|
||||||
|
for (final statusCode in successResponses) {
|
||||||
|
final response = path.responses[statusCode];
|
||||||
|
if (response != null) {
|
||||||
|
final type = _g._extractResponseType(response);
|
||||||
|
if (type != null) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final response in path.responses.values) {
|
||||||
|
final type = _g._extractResponseType(response);
|
||||||
|
if (type != null) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 从响应中提取返回类型
|
||||||
|
String? _extractResponseType(ApiResponse response) {
|
||||||
|
final applicationJsonMediaType = response.content['application/json'];
|
||||||
|
if (applicationJsonMediaType != null) {
|
||||||
|
final schema = applicationJsonMediaType.schema;
|
||||||
|
final type = _g._extractTypeFromSchema(schema);
|
||||||
|
if (type != null) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final type = _g._extractTypeFromSchema(response.schema);
|
||||||
|
if (type != null) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 从 schema 中提取类型
|
||||||
|
String? _extractTypeFromSchema(Map<String, dynamic>? schema) {
|
||||||
|
if (schema == null) return null;
|
||||||
|
|
||||||
|
final advancedType = _g._handleAdvancedSchemaFeatures(schema);
|
||||||
|
if (advancedType != null) {
|
||||||
|
return advancedType;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema['allOf'] != null ||
|
||||||
|
schema['oneOf'] != null ||
|
||||||
|
schema['anyOf'] != null) {
|
||||||
|
return _g._extractTypeFromCompositionSchema(schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema[r'$ref'] != null) {
|
||||||
|
final ref = schema[r'$ref'] as String;
|
||||||
|
final parts = ref.split('/');
|
||||||
|
if (parts.isNotEmpty) {
|
||||||
|
final refName = parts.last;
|
||||||
|
if (_g.document.models.containsKey(refName)) {
|
||||||
|
final model = _g.document.models[refName]!;
|
||||||
|
if (_g._isPaginationResponseModel(model)) {
|
||||||
|
final itemsProp = model.properties['items'];
|
||||||
|
if (itemsProp != null && itemsProp.type == PropertyType.array) {
|
||||||
|
var itemType = 'dynamic';
|
||||||
|
if (itemsProp.reference != null) {
|
||||||
|
itemType = StringUtils.generateClassName(itemsProp.reference!);
|
||||||
|
} else if (itemsProp.items != null) {
|
||||||
|
itemType = StringUtils.generateClassName(itemsProp.items!.name);
|
||||||
|
} else if (itemsProp.name.isNotEmpty) {
|
||||||
|
itemType = StringUtils.generateClassName(itemsProp.name);
|
||||||
|
} else if (itemsProp.type != PropertyType.array &&
|
||||||
|
itemsProp.type != PropertyType.reference) {
|
||||||
|
itemType = itemsProp.type.value;
|
||||||
|
}
|
||||||
|
return 'List<$itemType>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return StringUtils.generateClassName(refName);
|
||||||
|
}
|
||||||
|
return StringUtils.generateClassName(refName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema['type'] == 'array' && schema['items'] != null) {
|
||||||
|
final items = schema['items'] as Map<String, dynamic>;
|
||||||
|
final itemType = _g._extractTypeFromSchema(items);
|
||||||
|
if (itemType != null) {
|
||||||
|
return 'List<$itemType>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema['type'] == 'object') {
|
||||||
|
if (schema['properties'] != null) {
|
||||||
|
final properties = schema['properties'] as Map<String, dynamic>;
|
||||||
|
|
||||||
|
if (properties.containsKey('total') &&
|
||||||
|
properties.containsKey('items')) {
|
||||||
|
final totalProp = properties['total'] as Map<String, dynamic>?;
|
||||||
|
final itemsProp = properties['items'] as Map<String, dynamic>?;
|
||||||
|
|
||||||
|
final isTotalNumeric = totalProp != null &&
|
||||||
|
(totalProp['type'] == 'integer' || totalProp['type'] == 'number');
|
||||||
|
|
||||||
|
final isItemsArray = itemsProp != null &&
|
||||||
|
itemsProp['type'] == 'array' &&
|
||||||
|
itemsProp['items'] != null;
|
||||||
|
|
||||||
|
if (isTotalNumeric && isItemsArray) {
|
||||||
|
final itemsSchema = itemsProp['items'] as Map<String, dynamic>;
|
||||||
|
final itemType = _extractTypeFromSchema(itemsSchema);
|
||||||
|
if (itemType != null) {
|
||||||
|
return 'List<$itemType>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Map<String, dynamic>';
|
||||||
|
}
|
||||||
|
if (schema['additionalProperties'] != null) {
|
||||||
|
return 'Map<String, dynamic>';
|
||||||
|
}
|
||||||
|
if (schema['allOf'] != null ||
|
||||||
|
schema['anyOf'] != null ||
|
||||||
|
schema['oneOf'] != null) {
|
||||||
|
return 'Map<String, dynamic>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema['type'] != null) {
|
||||||
|
final type = schema['type'] as String;
|
||||||
|
switch (type) {
|
||||||
|
case 'string':
|
||||||
|
final format = schema['format'] as String?;
|
||||||
|
if (format == 'date-time' || format == 'date') {
|
||||||
|
return 'String';
|
||||||
|
}
|
||||||
|
if (format == 'uuid') {
|
||||||
|
return 'String';
|
||||||
|
}
|
||||||
|
return 'String';
|
||||||
|
case 'integer':
|
||||||
|
return 'int';
|
||||||
|
case 'number':
|
||||||
|
return 'double';
|
||||||
|
case 'boolean':
|
||||||
|
return 'bool';
|
||||||
|
case 'array':
|
||||||
|
final items = schema['items'] as Map<String, dynamic>?;
|
||||||
|
if (items != null) {
|
||||||
|
final itemType = _extractTypeFromSchema(items);
|
||||||
|
return 'List<${itemType ?? 'dynamic'}>';
|
||||||
|
}
|
||||||
|
return 'List<dynamic>';
|
||||||
|
case 'null':
|
||||||
|
return 'dynamic';
|
||||||
|
default:
|
||||||
|
return 'dynamic';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema['enum'] != null) {
|
||||||
|
return 'String';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 处理高级 Schema 特性
|
||||||
|
String? _handleAdvancedSchemaFeatures(Map<String, dynamic> schema) {
|
||||||
|
if (schema['const'] != null) {
|
||||||
|
final constValue = schema['const'];
|
||||||
|
if (constValue is String) {
|
||||||
|
return 'String';
|
||||||
|
} else if (constValue is num) {
|
||||||
|
return constValue is int ? 'int' : 'double';
|
||||||
|
} else if (constValue is bool) {
|
||||||
|
return 'bool';
|
||||||
|
}
|
||||||
|
return 'dynamic';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema['additionalProperties'] != null) {
|
||||||
|
final additionalProps = schema['additionalProperties'];
|
||||||
|
if (additionalProps is bool) {
|
||||||
|
return additionalProps ? 'Map<String, dynamic>' : 'Map<String, never>';
|
||||||
|
} else if (additionalProps is Map<String, dynamic>) {
|
||||||
|
final valueType = _extractTypeFromSchema(additionalProps);
|
||||||
|
return 'Map<String, ${valueType ?? 'dynamic'}>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema['patternProperties'] != null) {
|
||||||
|
final patternProps = schema['patternProperties'] as Map<String, dynamic>?;
|
||||||
|
if (patternProps != null && patternProps.isNotEmpty) {
|
||||||
|
return 'Map<String, dynamic>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema['if'] != null ||
|
||||||
|
schema['then'] != null ||
|
||||||
|
schema['else'] != null) {
|
||||||
|
if (schema['then'] != null) {
|
||||||
|
final thenType =
|
||||||
|
_extractTypeFromSchema(schema['then'] as Map<String, dynamic>?);
|
||||||
|
if (thenType != null) return thenType;
|
||||||
|
}
|
||||||
|
if (schema['else'] != null) {
|
||||||
|
final elseType =
|
||||||
|
_extractTypeFromSchema(schema['else'] as Map<String, dynamic>?);
|
||||||
|
if (elseType != null) return elseType;
|
||||||
|
}
|
||||||
|
return 'dynamic';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 检查 schema 是否包含分页结构(total 和 items 字段)
|
||||||
|
bool _hasPaginationSchema(Map<String, dynamic> schema) {
|
||||||
|
if (schema['type'] != 'object') return false;
|
||||||
|
|
||||||
|
final properties = schema['properties'] as Map<String, dynamic>?;
|
||||||
|
if (properties == null) return false;
|
||||||
|
|
||||||
|
if (!properties.containsKey('total') || !properties.containsKey('items')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final totalProp = properties['total'] as Map<String, dynamic>?;
|
||||||
|
final itemsProp = properties['items'] as Map<String, dynamic>?;
|
||||||
|
|
||||||
|
final isTotalNumeric = totalProp != null &&
|
||||||
|
(totalProp['type'] == 'integer' || totalProp['type'] == 'number');
|
||||||
|
|
||||||
|
final isItemsArray = itemsProp != null &&
|
||||||
|
itemsProp['type'] == 'array' &&
|
||||||
|
itemsProp['items'] != null;
|
||||||
|
|
||||||
|
return isTotalNumeric && isItemsArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 检查是否是分页响应模型(包含 total 和 items 字段)
|
||||||
|
bool _isPaginationResponseModel(ApiModel model) {
|
||||||
|
if (!model.properties.containsKey('total') ||
|
||||||
|
!model.properties.containsKey('items')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final totalProp = model.properties['total']!;
|
||||||
|
final itemsProp = model.properties['items']!;
|
||||||
|
|
||||||
|
final isTotalNumeric = totalProp.type == PropertyType.integer ||
|
||||||
|
totalProp.type == PropertyType.number;
|
||||||
|
final isItemsArray = itemsProp.type == PropertyType.array;
|
||||||
|
|
||||||
|
return isTotalNumeric && isItemsArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isArraySchema(Map<String, dynamic> schema) {
|
||||||
|
return schema['type'] == 'array';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,325 @@
|
||||||
|
part of 'package:swagger_generator_flutter/generators/retrofit_api_generator.dart';
|
||||||
|
|
||||||
|
mixin RetrofitApiTemplateData {
|
||||||
|
RetrofitApiGenerator get _g => this as RetrofitApiGenerator;
|
||||||
|
List<String> _getMainImports() {
|
||||||
|
final tagGroups = _g._groupPathsByTags();
|
||||||
|
final tagImports = tagGroups.keys
|
||||||
|
.map((tagName) => StringUtils.generateFileName('${tagName}Api'))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
final config = ConfigRepository.loadSync();
|
||||||
|
return [
|
||||||
|
...config.packageImports,
|
||||||
|
'package:dio/dio.dart',
|
||||||
|
...tagImports,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Map<String, dynamic>> _buildTagApisData() {
|
||||||
|
final tagGroups = _g._groupPathsByTags();
|
||||||
|
return tagGroups.keys
|
||||||
|
.map(
|
||||||
|
(tagName) => {
|
||||||
|
'tagName': tagName,
|
||||||
|
'apiClassName': '${StringUtils.toPascalCase(tagName)}Api',
|
||||||
|
'propertyName': StringUtils.toCamelCase(tagName),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> _buildApiClassData(List<ApiPath> paths) {
|
||||||
|
final baseUrl =
|
||||||
|
_g.document.servers.isNotEmpty ? _g.document.servers.first.url : '';
|
||||||
|
final fileName =
|
||||||
|
StringUtils.generateFileName(_g.className).replaceAll('.dart', '');
|
||||||
|
|
||||||
|
return {
|
||||||
|
'description': 'Retrofit 风格 API 接口定义',
|
||||||
|
'apiUrl': baseUrl,
|
||||||
|
'imports': _getImports(),
|
||||||
|
'parts': ['$fileName.g.dart'],
|
||||||
|
'docLines': [
|
||||||
|
'${_g.className} API 接口',
|
||||||
|
'使用 Retrofit 和 Dio 进行网络请求',
|
||||||
|
'支持多种媒体类型、文件上传、认证等功能',
|
||||||
|
],
|
||||||
|
'hasRestApi': _g.useRetrofit,
|
||||||
|
'baseUrl': baseUrl,
|
||||||
|
'className': _g.className,
|
||||||
|
'hasRetrofit': _g.useRetrofit,
|
||||||
|
'methods': _buildMethodsData(paths),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> _getImports() {
|
||||||
|
final config = ConfigRepository.loadSync();
|
||||||
|
return [
|
||||||
|
...config.packageImports,
|
||||||
|
'../../api_models/index.dart',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Map<String, dynamic>> _buildMethodsData(List<ApiPath> paths) {
|
||||||
|
return paths.map(_buildMethodData).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> _buildMethodData(ApiPath path) {
|
||||||
|
return {
|
||||||
|
'docLines': _buildDocLines(path),
|
||||||
|
'annotations': _buildAnnotations(path),
|
||||||
|
'returnType': _g._generateReturnType(path),
|
||||||
|
'methodName': _g._generateSimpleMethodName(path),
|
||||||
|
'params': _buildParamsData(path),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> _buildDocLines(ApiPath path) {
|
||||||
|
final lines = <String>[];
|
||||||
|
if (path.summary.isNotEmpty) {
|
||||||
|
lines.addAll(_wrapDocLine(StringUtils.cleanDescription(path.summary)));
|
||||||
|
}
|
||||||
|
if (path.description.isNotEmpty && path.description != path.summary) {
|
||||||
|
lines.addAll(
|
||||||
|
_wrapDocLine(StringUtils.cleanDescription(path.description)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final parameters = _g._generateParameters(path);
|
||||||
|
final paramsWithDescription = parameters
|
||||||
|
.where((p) => p.description.isNotEmpty || p.defaultValue != null)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if (paramsWithDescription.isNotEmpty) {
|
||||||
|
lines
|
||||||
|
..add('')
|
||||||
|
..add('参数:');
|
||||||
|
for (final param in paramsWithDescription) {
|
||||||
|
final commentParts = <String>[];
|
||||||
|
if (param.description.isNotEmpty) {
|
||||||
|
commentParts.add(StringUtils.cleanDescription(param.description));
|
||||||
|
}
|
||||||
|
if (param.defaultValue != null) {
|
||||||
|
commentParts.add('默认值: ${param.defaultValue}');
|
||||||
|
}
|
||||||
|
final paramDoc = '- ${param.name}: ${commentParts.join(' - ')}';
|
||||||
|
// 使用改进的换行方法,处理参数文档
|
||||||
|
lines.addAll(_wrapParamDocLine(paramDoc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 将参数文档行拆分为多行,确保每行不超过80字符
|
||||||
|
/// 专门处理 "- paramName: description" 格式的参数文档
|
||||||
|
List<String> _wrapParamDocLine(String paramDoc) {
|
||||||
|
const maxLength = 76; // 80 - '/// '.length
|
||||||
|
|
||||||
|
if (paramDoc.length <= maxLength) {
|
||||||
|
return [paramDoc];
|
||||||
|
}
|
||||||
|
|
||||||
|
final lines = <String>[];
|
||||||
|
|
||||||
|
// 提取参数名和描述部分
|
||||||
|
final match = RegExp(r'^- ([^:]+): (.+)$').firstMatch(paramDoc);
|
||||||
|
if (match == null) {
|
||||||
|
// 如果格式不匹配,使用通用换行方法
|
||||||
|
return _wrapDocLine(paramDoc);
|
||||||
|
}
|
||||||
|
|
||||||
|
final paramName = match.group(1)!;
|
||||||
|
final description = match.group(2)!;
|
||||||
|
final firstLinePrefix = '- $paramName: ';
|
||||||
|
const continuationPrefix = ' '; // 续行使用两个空格缩进
|
||||||
|
|
||||||
|
// 计算第一行可用长度
|
||||||
|
final firstLineMaxLength = maxLength - firstLinePrefix.length;
|
||||||
|
|
||||||
|
if (description.length <= firstLineMaxLength) {
|
||||||
|
// 描述足够短,可以放在一行
|
||||||
|
return [paramDoc];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 需要分多行
|
||||||
|
var remaining = description;
|
||||||
|
var isFirstLine = true;
|
||||||
|
|
||||||
|
while (remaining.isNotEmpty) {
|
||||||
|
final currentPrefix = isFirstLine ? firstLinePrefix : continuationPrefix;
|
||||||
|
final currentMaxLength = maxLength - currentPrefix.length;
|
||||||
|
|
||||||
|
if (remaining.length <= currentMaxLength) {
|
||||||
|
// 剩余内容可以放在当前行
|
||||||
|
lines.add(currentPrefix + remaining);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 寻找合适的断点
|
||||||
|
var breakPoint = currentMaxLength;
|
||||||
|
final breakChars = [' ', ',', ',', '、', ';', ';', '|', '/'];
|
||||||
|
var bestIdx = -1;
|
||||||
|
|
||||||
|
for (final ch in breakChars) {
|
||||||
|
final idx = remaining.lastIndexOf(ch, currentMaxLength);
|
||||||
|
if (idx > bestIdx && idx > currentMaxLength * 0.5) {
|
||||||
|
bestIdx = idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bestIdx >= 0) {
|
||||||
|
breakPoint = bestIdx + 1; // 包含分隔符
|
||||||
|
}
|
||||||
|
|
||||||
|
final lineContent = remaining.substring(0, breakPoint).trim();
|
||||||
|
lines.add(currentPrefix + lineContent);
|
||||||
|
remaining = remaining.substring(breakPoint).trim();
|
||||||
|
isFirstLine = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 将长文档行拆分为多行,确保每行不超过80字符
|
||||||
|
List<String> _wrapDocLine(String text, {String prefix = ''}) {
|
||||||
|
const maxLength = 76; // 80 - '/// '.length,留一点余量
|
||||||
|
final effectiveMaxLength = maxLength - prefix.length;
|
||||||
|
|
||||||
|
if (text.length <= effectiveMaxLength) {
|
||||||
|
return [prefix + text];
|
||||||
|
}
|
||||||
|
|
||||||
|
final lines = <String>[];
|
||||||
|
var remaining = text;
|
||||||
|
|
||||||
|
while (remaining.length > effectiveMaxLength) {
|
||||||
|
// 优先在空格或常见标点处断行
|
||||||
|
var breakPoint = effectiveMaxLength;
|
||||||
|
final breakChars = [' ', ',', ',', '、', ';', ';', '|', '/'];
|
||||||
|
var bestIdx = -1;
|
||||||
|
for (final ch in breakChars) {
|
||||||
|
final idx = remaining.lastIndexOf(ch, effectiveMaxLength);
|
||||||
|
if (idx > bestIdx) bestIdx = idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bestIdx >= 0 && bestIdx > effectiveMaxLength * 0.5) {
|
||||||
|
// 如果找到合适的断点(不要太靠前),使用该断点
|
||||||
|
breakPoint = bestIdx + 1; // 包含分隔符,避免遗留前导分隔符
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.add(prefix + remaining.substring(0, breakPoint).trim());
|
||||||
|
remaining = remaining.substring(breakPoint).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remaining.isNotEmpty) {
|
||||||
|
lines.add(prefix + remaining);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> _buildAnnotations(ApiPath path) {
|
||||||
|
final annotations = <String>[];
|
||||||
|
if (_g.useRetrofit) {
|
||||||
|
final httpMethod = path.method.value.toUpperCase();
|
||||||
|
final cleanPath = StringUtils.cleanPath(path.path);
|
||||||
|
annotations.add("@$httpMethod('$cleanPath')");
|
||||||
|
|
||||||
|
if (path.requestBody?.content.containsKey('multipart/form-data') ??
|
||||||
|
false) {
|
||||||
|
annotations.add('@MultiPart()');
|
||||||
|
}
|
||||||
|
if (path.requestBody?.content
|
||||||
|
.containsKey('application/x-www-form-urlencoded') ??
|
||||||
|
false) {
|
||||||
|
annotations.add('@FormUrlEncoded()');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return annotations;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Map<String, dynamic>> _buildParamsData(ApiPath path) {
|
||||||
|
final parameters = _g._generateParameters(path);
|
||||||
|
return parameters.map((param) {
|
||||||
|
var annotation = '';
|
||||||
|
if (param.annotation.isNotEmpty) {
|
||||||
|
annotation = param.annotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'annotation': annotation,
|
||||||
|
'type': param.type,
|
||||||
|
'name': param.name,
|
||||||
|
'required': param.required,
|
||||||
|
'description': param.description,
|
||||||
|
};
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> _buildSecuritySchemesData(SwaggerDocument document) {
|
||||||
|
final schemes = <Map<String, dynamic>>[];
|
||||||
|
|
||||||
|
document.components.securitySchemes.forEach((name, scheme) {
|
||||||
|
final constantName = StringUtils.generateConstantName(name);
|
||||||
|
|
||||||
|
final schemeData = <String, dynamic>{
|
||||||
|
'name': name,
|
||||||
|
'description': scheme.description,
|
||||||
|
'constantName': constantName,
|
||||||
|
'isApiKey': scheme.type == SecuritySchemeType.apiKey,
|
||||||
|
'isHttp': scheme.type == SecuritySchemeType.http,
|
||||||
|
'isOAuth2': scheme.type == SecuritySchemeType.oauth2,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (scheme.type == SecuritySchemeType.apiKey) {
|
||||||
|
schemeData['paramName'] = scheme.name;
|
||||||
|
schemeData['location'] = scheme.location?.value;
|
||||||
|
} else if (scheme.type == SecuritySchemeType.http) {
|
||||||
|
schemeData['scheme'] = scheme.scheme;
|
||||||
|
schemeData['hasBearerFormat'] = scheme.bearerFormat != null;
|
||||||
|
schemeData['bearerFormat'] = scheme.bearerFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
schemes.add(schemeData);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {'schemes': schemes};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 生成简化的方法名称
|
||||||
|
String _generateSimpleMethodName(ApiPath path) {
|
||||||
|
final method = path.method.value.toLowerCase();
|
||||||
|
|
||||||
|
// 优先使用 operationId(如果存在且有意义)
|
||||||
|
if (path.operationId.isNotEmpty) {
|
||||||
|
final operationId = path.operationId;
|
||||||
|
if (operationId.toLowerCase().startsWith(method)) {
|
||||||
|
return StringUtils.toCamelCase(operationId);
|
||||||
|
}
|
||||||
|
return StringUtils.toCamelCase(operationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理路径,移除 /api/v1 前缀
|
||||||
|
var cleanedPath = path.path.replaceFirst(RegExp(r'^/api/v\d+'), '');
|
||||||
|
if (cleanedPath.isEmpty) {
|
||||||
|
cleanedPath = path.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
final pathParts = cleanedPath
|
||||||
|
.split('/')
|
||||||
|
.where((part) => part.isNotEmpty && !part.startsWith('{'))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if (pathParts.length >= 2) {
|
||||||
|
final action = StringUtils.toPascalCase(pathParts[1]);
|
||||||
|
return StringUtils.toCamelCase(action);
|
||||||
|
} else if (pathParts.length == 1) {
|
||||||
|
final action = StringUtils.toPascalCase(pathParts[0]);
|
||||||
|
return StringUtils.toCamelCase(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
final sanitizedPath = pathParts.map(StringUtils.toPascalCase).join();
|
||||||
|
return StringUtils.toCamelCase(sanitizedPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import 'package:swagger_generator_flutter/core/config_repository.dart';
|
import 'package:swagger_generator_flutter/core/config_repository.dart';
|
||||||
import 'package:swagger_generator_flutter/core/models.dart';
|
import 'package:swagger_generator_flutter/core/models.dart';
|
||||||
import 'package:swagger_generator_flutter/core/template_renderer.dart';
|
import 'package:swagger_generator_flutter/core/template_renderer.dart';
|
||||||
import 'package:swagger_generator_flutter/pipeline/generate/impl/base_generator.dart';
|
import 'package:swagger_generator_flutter/generators/base_generator.dart';
|
||||||
import 'package:swagger_generator_flutter/utils/string_helper.dart';
|
import 'package:swagger_generator_flutter/utils/string_utils.dart';
|
||||||
|
|
||||||
part 'retrofit_api/api_grouping.dart';
|
part 'retrofit_api/api_grouping.dart';
|
||||||
part 'retrofit_api/api_method_parameter.dart';
|
part 'retrofit_api/api_method_parameter.dart';
|
||||||
|
|
@ -18,12 +18,12 @@ part 'retrofit_api/api_template_data.dart';
|
||||||
class RetrofitApiGenerator extends BaseGenerator
|
class RetrofitApiGenerator extends BaseGenerator
|
||||||
with
|
with
|
||||||
RetrofitApiGrouping,
|
RetrofitApiGrouping,
|
||||||
|
RetrofitApiTemplateData,
|
||||||
RetrofitApiSchemaComposition,
|
RetrofitApiSchemaComposition,
|
||||||
RetrofitApiSchema,
|
RetrofitApiSchema,
|
||||||
RetrofitApiReturnTypes,
|
RetrofitApiReturnTypes,
|
||||||
RetrofitApiParameters,
|
RetrofitApiParameters,
|
||||||
RetrofitApiParameterEntities,
|
RetrofitApiParameterEntities {
|
||||||
RetrofitApiTemplateData {
|
|
||||||
RetrofitApiGenerator({
|
RetrofitApiGenerator({
|
||||||
this.className = 'ApiClient',
|
this.className = 'ApiClient',
|
||||||
this.useRetrofit = true,
|
this.useRetrofit = true,
|
||||||
|
|
@ -43,9 +43,6 @@ class RetrofitApiGenerator extends BaseGenerator
|
||||||
late SwaggerDocument document;
|
late SwaggerDocument document;
|
||||||
final templateRenderer = TemplateRenderer();
|
final templateRenderer = TemplateRenderer();
|
||||||
|
|
||||||
@override
|
|
||||||
RetrofitApiGenerator get _g => this;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get generatorType => 'RetrofitApiGenerator';
|
String get generatorType => 'RetrofitApiGenerator';
|
||||||
|
|
||||||
|
|
@ -108,8 +105,8 @@ class RetrofitApiGenerator extends BaseGenerator
|
||||||
final tagName = entry.key;
|
final tagName = entry.key;
|
||||||
final paths = entry.value;
|
final paths = entry.value;
|
||||||
// Use ${tagName}Api to match old behavior (user -> user_api.dart)
|
// Use ${tagName}Api to match old behavior (user -> user_api.dart)
|
||||||
final fileName = StringHelper.generateFileName('${tagName}Api');
|
final fileName = StringUtils.generateFileName('${tagName}Api');
|
||||||
final apiClassName = '${StringHelper.toPascalCase(tagName)}Api';
|
final apiClassName = '${StringUtils.toPascalCase(tagName)}Api';
|
||||||
|
|
||||||
final data = _buildApiClassData(paths);
|
final data = _buildApiClassData(paths);
|
||||||
data['className'] = apiClassName;
|
data['className'] = apiClassName;
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
/// # Swagger Generator Flutter
|
|
||||||
///
|
|
||||||
/// A powerful Flutter OpenAPI 3.0 code generator, optimized for the Dio + Retrofit stack.
|
|
||||||
///
|
|
||||||
/// ## Core Components
|
|
||||||
///
|
|
||||||
/// - **Config**: `ConfigRepository` for loading and accessing configuration.
|
|
||||||
/// - **Parse**: `SwaggerDataParser` to fetch and parse OpenAPI documents.
|
|
||||||
/// - **Validate**: `EnhancedValidator` to validate documents against OpenAPI specs and best practices.
|
|
||||||
/// - **Generate**: `ModelCodeGenerator` and `RetrofitApiGenerator` for creating Dart code.
|
|
||||||
/// - **Render**: `TemplateRenderer` for Mustache-based template rendering.
|
|
||||||
/// - **Output**: `GenerationOutputService` to write generated files to disk.
|
|
||||||
|
|
||||||
library;
|
|
||||||
|
|
||||||
// --- Core Public API ---
|
|
||||||
export 'core/config_repository.dart';
|
|
||||||
export 'core/error_reporter.dart';
|
|
||||||
export 'core/models.dart';
|
|
||||||
// --- Pipeline API ---
|
|
||||||
export 'pipeline/generate/apis.dart';
|
|
||||||
export 'pipeline/generate/models.dart';
|
|
||||||
export 'pipeline/output/generation_output_service.dart';
|
|
||||||
export 'pipeline/parse/swagger_data_parser.dart';
|
|
||||||
export 'pipeline/render/template_renderer.dart';
|
|
||||||
export 'pipeline/validate/enhanced_validator.dart';
|
|
||||||
// --- Utilities ---
|
|
||||||
export 'utils/logger.dart';
|
|
||||||
export 'utils/path_resolver.dart';
|
|
||||||
|
|
@ -3,12 +3,12 @@ import 'dart:convert';
|
||||||
import 'package:swagger_generator_flutter/core/config.dart';
|
import 'package:swagger_generator_flutter/core/config.dart';
|
||||||
import 'package:swagger_generator_flutter/core/exceptions.dart';
|
import 'package:swagger_generator_flutter/core/exceptions.dart';
|
||||||
import 'package:swagger_generator_flutter/core/models.dart';
|
import 'package:swagger_generator_flutter/core/models.dart';
|
||||||
import 'package:swagger_generator_flutter/pipeline/parse/impl/swagger_fetcher.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/cache_manager.dart';
|
||||||
import 'package:swagger_generator_flutter/utils/logger.dart';
|
import 'package:swagger_generator_flutter/utils/logger.dart';
|
||||||
import 'package:swagger_generator_flutter/utils/performance_monitor.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/reference_resolver.dart';
|
||||||
import 'package:swagger_generator_flutter/utils/string_helper.dart';
|
import 'package:swagger_generator_flutter/utils/string_utils.dart';
|
||||||
|
|
||||||
/// Swagger数据解析器
|
/// Swagger数据解析器
|
||||||
/// 负责解析Swagger JSON文档并提取相关信息
|
/// 负责解析Swagger JSON文档并提取相关信息
|
||||||
|
|
@ -390,7 +390,7 @@ class SwaggerDataParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 默认为自定义类型
|
// 默认为自定义类型
|
||||||
return StringHelper.generateClassName(swaggerType);
|
return StringUtils.generateClassName(swaggerType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
/// Pipeline: generate -> apis
|
|
||||||
/// Re-export Retrofit API generator for pipeline-oriented imports.
|
|
||||||
library;
|
|
||||||
|
|
||||||
export 'impl/retrofit_api_generator.dart';
|
|
||||||
|
|
@ -1,149 +0,0 @@
|
||||||
part of '../retrofit_api_generator.dart';
|
|
||||||
|
|
||||||
mixin RetrofitApiSchema {
|
|
||||||
RetrofitApiGenerator get _g => this as RetrofitApiGenerator;
|
|
||||||
|
|
||||||
/// 从路径的响应中提取返回类型
|
|
||||||
String? _extractResponseTypeFromPath(ApiPath path) {
|
|
||||||
final successResponses = ['200', '201', '202'];
|
|
||||||
|
|
||||||
for (final statusCode in successResponses) {
|
|
||||||
final response = path.responses[statusCode];
|
|
||||||
if (response != null) {
|
|
||||||
final type = _extractResponseType(response);
|
|
||||||
if (type != null) {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 从响应中提取返回类型
|
|
||||||
String? _extractResponseType(ApiResponse response) {
|
|
||||||
final applicationJsonMediaType = response.content['application/json'];
|
|
||||||
if (applicationJsonMediaType != null) {
|
|
||||||
final schema = applicationJsonMediaType.schema;
|
|
||||||
final type = _extractTypeFromSchema(schema);
|
|
||||||
if (type != null) {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.schema != null) {
|
|
||||||
final type = _extractTypeFromSchema(response.schema);
|
|
||||||
if (type != null) {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 从 schema 中提取类型
|
|
||||||
String? _extractTypeFromSchema(Map<String, dynamic>? schema) {
|
|
||||||
if (schema == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (schema[r'$ref'] != null) {
|
|
||||||
final ref = schema[r'$ref'] as String;
|
|
||||||
final refName = ref.split('/').last;
|
|
||||||
return StringHelper.generateClassName(refName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (schema['type'] != null) {
|
|
||||||
final type = schema['type'] as String;
|
|
||||||
if (type == 'array') {
|
|
||||||
final items = schema['items'];
|
|
||||||
if (items is Map<String, dynamic>) {
|
|
||||||
final itemType = _extractTypeFromSchema(items);
|
|
||||||
return 'List<${itemType ?? 'dynamic'}>';
|
|
||||||
} else {
|
|
||||||
return 'List<dynamic>';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return _g.mapJsonTypeToFlutterType(type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (schema['allOf'] != null ||
|
|
||||||
schema['oneOf'] != null ||
|
|
||||||
schema['anyOf'] != null) {
|
|
||||||
return _g.extractTypeFromCompositionSchema(schema);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'dynamic';
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 检查 schema 是否为数组类型
|
|
||||||
bool _isArraySchema(Map<String, dynamic> schema) {
|
|
||||||
if (schema['type'] == 'array') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (schema[r'$ref'] != null) {
|
|
||||||
final refName = (schema[r'$ref'] as String).split('/').last;
|
|
||||||
final refModel = _g.document.models[refName];
|
|
||||||
if (refModel != null) {
|
|
||||||
return refModel.type == 'array';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 检查 schema 是否为分页模型
|
|
||||||
bool _hasPaginationSchema(Map<String, dynamic> schema) {
|
|
||||||
if (schema[r'$ref'] != null) {
|
|
||||||
final refName = (schema[r'$ref'] as String).split('/').last;
|
|
||||||
final refModel = _g.document.models[refName];
|
|
||||||
if (refModel != null) {
|
|
||||||
return _isPaginationResponseModel(refModel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 生成简单方法名
|
|
||||||
String _generateSimpleMethodName(ApiPath path) {
|
|
||||||
if (path.operationId.isNotEmpty) {
|
|
||||||
return StringHelper.toCamelCase(path.operationId);
|
|
||||||
}
|
|
||||||
|
|
||||||
final pathParts = path.path
|
|
||||||
.split('/')
|
|
||||||
.where((part) => part.isNotEmpty && !part.startsWith('{'))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
if (pathParts.isEmpty) {
|
|
||||||
return 'unnamedMethod';
|
|
||||||
}
|
|
||||||
|
|
||||||
final lastPart = pathParts.last.replaceAll(RegExp(r'\W+'), '');
|
|
||||||
final method = path.method.value.toLowerCase();
|
|
||||||
final pascalPart = StringHelper.toPascalCase(lastPart);
|
|
||||||
final methodName = '$method$pascalPart';
|
|
||||||
|
|
||||||
return methodName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 检查是否是分页响应模型(包含 total 和 items 字段)
|
|
||||||
bool _isPaginationResponseModel(ApiModel model) {
|
|
||||||
if (!model.properties.containsKey('total') ||
|
|
||||||
!model.properties.containsKey('items')) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final totalProp = model.properties['total']!;
|
|
||||||
final itemsProp = model.properties['items']!;
|
|
||||||
|
|
||||||
final isTotalNumeric = totalProp.type == PropertyType.integer ||
|
|
||||||
totalProp.type == PropertyType.number;
|
|
||||||
final isItemsArray = itemsProp.type == PropertyType.array;
|
|
||||||
|
|
||||||
return isTotalNumeric && isItemsArray;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,119 +0,0 @@
|
||||||
part of '../retrofit_api_generator.dart';
|
|
||||||
|
|
||||||
mixin RetrofitApiTemplateData {
|
|
||||||
RetrofitApiGenerator get _g => this as RetrofitApiGenerator;
|
|
||||||
|
|
||||||
List<String> _getMainImports() {
|
|
||||||
final tagGroups = _g._groupPathsByTags();
|
|
||||||
final tagImports = tagGroups.keys.map((tag) {
|
|
||||||
final fileName = StringHelper.generateFileName(tag);
|
|
||||||
return "import '$fileName.dart';";
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
final config = ConfigRepository.loadSync();
|
|
||||||
final customImports = config.packageImports;
|
|
||||||
|
|
||||||
return [...customImports, ...tagImports];
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> _buildTagApisData() {
|
|
||||||
final tagGroups = _g._groupPathsByTags();
|
|
||||||
return {
|
|
||||||
'apis': tagGroups.entries.map((entry) {
|
|
||||||
final tagName = entry.key;
|
|
||||||
return {
|
|
||||||
'name': StringHelper.toCamelCase(tagName),
|
|
||||||
'className': '${StringHelper.toPascalCase(tagName)}Api',
|
|
||||||
};
|
|
||||||
}).toList(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> _buildApiClassData(List<ApiPath> paths) {
|
|
||||||
final baseUrl =
|
|
||||||
_g.document.servers.isNotEmpty ? _g.document.servers.first.url : '';
|
|
||||||
var fileName = '';
|
|
||||||
if (_g.className.isNotEmpty) {
|
|
||||||
fileName = StringHelper.generateFileName(_g.className);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
'description': _g.document.description,
|
|
||||||
'apiUrl': baseUrl,
|
|
||||||
'className': _g.className,
|
|
||||||
'imports': _getImportsForPaths(paths),
|
|
||||||
'methods': _buildMethodsData(paths),
|
|
||||||
'parts': [fileName.replaceAll('.dart', '.g.dart')],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> _getImportsForPaths(List<ApiPath> paths) {
|
|
||||||
final imports = <String>{};
|
|
||||||
final config = ConfigRepository.loadSync();
|
|
||||||
imports
|
|
||||||
..add("import 'package:dio/dio.dart';")
|
|
||||||
..add("import 'package:retrofit/retrofit.dart';")
|
|
||||||
..addAll(config.packageImports.map((i) => "import '$i';"));
|
|
||||||
|
|
||||||
return imports.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Map<String, dynamic>> _buildMethodsData(List<ApiPath> paths) {
|
|
||||||
return paths.map(_buildMethodData).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> _buildMethodData(ApiPath path) {
|
|
||||||
return {
|
|
||||||
'docLines': _buildDocLines(path),
|
|
||||||
'annotations': _buildAnnotations(path),
|
|
||||||
'returnType': _g._generateReturnType(path),
|
|
||||||
'methodName': _g._generateSimpleMethodName(path),
|
|
||||||
'parameters': _buildParametersData(path),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Map<String, String>> _buildDocLines(ApiPath path) {
|
|
||||||
final docLines = <String>[];
|
|
||||||
if (path.summary.isNotEmpty) {
|
|
||||||
docLines.add(path.summary);
|
|
||||||
}
|
|
||||||
if (path.description.isNotEmpty) {
|
|
||||||
docLines.add(path.description);
|
|
||||||
}
|
|
||||||
return docLines.map((line) => {'line': line}).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Map<String, String>> _buildAnnotations(ApiPath path) {
|
|
||||||
final annotations = <String>[];
|
|
||||||
final method = path.method.value.toUpperCase();
|
|
||||||
annotations.add('@$method("${path.path}")');
|
|
||||||
|
|
||||||
if (path.isMultipart) {
|
|
||||||
annotations.add('@MultiPart()');
|
|
||||||
}
|
|
||||||
|
|
||||||
return annotations.map((line) => {'line': line}).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Map<String, dynamic>> _buildParametersData(ApiPath path) {
|
|
||||||
return _g._generateParameters(path).map((p) {
|
|
||||||
return {
|
|
||||||
'annotation': p.annotation,
|
|
||||||
'type': p.type,
|
|
||||||
'name': p.name,
|
|
||||||
'required': p.required,
|
|
||||||
'isLast': false, // This will be updated later
|
|
||||||
};
|
|
||||||
}).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> _buildSecuritySchemesData(SwaggerDocument document) {
|
|
||||||
final schemes = document.components.securitySchemes.values.toList();
|
|
||||||
return {
|
|
||||||
'hasSecuritySchemes': schemes.isNotEmpty,
|
|
||||||
'securitySchemes': schemes
|
|
||||||
.map((s) => {'name': s.name, 'type': s.type, 'scheme': s.scheme})
|
|
||||||
.toList(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
/// Pipeline: generate -> models
|
|
||||||
/// Re-export model code generator for pipeline-oriented imports.
|
|
||||||
library;
|
|
||||||
|
|
||||||
export 'impl/model_code_generator.dart';
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
/// Pipeline: output -> generation output service
|
|
||||||
/// Re-export GenerationOutputService for pipeline-oriented imports.
|
|
||||||
library;
|
|
||||||
|
|
||||||
export 'package:swagger_generator_flutter/commands/services/generation_output_service.dart';
|
|
||||||
|
|
@ -1,655 +0,0 @@
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:path/path.dart' as path;
|
|
||||||
import 'package:swagger_generator_flutter/commands/generate_command.dart'
|
|
||||||
show GenerateOptions;
|
|
||||||
import 'package:swagger_generator_flutter/commands/services/service_typedefs.dart';
|
|
||||||
import 'package:swagger_generator_flutter/core/config.dart';
|
|
||||||
import 'package:swagger_generator_flutter/core/config_repository.dart';
|
|
||||||
import 'package:swagger_generator_flutter/core/models.dart';
|
|
||||||
import 'package:swagger_generator_flutter/pipeline/generate/impl/model_code_generator.dart';
|
|
||||||
import 'package:swagger_generator_flutter/pipeline/generate/impl/retrofit_api_generator.dart';
|
|
||||||
import 'package:swagger_generator_flutter/utils/file_utils.dart';
|
|
||||||
import 'package:swagger_generator_flutter/utils/logger.dart';
|
|
||||||
|
|
||||||
class GenerationOutputService {
|
|
||||||
const GenerationOutputService();
|
|
||||||
static ConfigRepository? _cachedConfig;
|
|
||||||
ConfigRepository get _config => _cachedConfig ??= ConfigRepository.loadSync();
|
|
||||||
|
|
||||||
Future<int> generateOutputs({
|
|
||||||
required SwaggerDocument document,
|
|
||||||
required GenerateOptions options,
|
|
||||||
required String baseDir,
|
|
||||||
required String apiDir,
|
|
||||||
required String modelsDir,
|
|
||||||
required LogCallback progress,
|
|
||||||
required LogCallback success,
|
|
||||||
}) async {
|
|
||||||
await FileUtils.ensureDirectoryExists(baseDir);
|
|
||||||
await FileUtils.ensureDirectoryExists(apiDir);
|
|
||||||
await FileUtils.ensureDirectoryExists(modelsDir);
|
|
||||||
|
|
||||||
var generatedFiles = 0;
|
|
||||||
|
|
||||||
if (options.generateModels) {
|
|
||||||
generatedFiles += await _generateModels(
|
|
||||||
document,
|
|
||||||
modelsDir,
|
|
||||||
progress,
|
|
||||||
success,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.generateApi) {
|
|
||||||
generatedFiles += await _generateApis(
|
|
||||||
document,
|
|
||||||
apiDir,
|
|
||||||
modelsDir,
|
|
||||||
progress,
|
|
||||||
success,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.generateModels || options.generateApi) {
|
|
||||||
await _regenerateModelsIndex(modelsDir, success);
|
|
||||||
}
|
|
||||||
|
|
||||||
await _generateSummary(document, baseDir);
|
|
||||||
return generatedFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<int> _generateModels(
|
|
||||||
SwaggerDocument document,
|
|
||||||
String modelsDir,
|
|
||||||
LogCallback progress,
|
|
||||||
LogCallback success,
|
|
||||||
) async {
|
|
||||||
progress('正在生成数据模型...');
|
|
||||||
final generator = ModelCodeGenerator(document);
|
|
||||||
final modelFiles = generator.generateSeparateModelFiles();
|
|
||||||
var generatedFiles = 0;
|
|
||||||
|
|
||||||
for (final entry in modelFiles.entries) {
|
|
||||||
final filePath = '$modelsDir/${entry.key}';
|
|
||||||
if (_config.shouldSkipFile(filePath)) {
|
|
||||||
progress('跳过文件: $filePath');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
await FileUtils.writeFile(filePath, entry.value);
|
|
||||||
success('模型文件已保存到: $filePath');
|
|
||||||
generatedFiles++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return generatedFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<int> _generateApis(
|
|
||||||
SwaggerDocument document,
|
|
||||||
String apiDir,
|
|
||||||
String modelsDir,
|
|
||||||
LogCallback progress,
|
|
||||||
LogCallback success,
|
|
||||||
) async {
|
|
||||||
progress('正在按版本和tags分组生成Retrofit风格API接口...');
|
|
||||||
|
|
||||||
await FileUtils.ensureDirectoryExists(apiDir);
|
|
||||||
final pathsByVersion = _groupPathsByVersion(document);
|
|
||||||
|
|
||||||
progress(
|
|
||||||
'检测到 ${pathsByVersion.keys.length} 个版本: '
|
|
||||||
'${pathsByVersion.keys.join(", ")}',
|
|
||||||
);
|
|
||||||
|
|
||||||
final versionedFiles = await _buildVersionedApis(
|
|
||||||
document,
|
|
||||||
pathsByVersion,
|
|
||||||
progress,
|
|
||||||
);
|
|
||||||
|
|
||||||
var generatedFiles = 0;
|
|
||||||
generatedFiles += await _writeVersionedApis(
|
|
||||||
apiDir,
|
|
||||||
versionedFiles,
|
|
||||||
progress,
|
|
||||||
success,
|
|
||||||
);
|
|
||||||
|
|
||||||
generatedFiles += await _writeMainApiFile(
|
|
||||||
apiDir,
|
|
||||||
versionedFiles,
|
|
||||||
success,
|
|
||||||
);
|
|
||||||
|
|
||||||
generatedFiles += await _writeParameterEntities(
|
|
||||||
document,
|
|
||||||
modelsDir,
|
|
||||||
success,
|
|
||||||
progress,
|
|
||||||
);
|
|
||||||
|
|
||||||
return generatedFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, List<ApiPath>> _groupPathsByVersion(SwaggerDocument document) {
|
|
||||||
final pathsByVersion = <String, List<ApiPath>>{};
|
|
||||||
for (final path in document.paths.values) {
|
|
||||||
final version = _extractVersionFromPath(path.path);
|
|
||||||
pathsByVersion.putIfAbsent(version, () => []).add(path);
|
|
||||||
}
|
|
||||||
return pathsByVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Map<String, Map<String, String>>> _buildVersionedApis(
|
|
||||||
SwaggerDocument document,
|
|
||||||
Map<String, List<ApiPath>> pathsByVersion,
|
|
||||||
LogCallback progress,
|
|
||||||
) async {
|
|
||||||
final versionedFiles = <String, Map<String, String>>{};
|
|
||||||
final apiClientClassName = _config.apiClientClassName;
|
|
||||||
|
|
||||||
for (final versionEntry in pathsByVersion.entries) {
|
|
||||||
final version = versionEntry.key;
|
|
||||||
final versionPaths = versionEntry.value;
|
|
||||||
|
|
||||||
progress(' 正在生成 $version 版本 API(${versionPaths.length} 个接口)...');
|
|
||||||
|
|
||||||
final versionTags = versionPaths.expand((p) => p.tags).toSet();
|
|
||||||
final versionControllers = {
|
|
||||||
for (final tag in versionTags)
|
|
||||||
if (document.controllers.containsKey(tag))
|
|
||||||
tag: document.controllers[tag]!,
|
|
||||||
};
|
|
||||||
|
|
||||||
final versionDocument = SwaggerDocument(
|
|
||||||
title: document.title,
|
|
||||||
description: document.description,
|
|
||||||
version: document.version,
|
|
||||||
paths: {
|
|
||||||
for (final p in versionPaths)
|
|
||||||
SwaggerDocument.buildPathKey(p.path, p.method): p,
|
|
||||||
},
|
|
||||||
models: document.models,
|
|
||||||
controllers: versionControllers,
|
|
||||||
);
|
|
||||||
|
|
||||||
final generator = RetrofitApiGenerator(
|
|
||||||
className: apiClientClassName,
|
|
||||||
)
|
|
||||||
..document = versionDocument
|
|
||||||
..ensureParameterEntitiesGenerated();
|
|
||||||
|
|
||||||
final tagApiFiles = generator.generateApiFilesByTags();
|
|
||||||
versionedFiles[version] = {};
|
|
||||||
|
|
||||||
for (final entry in tagApiFiles.entries) {
|
|
||||||
final fileName = entry.key;
|
|
||||||
var code = entry.value;
|
|
||||||
code = _addVersionSuffixToCode(code, version);
|
|
||||||
versionedFiles[version]![fileName] = code;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return versionedFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<int> _writeVersionedApis(
|
|
||||||
String apiDir,
|
|
||||||
Map<String, Map<String, String>> versionedFiles,
|
|
||||||
LogCallback progress,
|
|
||||||
LogCallback success,
|
|
||||||
) async {
|
|
||||||
var generatedFiles = 0;
|
|
||||||
|
|
||||||
for (final versionEntry in versionedFiles.entries) {
|
|
||||||
final version = versionEntry.key;
|
|
||||||
final files = versionEntry.value;
|
|
||||||
final versionDir = '$apiDir/$version';
|
|
||||||
|
|
||||||
if (_config.shouldSkipFile(versionDir)) {
|
|
||||||
progress('跳过版本目录: $versionDir');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
await FileUtils.ensureDirectoryExists(versionDir);
|
|
||||||
|
|
||||||
for (final fileEntry in files.entries) {
|
|
||||||
final fileName = fileEntry.key;
|
|
||||||
final code = fileEntry.value;
|
|
||||||
final filePath = '$versionDir/$fileName';
|
|
||||||
|
|
||||||
if (_config.shouldSkipFile(filePath)) {
|
|
||||||
progress('跳过文件: $filePath');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
await FileUtils.writeFile(filePath, code);
|
|
||||||
success('API接口文件已保存到: $filePath');
|
|
||||||
generatedFiles++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_config.shouldSkipFile(versionDir)) {
|
|
||||||
await _generateVersionIndexFile(versionDir, files.keys.toList());
|
|
||||||
success('$version/index.dart 已生成');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return generatedFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<int> _writeMainApiFile(
|
|
||||||
String apiDir,
|
|
||||||
Map<String, Map<String, String>> versionedFiles,
|
|
||||||
LogCallback success,
|
|
||||||
) async {
|
|
||||||
final apiClientFileName = _config.apiClientFileName;
|
|
||||||
final mainCode = _generateVersionedApiClient(versionedFiles);
|
|
||||||
final mainFilePath = '$apiDir/$apiClientFileName.dart';
|
|
||||||
|
|
||||||
if (!_config.shouldSkipFile(mainFilePath)) {
|
|
||||||
await FileUtils.writeFile(mainFilePath, mainCode);
|
|
||||||
success('主API接口文件已保存到: $mainFilePath');
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<int> _writeParameterEntities(
|
|
||||||
SwaggerDocument document,
|
|
||||||
String modelsDir,
|
|
||||||
LogCallback success,
|
|
||||||
LogCallback progress,
|
|
||||||
) async {
|
|
||||||
final apiClientClassName = _config.apiClientClassName;
|
|
||||||
final lastGenerator = RetrofitApiGenerator(
|
|
||||||
className: apiClientClassName,
|
|
||||||
)
|
|
||||||
..document = document
|
|
||||||
..ensureParameterEntitiesGenerated();
|
|
||||||
|
|
||||||
final parameterEntityFiles = lastGenerator.generateParameterEntityFiles();
|
|
||||||
if (parameterEntityFiles.isEmpty) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
final parametersDir = '$modelsDir/parameters';
|
|
||||||
await FileUtils.ensureDirectoryExists(parametersDir);
|
|
||||||
var generatedFiles = 0;
|
|
||||||
|
|
||||||
for (final entry in parameterEntityFiles.entries) {
|
|
||||||
final filePath = '$parametersDir/${entry.key}';
|
|
||||||
|
|
||||||
if (_config.shouldSkipFile(filePath)) {
|
|
||||||
progress('跳过文件: $filePath');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
await FileUtils.writeFile(filePath, entry.value);
|
|
||||||
success('参数实体类文件已保存到: $filePath');
|
|
||||||
generatedFiles++;
|
|
||||||
}
|
|
||||||
|
|
||||||
await _generateSubDirectoryIndexFile(parametersDir, success);
|
|
||||||
return generatedFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _regenerateModelsIndex(
|
|
||||||
String modelsDir,
|
|
||||||
LogCallback success,
|
|
||||||
) async {
|
|
||||||
final allFiles = await _getAllModelFiles(modelsDir);
|
|
||||||
final indexContent = _generateUpdatedIndexFile(allFiles);
|
|
||||||
final indexPath = '$modelsDir/index.dart';
|
|
||||||
await FileUtils.writeFile(indexPath, indexContent);
|
|
||||||
success('index.dart 文件已更新');
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<String>> _getAllModelFiles(String modelsDir) async {
|
|
||||||
try {
|
|
||||||
final directory = Directory(modelsDir);
|
|
||||||
if (!directory.existsSync()) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
final files = directory.listSync();
|
|
||||||
final exportPaths = <String>[];
|
|
||||||
|
|
||||||
for (final entity in files) {
|
|
||||||
if (entity is Directory) {
|
|
||||||
final dirName = path.basename(entity.path);
|
|
||||||
final subIndexPath = path.join(entity.path, 'index.dart');
|
|
||||||
if (File(subIndexPath).existsSync()) {
|
|
||||||
exportPaths.add('$dirName/index.dart');
|
|
||||||
}
|
|
||||||
} else if (entity is File && entity.path.endsWith('.dart')) {
|
|
||||||
final fileName = path.basename(entity.path);
|
|
||||||
if (fileName != 'index.dart' && !fileName.endsWith('.g.dart')) {
|
|
||||||
exportPaths.add(fileName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exportPaths.sort((a, b) {
|
|
||||||
final aIsDir = a.contains('/');
|
|
||||||
final bIsDir = b.contains('/');
|
|
||||||
if (aIsDir && !bIsDir) return -1;
|
|
||||||
if (!aIsDir && bIsDir) return 1;
|
|
||||||
return a.compareTo(b);
|
|
||||||
});
|
|
||||||
|
|
||||||
return exportPaths;
|
|
||||||
} on Exception catch (e, stackTrace) {
|
|
||||||
appLogger.severe('获取模型文件列表失败', e, stackTrace);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _generateSubDirectoryIndexFile(
|
|
||||||
String subDir,
|
|
||||||
LogCallback success,
|
|
||||||
) async {
|
|
||||||
final directory = Directory(subDir);
|
|
||||||
if (!directory.existsSync()) return;
|
|
||||||
|
|
||||||
final dirName = path.basename(subDir);
|
|
||||||
final files = directory.listSync();
|
|
||||||
final dartFiles = <String>[];
|
|
||||||
|
|
||||||
for (final entity in files) {
|
|
||||||
if (entity is File && entity.path.endsWith('.dart')) {
|
|
||||||
final fileName = path.basename(entity.path);
|
|
||||||
if (fileName != 'index.dart' && !fileName.endsWith('.g.dart')) {
|
|
||||||
final filePath = path.join(subDir, fileName);
|
|
||||||
if (!_config.shouldSkipFile(filePath)) {
|
|
||||||
dartFiles.add(fileName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dartFiles.sort();
|
|
||||||
|
|
||||||
final buffer = StringBuffer()
|
|
||||||
..writeln('// 模型导出文件')
|
|
||||||
..writeln('// 基于 Swagger API 文档: ')
|
|
||||||
..writeln('// 由 xy_swagger_generator by max 生成')
|
|
||||||
..writeln('// Copyright (C) 2025 YuanXuan. All rights reserved.')
|
|
||||||
..writeln()
|
|
||||||
..writeln()
|
|
||||||
..writeln('library;')
|
|
||||||
..writeln();
|
|
||||||
|
|
||||||
for (final fileName in dartFiles) {
|
|
||||||
buffer.writeln("export '$fileName';");
|
|
||||||
}
|
|
||||||
|
|
||||||
final indexPath = path.join(subDir, 'index.dart');
|
|
||||||
await FileUtils.writeFile(indexPath, buffer.toString());
|
|
||||||
success('$dirName/index.dart 已生成,包含 ${dartFiles.length} 个文件');
|
|
||||||
}
|
|
||||||
|
|
||||||
String _generateUpdatedIndexFile(List<String> fileNames) {
|
|
||||||
final buffer = StringBuffer()
|
|
||||||
..writeln('// API 模型导出文件')
|
|
||||||
..writeln('// 基于 Swagger API 文档: ')
|
|
||||||
..writeln('// 由 xy_swagger_generator by max 生成')
|
|
||||||
..writeln('// Copyright (C) 2025 YuanXuan. All rights reserved.')
|
|
||||||
..writeln()
|
|
||||||
..writeln('library;')
|
|
||||||
..writeln();
|
|
||||||
|
|
||||||
final baseResultImport = SwaggerConfig.baseResultImport;
|
|
||||||
final basePageResultImport = SwaggerConfig.basePageResultImport;
|
|
||||||
|
|
||||||
if (baseResultImport.isNotEmpty) {
|
|
||||||
buffer.writeln("export '$baseResultImport';");
|
|
||||||
}
|
|
||||||
if (basePageResultImport.isNotEmpty) {
|
|
||||||
buffer.writeln("export '$basePageResultImport';");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((baseResultImport.isNotEmpty || basePageResultImport.isNotEmpty) &&
|
|
||||||
fileNames.isNotEmpty) {
|
|
||||||
buffer.writeln();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final fileName in fileNames) {
|
|
||||||
buffer.writeln("export '$fileName';");
|
|
||||||
}
|
|
||||||
|
|
||||||
return buffer.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _generateSummary(
|
|
||||||
SwaggerDocument document,
|
|
||||||
String outputDir,
|
|
||||||
) async {
|
|
||||||
final summary = StringBuffer()
|
|
||||||
..writeln('# 代码生成摘要')
|
|
||||||
..writeln()
|
|
||||||
..writeln('**API标题**: ${document.title}')
|
|
||||||
..writeln('**API版本**: ${document.version}')
|
|
||||||
..writeln('**生成时间**: ${DateTime.now().toIso8601String()}')
|
|
||||||
..writeln()
|
|
||||||
..writeln('## 统计信息')
|
|
||||||
..writeln('- 控制器数量: ${document.controllers.length}')
|
|
||||||
..writeln('- API路径数量: ${document.paths.length}')
|
|
||||||
..writeln('- 数据模型数量: ${document.models.length}')
|
|
||||||
..writeln()
|
|
||||||
..writeln('## 控制器列表');
|
|
||||||
document.controllers.forEach((name, controller) {
|
|
||||||
summary.writeln(
|
|
||||||
'- **$name**: ${controller.description} (${controller.paths.length} 个路径)',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
await FileUtils.writeFile('$outputDir/SUMMARY.md', summary.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
String _extractVersionFromPath(String path) {
|
|
||||||
final pattern = _config.versionExtractionPattern;
|
|
||||||
final defaultVersion = _config.defaultVersion;
|
|
||||||
|
|
||||||
try {
|
|
||||||
final versionMatch = RegExp(pattern).firstMatch(path);
|
|
||||||
if (versionMatch != null && versionMatch.groupCount > 0) {
|
|
||||||
return 'v${versionMatch.group(1)}';
|
|
||||||
}
|
|
||||||
} on FormatException {
|
|
||||||
const defaultPattern = r'/api/v(\d+)/';
|
|
||||||
final versionMatch = RegExp(defaultPattern).firstMatch(path);
|
|
||||||
if (versionMatch != null) {
|
|
||||||
return 'v${versionMatch.group(1)}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaultVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
String _addVersionSuffixToCode(String code, String version) {
|
|
||||||
if (version == 'v1') {
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
final versionUpper = version.toUpperCase();
|
|
||||||
var updatedCode = code;
|
|
||||||
|
|
||||||
updatedCode = updatedCode.replaceAllMapped(
|
|
||||||
RegExp(r'abstract class (\w+Api)\b'),
|
|
||||||
(match) => 'abstract class ${match.group(1)}$versionUpper',
|
|
||||||
);
|
|
||||||
|
|
||||||
updatedCode = updatedCode.replaceAllMapped(
|
|
||||||
RegExp(r'factory (\w+Api)\('),
|
|
||||||
(match) => 'factory ${match.group(1)}$versionUpper(',
|
|
||||||
);
|
|
||||||
|
|
||||||
updatedCode = updatedCode.replaceAllMapped(
|
|
||||||
RegExp(r'= _(\w+Api);'),
|
|
||||||
(match) => '= _${match.group(1)}$versionUpper;',
|
|
||||||
);
|
|
||||||
|
|
||||||
updatedCode = updatedCode.replaceAllMapped(
|
|
||||||
RegExp(r"part '(\w+)\.g\.dart';"),
|
|
||||||
(match) => "part '${match.group(1)}.g.dart';",
|
|
||||||
);
|
|
||||||
|
|
||||||
updatedCode = updatedCode.replaceAllMapped(
|
|
||||||
RegExp(r"import '../(\w+_api)\.dart';"),
|
|
||||||
(match) => "import '../$version/${match.group(1)}.dart';",
|
|
||||||
);
|
|
||||||
|
|
||||||
return updatedCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
String _generateVersionedApiClient(
|
|
||||||
Map<String, Map<String, String>> versionedFiles,
|
|
||||||
) {
|
|
||||||
final buffer = StringBuffer()
|
|
||||||
..writeln('// 统一 API 客户端')
|
|
||||||
..writeln('// 支持多版本 API 管理')
|
|
||||||
..writeln('// 由 xy_swagger_generator by max 生成')
|
|
||||||
..writeln('// Copyright (C) 2025 YuanXuan. All rights reserved.')
|
|
||||||
..writeln()
|
|
||||||
..writeln("import 'package:dio/dio.dart';")
|
|
||||||
..writeln();
|
|
||||||
|
|
||||||
final apiClasses = <String, Set<String>>{};
|
|
||||||
|
|
||||||
for (final versionEntry in versionedFiles.entries) {
|
|
||||||
final version = versionEntry.key;
|
|
||||||
final files = versionEntry.value;
|
|
||||||
apiClasses[version] = {};
|
|
||||||
|
|
||||||
for (final entry in files.entries) {
|
|
||||||
final code = entry.value;
|
|
||||||
final extracted = _extractApiClassNamesFromCode(code);
|
|
||||||
if (extracted.isNotEmpty) {
|
|
||||||
apiClasses[version]!.addAll(extracted.toSet());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
final fileName = entry.key;
|
|
||||||
final className = fileName
|
|
||||||
.replaceAll('.dart', '')
|
|
||||||
.split('_')
|
|
||||||
.map(
|
|
||||||
(word) => word.isEmpty
|
|
||||||
? ''
|
|
||||||
: (word[0].toUpperCase() + word.substring(1)),
|
|
||||||
)
|
|
||||||
.join();
|
|
||||||
apiClasses[version]!.add(className);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final versions = apiClasses.keys.toList()..sort();
|
|
||||||
for (final version in versions) {
|
|
||||||
buffer.writeln("import '$version/index.dart';");
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer
|
|
||||||
..writeln()
|
|
||||||
..writeln('/// 统一 API 客户端');
|
|
||||||
|
|
||||||
final apiClientClassName = _config.apiClientClassName;
|
|
||||||
buffer
|
|
||||||
..writeln('/// 支持多版本 API 访问')
|
|
||||||
..writeln('class $apiClientClassName {')
|
|
||||||
..writeln(' final Dio _dio;')
|
|
||||||
..writeln();
|
|
||||||
|
|
||||||
for (final versionEntry in apiClasses.entries) {
|
|
||||||
final version = versionEntry.key;
|
|
||||||
final versionUpper = version == 'v1' ? '' : version.toUpperCase();
|
|
||||||
|
|
||||||
for (final className in versionEntry.value) {
|
|
||||||
final suffix = version == 'v1' ? '' : versionUpper;
|
|
||||||
buffer.writeln(
|
|
||||||
' late final $className$suffix _${_toLowerCamelCase(className)}$suffix;',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer
|
|
||||||
..writeln()
|
|
||||||
..writeln(' $apiClientClassName(this._dio) {')
|
|
||||||
..writeln(' _initApis();')
|
|
||||||
..writeln(' }')
|
|
||||||
..writeln()
|
|
||||||
..writeln(' void _initApis() {');
|
|
||||||
|
|
||||||
for (final versionEntry in apiClasses.entries) {
|
|
||||||
final version = versionEntry.key;
|
|
||||||
final versionUpper = version == 'v1' ? '' : version.toUpperCase();
|
|
||||||
|
|
||||||
for (final className in versionEntry.value) {
|
|
||||||
final fieldName = _toLowerCamelCase(className);
|
|
||||||
final suffix = version == 'v1' ? '' : versionUpper;
|
|
||||||
buffer.writeln(' _$fieldName$suffix = $className$suffix(_dio);');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer
|
|
||||||
..writeln(' }')
|
|
||||||
..writeln()
|
|
||||||
..writeln(' // ========== 版本化 API 访问 ==========')
|
|
||||||
..writeln();
|
|
||||||
|
|
||||||
for (final versionEntry in apiClasses.entries) {
|
|
||||||
final version = versionEntry.key;
|
|
||||||
final versionUpper = version == 'v1' ? '' : version.toUpperCase();
|
|
||||||
final versionLabel =
|
|
||||||
version == 'v1' ? 'V1(默认版本)' : '${version.toUpperCase()} 版本';
|
|
||||||
|
|
||||||
buffer.writeln(' /// $versionLabel API');
|
|
||||||
for (final className in versionEntry.value) {
|
|
||||||
final fieldName = _toLowerCamelCase(className);
|
|
||||||
final suffix = version == 'v1' ? '' : versionUpper;
|
|
||||||
buffer.writeln(
|
|
||||||
' $className$suffix get $fieldName$suffix => _$fieldName$suffix;',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
buffer.writeln();
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.writeln('}');
|
|
||||||
return buffer.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> _extractApiClassNamesFromCode(String code) {
|
|
||||||
try {
|
|
||||||
final regex = RegExp(r'abstract\s+class\s+(\w+Api)\b');
|
|
||||||
final matches = regex.allMatches(code);
|
|
||||||
if (matches.isEmpty) return const [];
|
|
||||||
return matches.map((m) => m.group(1)!).toList();
|
|
||||||
} on FormatException {
|
|
||||||
return const [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String _toLowerCamelCase(String className) {
|
|
||||||
final name = className.replaceAll('Api', '');
|
|
||||||
return name[0].toLowerCase() + name.substring(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _generateVersionIndexFile(
|
|
||||||
String versionDir,
|
|
||||||
List<String> fileNames,
|
|
||||||
) async {
|
|
||||||
final buffer = StringBuffer()
|
|
||||||
..writeln('// API 接口导出文件')
|
|
||||||
..writeln('// 由 xy_swagger_generator by max 生成')
|
|
||||||
..writeln('// Copyright (C) 2025 YuanXuan. All rights reserved.')
|
|
||||||
..writeln();
|
|
||||||
|
|
||||||
final sortedFiles = fileNames.toList()..sort();
|
|
||||||
for (final fileName in sortedFiles) {
|
|
||||||
buffer.writeln("export '$fileName';");
|
|
||||||
}
|
|
||||||
|
|
||||||
final indexPath = '$versionDir/index.dart';
|
|
||||||
await FileUtils.writeFile(indexPath, buffer.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
/// Pipeline: parse
|
|
||||||
/// Re-export swagger data parser for pipeline-oriented imports.
|
|
||||||
library;
|
|
||||||
|
|
||||||
export './swagger_data_parser.dart';
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
/// Pipeline: parse
|
|
||||||
/// Re-export swagger fetcher for pipeline-oriented imports.
|
|
||||||
library;
|
|
||||||
|
|
||||||
export 'impl/swagger_fetcher.dart';
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:mustache_template/mustache_template.dart';
|
|
||||||
import 'package:path/path.dart' as p;
|
|
||||||
import 'package:swagger_generator_flutter/core/config_repository.dart';
|
|
||||||
import 'package:swagger_generator_flutter/utils/path_resolver.dart';
|
|
||||||
|
|
||||||
part 'template/template_loader.dart';
|
|
||||||
|
|
||||||
/// 模板渲染器
|
|
||||||
/// 负责加载和渲染 Mustache 模板,支持文件覆盖与内置模板
|
|
||||||
class TemplateRenderer {
|
|
||||||
TemplateRenderer({
|
|
||||||
String? templateRoot,
|
|
||||||
List<String>? extraTemplateRoots,
|
|
||||||
}) : _loader = TemplateLoader(
|
|
||||||
customRoot: templateRoot,
|
|
||||||
extraRoots: extraTemplateRoots,
|
|
||||||
),
|
|
||||||
_baseContext = _buildBaseContext();
|
|
||||||
|
|
||||||
final TemplateLoader _loader;
|
|
||||||
final Map<String, Template> _templateCache = {};
|
|
||||||
final Map<String, dynamic> _baseContext;
|
|
||||||
|
|
||||||
/// 渲染模板
|
|
||||||
///
|
|
||||||
/// [templateName] 模板名称(不含 .mustache 扩展名)
|
|
||||||
/// [data] 模板数据
|
|
||||||
String render(
|
|
||||||
String templateName,
|
|
||||||
Map<String, dynamic> data, {
|
|
||||||
Map<String, String>? partials,
|
|
||||||
}) {
|
|
||||||
final template = _getTemplate(templateName);
|
|
||||||
final context = {..._baseContext, ...data};
|
|
||||||
return template.renderString(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 部分模板解析器
|
|
||||||
Template? _partialResolver(String name) {
|
|
||||||
try {
|
|
||||||
return _getTemplate(name);
|
|
||||||
} on Exception {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取模板(带缓存)
|
|
||||||
Template _getTemplate(String templateName) {
|
|
||||||
if (_templateCache.containsKey(templateName)) {
|
|
||||||
return _templateCache[templateName]!;
|
|
||||||
}
|
|
||||||
|
|
||||||
final source = _getTemplateSource(templateName);
|
|
||||||
final template = Template(
|
|
||||||
source,
|
|
||||||
name: templateName,
|
|
||||||
lenient: true,
|
|
||||||
htmlEscapeValues: false,
|
|
||||||
partialResolver: _partialResolver,
|
|
||||||
);
|
|
||||||
_templateCache[templateName] = template;
|
|
||||||
return template;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取模板源码:优先文件,其次内嵌
|
|
||||||
String _getTemplateSource(String templateName) {
|
|
||||||
final fileTemplate = _loader.load(templateName);
|
|
||||||
if (fileTemplate != null) {
|
|
||||||
return fileTemplate;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw Exception('Template not found in file system: $templateName');
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 清除模板缓存
|
|
||||||
void clearCache() {
|
|
||||||
_templateCache.clear();
|
|
||||||
_loader.clearCache();
|
|
||||||
}
|
|
||||||
|
|
||||||
static Map<String, dynamic> _buildBaseContext() {
|
|
||||||
// Load once synchronously to avoid repeated disk IO
|
|
||||||
final config = ConfigRepository.loadSync();
|
|
||||||
return {
|
|
||||||
'generatorName': config.generatorName,
|
|
||||||
'author': config.author,
|
|
||||||
'copyright': config.copyright,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
/// Pipeline: render -> template renderer
|
|
||||||
/// Re-export TemplateRenderer for pipeline-oriented imports.
|
|
||||||
library;
|
|
||||||
|
|
||||||
export 'package:swagger_generator_flutter/core/template_renderer.dart';
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
/// Pipeline: validate
|
|
||||||
/// Re-export enhanced validator (decorator over schema validator).
|
|
||||||
library;
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
/// Schema 验证器
|
|
||||||
/// 验证 OpenAPI 3.0 文档的完整性和正确性
|
|
||||||
library;
|
|
||||||
|
|
||||||
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';
|
|
||||||
import 'package:swagger_generator_flutter/pipeline/validate/rules/component_rules.dart';
|
|
||||||
import 'package:swagger_generator_flutter/pipeline/validate/rules/info_rules.dart';
|
|
||||||
import 'package:swagger_generator_flutter/pipeline/validate/rules/path_rules.dart';
|
|
||||||
import 'package:swagger_generator_flutter/pipeline/validate/rules/security_rules.dart';
|
|
||||||
import 'package:swagger_generator_flutter/pipeline/validate/rules/server_rules.dart';
|
|
||||||
import 'package:swagger_generator_flutter/pipeline/validate/rules/structure_rules.dart';
|
|
||||||
|
|
||||||
export 'package:swagger_generator_flutter/pipeline/validate/core/validation_context.dart';
|
|
||||||
export 'package:swagger_generator_flutter/pipeline/validate/core/validation_result.dart';
|
|
||||||
export 'package:swagger_generator_flutter/pipeline/validate/core/validation_rule.dart';
|
|
||||||
|
|
||||||
/// Schema 验证器
|
|
||||||
class SchemaValidator {
|
|
||||||
SchemaValidator({
|
|
||||||
List<ValidationRule>? rules,
|
|
||||||
}) : _rules = rules ?? _defaultRules;
|
|
||||||
|
|
||||||
final List<ValidationRule> _rules;
|
|
||||||
|
|
||||||
static final List<ValidationRule> _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 = <ValidationResult>[];
|
|
||||||
for (final rule in _rules) {
|
|
||||||
results.add(rule.validate(context));
|
|
||||||
}
|
|
||||||
|
|
||||||
return results.merge();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
/// Pipeline: validate
|
|
||||||
/// Re-export schema validator impl during Phase 2.
|
|
||||||
library;
|
|
||||||
|
|
||||||
export 'package:swagger_generator_flutter/pipeline/validate/impl/schema_validator.dart';
|
|
||||||
|
|
@ -4,8 +4,7 @@ import 'package:logging/logging.dart';
|
||||||
import 'package:swagger_generator_flutter/commands/base_command.dart';
|
import 'package:swagger_generator_flutter/commands/base_command.dart';
|
||||||
import 'package:swagger_generator_flutter/commands/generate_command.dart';
|
import 'package:swagger_generator_flutter/commands/generate_command.dart';
|
||||||
import 'package:swagger_generator_flutter/core/config.dart';
|
import 'package:swagger_generator_flutter/core/config.dart';
|
||||||
import 'package:swagger_generator_flutter/utils/string_helper.dart';
|
import 'package:swagger_generator_flutter/utils/string_utils.dart';
|
||||||
import 'package:swagger_generator_flutter/utils/string_utils/index.dart';
|
|
||||||
|
|
||||||
/// Swagger CLI 应用程序
|
/// Swagger CLI 应用程序
|
||||||
/// 使用命令模式架构的新版本CLI工具
|
/// 使用命令模式架构的新版本CLI工具
|
||||||
|
|
@ -79,9 +78,7 @@ class SwaggerCLI {
|
||||||
if (exitCode == 0) {
|
if (exitCode == 0) {
|
||||||
_logger
|
_logger
|
||||||
..info('')
|
..info('')
|
||||||
..info(
|
..info('⏱️ 执行时间: ${StringUtils.formatDuration(stopwatch.elapsed)}');
|
||||||
'⏱️ 执行时间: ${StringHelper.formatDuration(stopwatch.elapsed)}',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return exitCode;
|
return exitCode;
|
||||||
|
|
|
||||||
|
|
@ -7,3 +7,10 @@ export 'core/error_reporter.dart';
|
||||||
// 核心模型
|
// 核心模型
|
||||||
export 'core/models.dart';
|
export 'core/models.dart';
|
||||||
export 'core/performance_parser.dart';
|
export 'core/performance_parser.dart';
|
||||||
|
// 生成器
|
||||||
|
export 'generators/retrofit_api_generator.dart';
|
||||||
|
// 工具类
|
||||||
|
export 'utils/string_utils.dart';
|
||||||
|
// 验证器
|
||||||
|
export 'validators/enhanced_validator.dart';
|
||||||
|
export 'validators/schema_validator.dart';
|
||||||
|
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
/// API naming utilities for endpoint and controller name generation
|
|
||||||
library;
|
|
||||||
|
|
||||||
import 'package:swagger_generator_flutter/core/models.dart';
|
|
||||||
import 'package:swagger_generator_flutter/utils/string_utils/naming_converter.dart';
|
|
||||||
|
|
||||||
/// API naming utilities
|
|
||||||
class ApiNamingUtils {
|
|
||||||
/// Generate endpoint name from path and operationId
|
|
||||||
static String generateEndpointName(String path, String? operationId) {
|
|
||||||
// If operationId exists, use it with priority
|
|
||||||
if (operationId != null && operationId.isNotEmpty) {
|
|
||||||
return NamingConverter.toCamelCase(operationId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove version prefix from path
|
|
||||||
var cleanPath = path.replaceFirst('/api/v1', '');
|
|
||||||
|
|
||||||
// Remove leading slash
|
|
||||||
if (cleanPath.startsWith('/')) {
|
|
||||||
cleanPath = cleanPath.substring(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert path to camelCase
|
|
||||||
final parts = cleanPath.split('/');
|
|
||||||
if (parts.length >= 2) {
|
|
||||||
final controller = parts[0];
|
|
||||||
final action = parts[1];
|
|
||||||
return NamingConverter.toCamelCase('${controller}_$action');
|
|
||||||
}
|
|
||||||
|
|
||||||
return NamingConverter.toCamelCase(cleanPath.replaceAll('/', '_'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extract controller name from API path
|
|
||||||
static String extractControllerName(ApiPath path) {
|
|
||||||
// Extract controller name from tags
|
|
||||||
if (path.tags.isNotEmpty) {
|
|
||||||
return path.tags.first;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract controller name from path
|
|
||||||
final pathParts = path.path.split('/');
|
|
||||||
if (pathParts.length > 1) {
|
|
||||||
return NamingConverter.toPascalCase(pathParts[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'General';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
|
|
||||||
import 'package:swagger_generator_flutter/utils/string_utils/index.dart';
|
import 'package:swagger_generator_flutter/utils/string_utils.dart';
|
||||||
|
|
||||||
/// 缓存管理器
|
/// 缓存管理器
|
||||||
/// 提供简单的内存缓存功能
|
/// 提供简单的内存缓存功能
|
||||||
|
|
@ -115,7 +115,7 @@ class CacheStats {
|
||||||
'memoryItems: $memoryItems, '
|
'memoryItems: $memoryItems, '
|
||||||
'diskItems: $diskItems, '
|
'diskItems: $diskItems, '
|
||||||
'hitRate: ${(hitRate * 100).toStringAsFixed(1)}%, '
|
'hitRate: ${(hitRate * 100).toStringAsFixed(1)}%, '
|
||||||
'totalSize: ${FormattingUtils.formatBytes(totalSize)}'
|
'totalSize: ${StringUtils.formatBytes(totalSize)}'
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,24 +5,22 @@
|
||||||
/// - NamingConverter: 命名转换(camelCase, PascalCase, snake_case 等)
|
/// - NamingConverter: 命名转换(camelCase, PascalCase, snake_case 等)
|
||||||
/// - TextCleaner: 文本清理和格式化
|
/// - TextCleaner: 文本清理和格式化
|
||||||
/// - TemplateService: 模板服务(注释生成、文件头生成等)
|
/// - TemplateService: 模板服务(注释生成、文件头生成等)
|
||||||
/// - FormattingUtils: 格式化工具(缩进、字节大小、持续时间等)
|
|
||||||
///
|
///
|
||||||
/// # 典型用法示例
|
/// # 典型用法示例
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// // snake_case 转 camelCase
|
/// // snake_case 转 camelCase
|
||||||
/// StringHelper.toDartPropertyName('user_id'); // userId
|
/// StringUtils.toDartPropertyName('user_id'); // userId
|
||||||
/// // 含特殊字符
|
/// // 含特殊字符
|
||||||
/// StringHelper.toDartPropertyName('user-id'); // userId
|
/// StringUtils.toDartPropertyName('user-id'); // userId
|
||||||
/// // 数字开头
|
/// // 数字开头
|
||||||
/// StringHelper.toDartPropertyName('1st_field'); // n1stField
|
/// StringUtils.toDartPropertyName('1st_field'); // n1stField
|
||||||
/// // 生成注释
|
/// // 生成注释
|
||||||
/// StringHelper.generateComment('API description'); // /// API description
|
/// StringUtils.generateComment('API description'); // /// API description
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
library;
|
library;
|
||||||
|
|
||||||
import 'package:swagger_generator_flutter/core/models.dart';
|
import 'package:swagger_generator_flutter/core/models.dart';
|
||||||
import 'package:swagger_generator_flutter/utils/string_utils/formatting_utils.dart';
|
|
||||||
import 'package:swagger_generator_flutter/utils/string_utils/naming_converter.dart';
|
import 'package:swagger_generator_flutter/utils/string_utils/naming_converter.dart';
|
||||||
import 'package:swagger_generator_flutter/utils/string_utils/template_service.dart';
|
import 'package:swagger_generator_flutter/utils/string_utils/template_service.dart';
|
||||||
import 'package:swagger_generator_flutter/utils/string_utils/text_cleaner.dart';
|
import 'package:swagger_generator_flutter/utils/string_utils/text_cleaner.dart';
|
||||||
|
|
@ -30,7 +28,7 @@ import 'package:swagger_generator_flutter/utils/string_utils/text_cleaner.dart';
|
||||||
/// 字符串工具类 - 统一导出接口
|
/// 字符串工具类 - 统一导出接口
|
||||||
///
|
///
|
||||||
/// 将各子模块的功能聚合到统一的 API 中,保持向后兼容性
|
/// 将各子模块的功能聚合到统一的 API 中,保持向后兼容性
|
||||||
class StringHelper {
|
class StringUtils {
|
||||||
// ==================== 命名转换 (NamingConverter) ====================
|
// ==================== 命名转换 (NamingConverter) ====================
|
||||||
/// 转换为 camelCase
|
/// 转换为 camelCase
|
||||||
static String toCamelCase(String input) => NamingConverter.toCamelCase(input);
|
static String toCamelCase(String input) => NamingConverter.toCamelCase(input);
|
||||||
|
|
@ -79,8 +77,10 @@ class StringHelper {
|
||||||
static String escapeString(String input) => TextCleaner.escapeString(input);
|
static String escapeString(String input) => TextCleaner.escapeString(input);
|
||||||
|
|
||||||
/// 缩进文本
|
/// 缩进文本
|
||||||
static String indent(String text, int spaces) =>
|
static String indent(String text, int spaces) {
|
||||||
FormattingUtils.indent(text, spaces);
|
final indentation = ' ' * spaces;
|
||||||
|
return text.split('\n').map((line) => '$indentation$line').join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
/// 截断文本到指定长度
|
/// 截断文本到指定长度
|
||||||
static String truncate(String text, int maxLength, {String suffix = '...'}) =>
|
static String truncate(String text, int maxLength, {String suffix = '...'}) =>
|
||||||
|
|
@ -111,21 +111,9 @@ class StringHelper {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 格式化工具 (FormattingUtils) ====================
|
// ==================== 业务逻辑辅助方法 ====================
|
||||||
|
|
||||||
/// 格式化字节大小
|
|
||||||
static String formatBytes(int bytes) => FormattingUtils.formatBytes(bytes);
|
|
||||||
|
|
||||||
/// 格式化持续时间
|
|
||||||
static String formatDuration(Duration duration) =>
|
|
||||||
FormattingUtils.formatDuration(duration);
|
|
||||||
|
|
||||||
// ==================== 已废弃的方法(向后兼容) ====================
|
|
||||||
// 这些方法已移至 ApiNamingUtils,保留此处以兼容旧代码
|
|
||||||
|
|
||||||
/// 生成端点名称
|
/// 生成端点名称
|
||||||
/// @deprecated 使用 ApiNamingUtils.generateEndpointName 代替
|
|
||||||
@Deprecated('Use ApiNamingUtils.generateEndpointName instead')
|
|
||||||
static String generateEndpointName(String path, String? operationId) {
|
static String generateEndpointName(String path, String? operationId) {
|
||||||
// 如果有 operationId,优先使用
|
// 如果有 operationId,优先使用
|
||||||
if (operationId != null && operationId.isNotEmpty) {
|
if (operationId != null && operationId.isNotEmpty) {
|
||||||
|
|
@ -152,8 +140,6 @@ class StringHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 提取控制器名称
|
/// 提取控制器名称
|
||||||
/// @deprecated 使用 ApiNamingUtils.extractControllerName 代替
|
|
||||||
@Deprecated('Use ApiNamingUtils.extractControllerName instead')
|
|
||||||
static String extractControllerName(ApiPath path) {
|
static String extractControllerName(ApiPath path) {
|
||||||
// 从 tags 中提取控制器名称
|
// 从 tags 中提取控制器名称
|
||||||
if (path.tags.isNotEmpty) {
|
if (path.tags.isNotEmpty) {
|
||||||
|
|
@ -168,4 +154,31 @@ class StringHelper {
|
||||||
|
|
||||||
return 'General';
|
return 'General';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 清理路径,保留版本前缀
|
||||||
|
static String cleanPath(String path) {
|
||||||
|
// 保留版本前缀,只清理其他不必要的字符
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 格式化工具 ====================
|
||||||
|
|
||||||
|
/// 格式化字节大小
|
||||||
|
static String formatBytes(int bytes) {
|
||||||
|
if (bytes < 1024) return '${bytes}B';
|
||||||
|
if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)}KB';
|
||||||
|
if (bytes < 1024 * 1024 * 1024) {
|
||||||
|
return '${(bytes / (1024 * 1024)).toStringAsFixed(1)}MB';
|
||||||
|
}
|
||||||
|
return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)}GB';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 格式化持续时间
|
||||||
|
static String formatDuration(Duration duration) {
|
||||||
|
if (duration.inMilliseconds >= 1000) {
|
||||||
|
return '${(duration.inMilliseconds / 1000).toStringAsFixed(2)}秒';
|
||||||
|
} else {
|
||||||
|
return '${duration.inMilliseconds}毫秒';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
/// Formatting utilities for various data types
|
|
||||||
library;
|
|
||||||
|
|
||||||
/// Formatting utilities for bytes, durations, and text indentation
|
|
||||||
class FormattingUtils {
|
|
||||||
/// Indent text by specified number of spaces
|
|
||||||
static String indent(String text, int spaces) {
|
|
||||||
final indentation = ' ' * spaces;
|
|
||||||
return text.split('\n').map((line) => '$indentation$line').join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Format byte size to human-readable string
|
|
||||||
static String formatBytes(int bytes) {
|
|
||||||
if (bytes < 1024) return '${bytes}B';
|
|
||||||
if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)}KB';
|
|
||||||
if (bytes < 1024 * 1024 * 1024) {
|
|
||||||
return '${(bytes / (1024 * 1024)).toStringAsFixed(1)}MB';
|
|
||||||
}
|
|
||||||
return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)}GB';
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Format duration to human-readable string
|
|
||||||
static String formatDuration(Duration duration) {
|
|
||||||
if (duration.inMilliseconds >= 1000) {
|
|
||||||
return '${(duration.inMilliseconds / 1000).toStringAsFixed(2)}秒';
|
|
||||||
} else {
|
|
||||||
return '${duration.inMilliseconds}毫秒';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
/// String utilities - unified export
|
|
||||||
///
|
|
||||||
/// This file provides a single export point for all string utilities
|
|
||||||
library;
|
|
||||||
|
|
||||||
export 'formatting_utils.dart';
|
|
||||||
export 'naming_converter.dart';
|
|
||||||
export 'template_service.dart';
|
|
||||||
export 'text_cleaner.dart';
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:swagger_generator_flutter/core/models.dart';
|
import 'package:swagger_generator_flutter/core/models.dart';
|
||||||
import 'package:swagger_generator_flutter/pipeline/validate/core/validation_result.dart';
|
import 'package:swagger_generator_flutter/validators/core/validation_result.dart';
|
||||||
|
|
||||||
/// 类型验证器
|
/// 类型验证器
|
||||||
/// 提供严格的类型检查和验证功能
|
/// 提供严格的类型检查和验证功能
|
||||||
|
|
@ -400,9 +400,7 @@ class TypeValidator {
|
||||||
|
|
||||||
/// 验证参数
|
/// 验证参数
|
||||||
static ValidationResult _validateParameter(
|
static ValidationResult _validateParameter(
|
||||||
ApiParameter parameter,
|
ApiParameter parameter, String parentPath,) {
|
||||||
String parentPath,
|
|
||||||
) {
|
|
||||||
final errors = <ValidationError>[];
|
final errors = <ValidationError>[];
|
||||||
final warnings = <ValidationWarning>[];
|
final warnings = <ValidationWarning>[];
|
||||||
final currentPath = '$parentPath.parameters[${parameter.name}]';
|
final currentPath = '$parentPath.parameters[${parameter.name}]';
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:swagger_generator_flutter/pipeline/validate/core/validation_context.dart';
|
import 'package:swagger_generator_flutter/validators/core/validation_context.dart';
|
||||||
import 'package:swagger_generator_flutter/pipeline/validate/core/validation_result.dart';
|
import 'package:swagger_generator_flutter/validators/core/validation_result.dart';
|
||||||
|
|
||||||
/// 验证规则基类
|
/// 验证规则基类
|
||||||
abstract class ValidationRule {
|
abstract class ValidationRule {
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
/// 增强的 OpenAPI 验证器(Pipeline Impl)
|
/// 增强的 OpenAPI 验证器
|
||||||
/// 集成详细的错误报告和修复建议
|
/// 集成详细的错误报告和修复建议
|
||||||
library;
|
library;
|
||||||
|
|
||||||
import 'package:swagger_generator_flutter/core/error_reporter.dart';
|
import 'package:swagger_generator_flutter/core/error_reporter.dart';
|
||||||
import 'package:swagger_generator_flutter/core/models.dart';
|
import 'package:swagger_generator_flutter/core/models.dart';
|
||||||
import 'package:swagger_generator_flutter/pipeline/validate/schema_validator.dart';
|
import 'package:swagger_generator_flutter/validators/schema_validator.dart';
|
||||||
|
|
||||||
/// 增强的 OpenAPI 验证器
|
/// 增强的 OpenAPI 验证器
|
||||||
class EnhancedValidator {
|
class EnhancedValidator {
|
||||||
|
|
@ -110,16 +110,16 @@ class EnhancedValidator {
|
||||||
_errorReporter.reportError(
|
_errorReporter.reportError(
|
||||||
id: 'MISSING_OPERATION_ID',
|
id: 'MISSING_OPERATION_ID',
|
||||||
title: 'Missing Operation ID',
|
title: 'Missing Operation ID',
|
||||||
description:
|
description: 'Operation should have an operationId for '
|
||||||
'Operation should have an operationId for better code generation.',
|
'better code generation.',
|
||||||
severity: ErrorSeverity.warning,
|
severity: ErrorSeverity.warning,
|
||||||
category: ErrorCategory.bestPractice,
|
category: ErrorCategory.bestPractice,
|
||||||
jsonPath: 'paths["$pathPattern"][${method.value}].operationId',
|
jsonPath: 'paths["$pathPattern"][${method.value}].operationId',
|
||||||
suggestions: [
|
suggestions: [
|
||||||
FixSuggestion(
|
FixSuggestion(
|
||||||
description: 'Add a unique operationId',
|
description: 'Add a unique operationId',
|
||||||
codeExample:
|
codeExample: '"operationId": '
|
||||||
'"operationId": "${_generateOperationId(pathPattern, method)}"',
|
'"${_generateOperationId(pathPattern, method)}"',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:swagger_generator_flutter/core/models.dart';
|
import 'package:swagger_generator_flutter/core/models.dart';
|
||||||
import 'package:swagger_generator_flutter/pipeline/validate/core/validation_context.dart';
|
import 'package:swagger_generator_flutter/validators/core/validation_context.dart';
|
||||||
import 'package:swagger_generator_flutter/pipeline/validate/core/validation_result.dart';
|
import 'package:swagger_generator_flutter/validators/core/validation_result.dart';
|
||||||
import 'package:swagger_generator_flutter/pipeline/validate/core/validation_rule.dart';
|
import 'package:swagger_generator_flutter/validators/core/validation_rule.dart';
|
||||||
|
|
||||||
/// 组件验证规则
|
/// 组件验证规则
|
||||||
class ComponentValidationRule extends ValidationRule {
|
class ComponentValidationRule extends ValidationRule {
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:swagger_generator_flutter/pipeline/validate/core/validation_context.dart';
|
import 'package:swagger_generator_flutter/validators/core/validation_context.dart';
|
||||||
import 'package:swagger_generator_flutter/pipeline/validate/core/validation_result.dart';
|
import 'package:swagger_generator_flutter/validators/core/validation_result.dart';
|
||||||
import 'package:swagger_generator_flutter/pipeline/validate/core/validation_rule.dart';
|
import 'package:swagger_generator_flutter/validators/core/validation_rule.dart';
|
||||||
|
|
||||||
/// API 基本信息验证规则
|
/// API 基本信息验证规则
|
||||||
class InfoValidationRule extends ValidationRule {
|
class InfoValidationRule extends ValidationRule {
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:swagger_generator_flutter/core/models.dart';
|
import 'package:swagger_generator_flutter/core/models.dart';
|
||||||
import 'package:swagger_generator_flutter/pipeline/validate/core/validation_context.dart';
|
import 'package:swagger_generator_flutter/validators/core/validation_context.dart';
|
||||||
import 'package:swagger_generator_flutter/pipeline/validate/core/validation_result.dart';
|
import 'package:swagger_generator_flutter/validators/core/validation_result.dart';
|
||||||
import 'package:swagger_generator_flutter/pipeline/validate/core/validation_rule.dart';
|
import 'package:swagger_generator_flutter/validators/core/validation_rule.dart';
|
||||||
|
|
||||||
/// 路径验证规则
|
/// 路径验证规则
|
||||||
class PathValidationRule extends ValidationRule {
|
class PathValidationRule extends ValidationRule {
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:swagger_generator_flutter/core/models.dart';
|
import 'package:swagger_generator_flutter/core/models.dart';
|
||||||
import 'package:swagger_generator_flutter/pipeline/validate/core/validation_context.dart';
|
import 'package:swagger_generator_flutter/validators/core/validation_context.dart';
|
||||||
import 'package:swagger_generator_flutter/pipeline/validate/core/validation_result.dart';
|
import 'package:swagger_generator_flutter/validators/core/validation_result.dart';
|
||||||
import 'package:swagger_generator_flutter/pipeline/validate/core/validation_rule.dart';
|
import 'package:swagger_generator_flutter/validators/core/validation_rule.dart';
|
||||||
|
|
||||||
/// 安全要求验证规则
|
/// 安全要求验证规则
|
||||||
class SecurityValidationRule extends ValidationRule {
|
class SecurityValidationRule extends ValidationRule {
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:swagger_generator_flutter/pipeline/validate/core/validation_context.dart';
|
import 'package:swagger_generator_flutter/validators/core/validation_context.dart';
|
||||||
import 'package:swagger_generator_flutter/pipeline/validate/core/validation_result.dart';
|
import 'package:swagger_generator_flutter/validators/core/validation_result.dart';
|
||||||
import 'package:swagger_generator_flutter/pipeline/validate/core/validation_rule.dart';
|
import 'package:swagger_generator_flutter/validators/core/validation_rule.dart';
|
||||||
|
|
||||||
/// 服务器配置验证规则
|
/// 服务器配置验证规则
|
||||||
class ServerValidationRule extends ValidationRule {
|
class ServerValidationRule extends ValidationRule {
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:swagger_generator_flutter/core/models.dart';
|
import 'package:swagger_generator_flutter/core/models.dart';
|
||||||
import 'package:swagger_generator_flutter/pipeline/validate/core/validation_context.dart';
|
import 'package:swagger_generator_flutter/validators/core/validation_context.dart';
|
||||||
import 'package:swagger_generator_flutter/pipeline/validate/core/validation_result.dart';
|
import 'package:swagger_generator_flutter/validators/core/validation_result.dart';
|
||||||
import 'package:swagger_generator_flutter/pipeline/validate/core/validation_rule.dart';
|
import 'package:swagger_generator_flutter/validators/core/validation_rule.dart';
|
||||||
|
|
||||||
/// 文档结构验证规则
|
/// 文档结构验证规则
|
||||||
class StructureValidationRule extends ValidationRule {
|
class StructureValidationRule extends ValidationRule {
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
/// 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<ValidationRule>? rules,
|
||||||
|
}) : _rules = rules ?? _defaultRules;
|
||||||
|
|
||||||
|
final List<ValidationRule> _rules;
|
||||||
|
|
||||||
|
static final List<ValidationRule> _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 = <ValidationResult>[];
|
||||||
|
for (final rule in _rules) {
|
||||||
|
results.add(rule.validate(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
return results.merge();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:swagger_generator_flutter/core/models.dart';
|
import 'package:swagger_generator_flutter/core/models.dart';
|
||||||
import 'package:swagger_generator_flutter/pipeline/generate/impl/retrofit_api_generator.dart';
|
import 'package:swagger_generator_flutter/generators/retrofit_api_generator.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ import 'dart:io';
|
||||||
|
|
||||||
import 'package:swagger_generator_flutter/core/error_reporter.dart';
|
import 'package:swagger_generator_flutter/core/error_reporter.dart';
|
||||||
import 'package:swagger_generator_flutter/core/performance_parser.dart';
|
import 'package:swagger_generator_flutter/core/performance_parser.dart';
|
||||||
import 'package:swagger_generator_flutter/pipeline/generate/impl/retrofit_api_generator.dart';
|
import 'package:swagger_generator_flutter/generators/retrofit_api_generator.dart';
|
||||||
import 'package:swagger_generator_flutter/pipeline/validate/impl/enhanced_validator.dart';
|
import 'package:swagger_generator_flutter/validators/enhanced_validator.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:swagger_generator_flutter/core/models.dart';
|
import 'package:swagger_generator_flutter/core/models.dart';
|
||||||
import 'package:swagger_generator_flutter/pipeline/generate/apis.dart';
|
import 'package:swagger_generator_flutter/generators/model_code_generator.dart';
|
||||||
import 'package:swagger_generator_flutter/pipeline/generate/models.dart';
|
import 'package:swagger_generator_flutter/generators/retrofit_api_generator.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue