feat: update
This commit is contained in:
parent
69aad6bda1
commit
f2e48277ea
|
|
@ -0,0 +1,159 @@
|
||||||
|
# 依赖包版本更新说明
|
||||||
|
|
||||||
|
## 更新日期
|
||||||
|
2025-11-21
|
||||||
|
|
||||||
|
## 更新内容
|
||||||
|
|
||||||
|
### 主项目 (pubspec.yaml)
|
||||||
|
|
||||||
|
#### 更新前
|
||||||
|
```yaml
|
||||||
|
dependencies:
|
||||||
|
json_annotation: ^4.8.1
|
||||||
|
freezed_annotation: ^2.4.1
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
build_runner: ^2.4.7
|
||||||
|
json_serializable: ^6.7.1
|
||||||
|
retrofit_generator: ^8.0.0
|
||||||
|
freezed: ^2.4.7
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 更新后
|
||||||
|
```yaml
|
||||||
|
dependencies:
|
||||||
|
json_annotation: ^4.9.0
|
||||||
|
freezed_annotation: ^3.1.0
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
build_runner: ^2.10.4
|
||||||
|
json_serializable: ^6.11.2
|
||||||
|
retrofit_generator: ^10.2.0
|
||||||
|
freezed: ^3.2.3
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example 项目 (example/pubspec.yaml)
|
||||||
|
|
||||||
|
#### 更新前
|
||||||
|
```yaml
|
||||||
|
dependencies:
|
||||||
|
json_annotation: ^4.9.0
|
||||||
|
freezed_annotation: ^2.4.1
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
build_runner: ^2.4.7
|
||||||
|
retrofit_generator: ^9.0.0
|
||||||
|
json_serializable: ^6.7.1
|
||||||
|
freezed: ^2.4.7
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 更新后
|
||||||
|
```yaml
|
||||||
|
dependencies:
|
||||||
|
json_annotation: ^4.9.0
|
||||||
|
freezed_annotation: ^3.1.0
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
build_runner: ^2.10.4
|
||||||
|
retrofit_generator: ^10.2.0
|
||||||
|
json_serializable: ^6.11.2
|
||||||
|
freezed: ^3.2.3
|
||||||
|
```
|
||||||
|
|
||||||
|
## 版本说明
|
||||||
|
|
||||||
|
### build_runner
|
||||||
|
- **旧版本**: 2.4.7
|
||||||
|
- **新版本**: 2.10.4
|
||||||
|
- **发布时间**: 2天前 (2025-11-19)
|
||||||
|
- **说明**: 最新稳定版本,修复了多个已知问题
|
||||||
|
|
||||||
|
### json_serializable
|
||||||
|
- **旧版本**: 6.7.1
|
||||||
|
- **新版本**: 6.11.2
|
||||||
|
- **说明**: 使用 6.11.2 而非 6.11.3,因为 6.11.3 与 freezed 3.2.3 存在 analyzer 版本冲突
|
||||||
|
|
||||||
|
### retrofit_generator
|
||||||
|
- **旧版本**: 8.0.0 / 9.0.0
|
||||||
|
- **新版本**: 10.2.0
|
||||||
|
- **发布时间**: 33小时前 (2025-11-20)
|
||||||
|
- **说明**: 最新版本,支持更多特性
|
||||||
|
|
||||||
|
### freezed
|
||||||
|
- **旧版本**: 2.4.7
|
||||||
|
- **新版本**: 3.2.3
|
||||||
|
- **发布时间**: 2个月前 (2025-09-10)
|
||||||
|
- **重要变更**:
|
||||||
|
- 需要 freezed_annotation 3.1.0+
|
||||||
|
- 支持 Dart 3 的原生模式匹配
|
||||||
|
- 建议使用 `switch` 表达式替代 `when`/`map` 方法
|
||||||
|
|
||||||
|
### freezed_annotation
|
||||||
|
- **旧版本**: 2.4.1 / 2.4.4
|
||||||
|
- **新版本**: 3.1.0
|
||||||
|
- **说明**: 必须升级到 3.x 以配合 freezed 3.x
|
||||||
|
|
||||||
|
### json_annotation
|
||||||
|
- **旧版本**: 4.8.1
|
||||||
|
- **新版本**: 4.9.0
|
||||||
|
- **说明**: 保持 4.x 版本以确保兼容性
|
||||||
|
|
||||||
|
## 版本兼容性矩阵
|
||||||
|
|
||||||
|
| 包名 | 版本 | analyzer 依赖 | 兼容性 |
|
||||||
|
|------|------|--------------|--------|
|
||||||
|
| freezed 3.2.3 | ✅ | 7.5.9 - 9.0.0 | ✅ |
|
||||||
|
| json_serializable 6.11.2 | ✅ | 8.x | ✅ |
|
||||||
|
| json_serializable 6.11.3 | ❌ | 9.0.0+ | ❌ 与 freezed 冲突 |
|
||||||
|
| build_runner 2.10.4 | ✅ | - | ✅ |
|
||||||
|
| retrofit_generator 10.2.0 | ✅ | - | ✅ |
|
||||||
|
|
||||||
|
## 解决的问题
|
||||||
|
|
||||||
|
1. **build_runner 缓存损坏**: 通过升级到最新版本解决
|
||||||
|
2. **版本冲突**:
|
||||||
|
- freezed 3.x 需要 freezed_annotation 3.x
|
||||||
|
- freezed 3.2.3 与 json_serializable 6.11.3 存在 analyzer 版本冲突
|
||||||
|
- 使用 json_serializable 6.11.2 解决冲突
|
||||||
|
|
||||||
|
## 清理步骤
|
||||||
|
|
||||||
|
如果遇到问题,执行以下清理步骤:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 清理缓存
|
||||||
|
rm -rf .dart_tool
|
||||||
|
rm -rf pubspec.lock
|
||||||
|
|
||||||
|
# 2. 重新获取依赖
|
||||||
|
flutter pub get
|
||||||
|
|
||||||
|
# 3. 清理构建文件
|
||||||
|
flutter clean
|
||||||
|
|
||||||
|
# 4. 重新运行 build_runner
|
||||||
|
flutter pub run build_runner build --delete-conflicting-outputs
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **Freezed 3.x 迁移**:
|
||||||
|
- 建议使用 Dart 3 的原生 `switch` 表达式
|
||||||
|
- 旧的 `when`/`map` 方法仍然可用,但已标记为遗留功能
|
||||||
|
|
||||||
|
2. **analyzer 版本**:
|
||||||
|
- 当前配置使用 analyzer 8.x
|
||||||
|
- 避免使用需要 analyzer 9.x 的包版本
|
||||||
|
|
||||||
|
3. **pub.dev 镜像**:
|
||||||
|
- 如果使用国内镜像 (pub.flutter-io.cn),可能需要等待同步
|
||||||
|
- 建议使用官方源或清华镜像
|
||||||
|
|
||||||
|
## 参考链接
|
||||||
|
|
||||||
|
- [build_runner](https://pub.dev/packages/build_runner)
|
||||||
|
- [json_serializable](https://pub.dev/packages/json_serializable)
|
||||||
|
- [retrofit_generator](https://pub.dev/packages/retrofit_generator)
|
||||||
|
- [freezed](https://pub.dev/packages/freezed)
|
||||||
|
|
||||||
26
README.md
26
README.md
|
|
@ -16,9 +16,9 @@
|
||||||
|
|
||||||
### 🎯 专为 Flutter 优化
|
### 🎯 专为 Flutter 优化
|
||||||
- **Dio + Retrofit 集成**:完美适配主流网络架构
|
- **Dio + Retrofit 集成**:完美适配主流网络架构
|
||||||
|
- **现代数据模型 (Freezed)**:生成基于 Freezed 的不可变数据模型,自动获得 `copyWith`、`toString`、`==/hashCode` 等功能。
|
||||||
- **类型安全**:生成强类型的 API 接口和模型
|
- **类型安全**:生成强类型的 API 接口和模型
|
||||||
- **JSON 序列化**:自动生成 json_serializable 代码
|
- **JSON 序列化**:与 `json_serializable` 无缝集成,自动生成序列化代码
|
||||||
- **String 默认值**:自动为 String 类型字段添加默认值,提升容错性
|
|
||||||
- **文件上传支持**:完整的 multipart/form-data 支持
|
- **文件上传支持**:完整的 multipart/form-data 支持
|
||||||
|
|
||||||
### 🔧 高级特性
|
### 🔧 高级特性
|
||||||
|
|
@ -54,8 +54,13 @@
|
||||||
|
|
||||||
### 📦 作为 dev_dependencies 使用(推荐)
|
### 📦 作为 dev_dependencies 使用(推荐)
|
||||||
|
|
||||||
1) 在宿主项目 `pubspec.yaml` 添加依赖
|
1) 在宿主项目 `pubspec.yaml` 添加依赖
|
||||||
```yaml
|
```yaml
|
||||||
|
dependencies:
|
||||||
|
# Freezed 模型需要
|
||||||
|
freezed_annotation: ^2.4.1
|
||||||
|
json_annotation: ^4.8.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
swagger_generator_flutter:
|
swagger_generator_flutter:
|
||||||
git:
|
git:
|
||||||
|
|
@ -64,9 +69,15 @@ dev_dependencies:
|
||||||
# 或在开发阶段使用本地路径
|
# 或在开发阶段使用本地路径
|
||||||
# swagger_generator_flutter:
|
# swagger_generator_flutter:
|
||||||
# path: ../swagger_generator_flutter
|
# path: ../swagger_generator_flutter
|
||||||
|
|
||||||
|
# 代码生成工具
|
||||||
|
build_runner: ^2.4.7
|
||||||
|
freezed: ^2.4.7
|
||||||
|
json_serializable: ^6.7.1
|
||||||
|
retrofit_generator: ^8.0.0 # 如果使用 Retrofit
|
||||||
```
|
```
|
||||||
|
|
||||||
2) 在宿主项目根目录准备 `generator_config.yaml`(复制 `generator_config.template.yaml`)
|
2) 在宿主项目根目录准备 `generator_config.yaml`(复制 `generator_config.template.yaml`)
|
||||||
重点字段:
|
重点字段:
|
||||||
- `input.swagger_urls`:可配置多个 URL,后面的会覆盖前面的同名模型/路径(适合 v1 → v2 迭代)
|
- `input.swagger_urls`:可配置多个 URL,后面的会覆盖前面的同名模型/路径(适合 v1 → v2 迭代)
|
||||||
- `output.base_dir/api_dir/models_dir`:输出目录,支持相对路径(基于配置文件所在目录)
|
- `output.base_dir/api_dir/models_dir`:输出目录,支持相对路径(基于配置文件所在目录)
|
||||||
|
|
@ -75,10 +86,13 @@ dev_dependencies:
|
||||||
- `generation.api.client`:设置 ApiClient 类名/文件名
|
- `generation.api.client`:设置 ApiClient 类名/文件名
|
||||||
- `generation.api.base_result_import / base_page_result_import`:接入自定义响应包装类型
|
- `generation.api.base_result_import / base_page_result_import`:接入自定义响应包装类型
|
||||||
|
|
||||||
3) 生成代码
|
3) 生成代码(两步)
|
||||||
```bash
|
```bash
|
||||||
|
# 步骤 1: 生成 Swagger API 和 Freezed 模型定义
|
||||||
flutter pub get
|
flutter pub get
|
||||||
dart run swagger_generator_flutter generate --all
|
dart run swagger_generator_flutter generate --all
|
||||||
|
|
||||||
|
# 步骤 2: 运行 build_runner 生成 Freezed 和 json_serializable 的 part 文件
|
||||||
dart run build_runner build --delete-conflicting-outputs
|
dart run build_runner build --delete-conflicting-outputs
|
||||||
```
|
```
|
||||||
CLI 里的 `--included-tags/--excluded-tags/--split-by-tags` 优先级高于配置文件。生成结果会按照版本落在 `api/<version>/` 下,模型分类在 `api_models/`。
|
CLI 里的 `--included-tags/--excluded-tags/--split-by-tags` 优先级高于配置文件。生成结果会按照版本落在 `api/<version>/` 下,模型分类在 `api_models/`。
|
||||||
|
|
@ -137,7 +151,7 @@ Future<void> main() async {
|
||||||
- `--api` / `-r`: 只生成 Retrofit 风格 API 接口
|
- `--api` / `-r`: 只生成 Retrofit 风格 API 接口
|
||||||
- `--models` / `-m`: 只生成数据模型
|
- `--models` / `-m`: 只生成数据模型
|
||||||
- `--docs` / `-d`: 只生成 API 文档
|
- `--docs` / `-d`: 只生成 API 文档
|
||||||
- `--simple` / `-s`: 使用简洁版模型生成
|
|
||||||
- `--split-by-tags` / `-t`: 按 tags 分组生成多个 API 文件(默认启用)
|
- `--split-by-tags` / `-t`: 按 tags 分组生成多个 API 文件(默认启用)
|
||||||
- `--output-dir` / `-o`: 指定输出目录(默认:generator)
|
- `--output-dir` / `-o`: 指定输出目录(默认:generator)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,77 +1,18 @@
|
||||||
# 1. 继承 Lint 规则集 (必选其一)
|
include: package:very_good_analysis/analysis_options.yaml
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
# 强烈推荐!根据你的项目类型选择一个 Lint 规则集作为起点。
|
|
||||||
# 这能大大减少手动配置的工作量,并与社区最佳实践保持一致。
|
|
||||||
# Linter 规则
|
|
||||||
|
|
||||||
# https://dart.ac.cn/tools/linter-rules
|
|
||||||
|
|
||||||
# 如果是 Flutter 项目,推荐使用:
|
|
||||||
include: package:flutter_lints/flutter.yaml
|
|
||||||
|
|
||||||
# 如果是纯 Dart 项目,推荐使用:
|
|
||||||
# include: package:lints/recommended.yaml
|
|
||||||
|
|
||||||
# 2. 配置分析器 (必选)
|
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
# 分析器用于检查代码的语法和潜在问题。
|
|
||||||
# 强烈建议启用所有规则,以确保代码质量和一致性。
|
|
||||||
|
|
||||||
analyzer:
|
analyzer:
|
||||||
errors:
|
|
||||||
require_trailing_commas: ignore
|
|
||||||
# 排除不想被分析的文件或目录。
|
|
||||||
# 对于由代码生成工具(如 json_serializable, freezed)生成的 *.g.dart 文件,
|
|
||||||
# 强烈建议排除,因为你通常不需要对它们进行 Lint 检查。
|
|
||||||
exclude:
|
exclude:
|
||||||
- '**/*.g.dart' # 排除所有以 .g.dart 结尾的文件
|
# 排除所有生成的文件
|
||||||
- 'lib/generated/**' # 排除 lib/generated/ 目录下的所有文件 (如果你的生成文件都在这里)
|
- "**/*.g.dart"
|
||||||
- 'build/**' # 排除 Flutter/Dart 构建输出目录
|
- "**/*.freezed.dart"
|
||||||
|
# 如果还有其他生成文件,也可以添加
|
||||||
|
# - "**/*.gr.dart" # auto_route 生成的文件
|
||||||
|
# - "**/*.config.dart" # injectable 生成的文件
|
||||||
|
|
||||||
|
|
||||||
# 3. 配置 Lint 规则
|
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
linter:
|
linter:
|
||||||
# 在此处启用或禁用特定的 Lint 规则。
|
|
||||||
# `include` 中的规则集已经包含了大部分常用规则,这里可以进行微调。
|
|
||||||
rules:
|
rules:
|
||||||
# 常用且推荐启用的规则 (即使默认集没有包含,也建议手动添加)
|
# 关闭强制文档注释 (很多业务开发觉得这条太累赘)
|
||||||
- avoid_empty_else # 避免空的 else 块
|
public_member_api_docs: false
|
||||||
# - avoid_print # 在生产代码中避免使用 print (可根据项目需求启用/禁用)
|
|
||||||
- avoid_relative_lib_imports # 避免从 'lib/' 相对导入
|
# 可选:如果你不喜欢强制构造函数必须写在最前面,也可以关掉
|
||||||
- directives_ordering # 强制 import/export 指令排序
|
# sort_constructors_first: false
|
||||||
# - avoid_return_and_type_annotation # 避免冗余的返回类型注解
|
|
||||||
- curly_braces_in_flow_control_structures # 控制流语句强制使用大括号
|
|
||||||
- empty_catches # 避免空的 catch 块
|
|
||||||
- empty_constructor_bodies # 避免空的构造函数体
|
|
||||||
- empty_statements # 避免空的语句
|
|
||||||
- file_names # 文件名使用小写下划线命名 (my_file.dart)
|
|
||||||
# - prefer_const_constructors # 尽可能使用 const 构造函数
|
|
||||||
# - prefer_const_declarations # 尽可能使用 const 声明
|
|
||||||
# - prefer_const_literals_to_create_immutables # 尽可能使用 const 创建不可变集合
|
|
||||||
# - prefer_single_quotes # 优先使用单引号 (或 prefer_double_quotes)
|
|
||||||
- prefer_final_fields # 类中的私有字段尽可能使用 final
|
|
||||||
- prefer_final_locals # 局部变量尽可能使用 final
|
|
||||||
# - prefer_for_elements_to_map_fromIterable # 优先使用 for 元素创建 Map
|
|
||||||
# - prefer_is_empty # 优先使用 .isEmpty
|
|
||||||
# - prefer_is_not_empty # 优先使用 .isNotEmpty
|
|
||||||
- unnecessary_new # Dart 2.0 后 new 关键字是可选的,推荐省略
|
|
||||||
- unnecessary_this # 避免不必要的 this 关键字
|
|
||||||
# - use_key_in_widget_constructors # Flutter Widget 构造函数中推荐使用 Key
|
|
||||||
|
|
||||||
# 根据项目特性考虑启用的规则 (可能需要团队讨论)
|
|
||||||
# - annotate_overrides # 推荐:覆写方法添加 @override 注解 (如果 flutter_lints 已包含则无需重复)
|
|
||||||
# - lines_longer_than_80_chars # 强制行长 80 字符 (默认是警告,但通常较严格)
|
|
||||||
# - public_member_api_docs # 推荐:为公共 API 编写文档注释 (对库项目非常重要,应用项目可酌情)
|
|
||||||
- require_trailing_commas # 强制多行参数列表使用尾随逗号 (有助于格式化)
|
|
||||||
# - sort_constructors_first # 构造函数在类中声明在前
|
|
||||||
# - sort_declarations_as_members # 类成员按字母顺序排序
|
|
||||||
# - sort_pub_dependencies # pubspec.yaml 依赖按字母排序
|
|
||||||
|
|
||||||
# 4. 格式化器配置
|
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
formatter:
|
|
||||||
# 设置 `dart format` 工具的行宽。
|
|
||||||
# Dart 官方推荐 80,但许多团队会使用 100 或 120 以适应现代宽屏显示器。
|
|
||||||
# 最重要的是整个团队**保持一致**。
|
|
||||||
page_width: 80
|
|
||||||
|
|
@ -268,135 +268,23 @@ print(report);
|
||||||
|
|
||||||
### 基本使用流程
|
### 基本使用流程
|
||||||
|
|
||||||
```dart
|
```bash
|
||||||
import 'dart:io';
|
# 步骤 1: 生成 API 和 Freezed 模型
|
||||||
import 'package:swagger_generator_flutter/swagger_generator_flutter.dart';
|
dart run swagger_generator_flutter generate --all
|
||||||
|
|
||||||
Future<void> generateApiCode() async {
|
# 步骤 2: 运行 build_runner 生成序列化代码
|
||||||
// 1. 创建解析器
|
dart run build_runner build --delete-conflicting-outputs
|
||||||
final parser = PerformanceParser(
|
|
||||||
config: ParseConfig(
|
|
||||||
enablePerformanceStats: true,
|
|
||||||
enableCaching: true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// 2. 创建验证器
|
|
||||||
final validator = EnhancedValidator(
|
|
||||||
includeWarnings: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 3. 创建生成器
|
|
||||||
final generator = RetrofitApiGenerator(
|
|
||||||
className: 'ApiService',
|
|
||||||
splitByTags: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 4. 解析文档
|
|
||||||
final jsonString = await File('swagger.json').readAsString();
|
|
||||||
final document = await parser.parseDocument(jsonString);
|
|
||||||
|
|
||||||
// 5. 验证文档
|
|
||||||
final isValid = validator.validateDocument(document);
|
|
||||||
if (!isValid) {
|
|
||||||
final report = validator.errorReporter.generateReport();
|
|
||||||
print('验证失败:\n$report');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. 生成代码
|
|
||||||
final generatedCode = generator.generateFromDocument(document);
|
|
||||||
|
|
||||||
// 7. 保存文件
|
|
||||||
await File('lib/api/api_service.dart').writeAsString(generatedCode);
|
|
||||||
|
|
||||||
print('✅ 代码生成完成!');
|
|
||||||
|
|
||||||
// 8. 显示性能统计
|
|
||||||
final stats = parser.lastStats;
|
|
||||||
if (stats != null) {
|
|
||||||
print('解析时间: ${stats.totalTime.inMilliseconds}ms');
|
|
||||||
print('生成的路径数: ${stats.pathCount}');
|
|
||||||
}
|
|
||||||
} catch (e, stackTrace) {
|
|
||||||
print('❌ 生成失败: $e');
|
|
||||||
print('堆栈跟踪: $stackTrace');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 高级使用流程 (企业级)
|
### 高级使用流程 (企业级)
|
||||||
|
|
||||||
```dart
|
```bash
|
||||||
Future<void> generateEnterpriseApiCode() async {
|
# 步骤 1: 生成 API 和 Freezed 模型
|
||||||
// 1. 配置高性能解析器
|
# 使用 --included-tags 或 --excluded-tags 过滤范围
|
||||||
final parser = PerformanceParser(
|
dart run swagger_generator_flutter generate --all --excluded-tags=Internal,Debug
|
||||||
config: ParseConfig(
|
|
||||||
enablePerformanceStats: true,
|
|
||||||
enableParallelParsing: true,
|
|
||||||
enableCaching: true,
|
|
||||||
maxConcurrency: 8,
|
|
||||||
enableMemoryOptimization: true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// 2. 配置增强验证器
|
# 步骤 2: 运行 build_runner 生成序列化代码
|
||||||
final validator = EnhancedValidator(
|
dart run build_runner build --delete-conflicting-outputs
|
||||||
strictMode: true,
|
|
||||||
includeWarnings: true,
|
|
||||||
enableAutoFix: true,
|
|
||||||
customRules: [
|
|
||||||
RequiredFieldRule(),
|
|
||||||
NamingConventionRule(),
|
|
||||||
TypeConsistencyRule(),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
// 3. 配置生成器(推荐)
|
|
||||||
final generator = RetrofitApiGenerator(
|
|
||||||
className: 'ApiService',
|
|
||||||
splitByTags: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 4. 解析文档
|
|
||||||
final jsonString = await File('swagger.json').readAsString();
|
|
||||||
final document = await parser.parseDocument(jsonString);
|
|
||||||
|
|
||||||
// 5. 验证文档
|
|
||||||
final isValid = validator.validateDocument(document);
|
|
||||||
if (!isValid) {
|
|
||||||
final errors = validator.errorReporter
|
|
||||||
.getErrorsBySeverity(ErrorSeverity.critical);
|
|
||||||
if (errors.isNotEmpty) {
|
|
||||||
throw Exception('文档包含严重错误,无法继续生成');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. 生成代码(单文件或模块化)
|
|
||||||
final code = generator.generateFromDocument(document);
|
|
||||||
|
|
||||||
// 7. 保存生成的文件
|
|
||||||
await File('lib/api/api_service.dart').writeAsString(code);
|
|
||||||
|
|
||||||
print('✅ 企业级代码生成完成!');
|
|
||||||
|
|
||||||
// 9. 生成性能报告
|
|
||||||
final performanceReport = parser.generatePerformanceReport();
|
|
||||||
await File('reports/performance_report.md')
|
|
||||||
.writeAsString(performanceReport);
|
|
||||||
|
|
||||||
// 10. 生成验证报告
|
|
||||||
final validationReport = validator.errorReporter.generateReport();
|
|
||||||
await File('reports/validation_report.md')
|
|
||||||
.writeAsString(validationReport);
|
|
||||||
|
|
||||||
} catch (e, stackTrace) {
|
|
||||||
print('❌ 企业级生成失败: $e');
|
|
||||||
print('堆栈跟踪: $stackTrace');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ SwaggerDataParser(下载/解析+缓存+性能监测)
|
||||||
- **SchemaValidator / EnhancedValidator**: 基础与增强校验器,用于在生成前验证文档一致性
|
- **SchemaValidator / EnhancedValidator**: 基础与增强校验器,用于在生成前验证文档一致性
|
||||||
|
|
||||||
#### 4. 生成器 (Generators)
|
#### 4. 生成器 (Generators)
|
||||||
- **ModelCodeGenerator**: 按 request/result/enums/parameters 分类生成模型,分页模型自动替换为 `BasePageResult<T>`,响应模型补全字符串/列表默认值
|
- **ModelCodeGenerator**: 生成基于 Freezed 的不可变数据模型,自动获得 `copyWith`、`toString`、`==/hashCode` 等功能,并与 `json_serializable` 无缝集成。
|
||||||
- **RetrofitApiGenerator**: 支持按 tag 拆分、多版本目录与统一 ApiClient,自动生成查询参数实体并处理版本化类名
|
- **RetrofitApiGenerator**: 支持按 tag 拆分、多版本目录与统一 ApiClient,自动生成查询参数实体并处理版本化类名
|
||||||
- **DocumentationGenerator**: 输出 Markdown API 文档与统计摘要
|
- **DocumentationGenerator**: 输出 Markdown API 文档与统计摘要
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,25 +22,16 @@ flutter pub get
|
||||||
|
|
||||||
### 基础使用
|
### 基础使用
|
||||||
|
|
||||||
#### 命令行方式 (推荐新手)
|
在项目根目录下(`generator_config.yaml` 所在目录)运行以下命令:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 克隆或下载项目
|
# 步骤 1: 生成 API 定义和 Freezed 模型
|
||||||
git clone <repository-url>
|
# 这会根据 swagger.json 生成 *.dart 文件,但它们还不完整
|
||||||
cd swagger_generator_flutter
|
dart run swagger_generator_flutter generate --all
|
||||||
|
|
||||||
# 安装依赖
|
# 步骤 2: 运行 build_runner 完成代码生成
|
||||||
flutter pub get
|
# 这会生成 *.freezed.dart 和 *.g.dart 文件,补全模型和序列化逻辑
|
||||||
|
dart run build_runner build --delete-conflicting-outputs
|
||||||
# 将你的 swagger.json 放在项目根目录
|
|
||||||
|
|
||||||
# 生成所有代码
|
|
||||||
sh run_swagger.sh all
|
|
||||||
|
|
||||||
# 或者分别生成
|
|
||||||
sh run_swagger.sh api # 只生成 API
|
|
||||||
sh run_swagger.sh models # 只生成模型
|
|
||||||
sh run_swagger.sh docs # 只生成文档
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 编程方式 (推荐进阶用户)
|
#### 编程方式 (推荐进阶用户)
|
||||||
|
|
@ -89,21 +80,17 @@ dependencies:
|
||||||
# 网络请求
|
# 网络请求
|
||||||
dio: ^5.4.0
|
dio: ^5.4.0
|
||||||
retrofit: ^4.0.0
|
retrofit: ^4.0.0
|
||||||
|
|
||||||
# JSON 序列化
|
# Freezed 模型
|
||||||
|
freezed_annotation: ^2.4.1
|
||||||
json_annotation: ^4.8.1
|
json_annotation: ^4.8.1
|
||||||
|
|
||||||
# 其他依赖
|
|
||||||
logging: ^1.2.0
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
# 代码生成
|
# 代码生成
|
||||||
build_runner: ^2.4.7
|
build_runner: ^2.4.7
|
||||||
retrofit_generator: ^8.0.0
|
retrofit_generator: ^8.0.0
|
||||||
json_serializable: ^6.7.1
|
json_serializable: ^6.7.1
|
||||||
|
freezed: ^2.4.7
|
||||||
# 测试
|
|
||||||
test: ^1.24.0
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. 项目结构
|
### 2. 项目结构
|
||||||
|
|
@ -242,28 +229,6 @@ templates:
|
||||||
|
|
||||||
### 2. 代码生成最佳实践
|
### 2. 代码生成最佳实践
|
||||||
|
|
||||||
#### 选择合适的生成器
|
|
||||||
|
|
||||||
```dart
|
|
||||||
// 小型项目 - 基础生成器
|
|
||||||
final generator = RetrofitApiGenerator(
|
|
||||||
className: 'ApiService',
|
|
||||||
splitByTags: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 中型项目 - 基础生成器(按标签拆分)
|
|
||||||
final generator = RetrofitApiGenerator(
|
|
||||||
className: 'ApiService',
|
|
||||||
splitByTags: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 大型项目 - 使用 RetrofitApiGenerator(按标签拆分),并在 CI 中并行处理多个模块
|
|
||||||
final generator = RetrofitApiGenerator(
|
|
||||||
className: 'ApiService',
|
|
||||||
splitByTags: true,
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 配置合适的解析器
|
#### 配置合适的解析器
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,18 @@
|
||||||
include: package:flutter_lints/flutter.yaml
|
include: package:very_good_analysis/analysis_options.yaml
|
||||||
|
|
||||||
linter:
|
|
||||||
rules:
|
|
||||||
- always_declare_return_types
|
|
||||||
- always_put_required_named_parameters_first
|
|
||||||
- avoid_print
|
|
||||||
- avoid_unnecessary_containers
|
|
||||||
- prefer_const_constructors
|
|
||||||
- prefer_const_literals_to_create_immutables
|
|
||||||
- prefer_final_fields
|
|
||||||
- prefer_single_quotes
|
|
||||||
- sort_child_properties_last
|
|
||||||
- use_key_in_widget_constructors
|
|
||||||
|
|
||||||
analyzer:
|
analyzer:
|
||||||
exclude:
|
exclude:
|
||||||
- '**/*.g.dart'
|
# 排除所有生成的文件
|
||||||
- '**/*.freezed.dart'
|
- "**/*.g.dart"
|
||||||
- 'lib/generated/**'
|
- "**/*.freezed.dart"
|
||||||
errors:
|
# 如果还有其他生成文件,也可以添加
|
||||||
invalid_annotation_target: ignore
|
# - "**/*.gr.dart" # auto_route 生成的文件
|
||||||
|
# - "**/*.config.dart" # injectable 生成的文件
|
||||||
|
|
||||||
|
linter:
|
||||||
|
rules:
|
||||||
|
# 关闭强制文档注释 (很多业务开发觉得这条太累赘)
|
||||||
|
public_member_api_docs: false
|
||||||
|
|
||||||
|
# 可选:如果你不喜欢强制构造函数必须写在最前面,也可以关掉
|
||||||
|
# sort_constructors_first: false
|
||||||
|
|
@ -57,9 +57,9 @@ echo ""
|
||||||
|
|
||||||
# 步骤 3: 修复和排序 imports
|
# 步骤 3: 修复和排序 imports
|
||||||
echo -e "${CYAN}🔧 步骤 3/4: 修复和排序 imports...${NC}"
|
echo -e "${CYAN}🔧 步骤 3/4: 修复和排序 imports...${NC}"
|
||||||
dart fix --apply lib/common/api
|
dart fix --apply lib/src/api
|
||||||
FIX_API_EXIT_CODE=$?
|
FIX_API_EXIT_CODE=$?
|
||||||
dart fix --apply lib/common/api_models
|
dart fix --apply lib/src/api_models
|
||||||
FIX_MODELS_EXIT_CODE=$?
|
FIX_MODELS_EXIT_CODE=$?
|
||||||
|
|
||||||
if [ $FIX_API_EXIT_CODE -ne 0 ] || [ $FIX_MODELS_EXIT_CODE -ne 0 ]; then
|
if [ $FIX_API_EXIT_CODE -ne 0 ] || [ $FIX_MODELS_EXIT_CODE -ne 0 ]; then
|
||||||
|
|
@ -72,7 +72,7 @@ echo ""
|
||||||
|
|
||||||
# 步骤 4: 格式化代码
|
# 步骤 4: 格式化代码
|
||||||
echo -e "${CYAN}📐 步骤 4/4: 格式化代码...${NC}"
|
echo -e "${CYAN}📐 步骤 4/4: 格式化代码...${NC}"
|
||||||
dart format lib/common/api lib/common/api_models --set-exit-if-changed
|
dart format lib/src/api lib/src/api_models --set-exit-if-changed
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo -e "${YELLOW}⚠️ 代码格式化失败,请检查错误信息${NC}"
|
echo -e "${YELLOW}⚠️ 代码格式化失败,请检查错误信息${NC}"
|
||||||
|
|
@ -86,8 +86,8 @@ echo -e "${GREEN}✨ 代码生成完成!${NC}"
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${CYAN}📋 生成的文件位置:${NC}"
|
echo -e "${CYAN}📋 生成的文件位置:${NC}"
|
||||||
echo " - API 接口: lib/common/api/"
|
echo " - API 接口: lib/src/api/"
|
||||||
echo " - API 模型: lib/common/api_models/"
|
echo " - API 模型: lib/src/api_models/"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${CYAN}💡 提示:${NC}"
|
echo -e "${CYAN}💡 提示:${NC}"
|
||||||
echo " - 如果生成的文件有错误,请检查并修复后重新运行 build_runner"
|
echo " - 如果生成的文件有错误,请检查并修复后重新运行 build_runner"
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,8 @@ class BasePageResult<T> extends Object {
|
||||||
factory BasePageResult.fromJson(
|
factory BasePageResult.fromJson(
|
||||||
Map<String, dynamic> json,
|
Map<String, dynamic> json,
|
||||||
T Function(dynamic json) fromJsonT,
|
T Function(dynamic json) fromJsonT,
|
||||||
) => _$BasePageResultFromJson(json, fromJsonT);
|
) =>
|
||||||
|
_$BasePageResultFromJson(json, fromJsonT);
|
||||||
|
|
||||||
Map<String, dynamic> toJson(Object Function(T value) toJsonT) =>
|
Map<String, dynamic> toJson(Object Function(T value) toJsonT) =>
|
||||||
_$BasePageResultToJson(this, toJsonT);
|
_$BasePageResultToJson(this, toJsonT);
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,8 @@ class BaseResult<T> extends BaseContainsParametersAbstract {
|
||||||
factory BaseResult.fromJson(
|
factory BaseResult.fromJson(
|
||||||
Map<String, dynamic> json,
|
Map<String, dynamic> json,
|
||||||
T Function(dynamic json) fromJsonT,
|
T Function(dynamic json) fromJsonT,
|
||||||
) => _$BaseResultFromJson(json, fromJsonT);
|
) =>
|
||||||
|
_$BaseResultFromJson(json, fromJsonT);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, dynamic> toJson(Object Function(T value) toJsonT) =>
|
Map<String, dynamic> toJson(Object Function(T value) toJsonT) =>
|
||||||
|
|
|
||||||
|
|
@ -5,18 +5,26 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _fe_analyzer_shared
|
name: _fe_analyzer_shared
|
||||||
sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f
|
sha256: f0bb5d1648339c8308cc0b9838d8456b3cfe5c91f9dc1a735b4d003269e5da9a
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "85.0.0"
|
version: "88.0.0"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d"
|
sha256: "0b7b9c329d2879f8f05d6c05b32ee9ec025f39b077864bdb5ac9a7b63418a98f"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.7.1"
|
version: "8.1.1"
|
||||||
|
ansicolor:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ansicolor
|
||||||
|
sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.3"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -45,18 +53,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build
|
name: build
|
||||||
sha256: "51dc711996cbf609b90cbe5b335bbce83143875a9d58e4b5c6d3c4f684d3dda7"
|
sha256: c1668065e9ba04752570ad7e038288559d1e2ca5c6d0131c0f5f55e39e777413
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.4"
|
version: "4.0.3"
|
||||||
build_config:
|
build_config:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_config
|
name: build_config
|
||||||
sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33"
|
sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
version: "1.2.0"
|
||||||
build_daemon:
|
build_daemon:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -65,30 +73,14 @@ packages:
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.0"
|
version: "4.1.0"
|
||||||
build_resolvers:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: build_resolvers
|
|
||||||
sha256: ee4257b3f20c0c90e72ed2b57ad637f694ccba48839a821e87db762548c22a62
|
|
||||||
url: "https://pub.flutter-io.cn"
|
|
||||||
source: hosted
|
|
||||||
version: "2.5.4"
|
|
||||||
build_runner:
|
build_runner:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: build_runner
|
name: build_runner
|
||||||
sha256: "382a4d649addbfb7ba71a3631df0ec6a45d5ab9b098638144faf27f02778eb53"
|
sha256: "110c56ef29b5eb367b4d17fc79375fa8c18a6cd7acd92c05bb3986c17a079057"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.4"
|
version: "2.10.4"
|
||||||
build_runner_core:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: build_runner_core
|
|
||||||
sha256: "85fbbb1036d576d966332a3f5ce83f2ce66a40bea1a94ad2d5fc29a19a0d3792"
|
|
||||||
url: "https://pub.flutter-io.cn"
|
|
||||||
source: hosted
|
|
||||||
version: "9.1.2"
|
|
||||||
built_collection:
|
built_collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -165,10 +157,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dart_style
|
name: dart_style
|
||||||
sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb"
|
sha256: c87dfe3d56f183ffe9106a18aebc6db431fc7c98c31a54b952a77f3d54a85697
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.1"
|
version: "3.1.2"
|
||||||
dio:
|
dio:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -218,15 +210,31 @@ packages:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: flutter_lints
|
name: flutter_lints
|
||||||
sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1"
|
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.2"
|
version: "6.0.0"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
freezed:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: freezed
|
||||||
|
sha256: "13065f10e135263a4f5a4391b79a8efc5fb8106f8dd555a9e49b750b45393d77"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.3"
|
||||||
|
freezed_annotation:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: freezed_annotation
|
||||||
|
sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.0"
|
||||||
frontend_server_client:
|
frontend_server_client:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -251,6 +259,14 @@ packages:
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.2"
|
version: "2.3.2"
|
||||||
|
hotreloader:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: hotreloader
|
||||||
|
sha256: bc167a1163807b03bada490bfe2df25b0d744df359227880220a5cbd04e5734b
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "4.3.0"
|
||||||
http:
|
http:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -283,14 +299,6 @@ packages:
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.5"
|
version: "1.0.5"
|
||||||
js:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: js
|
|
||||||
sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
|
|
||||||
url: "https://pub.flutter-io.cn"
|
|
||||||
source: hosted
|
|
||||||
version: "0.7.2"
|
|
||||||
json_annotation:
|
json_annotation:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -303,10 +311,10 @@ packages:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: json_serializable
|
name: json_serializable
|
||||||
sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c
|
sha256: c5b2ee75210a0f263c6c7b9eeea80553dbae96ea1bf57f02484e806a3ffdffa3
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.9.5"
|
version: "6.11.2"
|
||||||
leak_tracker:
|
leak_tracker:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -331,14 +339,22 @@ packages:
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.2"
|
version: "3.0.2"
|
||||||
|
lean_builder:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: lean_builder
|
||||||
|
sha256: ef5cd5f907157eb7aa87d1704504b5a6386d2cbff88a3c2b3344477bab323ee9
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.2"
|
||||||
lints:
|
lints:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: lints
|
name: lints
|
||||||
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
|
sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "6.0.0"
|
||||||
logger:
|
logger:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -415,10 +431,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: protobuf
|
name: protobuf
|
||||||
sha256: de9c9eb2c33f8e933a42932fe1dc504800ca45ebc3d673e6ed7f39754ee4053e
|
sha256: "2fcc8a202ca7ec17dab7c97d6b6d91cf03aa07fe6f65f8afbb6dfa52cc5bd902"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2.0"
|
version: "5.1.0"
|
||||||
pub_semver:
|
pub_semver:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -439,18 +455,18 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: retrofit
|
name: retrofit
|
||||||
sha256: "7d78824afa6eeeaf6ac58220910ee7a97597b39e93360d4bda230b7c6df45089"
|
sha256: "84063c18a00d55af41d6b8401edf8473e8c215bd7068ef7ec5e34c60657ffdbe"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.9.0"
|
version: "4.9.1"
|
||||||
retrofit_generator:
|
retrofit_generator:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: retrofit_generator
|
name: retrofit_generator
|
||||||
sha256: "9abcf21acb95bf7040546eafff87f60cf0aee20b05101d71f99876fc4df1f522"
|
sha256: "7ec323f3329ad2ca0bcdc96fe02ec7f2486ecfac6cd2d035b03c398ef6f42308"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.7.0"
|
version: "10.2.0"
|
||||||
shelf:
|
shelf:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -476,18 +492,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: source_gen
|
name: source_gen
|
||||||
sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b"
|
sha256: "07b277b67e0096c45196cbddddf2d8c6ffc49342e88bf31d460ce04605ddac75"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "4.1.1"
|
||||||
source_helper:
|
source_helper:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: source_helper
|
name: source_helper
|
||||||
sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c"
|
sha256: "6a3c6cc82073a8797f8c4dc4572146114a39652851c157db37e964d9c7038723"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.5"
|
version: "1.3.8"
|
||||||
source_span:
|
source_span:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -534,7 +550,7 @@ packages:
|
||||||
path: ".."
|
path: ".."
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "2.1.1"
|
version: "3.0.0"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -551,14 +567,6 @@ packages:
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.6"
|
version: "0.7.6"
|
||||||
timing:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: timing
|
|
||||||
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
|
|
||||||
url: "https://pub.flutter-io.cn"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.2"
|
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -575,6 +583,14 @@ packages:
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.0"
|
||||||
|
very_good_analysis:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: very_good_analysis
|
||||||
|
sha256: "96245839dbcc45dfab1af5fa551603b5c7a282028a64746c19c547d21a7f1e3a"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "10.0.0"
|
||||||
vm_service:
|
vm_service:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -615,6 +631,14 @@ packages:
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.3"
|
version: "3.0.3"
|
||||||
|
xxh3:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: xxh3
|
||||||
|
sha256: "399a0438f5d426785723c99da6b16e136f4953fb1e9db0bf270bd41dd4619916"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.0"
|
||||||
yaml:
|
yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -624,5 +648,5 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.8.0 <4.0.0"
|
dart: ">=3.9.0 <4.0.0"
|
||||||
flutter: ">=3.18.0-18.0.pre.54"
|
flutter: ">=3.18.0-18.0.pre.54"
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,19 @@ description: Example Flutter app using swagger_generator_flutter as dev_dependen
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.0.0 <4.0.0'
|
sdk: ^3.8.0
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
# HTTP 客户端
|
# HTTP 客户端
|
||||||
dio: ^5.0.0
|
dio: ^5.9.0
|
||||||
retrofit: ^4.0.0
|
# API 客户端
|
||||||
|
retrofit: ^4.9.1
|
||||||
# JSON 序列化
|
# JSON 序列化
|
||||||
json_annotation: ^4.9.0
|
json_annotation: ^4.9.0
|
||||||
|
freezed_annotation: ^3.1.0
|
||||||
|
|
||||||
# 其他依赖
|
# 其他依赖
|
||||||
logger: ^2.0.0
|
logger: ^2.0.0
|
||||||
|
|
@ -29,12 +30,14 @@ dev_dependencies:
|
||||||
path: ../
|
path: ../
|
||||||
|
|
||||||
# 代码生成工具
|
# 代码生成工具
|
||||||
build_runner: ^2.4.7
|
build_runner: ^2.10.4
|
||||||
retrofit_generator: ^9.0.0
|
retrofit_generator: ^10.2.0
|
||||||
json_serializable: ^6.7.1
|
json_serializable: ^6.11.2
|
||||||
|
freezed: ^3.2.3
|
||||||
|
|
||||||
# 代码分析
|
# 代码分析
|
||||||
flutter_lints: ^3.0.0
|
flutter_lints: 6.0.0
|
||||||
|
very_good_analysis: ^10.0.0
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,130 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
自动修复 Dart 代码中的 cascade_invocations lint 问题
|
||||||
|
将连续的 buffer.writeln() 和 buffer.write() 调用转换为使用级联操作符 (..)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def fix_cascade_invocations(content: str) -> str:
|
||||||
|
"""修复 cascade_invocations 问题"""
|
||||||
|
lines = content.split('\n')
|
||||||
|
result = []
|
||||||
|
i = 0
|
||||||
|
|
||||||
|
while i < len(lines):
|
||||||
|
line = lines[i]
|
||||||
|
|
||||||
|
# 检测是否是 buffer.writeln() 或 buffer.write() 调用
|
||||||
|
if re.match(r'\s+buffer\.(writeln|write)\(', line):
|
||||||
|
# 收集连续的 buffer 调用
|
||||||
|
buffer_calls = [line]
|
||||||
|
indent = len(line) - len(line.lstrip())
|
||||||
|
j = i + 1
|
||||||
|
|
||||||
|
# 查找连续的 buffer 调用
|
||||||
|
while j < len(lines):
|
||||||
|
next_line = lines[j]
|
||||||
|
# 跳过空行和注释
|
||||||
|
if not next_line.strip() or next_line.strip().startswith('//'):
|
||||||
|
j += 1
|
||||||
|
continue
|
||||||
|
# 检查是否是相同缩进的 buffer 调用
|
||||||
|
if re.match(r'\s+buffer\.(writeln|write)\(', next_line):
|
||||||
|
next_indent = len(next_line) - len(next_line.lstrip())
|
||||||
|
if next_indent == indent:
|
||||||
|
buffer_calls.append(next_line)
|
||||||
|
j += 1
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
# 如果有多个连续的 buffer 调用,转换为级联
|
||||||
|
if len(buffer_calls) >= 2:
|
||||||
|
# 第一行改为 buffer
|
||||||
|
first_line = buffer_calls[0]
|
||||||
|
indent_str = ' ' * indent
|
||||||
|
|
||||||
|
# 提取第一个调用
|
||||||
|
match = re.match(r'(\s+)buffer\.(writeln|write)\((.*)\);?', first_line)
|
||||||
|
if match:
|
||||||
|
method = match.group(2)
|
||||||
|
args = match.group(3)
|
||||||
|
|
||||||
|
result.append(f'{indent_str}buffer')
|
||||||
|
result.append(f'{indent_str} ..{method}({args})')
|
||||||
|
|
||||||
|
# 处理后续调用
|
||||||
|
for call in buffer_calls[1:]:
|
||||||
|
match = re.match(r'\s+buffer\.(writeln|write)\((.*)\);?', call)
|
||||||
|
if match:
|
||||||
|
method = match.group(1)
|
||||||
|
args = match.group(2)
|
||||||
|
result.append(f'{indent_str} ..{method}({args})')
|
||||||
|
|
||||||
|
# 添加分号
|
||||||
|
result[-1] = result[-1].rstrip() + ';'
|
||||||
|
|
||||||
|
i = j
|
||||||
|
continue
|
||||||
|
|
||||||
|
result.append(line)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
return '\n'.join(result)
|
||||||
|
|
||||||
|
|
||||||
|
def process_file(file_path: Path) -> bool:
|
||||||
|
"""处理单个文件"""
|
||||||
|
try:
|
||||||
|
content = file_path.read_text(encoding='utf-8')
|
||||||
|
fixed_content = fix_cascade_invocations(content)
|
||||||
|
|
||||||
|
if content != fixed_content:
|
||||||
|
file_path.write_text(fixed_content, encoding='utf-8')
|
||||||
|
print(f'✓ Fixed: {file_path}')
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f'- Skipped: {file_path} (no changes needed)')
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f'✗ Error processing {file_path}: {e}')
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函数"""
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print('Usage: python fix_cascades.py <file_or_directory>')
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
path = Path(sys.argv[1])
|
||||||
|
|
||||||
|
if not path.exists():
|
||||||
|
print(f'Error: {path} does not exist')
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
files_to_process = []
|
||||||
|
|
||||||
|
if path.is_file():
|
||||||
|
files_to_process = [path]
|
||||||
|
else:
|
||||||
|
# 递归查找所有 .dart 文件
|
||||||
|
files_to_process = list(path.rglob('*.dart'))
|
||||||
|
|
||||||
|
print(f'Processing {len(files_to_process)} files...\n')
|
||||||
|
|
||||||
|
fixed_count = 0
|
||||||
|
for file_path in files_to_process:
|
||||||
|
if process_file(file_path):
|
||||||
|
fixed_count += 1
|
||||||
|
|
||||||
|
print(f'\n✓ Fixed {fixed_count} files')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import '../core/exceptions.dart';
|
import 'package:swagger_generator_flutter/core/exceptions.dart';
|
||||||
|
|
||||||
/// 命令基类
|
/// 命令基类
|
||||||
/// 实现命令模式,提供统一的命令接口
|
/// 实现命令模式,提供统一的命令接口
|
||||||
|
|
@ -125,36 +125,34 @@ abstract class BaseCommand {
|
||||||
|
|
||||||
/// 命令选项
|
/// 命令选项
|
||||||
class CommandOption {
|
class CommandOption {
|
||||||
|
const CommandOption({
|
||||||
|
required this.name,
|
||||||
|
required this.description,
|
||||||
|
this.shortName,
|
||||||
|
this.required = false,
|
||||||
|
this.defaultValue,
|
||||||
|
this.type = OptionType.flag,
|
||||||
|
});
|
||||||
final String name;
|
final String name;
|
||||||
final String? shortName;
|
final String? shortName;
|
||||||
final String description;
|
final String description;
|
||||||
final bool required;
|
final bool required;
|
||||||
final dynamic defaultValue;
|
final dynamic defaultValue;
|
||||||
final OptionType type;
|
final OptionType type;
|
||||||
|
|
||||||
const CommandOption({
|
|
||||||
required this.name,
|
|
||||||
this.shortName,
|
|
||||||
required this.description,
|
|
||||||
this.required = false,
|
|
||||||
this.defaultValue,
|
|
||||||
this.type = OptionType.flag,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 命令参数
|
/// 命令参数
|
||||||
class CommandArgument {
|
class CommandArgument {
|
||||||
final String name;
|
|
||||||
final String description;
|
|
||||||
final bool required;
|
|
||||||
final dynamic defaultValue;
|
|
||||||
|
|
||||||
const CommandArgument({
|
const CommandArgument({
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.description,
|
required this.description,
|
||||||
this.required = true,
|
this.required = true,
|
||||||
this.defaultValue,
|
this.defaultValue,
|
||||||
});
|
});
|
||||||
|
final String name;
|
||||||
|
final String description;
|
||||||
|
final bool required;
|
||||||
|
final dynamic defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 选项类型
|
/// 选项类型
|
||||||
|
|
@ -204,16 +202,15 @@ class ParsedArguments {
|
||||||
|
|
||||||
/// 参数解析器
|
/// 参数解析器
|
||||||
class ArgumentParser {
|
class ArgumentParser {
|
||||||
final BaseCommand command;
|
|
||||||
|
|
||||||
ArgumentParser(this.command);
|
ArgumentParser(this.command);
|
||||||
|
final BaseCommand command;
|
||||||
|
|
||||||
/// 解析参数
|
/// 解析参数
|
||||||
ParsedArguments parse(List<String> args) {
|
ParsedArguments parse(List<String> args) {
|
||||||
final result = ParsedArguments();
|
final result = ParsedArguments();
|
||||||
final argQueue = List<String>.from(args);
|
final argQueue = List<String>.from(args);
|
||||||
|
|
||||||
int argumentIndex = 0;
|
var argumentIndex = 0;
|
||||||
|
|
||||||
while (argQueue.isNotEmpty) {
|
while (argQueue.isNotEmpty) {
|
||||||
final current = argQueue.removeAt(0);
|
final current = argQueue.removeAt(0);
|
||||||
|
|
@ -343,11 +340,10 @@ class ArgumentParser {
|
||||||
|
|
||||||
/// 命令异常
|
/// 命令异常
|
||||||
class CommandException implements Exception {
|
class CommandException implements Exception {
|
||||||
|
const CommandException(this.message, {this.details});
|
||||||
final String message;
|
final String message;
|
||||||
final String? details;
|
final String? details;
|
||||||
|
|
||||||
const CommandException(this.message, {this.details});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'CommandException: $message${details != null ? ' ($details)' : ''}';
|
return 'CommandException: $message${details != null ? ' ($details)' : ''}';
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
|
import 'package:swagger_generator_flutter/commands/base_command.dart';
|
||||||
import '../core/config.dart';
|
import 'package:swagger_generator_flutter/core/config.dart';
|
||||||
import '../core/config_loader.dart';
|
import 'package:swagger_generator_flutter/core/config_loader.dart';
|
||||||
import '../core/models.dart';
|
import 'package:swagger_generator_flutter/core/models.dart';
|
||||||
import '../generators/model_code_generator.dart';
|
import 'package:swagger_generator_flutter/generators/model_code_generator.dart';
|
||||||
import '../generators/retrofit_api_generator.dart';
|
import 'package:swagger_generator_flutter/generators/retrofit_api_generator.dart';
|
||||||
import '../parsers/swagger_data_parser.dart';
|
import 'package:swagger_generator_flutter/parsers/swagger_data_parser.dart';
|
||||||
import '../utils/file_utils.dart';
|
import 'package:swagger_generator_flutter/utils/file_utils.dart';
|
||||||
import 'base_command.dart';
|
|
||||||
|
|
||||||
/// Generate命令
|
/// Generate命令
|
||||||
/// 用于生成各种代码文件
|
/// 用于生成各种代码文件
|
||||||
|
|
@ -29,31 +28,21 @@ class GenerateCommand extends BaseCommand {
|
||||||
name: 'models',
|
name: 'models',
|
||||||
shortName: 'm',
|
shortName: 'm',
|
||||||
description: '生成数据模型',
|
description: '生成数据模型',
|
||||||
type: OptionType.flag,
|
|
||||||
),
|
),
|
||||||
const CommandOption(
|
const CommandOption(
|
||||||
name: 'api',
|
name: 'api',
|
||||||
shortName: 'r',
|
shortName: 'r',
|
||||||
description: '生成Retrofit风格API接口',
|
description: '生成Retrofit风格API接口',
|
||||||
type: OptionType.flag,
|
|
||||||
),
|
),
|
||||||
const CommandOption(
|
const CommandOption(
|
||||||
name: 'split-by-tags',
|
name: 'split-by-tags',
|
||||||
shortName: 't',
|
shortName: 't',
|
||||||
description: '按tags分组生成多个API文件(默认启用)',
|
description: '按tags分组生成多个API文件(默认启用)',
|
||||||
type: OptionType.flag,
|
|
||||||
),
|
),
|
||||||
const CommandOption(
|
const CommandOption(
|
||||||
name: 'all',
|
name: 'all',
|
||||||
shortName: 'a',
|
shortName: 'a',
|
||||||
description: '生成所有文件(默认)',
|
description: '生成所有文件(默认)',
|
||||||
type: OptionType.flag,
|
|
||||||
),
|
|
||||||
const CommandOption(
|
|
||||||
name: 'simple',
|
|
||||||
shortName: 's',
|
|
||||||
description: '使用简洁版模型生成',
|
|
||||||
type: OptionType.flag,
|
|
||||||
),
|
),
|
||||||
const CommandOption(
|
const CommandOption(
|
||||||
name: 'output-dir',
|
name: 'output-dir',
|
||||||
|
|
@ -96,7 +85,7 @@ class GenerateCommand extends BaseCommand {
|
||||||
final urls = SwaggerConfig.swaggerJsonUrls;
|
final urls = SwaggerConfig.swaggerJsonUrls;
|
||||||
progress('URL 处理顺序: ${urls.join(" -> ")}');
|
progress('URL 处理顺序: ${urls.join(" -> ")}');
|
||||||
|
|
||||||
for (int i = 0; i < urls.length; i++) {
|
for (var i = 0; i < urls.length; i++) {
|
||||||
final url = urls[i];
|
final url = urls[i];
|
||||||
progress(' [${i + 1}/${urls.length}] 正在解析: $url');
|
progress(' [${i + 1}/${urls.length}] 正在解析: $url');
|
||||||
|
|
||||||
|
|
@ -124,7 +113,8 @@ class GenerateCommand extends BaseCommand {
|
||||||
|
|
||||||
if (overlappingModels.isNotEmpty) {
|
if (overlappingModels.isNotEmpty) {
|
||||||
progress(
|
progress(
|
||||||
' 发现 ${overlappingModels.length} 个同名模型将被覆盖: ${overlappingModels.take(5).join(", ")}${overlappingModels.length > 5 ? "..." : ""}');
|
' 发现 ${overlappingModels.length} 个同名模型将被覆盖: ${overlappingModels.take(5).join(", ")}${overlappingModels.length > 5 ? "..." : ""}',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
mergedDocument = SwaggerDocument(
|
mergedDocument = SwaggerDocument(
|
||||||
|
|
@ -140,12 +130,14 @@ class GenerateCommand extends BaseCommand {
|
||||||
|
|
||||||
final afterModelCount = mergedDocument.models.length;
|
final afterModelCount = mergedDocument.models.length;
|
||||||
progress(
|
progress(
|
||||||
' 合并后: $beforeModelCount + $currentModelCount -> $afterModelCount 个模型');
|
' 合并后: $beforeModelCount + $currentModelCount -> $afterModelCount 个模型',
|
||||||
|
);
|
||||||
|
|
||||||
// 验证同名模型是否被正确覆盖
|
// 验证同名模型是否被正确覆盖
|
||||||
if (overlappingModels.isNotEmpty) {
|
if (overlappingModels.isNotEmpty) {
|
||||||
progress(
|
progress(
|
||||||
' 同名模型列表: ${overlappingModels.take(10).join(", ")}${overlappingModels.length > 10 ? "..." : ""}');
|
' 同名模型列表: ${overlappingModels.take(10).join(", ")}${overlappingModels.length > 10 ? "..." : ""}',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -181,15 +173,12 @@ class GenerateCommand extends BaseCommand {
|
||||||
await FileUtils.ensureDirectoryExists(apiDir);
|
await FileUtils.ensureDirectoryExists(apiDir);
|
||||||
await FileUtils.ensureDirectoryExists(modelsDir);
|
await FileUtils.ensureDirectoryExists(modelsDir);
|
||||||
|
|
||||||
int generatedFiles = 0;
|
var generatedFiles = 0;
|
||||||
|
|
||||||
// 生成模型代码
|
// 生成模型代码
|
||||||
if (options.generateModels) {
|
if (options.generateModels) {
|
||||||
progress('正在生成数据模型...');
|
progress('正在生成数据模型...');
|
||||||
final generator = ModelCodeGenerator(
|
final generator = ModelCodeGenerator(document);
|
||||||
document,
|
|
||||||
useSimpleModels: options.useSimpleModels,
|
|
||||||
);
|
|
||||||
|
|
||||||
await FileUtils.ensureDirectoryExists(modelsDir);
|
await FileUtils.ensureDirectoryExists(modelsDir);
|
||||||
|
|
||||||
|
|
@ -224,7 +213,8 @@ class GenerateCommand extends BaseCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
progress(
|
progress(
|
||||||
'检测到 ${pathsByVersion.keys.length} 个版本: ${pathsByVersion.keys.join(", ")}');
|
'检测到 ${pathsByVersion.keys.length} 个版本: ${pathsByVersion.keys.join(", ")}',
|
||||||
|
);
|
||||||
|
|
||||||
// ✨ 按版本分别生成 API 文件
|
// ✨ 按版本分别生成 API 文件
|
||||||
final versionedFiles = <String, Map<String, String>>{};
|
final versionedFiles = <String, Map<String, String>>{};
|
||||||
|
|
@ -238,9 +228,9 @@ class GenerateCommand extends BaseCommand {
|
||||||
// 筛选出当前版本实际使用的 controllers
|
// 筛选出当前版本实际使用的 controllers
|
||||||
final versionTags = versionPaths.expand((p) => p.tags).toSet();
|
final versionTags = versionPaths.expand((p) => p.tags).toSet();
|
||||||
final versionControllers = {
|
final versionControllers = {
|
||||||
for (var tag in versionTags)
|
for (final tag in versionTags)
|
||||||
if (document.controllers.containsKey(tag))
|
if (document.controllers.containsKey(tag))
|
||||||
tag: document.controllers[tag]!
|
tag: document.controllers[tag]!,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 创建该版本的临时文档
|
// 创建该版本的临时文档
|
||||||
|
|
@ -248,7 +238,7 @@ class GenerateCommand extends BaseCommand {
|
||||||
title: document.title,
|
title: document.title,
|
||||||
description: document.description,
|
description: document.description,
|
||||||
version: document.version,
|
version: document.version,
|
||||||
paths: {for (var p in versionPaths) p.path: p},
|
paths: {for (final p in versionPaths) p.path: p},
|
||||||
models: document.models,
|
models: document.models,
|
||||||
controllers: versionControllers, // 使用过滤后的 controllers
|
controllers: versionControllers, // 使用过滤后的 controllers
|
||||||
);
|
);
|
||||||
|
|
@ -257,10 +247,6 @@ class GenerateCommand extends BaseCommand {
|
||||||
final apiClientClassName = ConfigLoader.getApiClientClassName();
|
final apiClientClassName = ConfigLoader.getApiClientClassName();
|
||||||
final generator = RetrofitApiGenerator(
|
final generator = RetrofitApiGenerator(
|
||||||
className: apiClientClassName,
|
className: apiClientClassName,
|
||||||
useRetrofit: true,
|
|
||||||
useDio: true,
|
|
||||||
splitByTags: true,
|
|
||||||
versionedApi: true,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
generator.document = versionDocument;
|
generator.document = versionDocument;
|
||||||
|
|
@ -337,8 +323,6 @@ class GenerateCommand extends BaseCommand {
|
||||||
// 生成参数实体类文件(使用最后一个生成器)
|
// 生成参数实体类文件(使用最后一个生成器)
|
||||||
final lastGenerator = RetrofitApiGenerator(
|
final lastGenerator = RetrofitApiGenerator(
|
||||||
className: apiClientClassName,
|
className: apiClientClassName,
|
||||||
useRetrofit: true,
|
|
||||||
useDio: true,
|
|
||||||
);
|
);
|
||||||
lastGenerator.document = document;
|
lastGenerator.document = document;
|
||||||
lastGenerator.ensureParameterEntitiesGenerated();
|
lastGenerator.ensureParameterEntitiesGenerated();
|
||||||
|
|
@ -459,7 +443,6 @@ class GenerateCommand extends BaseCommand {
|
||||||
generateApi: hasAnyFlag
|
generateApi: hasAnyFlag
|
||||||
? (args.getOption<bool>('api') ?? false)
|
? (args.getOption<bool>('api') ?? false)
|
||||||
: (args.getOption<bool>('all') ?? true),
|
: (args.getOption<bool>('all') ?? true),
|
||||||
useSimpleModels: args.getOption<bool>('simple') ?? false,
|
|
||||||
splitByTags: splitByTags,
|
splitByTags: splitByTags,
|
||||||
includedTags: includedTags,
|
includedTags: includedTags,
|
||||||
excludedTags: excludedTags,
|
excludedTags: excludedTags,
|
||||||
|
|
@ -549,13 +532,13 @@ class GenerateCommand extends BaseCommand {
|
||||||
buffer.writeln('// 基于 Swagger API 文档: ');
|
buffer.writeln('// 基于 Swagger API 文档: ');
|
||||||
buffer.writeln('// 由 xy_swagger_generator by max 生成');
|
buffer.writeln('// 由 xy_swagger_generator by max 生成');
|
||||||
buffer.writeln('// Copyright (C) 2025 YuanXuan. All rights reserved.');
|
buffer.writeln('// Copyright (C) 2025 YuanXuan. All rights reserved.');
|
||||||
buffer.writeln('');
|
buffer.writeln();
|
||||||
buffer.writeln('');
|
buffer.writeln();
|
||||||
buffer.writeln('library;');
|
buffer.writeln('library;');
|
||||||
buffer.writeln('');
|
buffer.writeln();
|
||||||
|
|
||||||
for (final fileName in dartFiles) {
|
for (final fileName in dartFiles) {
|
||||||
buffer.writeln('export \'$fileName\';');
|
buffer.writeln("export '$fileName';");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 写入文件
|
// 写入文件
|
||||||
|
|
@ -576,29 +559,29 @@ class GenerateCommand extends BaseCommand {
|
||||||
buffer.writeln('// 基于 Swagger API 文档: ');
|
buffer.writeln('// 基于 Swagger API 文档: ');
|
||||||
buffer.writeln('// 由 xy_swagger_generator by max 生成');
|
buffer.writeln('// 由 xy_swagger_generator by max 生成');
|
||||||
buffer.writeln('// Copyright (C) 2025 YuanXuan. All rights reserved.');
|
buffer.writeln('// Copyright (C) 2025 YuanXuan. All rights reserved.');
|
||||||
buffer.writeln('');
|
buffer.writeln();
|
||||||
buffer.writeln('library;');
|
buffer.writeln('library;');
|
||||||
buffer.writeln('');
|
buffer.writeln();
|
||||||
|
|
||||||
// 导出 base_result 和 base_page_result(如果配置了)
|
// 导出 base_result 和 base_page_result(如果配置了)
|
||||||
final baseResultImport = SwaggerConfig.baseResultImport;
|
final baseResultImport = SwaggerConfig.baseResultImport;
|
||||||
final basePageResultImport = SwaggerConfig.basePageResultImport;
|
final basePageResultImport = SwaggerConfig.basePageResultImport;
|
||||||
|
|
||||||
if (baseResultImport.isNotEmpty) {
|
if (baseResultImport.isNotEmpty) {
|
||||||
buffer.writeln('export \'$baseResultImport\';');
|
buffer.writeln("export '$baseResultImport';");
|
||||||
}
|
}
|
||||||
if (basePageResultImport.isNotEmpty) {
|
if (basePageResultImport.isNotEmpty) {
|
||||||
buffer.writeln('export \'$basePageResultImport\';');
|
buffer.writeln("export '$basePageResultImport';");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((baseResultImport.isNotEmpty || basePageResultImport.isNotEmpty) &&
|
if ((baseResultImport.isNotEmpty || basePageResultImport.isNotEmpty) &&
|
||||||
fileNames.isNotEmpty) {
|
fileNames.isNotEmpty) {
|
||||||
buffer.writeln('');
|
buffer.writeln();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 导出所有文件
|
// 导出所有文件
|
||||||
for (final fileName in fileNames) {
|
for (final fileName in fileNames) {
|
||||||
buffer.writeln('export \'$fileName\';');
|
buffer.writeln("export '$fileName';");
|
||||||
}
|
}
|
||||||
|
|
||||||
return buffer.toString();
|
return buffer.toString();
|
||||||
|
|
@ -608,20 +591,21 @@ class GenerateCommand extends BaseCommand {
|
||||||
void _generateSummary(SwaggerDocument document, String outputDir) {
|
void _generateSummary(SwaggerDocument document, String outputDir) {
|
||||||
final summary = StringBuffer();
|
final summary = StringBuffer();
|
||||||
summary.writeln('# 代码生成摘要');
|
summary.writeln('# 代码生成摘要');
|
||||||
summary.writeln('');
|
summary.writeln();
|
||||||
summary.writeln('**API标题**: ${document.title}');
|
summary.writeln('**API标题**: ${document.title}');
|
||||||
summary.writeln('**API版本**: ${document.version}');
|
summary.writeln('**API版本**: ${document.version}');
|
||||||
summary.writeln('**生成时间**: ${DateTime.now().toIso8601String()}');
|
summary.writeln('**生成时间**: ${DateTime.now().toIso8601String()}');
|
||||||
summary.writeln('');
|
summary.writeln();
|
||||||
summary.writeln('## 统计信息');
|
summary.writeln('## 统计信息');
|
||||||
summary.writeln('- 控制器数量: ${document.controllers.length}');
|
summary.writeln('- 控制器数量: ${document.controllers.length}');
|
||||||
summary.writeln('- API路径数量: ${document.paths.length}');
|
summary.writeln('- API路径数量: ${document.paths.length}');
|
||||||
summary.writeln('- 数据模型数量: ${document.models.length}');
|
summary.writeln('- 数据模型数量: ${document.models.length}');
|
||||||
summary.writeln('');
|
summary.writeln();
|
||||||
summary.writeln('## 控制器列表');
|
summary.writeln('## 控制器列表');
|
||||||
document.controllers.forEach((name, controller) {
|
document.controllers.forEach((name, controller) {
|
||||||
summary.writeln(
|
summary.writeln(
|
||||||
'- **$name**: ${controller.description} (${controller.paths.length} 个路径)');
|
'- **$name**: ${controller.description} (${controller.paths.length} 个路径)',
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
FileUtils.writeFile('$outputDir/SUMMARY.md', summary.toString());
|
FileUtils.writeFile('$outputDir/SUMMARY.md', summary.toString());
|
||||||
|
|
@ -644,7 +628,7 @@ class GenerateCommand extends BaseCommand {
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// 如果正则表达式无效,使用默认模式
|
// 如果正则表达式无效,使用默认模式
|
||||||
final defaultPattern = r'/api/v(\d+)/';
|
const defaultPattern = r'/api/v(\d+)/';
|
||||||
final versionMatch = RegExp(defaultPattern).firstMatch(path);
|
final versionMatch = RegExp(defaultPattern).firstMatch(path);
|
||||||
if (versionMatch != null) {
|
if (versionMatch != null) {
|
||||||
return 'v${versionMatch.group(1)}';
|
return 'v${versionMatch.group(1)}';
|
||||||
|
|
@ -700,7 +684,8 @@ class GenerateCommand extends BaseCommand {
|
||||||
|
|
||||||
/// 生成版本化的 ApiClient
|
/// 生成版本化的 ApiClient
|
||||||
String _generateVersionedApiClient(
|
String _generateVersionedApiClient(
|
||||||
Map<String, Map<String, String>> versionedFiles) {
|
Map<String, Map<String, String>> versionedFiles,
|
||||||
|
) {
|
||||||
final buffer = StringBuffer();
|
final buffer = StringBuffer();
|
||||||
|
|
||||||
// 文件头
|
// 文件头
|
||||||
|
|
@ -709,7 +694,7 @@ class GenerateCommand extends BaseCommand {
|
||||||
buffer.writeln('// 由 xy_swagger_generator by max 生成');
|
buffer.writeln('// 由 xy_swagger_generator by max 生成');
|
||||||
buffer.writeln('// Copyright (C) 2025 YuanXuan. All rights reserved.');
|
buffer.writeln('// Copyright (C) 2025 YuanXuan. All rights reserved.');
|
||||||
buffer.writeln();
|
buffer.writeln();
|
||||||
buffer.writeln('import \'package:dio/dio.dart\';');
|
buffer.writeln("import 'package:dio/dio.dart';");
|
||||||
buffer.writeln();
|
buffer.writeln();
|
||||||
|
|
||||||
// 收集所有 API 类
|
// 收集所有 API 类
|
||||||
|
|
@ -736,9 +721,12 @@ class GenerateCommand extends BaseCommand {
|
||||||
final className = fileName
|
final className = fileName
|
||||||
.replaceAll('.dart', '')
|
.replaceAll('.dart', '')
|
||||||
.split('_')
|
.split('_')
|
||||||
.map((word) =>
|
.map(
|
||||||
word.isEmpty ? '' : (word[0].toUpperCase() + word.substring(1)))
|
(word) => word.isEmpty
|
||||||
.join('');
|
? ''
|
||||||
|
: (word[0].toUpperCase() + word.substring(1)),
|
||||||
|
)
|
||||||
|
.join();
|
||||||
apiClasses[version]!.add(className);
|
apiClasses[version]!.add(className);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -746,7 +734,7 @@ class GenerateCommand extends BaseCommand {
|
||||||
// 导入所有版本的 index.dart
|
// 导入所有版本的 index.dart
|
||||||
final versions = apiClasses.keys.toList()..sort();
|
final versions = apiClasses.keys.toList()..sort();
|
||||||
for (final version in versions) {
|
for (final version in versions) {
|
||||||
buffer.writeln('import \'$version/index.dart\';');
|
buffer.writeln("import '$version/index.dart';");
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.writeln();
|
buffer.writeln();
|
||||||
|
|
@ -768,7 +756,8 @@ class GenerateCommand extends BaseCommand {
|
||||||
for (final className in versionEntry.value) {
|
for (final className in versionEntry.value) {
|
||||||
final suffix = version == 'v1' ? '' : versionUpper;
|
final suffix = version == 'v1' ? '' : versionUpper;
|
||||||
buffer.writeln(
|
buffer.writeln(
|
||||||
' late final $className$suffix _${_toLowerCamelCase(className)}$suffix;');
|
' late final $className$suffix _${_toLowerCamelCase(className)}$suffix;',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -812,7 +801,8 @@ class GenerateCommand extends BaseCommand {
|
||||||
final fieldName = _toLowerCamelCase(className);
|
final fieldName = _toLowerCamelCase(className);
|
||||||
final suffix = version == 'v1' ? '' : versionUpper;
|
final suffix = version == 'v1' ? '' : versionUpper;
|
||||||
buffer.writeln(
|
buffer.writeln(
|
||||||
' $className$suffix get $fieldName$suffix => _$fieldName$suffix;');
|
' $className$suffix get $fieldName$suffix => _$fieldName$suffix;',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
buffer.writeln();
|
buffer.writeln();
|
||||||
}
|
}
|
||||||
|
|
@ -847,7 +837,9 @@ class GenerateCommand extends BaseCommand {
|
||||||
|
|
||||||
/// 生成版本目录的 index.dart
|
/// 生成版本目录的 index.dart
|
||||||
Future<void> _generateVersionIndexFile(
|
Future<void> _generateVersionIndexFile(
|
||||||
String versionDir, List<String> fileNames) async {
|
String versionDir,
|
||||||
|
List<String> fileNames,
|
||||||
|
) async {
|
||||||
final buffer = StringBuffer();
|
final buffer = StringBuffer();
|
||||||
|
|
||||||
// 文件头
|
// 文件头
|
||||||
|
|
@ -859,7 +851,7 @@ class GenerateCommand extends BaseCommand {
|
||||||
// 导出所有 API 文件
|
// 导出所有 API 文件
|
||||||
final sortedFiles = fileNames.toList()..sort();
|
final sortedFiles = fileNames.toList()..sort();
|
||||||
for (final fileName in sortedFiles) {
|
for (final fileName in sortedFiles) {
|
||||||
buffer.writeln('export \'$fileName\';');
|
buffer.writeln("export '$fileName';");
|
||||||
}
|
}
|
||||||
|
|
||||||
final indexPath = '$versionDir/index.dart';
|
final indexPath = '$versionDir/index.dart';
|
||||||
|
|
@ -943,7 +935,7 @@ class GenerateCommand extends BaseCommand {
|
||||||
final filteredControllers = <String, ApiController>{};
|
final filteredControllers = <String, ApiController>{};
|
||||||
for (final entry in document.controllers.entries) {
|
for (final entry in document.controllers.entries) {
|
||||||
final tagName = entry.key;
|
final tagName = entry.key;
|
||||||
bool shouldKeep = true;
|
var shouldKeep = true;
|
||||||
if (hasIncludes && !includedTags.contains(tagName)) {
|
if (hasIncludes && !includedTags.contains(tagName)) {
|
||||||
shouldKeep = false;
|
shouldKeep = false;
|
||||||
}
|
}
|
||||||
|
|
@ -956,7 +948,8 @@ class GenerateCommand extends BaseCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
progress(
|
progress(
|
||||||
' 保留了 ${filteredControllers.length}/${document.controllers.length} 个控制器');
|
' 保留了 ${filteredControllers.length}/${document.controllers.length} 个控制器',
|
||||||
|
);
|
||||||
|
|
||||||
// 返回过滤后的文档
|
// 返回过滤后的文档
|
||||||
return SwaggerDocument(
|
return SwaggerDocument(
|
||||||
|
|
@ -976,8 +969,8 @@ class GenerateCommand extends BaseCommand {
|
||||||
void _collectUsedModels(ApiPath path, Set<String> usedModelNames) {
|
void _collectUsedModels(ApiPath path, Set<String> usedModelNames) {
|
||||||
// 递归地从 schema 中提取模型名称
|
// 递归地从 schema 中提取模型名称
|
||||||
void extractModelsFromSchema(Map<String, dynamic> schema) {
|
void extractModelsFromSchema(Map<String, dynamic> schema) {
|
||||||
if (schema.containsKey('\$ref')) {
|
if (schema.containsKey(r'$ref')) {
|
||||||
final modelName = _extractModelNameFromRef(schema['\$ref']);
|
final modelName = _extractModelNameFromRef(schema[r'$ref'] as String);
|
||||||
if (modelName != null) {
|
if (modelName != null) {
|
||||||
usedModelNames.add(modelName);
|
usedModelNames.add(modelName);
|
||||||
}
|
}
|
||||||
|
|
@ -987,11 +980,11 @@ class GenerateCommand extends BaseCommand {
|
||||||
if (schema.containsKey('type')) {
|
if (schema.containsKey('type')) {
|
||||||
final type = schema['type'];
|
final type = schema['type'];
|
||||||
if (type == 'array' && schema.containsKey('items')) {
|
if (type == 'array' && schema.containsKey('items')) {
|
||||||
extractModelsFromSchema(schema['items']);
|
extractModelsFromSchema(schema['items'] as Map<String, dynamic>);
|
||||||
} else if (type == 'object' && schema.containsKey('properties')) {
|
} else if (type == 'object' && schema.containsKey('properties')) {
|
||||||
final properties = schema['properties'] as Map<String, dynamic>;
|
final properties = schema['properties'] as Map<String, dynamic>;
|
||||||
for (final propSchema in properties.values) {
|
for (final propSchema in properties.values) {
|
||||||
extractModelsFromSchema(propSchema);
|
extractModelsFromSchema(propSchema as Map<String, dynamic>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1000,7 +993,7 @@ class GenerateCommand extends BaseCommand {
|
||||||
if (schema.containsKey(key)) {
|
if (schema.containsKey(key)) {
|
||||||
final subSchemas = schema[key] as List<dynamic>;
|
final subSchemas = schema[key] as List<dynamic>;
|
||||||
for (final subSchema in subSchemas) {
|
for (final subSchema in subSchemas) {
|
||||||
extractModelsFromSchema(subSchema);
|
extractModelsFromSchema(subSchema as Map<String, dynamic>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1035,7 +1028,7 @@ class GenerateCommand extends BaseCommand {
|
||||||
for (final property in model.properties.values) {
|
for (final property in model.properties.values) {
|
||||||
// 使用 reference 字段
|
// 使用 reference 字段
|
||||||
if (property.reference != null &&
|
if (property.reference != null &&
|
||||||
!checkedModels.contains(property.reference!)) {
|
!checkedModels.contains(property.reference)) {
|
||||||
modelsToCheck.add(property.reference!);
|
modelsToCheck.add(property.reference!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1050,7 +1043,7 @@ class GenerateCommand extends BaseCommand {
|
||||||
// 处理嵌套属性中的引用
|
// 处理嵌套属性中的引用
|
||||||
for (final nestedProp in property.nestedProperties.values) {
|
for (final nestedProp in property.nestedProperties.values) {
|
||||||
if (nestedProp.reference != null &&
|
if (nestedProp.reference != null &&
|
||||||
!checkedModels.contains(nestedProp.reference!)) {
|
!checkedModels.contains(nestedProp.reference)) {
|
||||||
modelsToCheck.add(nestedProp.reference!);
|
modelsToCheck.add(nestedProp.reference!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1082,19 +1075,16 @@ class GenerateCommand extends BaseCommand {
|
||||||
|
|
||||||
/// 生成选项
|
/// 生成选项
|
||||||
class GenerateOptions {
|
class GenerateOptions {
|
||||||
final bool generateModels;
|
|
||||||
final bool generateApi;
|
|
||||||
final bool useSimpleModels;
|
|
||||||
final bool splitByTags;
|
|
||||||
final List<String>? includedTags;
|
|
||||||
final List<String>? excludedTags;
|
|
||||||
|
|
||||||
const GenerateOptions({
|
const GenerateOptions({
|
||||||
required this.generateModels,
|
required this.generateModels,
|
||||||
required this.generateApi,
|
required this.generateApi,
|
||||||
required this.useSimpleModels,
|
|
||||||
required this.splitByTags,
|
required this.splitByTags,
|
||||||
this.includedTags,
|
this.includedTags,
|
||||||
this.excludedTags,
|
this.excludedTags,
|
||||||
});
|
});
|
||||||
|
final bool generateModels;
|
||||||
|
final bool generateApi;
|
||||||
|
final bool splitByTags;
|
||||||
|
final List<String>? includedTags;
|
||||||
|
final List<String>? excludedTags;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import 'config_loader.dart';
|
import 'package:swagger_generator_flutter/core/config_loader.dart';
|
||||||
|
|
||||||
/// Swagger配置管理
|
/// Swagger配置管理
|
||||||
/// 集中管理所有Swagger相关的配置项
|
/// 集中管理所有Swagger相关的配置项
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
|
import 'package:swagger_generator_flutter/core/config.dart';
|
||||||
import 'package:yaml/yaml.dart';
|
import 'package:yaml/yaml.dart';
|
||||||
import 'config.dart';
|
|
||||||
|
|
||||||
/// 配置加载器
|
/// 配置加载器
|
||||||
/// 负责从 generator_config.yaml 文件读取配置
|
/// 负责从 generator_config.yaml 文件读取配置
|
||||||
|
|
@ -74,7 +75,7 @@ class ConfigLoader {
|
||||||
/// 从当前工作目录向上查找 generator_config.yaml
|
/// 从当前工作目录向上查找 generator_config.yaml
|
||||||
static String? _findConfigFile() {
|
static String? _findConfigFile() {
|
||||||
var currentDir = Directory.current;
|
var currentDir = Directory.current;
|
||||||
final maxDepth = 10; // 最多向上查找 10 层
|
const maxDepth = 10; // 最多向上查找 10 层
|
||||||
var depth = 0;
|
var depth = 0;
|
||||||
|
|
||||||
while (depth < maxDepth) {
|
while (depth < maxDepth) {
|
||||||
|
|
@ -200,11 +201,11 @@ class ConfigLoader {
|
||||||
final ignoredDirs = getIgnoredDirectories(config);
|
final ignoredDirs = getIgnoredDirectories(config);
|
||||||
if (ignoredDirs.isNotEmpty) {
|
if (ignoredDirs.isNotEmpty) {
|
||||||
// 标准化路径,统一使用正斜杠
|
// 标准化路径,统一使用正斜杠
|
||||||
final normalizedPath = filePath.replaceAll('\\', '/');
|
final normalizedPath = filePath.replaceAll(r'\', '/');
|
||||||
|
|
||||||
for (final ignoredDir in ignoredDirs) {
|
for (final ignoredDir in ignoredDirs) {
|
||||||
// 标准化忽略目录路径
|
// 标准化忽略目录路径
|
||||||
var normalizedDir = ignoredDir.toString().replaceAll('\\', '/');
|
var normalizedDir = ignoredDir.replaceAll(r'\', '/');
|
||||||
|
|
||||||
// 移除末尾的斜杠(如果有)
|
// 移除末尾的斜杠(如果有)
|
||||||
if (normalizedDir.endsWith('/')) {
|
if (normalizedDir.endsWith('/')) {
|
||||||
|
|
@ -236,7 +237,7 @@ class ConfigLoader {
|
||||||
final fileName = path.basename(filePath);
|
final fileName = path.basename(filePath);
|
||||||
|
|
||||||
for (final ignoredFile in ignoredFiles) {
|
for (final ignoredFile in ignoredFiles) {
|
||||||
final ignoredFileName = ignoredFile.toString();
|
final ignoredFileName = ignoredFile;
|
||||||
|
|
||||||
// 精确匹配文件名
|
// 精确匹配文件名
|
||||||
if (fileName == ignoredFileName) {
|
if (fileName == ignoredFileName) {
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,14 @@ extension ErrorCategoryExtension on ErrorCategory {
|
||||||
|
|
||||||
/// 错误位置信息
|
/// 错误位置信息
|
||||||
class ErrorLocation {
|
class ErrorLocation {
|
||||||
|
const ErrorLocation({
|
||||||
|
required this.jsonPath,
|
||||||
|
this.line,
|
||||||
|
this.column,
|
||||||
|
this.offset,
|
||||||
|
this.snippet,
|
||||||
|
});
|
||||||
|
|
||||||
/// JSON 路径(如 "paths./users.get.responses.200")
|
/// JSON 路径(如 "paths./users.get.responses.200")
|
||||||
final String jsonPath;
|
final String jsonPath;
|
||||||
|
|
||||||
|
|
@ -92,14 +100,6 @@ class ErrorLocation {
|
||||||
/// 相关的 JSON 片段
|
/// 相关的 JSON 片段
|
||||||
final String? snippet;
|
final String? snippet;
|
||||||
|
|
||||||
const ErrorLocation({
|
|
||||||
required this.jsonPath,
|
|
||||||
this.line,
|
|
||||||
this.column,
|
|
||||||
this.offset,
|
|
||||||
this.snippet,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
final buffer = StringBuffer();
|
final buffer = StringBuffer();
|
||||||
|
|
@ -119,6 +119,13 @@ class ErrorLocation {
|
||||||
|
|
||||||
/// 修复建议
|
/// 修复建议
|
||||||
class FixSuggestion {
|
class FixSuggestion {
|
||||||
|
const FixSuggestion({
|
||||||
|
required this.description,
|
||||||
|
this.codeExample,
|
||||||
|
this.documentationUrl,
|
||||||
|
this.autoFix,
|
||||||
|
});
|
||||||
|
|
||||||
/// 建议描述
|
/// 建议描述
|
||||||
final String description;
|
final String description;
|
||||||
|
|
||||||
|
|
@ -130,17 +137,22 @@ class FixSuggestion {
|
||||||
|
|
||||||
/// 自动修复函数(如果支持)
|
/// 自动修复函数(如果支持)
|
||||||
final String Function(String original)? autoFix;
|
final String Function(String original)? autoFix;
|
||||||
|
|
||||||
const FixSuggestion({
|
|
||||||
required this.description,
|
|
||||||
this.codeExample,
|
|
||||||
this.documentationUrl,
|
|
||||||
this.autoFix,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 详细错误报告
|
/// 详细错误报告
|
||||||
class DetailedError {
|
class DetailedError {
|
||||||
|
DetailedError({
|
||||||
|
required this.id,
|
||||||
|
required this.title,
|
||||||
|
required this.description,
|
||||||
|
required this.severity,
|
||||||
|
required this.category,
|
||||||
|
required this.location,
|
||||||
|
this.suggestions = const [],
|
||||||
|
this.relatedErrors = const [],
|
||||||
|
DateTime? timestamp,
|
||||||
|
}) : timestamp = timestamp ?? DateTime.now();
|
||||||
|
|
||||||
/// 错误 ID(用于查找和分类)
|
/// 错误 ID(用于查找和分类)
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
|
|
@ -168,18 +180,6 @@ class DetailedError {
|
||||||
/// 错误发生时间
|
/// 错误发生时间
|
||||||
final DateTime timestamp;
|
final DateTime timestamp;
|
||||||
|
|
||||||
DetailedError({
|
|
||||||
required this.id,
|
|
||||||
required this.title,
|
|
||||||
required this.description,
|
|
||||||
required this.severity,
|
|
||||||
required this.category,
|
|
||||||
required this.location,
|
|
||||||
this.suggestions = const [],
|
|
||||||
this.relatedErrors = const [],
|
|
||||||
DateTime? timestamp,
|
|
||||||
}) : timestamp = timestamp ?? DateTime.now();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
final buffer = StringBuffer();
|
final buffer = StringBuffer();
|
||||||
|
|
@ -205,7 +205,7 @@ class DetailedError {
|
||||||
// 修复建议
|
// 修复建议
|
||||||
if (suggestions.isNotEmpty) {
|
if (suggestions.isNotEmpty) {
|
||||||
buffer.writeln('Suggestions:');
|
buffer.writeln('Suggestions:');
|
||||||
for (int i = 0; i < suggestions.length; i++) {
|
for (var i = 0; i < suggestions.length; i++) {
|
||||||
final suggestion = suggestions[i];
|
final suggestion = suggestions[i];
|
||||||
buffer.writeln(' ${i + 1}. ${suggestion.description}');
|
buffer.writeln(' ${i + 1}. ${suggestion.description}');
|
||||||
|
|
||||||
|
|
@ -232,11 +232,10 @@ class DetailedError {
|
||||||
|
|
||||||
/// 错误报告器
|
/// 错误报告器
|
||||||
class ErrorReporter {
|
class ErrorReporter {
|
||||||
|
ErrorReporter();
|
||||||
final List<DetailedError> _errors = [];
|
final List<DetailedError> _errors = [];
|
||||||
final Map<String, int> _errorCounts = {};
|
final Map<String, int> _errorCounts = {};
|
||||||
|
|
||||||
ErrorReporter();
|
|
||||||
|
|
||||||
/// 添加错误
|
/// 添加错误
|
||||||
void addError(DetailedError error) {
|
void addError(DetailedError error) {
|
||||||
_errors.add(error);
|
_errors.add(error);
|
||||||
|
|
@ -306,9 +305,11 @@ class ErrorReporter {
|
||||||
_errors.any((e) => e.severity == ErrorSeverity.critical);
|
_errors.any((e) => e.severity == ErrorSeverity.critical);
|
||||||
|
|
||||||
/// 检查是否有错误(不包括警告和信息)
|
/// 检查是否有错误(不包括警告和信息)
|
||||||
bool get hasErrorsOrCritical => _errors.any((e) =>
|
bool get hasErrorsOrCritical => _errors.any(
|
||||||
e.severity == ErrorSeverity.error ||
|
(e) =>
|
||||||
e.severity == ErrorSeverity.critical);
|
e.severity == ErrorSeverity.error ||
|
||||||
|
e.severity == ErrorSeverity.critical,
|
||||||
|
);
|
||||||
|
|
||||||
/// 清除所有错误
|
/// 清除所有错误
|
||||||
void clear() {
|
void clear() {
|
||||||
|
|
@ -360,7 +361,9 @@ class ErrorReporter {
|
||||||
|
|
||||||
/// 按类别生成报告
|
/// 按类别生成报告
|
||||||
void _generateReportByCategory(
|
void _generateReportByCategory(
|
||||||
StringBuffer buffer, List<DetailedError> errors) {
|
StringBuffer buffer,
|
||||||
|
List<DetailedError> errors,
|
||||||
|
) {
|
||||||
final errorsByCategory = <ErrorCategory, List<DetailedError>>{};
|
final errorsByCategory = <ErrorCategory, List<DetailedError>>{};
|
||||||
|
|
||||||
for (final error in errors) {
|
for (final error in errors) {
|
||||||
|
|
@ -383,7 +386,7 @@ class ErrorReporter {
|
||||||
buffer.writeln('🔍 Detailed Error Report');
|
buffer.writeln('🔍 Detailed Error Report');
|
||||||
buffer.writeln('=' * 50);
|
buffer.writeln('=' * 50);
|
||||||
|
|
||||||
for (int i = 0; i < errors.length; i++) {
|
for (var i = 0; i < errors.length; i++) {
|
||||||
buffer.writeln('Error ${i + 1}/${errors.length}:');
|
buffer.writeln('Error ${i + 1}/${errors.length}:');
|
||||||
buffer.writeln(errors[i].toString());
|
buffer.writeln(errors[i].toString());
|
||||||
|
|
||||||
|
|
@ -402,28 +405,32 @@ class ErrorReporter {
|
||||||
'by_severity': getErrorStatistics().map((k, v) => MapEntry(k.name, v)),
|
'by_severity': getErrorStatistics().map((k, v) => MapEntry(k.name, v)),
|
||||||
},
|
},
|
||||||
'errors': _errors
|
'errors': _errors
|
||||||
.map((error) => {
|
.map(
|
||||||
'id': error.id,
|
(error) => {
|
||||||
'title': error.title,
|
'id': error.id,
|
||||||
'description': error.description,
|
'title': error.title,
|
||||||
'severity': error.severity.name,
|
'description': error.description,
|
||||||
'category': error.category.name,
|
'severity': error.severity.name,
|
||||||
'location': {
|
'category': error.category.name,
|
||||||
'json_path': error.location.jsonPath,
|
'location': {
|
||||||
'line': error.location.line,
|
'json_path': error.location.jsonPath,
|
||||||
'column': error.location.column,
|
'line': error.location.line,
|
||||||
'snippet': error.location.snippet,
|
'column': error.location.column,
|
||||||
},
|
'snippet': error.location.snippet,
|
||||||
'suggestions': error.suggestions
|
},
|
||||||
.map((suggestion) => {
|
'suggestions': error.suggestions
|
||||||
'description': suggestion.description,
|
.map(
|
||||||
'code_example': suggestion.codeExample,
|
(suggestion) => {
|
||||||
'documentation_url': suggestion.documentationUrl,
|
'description': suggestion.description,
|
||||||
})
|
'code_example': suggestion.codeExample,
|
||||||
.toList(),
|
'documentation_url': suggestion.documentationUrl,
|
||||||
'related_errors': error.relatedErrors,
|
},
|
||||||
'timestamp': error.timestamp.toIso8601String(),
|
)
|
||||||
})
|
.toList(),
|
||||||
|
'related_errors': error.relatedErrors,
|
||||||
|
'timestamp': error.timestamp.toIso8601String(),
|
||||||
|
},
|
||||||
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,10 @@
|
||||||
/// 定义常见的错误模式和修复建议
|
/// 定义常见的错误模式和修复建议
|
||||||
library;
|
library;
|
||||||
|
|
||||||
import 'error_reporter.dart';
|
import 'package:swagger_generator_flutter/core/error_reporter.dart';
|
||||||
|
|
||||||
/// 错误规则定义
|
/// 错误规则定义
|
||||||
class ErrorRule {
|
class ErrorRule {
|
||||||
final String id;
|
|
||||||
final String pattern;
|
|
||||||
final ErrorSeverity severity;
|
|
||||||
final ErrorCategory category;
|
|
||||||
final String title;
|
|
||||||
final String description;
|
|
||||||
final List<FixSuggestion> suggestions;
|
|
||||||
|
|
||||||
const ErrorRule({
|
const ErrorRule({
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.pattern,
|
required this.pattern,
|
||||||
|
|
@ -23,6 +15,13 @@ class ErrorRule {
|
||||||
required this.description,
|
required this.description,
|
||||||
this.suggestions = const [],
|
this.suggestions = const [],
|
||||||
});
|
});
|
||||||
|
final String id;
|
||||||
|
final String pattern;
|
||||||
|
final ErrorSeverity severity;
|
||||||
|
final ErrorCategory category;
|
||||||
|
final String title;
|
||||||
|
final String description;
|
||||||
|
final List<FixSuggestion> suggestions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// OpenAPI 错误规则库
|
/// OpenAPI 错误规则库
|
||||||
|
|
@ -200,7 +199,7 @@ class OpenApiErrorRules {
|
||||||
description:
|
description:
|
||||||
'Consider using allOf or breaking the circular dependency',
|
'Consider using allOf or breaking the circular dependency',
|
||||||
codeExample:
|
codeExample:
|
||||||
'"allOf": [{ "\$ref": "#/components/schemas/BaseModel" }]',
|
r'"allOf": [{ "$ref": "#/components/schemas/BaseModel" }]',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -327,7 +326,7 @@ class OpenApiErrorRules {
|
||||||
FixSuggestion(
|
FixSuggestion(
|
||||||
description: 'Consider using composition with allOf',
|
description: 'Consider using composition with allOf',
|
||||||
codeExample:
|
codeExample:
|
||||||
'"allOf": [{ "\$ref": "#/components/schemas/BaseModel" }, { "type": "object", "properties": {...} }]',
|
r'"allOf": [{ "$ref": "#/components/schemas/BaseModel" }, { "type": "object", "properties": {...} }]',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -390,7 +389,9 @@ class OpenApiErrorRules {
|
||||||
|
|
||||||
/// 创建常见错误的快捷方法
|
/// 创建常见错误的快捷方法
|
||||||
static DetailedError createMissingFieldError(
|
static DetailedError createMissingFieldError(
|
||||||
String fieldPath, String fieldName) {
|
String fieldPath,
|
||||||
|
String fieldName,
|
||||||
|
) {
|
||||||
return DetailedError(
|
return DetailedError(
|
||||||
id: 'MISSING_FIELD',
|
id: 'MISSING_FIELD',
|
||||||
title: 'Missing Required Field',
|
title: 'Missing Required Field',
|
||||||
|
|
@ -408,7 +409,10 @@ class OpenApiErrorRules {
|
||||||
}
|
}
|
||||||
|
|
||||||
static DetailedError createInvalidTypeError(
|
static DetailedError createInvalidTypeError(
|
||||||
String fieldPath, String expectedType, String actualType) {
|
String fieldPath,
|
||||||
|
String expectedType,
|
||||||
|
String actualType,
|
||||||
|
) {
|
||||||
return DetailedError(
|
return DetailedError(
|
||||||
id: 'INVALID_TYPE',
|
id: 'INVALID_TYPE',
|
||||||
title: 'Invalid Field Type',
|
title: 'Invalid Field Type',
|
||||||
|
|
@ -427,7 +431,10 @@ class OpenApiErrorRules {
|
||||||
}
|
}
|
||||||
|
|
||||||
static DetailedError createUnknownFieldError(
|
static DetailedError createUnknownFieldError(
|
||||||
String fieldPath, String fieldName, List<String> validFields) {
|
String fieldPath,
|
||||||
|
String fieldName,
|
||||||
|
List<String> validFields,
|
||||||
|
) {
|
||||||
return DetailedError(
|
return DetailedError(
|
||||||
id: 'UNKNOWN_FIELD',
|
id: 'UNKNOWN_FIELD',
|
||||||
title: 'Unknown Field',
|
title: 'Unknown Field',
|
||||||
|
|
@ -446,7 +453,9 @@ class OpenApiErrorRules {
|
||||||
}
|
}
|
||||||
|
|
||||||
static DetailedError createReferenceError(
|
static DetailedError createReferenceError(
|
||||||
String fieldPath, String reference) {
|
String fieldPath,
|
||||||
|
String reference,
|
||||||
|
) {
|
||||||
return DetailedError(
|
return DetailedError(
|
||||||
id: 'INVALID_REFERENCE',
|
id: 'INVALID_REFERENCE',
|
||||||
title: 'Invalid Reference',
|
title: 'Invalid Reference',
|
||||||
|
|
@ -461,7 +470,7 @@ class OpenApiErrorRules {
|
||||||
),
|
),
|
||||||
const FixSuggestion(
|
const FixSuggestion(
|
||||||
description: 'Verify the reference path is correct',
|
description: 'Verify the reference path is correct',
|
||||||
codeExample: '"\$ref": "#/components/schemas/ComponentName"',
|
codeExample: r'"$ref": "#/components/schemas/ComponentName"',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,11 @@ import 'dart:io';
|
||||||
|
|
||||||
/// Swagger CLI 基础异常类
|
/// Swagger CLI 基础异常类
|
||||||
abstract class SwaggerException implements Exception {
|
abstract class SwaggerException implements Exception {
|
||||||
|
SwaggerException(this.message, {this.details}) : timestamp = DateTime.now();
|
||||||
final String message;
|
final String message;
|
||||||
final String? details;
|
final String? details;
|
||||||
final DateTime timestamp;
|
final DateTime timestamp;
|
||||||
|
|
||||||
SwaggerException(this.message, {this.details}) : timestamp = DateTime.now();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
if (details != null) {
|
if (details != null) {
|
||||||
|
|
@ -19,10 +18,6 @@ abstract class SwaggerException implements Exception {
|
||||||
|
|
||||||
/// Swagger解析异常
|
/// Swagger解析异常
|
||||||
class SwaggerParseException extends SwaggerException {
|
class SwaggerParseException extends SwaggerException {
|
||||||
final String? url;
|
|
||||||
final int? statusCode;
|
|
||||||
final String? operation;
|
|
||||||
|
|
||||||
SwaggerParseException(
|
SwaggerParseException(
|
||||||
super.message, {
|
super.message, {
|
||||||
super.details,
|
super.details,
|
||||||
|
|
@ -30,6 +25,9 @@ class SwaggerParseException extends SwaggerException {
|
||||||
this.statusCode,
|
this.statusCode,
|
||||||
this.operation,
|
this.operation,
|
||||||
});
|
});
|
||||||
|
final String? url;
|
||||||
|
final int? statusCode;
|
||||||
|
final String? operation;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
|
@ -58,10 +56,6 @@ class SwaggerParseException extends SwaggerException {
|
||||||
|
|
||||||
/// 代码生成异常
|
/// 代码生成异常
|
||||||
class CodeGenerationException extends SwaggerException {
|
class CodeGenerationException extends SwaggerException {
|
||||||
final String? generatorType;
|
|
||||||
final String? modelName;
|
|
||||||
final String? phase;
|
|
||||||
|
|
||||||
CodeGenerationException(
|
CodeGenerationException(
|
||||||
super.message, {
|
super.message, {
|
||||||
super.details,
|
super.details,
|
||||||
|
|
@ -69,6 +63,9 @@ class CodeGenerationException extends SwaggerException {
|
||||||
this.modelName,
|
this.modelName,
|
||||||
this.phase,
|
this.phase,
|
||||||
});
|
});
|
||||||
|
final String? generatorType;
|
||||||
|
final String? modelName;
|
||||||
|
final String? phase;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
|
@ -97,10 +94,6 @@ class CodeGenerationException extends SwaggerException {
|
||||||
|
|
||||||
/// 文件操作异常
|
/// 文件操作异常
|
||||||
class FileOperationException extends SwaggerException {
|
class FileOperationException extends SwaggerException {
|
||||||
final String? filePath;
|
|
||||||
final String? operation;
|
|
||||||
final int? errorCode;
|
|
||||||
|
|
||||||
FileOperationException(
|
FileOperationException(
|
||||||
super.message, {
|
super.message, {
|
||||||
super.details,
|
super.details,
|
||||||
|
|
@ -108,6 +101,9 @@ class FileOperationException extends SwaggerException {
|
||||||
this.operation,
|
this.operation,
|
||||||
this.errorCode,
|
this.errorCode,
|
||||||
});
|
});
|
||||||
|
final String? filePath;
|
||||||
|
final String? operation;
|
||||||
|
final int? errorCode;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
|
@ -136,10 +132,6 @@ class FileOperationException extends SwaggerException {
|
||||||
|
|
||||||
/// 命令异常
|
/// 命令异常
|
||||||
class CommandException extends SwaggerException {
|
class CommandException extends SwaggerException {
|
||||||
final String? commandName;
|
|
||||||
final List<String>? arguments;
|
|
||||||
final int? exitCode;
|
|
||||||
|
|
||||||
CommandException(
|
CommandException(
|
||||||
super.message, {
|
super.message, {
|
||||||
super.details,
|
super.details,
|
||||||
|
|
@ -147,6 +139,9 @@ class CommandException extends SwaggerException {
|
||||||
this.arguments,
|
this.arguments,
|
||||||
this.exitCode,
|
this.exitCode,
|
||||||
});
|
});
|
||||||
|
final String? commandName;
|
||||||
|
final List<String>? arguments;
|
||||||
|
final int? exitCode;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
|
@ -175,10 +170,6 @@ class CommandException extends SwaggerException {
|
||||||
|
|
||||||
/// 验证异常
|
/// 验证异常
|
||||||
class ValidationException extends SwaggerException {
|
class ValidationException extends SwaggerException {
|
||||||
final String? field;
|
|
||||||
final dynamic value;
|
|
||||||
final String? rule;
|
|
||||||
|
|
||||||
ValidationException(
|
ValidationException(
|
||||||
super.message, {
|
super.message, {
|
||||||
super.details,
|
super.details,
|
||||||
|
|
@ -186,6 +177,9 @@ class ValidationException extends SwaggerException {
|
||||||
this.value,
|
this.value,
|
||||||
this.rule,
|
this.rule,
|
||||||
});
|
});
|
||||||
|
final String? field;
|
||||||
|
final dynamic value;
|
||||||
|
final String? rule;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
|
@ -214,10 +208,6 @@ class ValidationException extends SwaggerException {
|
||||||
|
|
||||||
/// 配置异常
|
/// 配置异常
|
||||||
class ConfigurationException extends SwaggerException {
|
class ConfigurationException extends SwaggerException {
|
||||||
final String? configKey;
|
|
||||||
final dynamic configValue;
|
|
||||||
final String? source;
|
|
||||||
|
|
||||||
ConfigurationException(
|
ConfigurationException(
|
||||||
super.message, {
|
super.message, {
|
||||||
super.details,
|
super.details,
|
||||||
|
|
@ -225,6 +215,9 @@ class ConfigurationException extends SwaggerException {
|
||||||
this.configValue,
|
this.configValue,
|
||||||
this.source,
|
this.source,
|
||||||
});
|
});
|
||||||
|
final String? configKey;
|
||||||
|
final dynamic configValue;
|
||||||
|
final String? source;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
|
@ -253,11 +246,6 @@ class ConfigurationException extends SwaggerException {
|
||||||
|
|
||||||
/// 网络异常
|
/// 网络异常
|
||||||
class NetworkException extends SwaggerException {
|
class NetworkException extends SwaggerException {
|
||||||
final String? url;
|
|
||||||
final int? statusCode;
|
|
||||||
final String? method;
|
|
||||||
final Duration? timeout;
|
|
||||||
|
|
||||||
NetworkException(
|
NetworkException(
|
||||||
super.message, {
|
super.message, {
|
||||||
super.details,
|
super.details,
|
||||||
|
|
@ -266,6 +254,10 @@ class NetworkException extends SwaggerException {
|
||||||
this.method,
|
this.method,
|
||||||
this.timeout,
|
this.timeout,
|
||||||
});
|
});
|
||||||
|
final String? url;
|
||||||
|
final int? statusCode;
|
||||||
|
final String? method;
|
||||||
|
final Duration? timeout;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
|
@ -298,10 +290,6 @@ class NetworkException extends SwaggerException {
|
||||||
|
|
||||||
/// 缓存异常
|
/// 缓存异常
|
||||||
class CacheException extends SwaggerException {
|
class CacheException extends SwaggerException {
|
||||||
final String? cacheKey;
|
|
||||||
final String? operation;
|
|
||||||
final String? cacheType;
|
|
||||||
|
|
||||||
CacheException(
|
CacheException(
|
||||||
super.message, {
|
super.message, {
|
||||||
super.details,
|
super.details,
|
||||||
|
|
@ -309,6 +297,9 @@ class CacheException extends SwaggerException {
|
||||||
this.operation,
|
this.operation,
|
||||||
this.cacheType,
|
this.cacheType,
|
||||||
});
|
});
|
||||||
|
final String? cacheKey;
|
||||||
|
final String? operation;
|
||||||
|
final String? cacheType;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
|
@ -337,10 +328,6 @@ class CacheException extends SwaggerException {
|
||||||
|
|
||||||
/// 性能异常
|
/// 性能异常
|
||||||
class PerformanceException extends SwaggerException {
|
class PerformanceException extends SwaggerException {
|
||||||
final String? operation;
|
|
||||||
final Duration? duration;
|
|
||||||
final Duration? threshold;
|
|
||||||
|
|
||||||
PerformanceException(
|
PerformanceException(
|
||||||
super.message, {
|
super.message, {
|
||||||
super.details,
|
super.details,
|
||||||
|
|
@ -348,6 +335,9 @@ class PerformanceException extends SwaggerException {
|
||||||
this.duration,
|
this.duration,
|
||||||
this.threshold,
|
this.threshold,
|
||||||
});
|
});
|
||||||
|
final String? operation;
|
||||||
|
final Duration? duration;
|
||||||
|
final Duration? threshold;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
|
@ -376,11 +366,6 @@ class PerformanceException extends SwaggerException {
|
||||||
|
|
||||||
/// 类型异常
|
/// 类型异常
|
||||||
class TypeException extends SwaggerException {
|
class TypeException extends SwaggerException {
|
||||||
final String? propertyName;
|
|
||||||
final String? expectedType;
|
|
||||||
final String? actualType;
|
|
||||||
final dynamic value;
|
|
||||||
|
|
||||||
TypeException(
|
TypeException(
|
||||||
super.message, {
|
super.message, {
|
||||||
super.details,
|
super.details,
|
||||||
|
|
@ -389,6 +374,10 @@ class TypeException extends SwaggerException {
|
||||||
this.actualType,
|
this.actualType,
|
||||||
this.value,
|
this.value,
|
||||||
});
|
});
|
||||||
|
final String? propertyName;
|
||||||
|
final String? expectedType;
|
||||||
|
final String? actualType;
|
||||||
|
final dynamic value;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
|
@ -443,7 +432,7 @@ class ExceptionHandler {
|
||||||
|
|
||||||
/// 默认异常处理
|
/// 默认异常处理
|
||||||
static void _defaultHandler(SwaggerException exception) {
|
static void _defaultHandler(SwaggerException exception) {
|
||||||
print('🚨 异常: ${exception.toString()}');
|
print('🚨 异常: $exception');
|
||||||
print('时间: ${exception.timestamp.toIso8601String()}');
|
print('时间: ${exception.timestamp.toIso8601String()}');
|
||||||
print('');
|
print('');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1132
lib/core/models.dart
1132
lib/core/models.dart
File diff suppressed because it is too large
Load Diff
|
|
@ -4,19 +4,10 @@ library;
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'models.dart';
|
import 'package:swagger_generator_flutter/core/models.dart';
|
||||||
|
|
||||||
/// 解析性能统计
|
/// 解析性能统计
|
||||||
class ParsePerformanceStats {
|
class ParsePerformanceStats {
|
||||||
final Duration totalTime;
|
|
||||||
final Duration parseTime;
|
|
||||||
final Duration validationTime;
|
|
||||||
final Duration modelCreationTime;
|
|
||||||
final int memoryUsage;
|
|
||||||
final int documentSize;
|
|
||||||
final int pathCount;
|
|
||||||
final int schemaCount;
|
|
||||||
|
|
||||||
const ParsePerformanceStats({
|
const ParsePerformanceStats({
|
||||||
required this.totalTime,
|
required this.totalTime,
|
||||||
required this.parseTime,
|
required this.parseTime,
|
||||||
|
|
@ -27,6 +18,14 @@ class ParsePerformanceStats {
|
||||||
required this.pathCount,
|
required this.pathCount,
|
||||||
required this.schemaCount,
|
required this.schemaCount,
|
||||||
});
|
});
|
||||||
|
final Duration totalTime;
|
||||||
|
final Duration parseTime;
|
||||||
|
final Duration validationTime;
|
||||||
|
final Duration modelCreationTime;
|
||||||
|
final int memoryUsage;
|
||||||
|
final int documentSize;
|
||||||
|
final int pathCount;
|
||||||
|
final int schemaCount;
|
||||||
|
|
||||||
double get pathsPerSecond => pathCount / totalTime.inMilliseconds * 1000;
|
double get pathsPerSecond => pathCount / totalTime.inMilliseconds * 1000;
|
||||||
double get schemasPerSecond => schemaCount / totalTime.inMilliseconds * 1000;
|
double get schemasPerSecond => schemaCount / totalTime.inMilliseconds * 1000;
|
||||||
|
|
@ -51,6 +50,17 @@ Performance Statistics:
|
||||||
|
|
||||||
/// 解析配置
|
/// 解析配置
|
||||||
class ParseConfig {
|
class ParseConfig {
|
||||||
|
const ParseConfig({
|
||||||
|
this.enableParallelParsing = true,
|
||||||
|
this.enableStreamParsing = false,
|
||||||
|
this.enableIncrementalParsing = false,
|
||||||
|
this.enableCaching = true,
|
||||||
|
this.maxConcurrency = 4,
|
||||||
|
this.streamBufferSize = 8192,
|
||||||
|
this.enablePerformanceStats = false,
|
||||||
|
this.enableMemoryOptimization = true,
|
||||||
|
});
|
||||||
|
|
||||||
/// 是否启用并行解析
|
/// 是否启用并行解析
|
||||||
final bool enableParallelParsing;
|
final bool enableParallelParsing;
|
||||||
|
|
||||||
|
|
@ -74,28 +84,16 @@ class ParseConfig {
|
||||||
|
|
||||||
/// 是否启用内存优化
|
/// 是否启用内存优化
|
||||||
final bool enableMemoryOptimization;
|
final bool enableMemoryOptimization;
|
||||||
|
|
||||||
const ParseConfig({
|
|
||||||
this.enableParallelParsing = true,
|
|
||||||
this.enableStreamParsing = false,
|
|
||||||
this.enableIncrementalParsing = false,
|
|
||||||
this.enableCaching = true,
|
|
||||||
this.maxConcurrency = 4,
|
|
||||||
this.streamBufferSize = 8192,
|
|
||||||
this.enablePerformanceStats = false,
|
|
||||||
this.enableMemoryOptimization = true,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 高性能解析器
|
/// 高性能解析器
|
||||||
class PerformanceParser {
|
class PerformanceParser {
|
||||||
|
PerformanceParser({ParseConfig? config})
|
||||||
|
: _config = config ?? const ParseConfig();
|
||||||
final ParseConfig _config;
|
final ParseConfig _config;
|
||||||
final Map<String, dynamic> _cache = {};
|
final Map<String, dynamic> _cache = {};
|
||||||
ParsePerformanceStats? _lastStats;
|
ParsePerformanceStats? _lastStats;
|
||||||
|
|
||||||
PerformanceParser({ParseConfig? config})
|
|
||||||
: _config = config ?? const ParseConfig();
|
|
||||||
|
|
||||||
/// 获取最后一次解析的性能统计
|
/// 获取最后一次解析的性能统计
|
||||||
ParsePerformanceStats? get lastStats => _lastStats;
|
ParsePerformanceStats? get lastStats => _lastStats;
|
||||||
|
|
||||||
|
|
@ -165,7 +163,7 @@ class PerformanceParser {
|
||||||
|
|
||||||
// 模拟流式解析(实际实现会更复杂)
|
// 模拟流式解析(实际实现会更复杂)
|
||||||
final chunks = <String>[];
|
final chunks = <String>[];
|
||||||
for (int i = 0; i < jsonString.length; i += _config.streamBufferSize) {
|
for (var i = 0; i < jsonString.length; i += _config.streamBufferSize) {
|
||||||
final end = (i + _config.streamBufferSize).clamp(0, jsonString.length);
|
final end = (i + _config.streamBufferSize).clamp(0, jsonString.length);
|
||||||
chunks.add(jsonString.substring(i, end));
|
chunks.add(jsonString.substring(i, end));
|
||||||
}
|
}
|
||||||
|
|
@ -196,25 +194,31 @@ class PerformanceParser {
|
||||||
|
|
||||||
/// 并行解析文档
|
/// 并行解析文档
|
||||||
Future<SwaggerDocument> _parseDocumentParallel(
|
Future<SwaggerDocument> _parseDocumentParallel(
|
||||||
Map<String, dynamic> json) async {
|
Map<String, dynamic> json,
|
||||||
final futures = <Future>[];
|
) async {
|
||||||
|
final futures = <Future<void>>[];
|
||||||
final results = <String, dynamic>{};
|
final results = <String, dynamic>{};
|
||||||
|
|
||||||
// 并行解析不同部分
|
// 并行解析不同部分
|
||||||
if (json.containsKey('paths')) {
|
if (json.containsKey('paths')) {
|
||||||
futures.add(_parsePathsParallel(json['paths'] as Map<String, dynamic>)
|
futures.add(
|
||||||
.then((paths) => results['paths'] = paths));
|
_parsePathsParallel(json['paths'] as Map<String, dynamic>)
|
||||||
|
.then((paths) => results['paths'] = paths),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (json.containsKey('components')) {
|
if (json.containsKey('components')) {
|
||||||
futures.add(
|
futures.add(
|
||||||
_parseComponentsParallel(json['components'] as Map<String, dynamic>)
|
_parseComponentsParallel(json['components'] as Map<String, dynamic>)
|
||||||
.then((components) => results['components'] = components));
|
.then((components) => results['components'] = components),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (json.containsKey('servers')) {
|
if (json.containsKey('servers')) {
|
||||||
futures.add(_parseServersParallel(json['servers'] as List<dynamic>)
|
futures.add(
|
||||||
.then((servers) => results['servers'] = servers));
|
_parseServersParallel(json['servers'] as List<dynamic>)
|
||||||
|
.then((servers) => results['servers'] = servers),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 等待所有并行任务完成
|
// 等待所有并行任务完成
|
||||||
|
|
@ -229,14 +233,15 @@ class PerformanceParser {
|
||||||
|
|
||||||
/// 并行解析路径
|
/// 并行解析路径
|
||||||
Future<Map<String, ApiPath>> _parsePathsParallel(
|
Future<Map<String, ApiPath>> _parsePathsParallel(
|
||||||
Map<String, dynamic> pathsJson) async {
|
Map<String, dynamic> pathsJson,
|
||||||
|
) async {
|
||||||
if (pathsJson.length <= _config.maxConcurrency) {
|
if (pathsJson.length <= _config.maxConcurrency) {
|
||||||
// 如果路径数量较少,直接解析
|
// 如果路径数量较少,直接解析
|
||||||
return _parsePathsSequential(pathsJson);
|
return _parsePathsSequential(pathsJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
final chunks = _chunkMap(pathsJson, _config.maxConcurrency);
|
final chunks = _chunkMap(pathsJson, _config.maxConcurrency);
|
||||||
final futures = chunks.map((chunk) => _parsePathChunk(chunk));
|
final futures = chunks.map(_parsePathChunk);
|
||||||
final results = await Future.wait(futures);
|
final results = await Future.wait(futures);
|
||||||
|
|
||||||
// 合并结果
|
// 合并结果
|
||||||
|
|
@ -250,20 +255,25 @@ class PerformanceParser {
|
||||||
|
|
||||||
/// 并行解析组件
|
/// 并行解析组件
|
||||||
Future<ApiComponents> _parseComponentsParallel(
|
Future<ApiComponents> _parseComponentsParallel(
|
||||||
Map<String, dynamic> componentsJson) async {
|
Map<String, dynamic> componentsJson,
|
||||||
final futures = <Future>[];
|
) async {
|
||||||
|
final futures = <Future<void>>[];
|
||||||
final results = <String, dynamic>{};
|
final results = <String, dynamic>{};
|
||||||
|
|
||||||
if (componentsJson.containsKey('schemas')) {
|
if (componentsJson.containsKey('schemas')) {
|
||||||
futures.add(_parseSchemasParallel(
|
futures.add(
|
||||||
componentsJson['schemas'] as Map<String, dynamic>)
|
_parseSchemasParallel(
|
||||||
.then((schemas) => results['schemas'] = schemas));
|
componentsJson['schemas'] as Map<String, dynamic>,
|
||||||
|
).then((schemas) => results['schemas'] = schemas),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (componentsJson.containsKey('securitySchemes')) {
|
if (componentsJson.containsKey('securitySchemes')) {
|
||||||
futures.add(_parseSecuritySchemesParallel(
|
futures.add(
|
||||||
componentsJson['securitySchemes'] as Map<String, dynamic>)
|
_parseSecuritySchemesParallel(
|
||||||
.then((schemes) => results['securitySchemes'] = schemes));
|
componentsJson['securitySchemes'] as Map<String, dynamic>,
|
||||||
|
).then((schemes) => results['securitySchemes'] = schemes),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await Future.wait(futures);
|
await Future.wait(futures);
|
||||||
|
|
@ -276,7 +286,8 @@ class PerformanceParser {
|
||||||
|
|
||||||
/// 并行解析服务器
|
/// 并行解析服务器
|
||||||
Future<List<ApiServer>> _parseServersParallel(
|
Future<List<ApiServer>> _parseServersParallel(
|
||||||
List<dynamic> serversJson) async {
|
List<dynamic> serversJson,
|
||||||
|
) async {
|
||||||
if (serversJson.length <= _config.maxConcurrency) {
|
if (serversJson.length <= _config.maxConcurrency) {
|
||||||
return serversJson
|
return serversJson
|
||||||
.map((json) => ApiServer.fromJson(json as Map<String, dynamic>))
|
.map((json) => ApiServer.fromJson(json as Map<String, dynamic>))
|
||||||
|
|
@ -284,7 +295,7 @@ class PerformanceParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
final chunks = _chunkList(serversJson, _config.maxConcurrency);
|
final chunks = _chunkList(serversJson, _config.maxConcurrency);
|
||||||
final futures = chunks.map((chunk) => _parseServerChunk(chunk));
|
final futures = chunks.map(_parseServerChunk);
|
||||||
final results = await Future.wait(futures);
|
final results = await Future.wait(futures);
|
||||||
|
|
||||||
// 合并结果
|
// 合并结果
|
||||||
|
|
@ -298,7 +309,8 @@ class PerformanceParser {
|
||||||
|
|
||||||
/// 解析路径块
|
/// 解析路径块
|
||||||
Future<Map<String, ApiPath>> _parsePathChunk(
|
Future<Map<String, ApiPath>> _parsePathChunk(
|
||||||
Map<String, dynamic> pathChunk) async {
|
Map<String, dynamic> pathChunk,
|
||||||
|
) async {
|
||||||
return _parsePathsSequential(pathChunk);
|
return _parsePathsSequential(pathChunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -318,8 +330,11 @@ class PerformanceParser {
|
||||||
pathData.forEach((method, operationData) {
|
pathData.forEach((method, operationData) {
|
||||||
if (operationData is Map<String, dynamic>) {
|
if (operationData is Map<String, dynamic>) {
|
||||||
try {
|
try {
|
||||||
final apiPath =
|
final apiPath = ApiPath.fromJson(
|
||||||
ApiPath.fromJson(pathPattern, method, operationData);
|
pathPattern,
|
||||||
|
HttpMethod.fromString(method),
|
||||||
|
operationData,
|
||||||
|
);
|
||||||
paths[pathPattern] = apiPath;
|
paths[pathPattern] = apiPath;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// 忽略解析错误的路径
|
// 忽略解析错误的路径
|
||||||
|
|
@ -334,13 +349,14 @@ class PerformanceParser {
|
||||||
|
|
||||||
/// 并行解析 Schemas
|
/// 并行解析 Schemas
|
||||||
Future<Map<String, ApiModel>> _parseSchemasParallel(
|
Future<Map<String, ApiModel>> _parseSchemasParallel(
|
||||||
Map<String, dynamic> schemasJson) async {
|
Map<String, dynamic> schemasJson,
|
||||||
|
) async {
|
||||||
if (schemasJson.length <= _config.maxConcurrency) {
|
if (schemasJson.length <= _config.maxConcurrency) {
|
||||||
return _parseSchemasSequential(schemasJson);
|
return _parseSchemasSequential(schemasJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
final chunks = _chunkMap(schemasJson, _config.maxConcurrency);
|
final chunks = _chunkMap(schemasJson, _config.maxConcurrency);
|
||||||
final futures = chunks.map((chunk) => _parseSchemaChunk(chunk));
|
final futures = chunks.map(_parseSchemaChunk);
|
||||||
final results = await Future.wait(futures);
|
final results = await Future.wait(futures);
|
||||||
|
|
||||||
// 合并结果
|
// 合并结果
|
||||||
|
|
@ -354,7 +370,8 @@ class PerformanceParser {
|
||||||
|
|
||||||
/// 并行解析安全方案
|
/// 并行解析安全方案
|
||||||
Future<Map<String, ApiSecurityScheme>> _parseSecuritySchemesParallel(
|
Future<Map<String, ApiSecurityScheme>> _parseSecuritySchemesParallel(
|
||||||
Map<String, dynamic> schemesJson) async {
|
Map<String, dynamic> schemesJson,
|
||||||
|
) async {
|
||||||
final schemes = <String, ApiSecurityScheme>{};
|
final schemes = <String, ApiSecurityScheme>{};
|
||||||
|
|
||||||
schemesJson.forEach((name, schemeData) {
|
schemesJson.forEach((name, schemeData) {
|
||||||
|
|
@ -373,13 +390,15 @@ class PerformanceParser {
|
||||||
|
|
||||||
/// 解析 Schema 块
|
/// 解析 Schema 块
|
||||||
Future<Map<String, ApiModel>> _parseSchemaChunk(
|
Future<Map<String, ApiModel>> _parseSchemaChunk(
|
||||||
Map<String, dynamic> schemaChunk) async {
|
Map<String, dynamic> schemaChunk,
|
||||||
|
) async {
|
||||||
return _parseSchemasSequential(schemaChunk);
|
return _parseSchemasSequential(schemaChunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 顺序解析 Schemas
|
/// 顺序解析 Schemas
|
||||||
Map<String, ApiModel> _parseSchemasSequential(
|
Map<String, ApiModel> _parseSchemasSequential(
|
||||||
Map<String, dynamic> schemasJson) {
|
Map<String, dynamic> schemasJson,
|
||||||
|
) {
|
||||||
final schemas = <String, ApiModel>{};
|
final schemas = <String, ApiModel>{};
|
||||||
|
|
||||||
schemasJson.forEach((name, schemaData) {
|
schemasJson.forEach((name, schemaData) {
|
||||||
|
|
@ -400,12 +419,14 @@ class PerformanceParser {
|
||||||
void _validateBasicStructure(Map<String, dynamic> json) {
|
void _validateBasicStructure(Map<String, dynamic> json) {
|
||||||
if (!json.containsKey('openapi') && !json.containsKey('swagger')) {
|
if (!json.containsKey('openapi') && !json.containsKey('swagger')) {
|
||||||
throw const FormatException(
|
throw const FormatException(
|
||||||
'Invalid OpenAPI document: missing version field');
|
'Invalid OpenAPI document: missing version field',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!json.containsKey('info')) {
|
if (!json.containsKey('info')) {
|
||||||
throw const FormatException(
|
throw const FormatException(
|
||||||
'Invalid OpenAPI document: missing info object');
|
'Invalid OpenAPI document: missing info object',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final info = json['info'] as Map<String, dynamic>?;
|
final info = json['info'] as Map<String, dynamic>?;
|
||||||
|
|
@ -413,21 +434,24 @@ class PerformanceParser {
|
||||||
!info.containsKey('title') ||
|
!info.containsKey('title') ||
|
||||||
!info.containsKey('version')) {
|
!info.containsKey('version')) {
|
||||||
throw const FormatException(
|
throw const FormatException(
|
||||||
'Invalid OpenAPI document: info object must contain title and version');
|
'Invalid OpenAPI document: info object must contain title and version',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 将 Map 分块
|
/// 将 Map 分块
|
||||||
List<Map<String, dynamic>> _chunkMap(
|
List<Map<String, dynamic>> _chunkMap(
|
||||||
Map<String, dynamic> map, int chunkSize) {
|
Map<String, dynamic> map,
|
||||||
|
int chunkSize,
|
||||||
|
) {
|
||||||
final chunks = <Map<String, dynamic>>[];
|
final chunks = <Map<String, dynamic>>[];
|
||||||
final entries = map.entries.toList();
|
final entries = map.entries.toList();
|
||||||
|
|
||||||
for (int i = 0; i < entries.length; i += chunkSize) {
|
for (var i = 0; i < entries.length; i += chunkSize) {
|
||||||
final end = (i + chunkSize).clamp(0, entries.length);
|
final end = (i + chunkSize).clamp(0, entries.length);
|
||||||
final chunk = <String, dynamic>{};
|
final chunk = <String, dynamic>{};
|
||||||
|
|
||||||
for (int j = i; j < end; j++) {
|
for (var j = i; j < end; j++) {
|
||||||
final entry = entries[j];
|
final entry = entries[j];
|
||||||
chunk[entry.key] = entry.value;
|
chunk[entry.key] = entry.value;
|
||||||
}
|
}
|
||||||
|
|
@ -442,7 +466,7 @@ class PerformanceParser {
|
||||||
List<List<dynamic>> _chunkList(List<dynamic> list, int chunkSize) {
|
List<List<dynamic>> _chunkList(List<dynamic> list, int chunkSize) {
|
||||||
final chunks = <List<dynamic>>[];
|
final chunks = <List<dynamic>>[];
|
||||||
|
|
||||||
for (int i = 0; i < list.length; i += chunkSize) {
|
for (var i = 0; i < list.length; i += chunkSize) {
|
||||||
final end = (i + chunkSize).clamp(0, list.length);
|
final end = (i + chunkSize).clamp(0, list.length);
|
||||||
chunks.add(list.sublist(i, end));
|
chunks.add(list.sublist(i, end));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import '../core/config.dart';
|
import 'package:swagger_generator_flutter/core/config.dart';
|
||||||
import '../core/exceptions.dart';
|
import 'package:swagger_generator_flutter/core/exceptions.dart';
|
||||||
import '../core/models.dart';
|
import 'package:swagger_generator_flutter/core/models.dart';
|
||||||
import '../utils/string_utils.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}) {
|
||||||
return StringUtils.generateFileHeader(
|
final header = StringUtils.generateFileHeader(
|
||||||
description,
|
description,
|
||||||
SwaggerConfig.swaggerJsonUrls.isNotEmpty
|
SwaggerConfig.swaggerJsonUrls.isNotEmpty
|
||||||
? SwaggerConfig.swaggerJsonUrls.first
|
? SwaggerConfig.swaggerJsonUrls.first
|
||||||
|
|
@ -24,6 +24,9 @@ abstract class BaseGenerator {
|
||||||
fileName: fileName,
|
fileName: fileName,
|
||||||
fileType: description,
|
fileType: description,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 添加 lint 忽略注释
|
||||||
|
return '$header\n// ignore_for_file: type=lint, invalid_annotation_target\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 生成类型安全的代码
|
/// 生成类型安全的代码
|
||||||
|
|
@ -37,6 +40,11 @@ abstract class BaseGenerator {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 确保文件以换行符结尾
|
||||||
|
if (!code.endsWith('\n')) {
|
||||||
|
return '$code\n';
|
||||||
|
}
|
||||||
|
|
||||||
return code;
|
return code;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw CodeGenerationException(
|
throw CodeGenerationException(
|
||||||
|
|
@ -81,11 +89,10 @@ abstract class BaseGenerator {
|
||||||
|
|
||||||
/// 模型代码生成器基类
|
/// 模型代码生成器基类
|
||||||
abstract class ModelGenerator extends BaseGenerator {
|
abstract class ModelGenerator extends BaseGenerator {
|
||||||
|
ModelGenerator(this.document, {this.useSimpleModels = false});
|
||||||
final SwaggerDocument document;
|
final SwaggerDocument document;
|
||||||
final bool useSimpleModels;
|
final bool useSimpleModels;
|
||||||
|
|
||||||
ModelGenerator(this.document, {this.useSimpleModels = false});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get generatorType => 'ModelGenerator';
|
String get generatorType => 'ModelGenerator';
|
||||||
|
|
||||||
|
|
@ -104,7 +111,7 @@ abstract class ModelGenerator extends BaseGenerator {
|
||||||
|
|
||||||
// 生成文件头
|
// 生成文件头
|
||||||
buffer.writeln(generateFileHeader('${model.name} 枚举定义'));
|
buffer.writeln(generateFileHeader('${model.name} 枚举定义'));
|
||||||
buffer.writeln('');
|
buffer.writeln();
|
||||||
|
|
||||||
// 生成枚举类
|
// 生成枚举类
|
||||||
if (model.description.isNotEmpty) {
|
if (model.description.isNotEmpty) {
|
||||||
|
|
@ -114,14 +121,14 @@ abstract class ModelGenerator extends BaseGenerator {
|
||||||
buffer.writeln('enum $className {');
|
buffer.writeln('enum $className {');
|
||||||
|
|
||||||
// 生成枚举值
|
// 生成枚举值
|
||||||
for (int 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 = StringUtils.generateEnumValueName(value, i);
|
final enumName = StringUtils.generateEnumValueName(value, i);
|
||||||
|
|
||||||
if (enumType == 'integer' || enumType == 'number') {
|
if (enumType == 'integer' || enumType == 'number') {
|
||||||
buffer.writeln(' $enumName($value),');
|
buffer.writeln(' $enumName($value),');
|
||||||
} else {
|
} else {
|
||||||
buffer.writeln(' $enumName(\'$value\'),');
|
buffer.writeln(" $enumName('$value'),");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,14 +137,14 @@ abstract class ModelGenerator extends BaseGenerator {
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
buffer.writeln(content.substring(0, content.lastIndexOf(',')));
|
buffer.writeln(content.substring(0, content.lastIndexOf(',')));
|
||||||
buffer.writeln(';');
|
buffer.writeln(';');
|
||||||
buffer.writeln('');
|
buffer.writeln();
|
||||||
|
|
||||||
// 生成构造函数和方法
|
// 生成构造函数和方法
|
||||||
buffer.writeln(' const $className(this.value);');
|
buffer.writeln(' const $className(this.value);');
|
||||||
buffer.writeln(
|
buffer.writeln(
|
||||||
' final ${enumType == 'integer' || enumType == 'number' ? 'int' : 'String'} value;',
|
' final ${enumType == 'integer' || enumType == 'number' ? 'int' : 'String'} value;',
|
||||||
);
|
);
|
||||||
buffer.writeln('');
|
buffer.writeln();
|
||||||
|
|
||||||
// 生成 fromValue 方法
|
// 生成 fromValue 方法
|
||||||
buffer.writeln(' static $className fromValue(dynamic value) {');
|
buffer.writeln(' static $className fromValue(dynamic value) {');
|
||||||
|
|
@ -146,19 +153,19 @@ abstract class ModelGenerator extends BaseGenerator {
|
||||||
buffer.writeln(' return enumValue;');
|
buffer.writeln(' return enumValue;');
|
||||||
buffer.writeln(' }');
|
buffer.writeln(' }');
|
||||||
buffer.writeln(' }');
|
buffer.writeln(' }');
|
||||||
buffer.writeln(' throw ArgumentError(\'Unknown enum value: \$value\');');
|
buffer.writeln(r" throw ArgumentError('Unknown enum value: $value');");
|
||||||
buffer.writeln(' }');
|
buffer.writeln(' }');
|
||||||
buffer.writeln('');
|
buffer.writeln();
|
||||||
|
|
||||||
// 生成 fromJson 方法
|
// 生成 fromJson 方法
|
||||||
buffer.writeln(' factory $className.fromJson(dynamic json) {');
|
buffer.writeln(' factory $className.fromJson(dynamic json) {');
|
||||||
buffer.writeln(' return fromValue(json);');
|
buffer.writeln(' return fromValue(json);');
|
||||||
buffer.writeln(' }');
|
buffer.writeln(' }');
|
||||||
buffer.writeln('');
|
buffer.writeln();
|
||||||
|
|
||||||
// 生成 toJson 方法
|
// 生成 toJson 方法
|
||||||
buffer.writeln(' dynamic toJson() => value;');
|
buffer.writeln(' dynamic toJson() => value;');
|
||||||
buffer.writeln('');
|
buffer.writeln();
|
||||||
|
|
||||||
buffer.writeln('}');
|
buffer.writeln('}');
|
||||||
|
|
||||||
|
|
@ -257,16 +264,6 @@ abstract class ModelGenerator extends BaseGenerator {
|
||||||
|
|
||||||
/// 选项配置类
|
/// 选项配置类
|
||||||
class GeneratorOptions {
|
class GeneratorOptions {
|
||||||
final bool generateEndpoints;
|
|
||||||
final bool generateModels;
|
|
||||||
final bool generateDocs;
|
|
||||||
final bool useSimpleModels;
|
|
||||||
final bool separateModelFiles;
|
|
||||||
final String modelsDirectory;
|
|
||||||
final String outputDirectory;
|
|
||||||
final String endpointsFileName;
|
|
||||||
final String docsFileName;
|
|
||||||
|
|
||||||
const GeneratorOptions({
|
const GeneratorOptions({
|
||||||
this.generateEndpoints = true,
|
this.generateEndpoints = true,
|
||||||
this.generateModels = true,
|
this.generateModels = true,
|
||||||
|
|
@ -281,67 +278,58 @@ class GeneratorOptions {
|
||||||
|
|
||||||
/// 从命令行参数创建选项
|
/// 从命令行参数创建选项
|
||||||
factory GeneratorOptions.fromArgs(List<String> args) {
|
factory GeneratorOptions.fromArgs(List<String> args) {
|
||||||
bool generateEndpoints = false;
|
var generateEndpoints = false;
|
||||||
bool generateModels = false;
|
var generateModels = false;
|
||||||
bool generateDocs = false;
|
var generateDocs = false;
|
||||||
bool useSimpleModels = false;
|
var useSimpleModels = false;
|
||||||
const bool separateModelFiles = true;
|
const separateModelFiles = true;
|
||||||
String modelsDirectory = 'models';
|
var modelsDirectory = 'models';
|
||||||
String outputDirectory = 'generator';
|
var outputDirectory = 'generator';
|
||||||
String endpointsFileName = 'api_paths.dart';
|
var endpointsFileName = 'api_paths.dart';
|
||||||
String docsFileName = 'api_documentation.md';
|
var docsFileName = 'api_documentation.md';
|
||||||
|
|
||||||
bool hasSpecificOption = false;
|
var hasSpecificOption = false;
|
||||||
|
|
||||||
for (int i = 0; i < args.length; i++) {
|
for (var i = 0; i < args.length; i++) {
|
||||||
final arg = args[i];
|
final arg = args[i];
|
||||||
|
|
||||||
switch (arg) {
|
switch (arg) {
|
||||||
case '--endpoints':
|
case '--endpoints':
|
||||||
generateEndpoints = true;
|
generateEndpoints = true;
|
||||||
hasSpecificOption = true;
|
hasSpecificOption = true;
|
||||||
break;
|
|
||||||
case '--models':
|
case '--models':
|
||||||
generateModels = true;
|
generateModels = true;
|
||||||
hasSpecificOption = true;
|
hasSpecificOption = true;
|
||||||
break;
|
|
||||||
case '--docs':
|
case '--docs':
|
||||||
generateDocs = true;
|
generateDocs = true;
|
||||||
hasSpecificOption = true;
|
hasSpecificOption = true;
|
||||||
break;
|
|
||||||
case '--all':
|
case '--all':
|
||||||
generateEndpoints = true;
|
generateEndpoints = true;
|
||||||
generateModels = true;
|
generateModels = true;
|
||||||
generateDocs = true;
|
generateDocs = true;
|
||||||
hasSpecificOption = true;
|
hasSpecificOption = true;
|
||||||
break;
|
|
||||||
case '--simple':
|
case '--simple':
|
||||||
useSimpleModels = true;
|
useSimpleModels = true;
|
||||||
break;
|
|
||||||
case '--models-dir':
|
case '--models-dir':
|
||||||
if (i + 1 < args.length) {
|
if (i + 1 < args.length) {
|
||||||
modelsDirectory = args[i + 1];
|
modelsDirectory = args[i + 1];
|
||||||
i++; // 跳过下一个参数
|
i++; // 跳过下一个参数
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case '--output-dir':
|
case '--output-dir':
|
||||||
if (i + 1 < args.length) {
|
if (i + 1 < args.length) {
|
||||||
outputDirectory = args[i + 1];
|
outputDirectory = args[i + 1];
|
||||||
i++; // 跳过下一个参数
|
i++; // 跳过下一个参数
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case '--endpoints-file':
|
case '--endpoints-file':
|
||||||
if (i + 1 < args.length) {
|
if (i + 1 < args.length) {
|
||||||
endpointsFileName = args[i + 1];
|
endpointsFileName = args[i + 1];
|
||||||
i++; // 跳过下一个参数
|
i++; // 跳过下一个参数
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case '--docs-file':
|
case '--docs-file':
|
||||||
if (i + 1 < args.length) {
|
if (i + 1 < args.length) {
|
||||||
docsFileName = args[i + 1];
|
docsFileName = args[i + 1];
|
||||||
i++; // 跳过下一个参数
|
i++; // 跳过下一个参数
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -357,11 +345,19 @@ class GeneratorOptions {
|
||||||
generateModels: generateModels,
|
generateModels: generateModels,
|
||||||
generateDocs: generateDocs,
|
generateDocs: generateDocs,
|
||||||
useSimpleModels: useSimpleModels,
|
useSimpleModels: useSimpleModels,
|
||||||
separateModelFiles: separateModelFiles,
|
|
||||||
modelsDirectory: modelsDirectory,
|
modelsDirectory: modelsDirectory,
|
||||||
outputDirectory: outputDirectory,
|
outputDirectory: outputDirectory,
|
||||||
endpointsFileName: endpointsFileName,
|
endpointsFileName: endpointsFileName,
|
||||||
docsFileName: docsFileName,
|
docsFileName: docsFileName,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
final bool generateEndpoints;
|
||||||
|
final bool generateModels;
|
||||||
|
final bool generateDocs;
|
||||||
|
final bool useSimpleModels;
|
||||||
|
final bool separateModelFiles;
|
||||||
|
final String modelsDirectory;
|
||||||
|
final String outputDirectory;
|
||||||
|
final String endpointsFileName;
|
||||||
|
final String docsFileName;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,56 +1,23 @@
|
||||||
import '../core/config.dart';
|
import 'package:swagger_generator_flutter/core/config.dart';
|
||||||
import '../core/models.dart';
|
import 'package:swagger_generator_flutter/core/models.dart';
|
||||||
import '../utils/string_utils.dart';
|
import 'package:swagger_generator_flutter/generators/base_generator.dart';
|
||||||
import 'base_generator.dart';
|
import 'package:swagger_generator_flutter/utils/string_utils.dart';
|
||||||
|
|
||||||
/// 模型代码生成器
|
/// 模型代码生成器
|
||||||
/// 负责生成Dart模型类代码
|
/// 负责生成Dart模型类代码
|
||||||
class ModelCodeGenerator extends ModelGenerator {
|
class ModelCodeGenerator extends ModelGenerator {
|
||||||
ModelCodeGenerator(super.document, {super.useSimpleModels});
|
ModelCodeGenerator(super.document);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get generatorType => 'ModelCodeGenerator';
|
String get generatorType => 'ModelCodeGenerator';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String generate() {
|
String generate() {
|
||||||
final buffer = StringBuffer();
|
// This method is deprecated and will not be used.
|
||||||
|
// The generator now uses generateSeparateModelFiles.
|
||||||
// 生成文件头
|
throw UnimplementedError(
|
||||||
buffer.writeln(generateFileHeader('API 数据模型定义'));
|
'Single file model generation is no longer supported.',
|
||||||
buffer.writeln('');
|
);
|
||||||
|
|
||||||
if (!useSimpleModels) {
|
|
||||||
buffer.writeln(
|
|
||||||
'import \'package:json_annotation/json_annotation.dart\';',
|
|
||||||
);
|
|
||||||
buffer.writeln('');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成所有模型
|
|
||||||
final models = document.models.values.toList();
|
|
||||||
for (int i = 0; i < models.length; i++) {
|
|
||||||
final model = models[i];
|
|
||||||
buffer.writeln(generateModelCode(model));
|
|
||||||
|
|
||||||
// 添加模型间的分隔符
|
|
||||||
if (i < models.length - 1) {
|
|
||||||
buffer.writeln('');
|
|
||||||
buffer.writeln('// ${'=' * 60}');
|
|
||||||
buffer.writeln('');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return generateTypeCheckedCode(buffer.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String generateModelCode(ApiModel model) {
|
|
||||||
if (model.isEnum) {
|
|
||||||
return generateEnumCode(model);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 只使用 JsonSerializable 注解版本
|
|
||||||
return generateAnnotatedModelCode(model);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -96,104 +63,15 @@ class ModelCodeGenerator extends ModelGenerator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 生成带注解的模型代码
|
@override
|
||||||
String generateAnnotatedModelCode(ApiModel model) {
|
@Deprecated(
|
||||||
final className = StringUtils.generateClassName(model.name);
|
'Use generateSingleModelFile or generateSeparateModelFiles instead',
|
||||||
final buffer = StringBuffer();
|
)
|
||||||
|
String generateModelCode(ApiModel model) {
|
||||||
// 生成导入依赖
|
// This method is deprecated and will not be used.
|
||||||
final importedTypes = getImportedTypes(model);
|
throw UnimplementedError(
|
||||||
for (final importType in importedTypes) {
|
'generateModelCode is no longer supported. Use generateSingleModelFile.',
|
||||||
final importFileName = StringUtils.generateFileName(importType);
|
|
||||||
buffer.writeln('import \'$importFileName\';');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (importedTypes.isNotEmpty) {
|
|
||||||
buffer.writeln('');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成 part 声明
|
|
||||||
final partFileName = StringUtils.generateFileName(model.name);
|
|
||||||
final generatedPart = partFileName.replaceAll('.dart', '.g.dart');
|
|
||||||
buffer.writeln('part \'$generatedPart\';');
|
|
||||||
buffer.writeln('');
|
|
||||||
|
|
||||||
// 生成类注释
|
|
||||||
if (model.description.isNotEmpty) {
|
|
||||||
buffer.writeln(StringUtils.generateComment(model.description));
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.writeln('@JsonSerializable(checked: true, includeIfNull: false)');
|
|
||||||
buffer.writeln('class $className {');
|
|
||||||
|
|
||||||
// 生成属性
|
|
||||||
model.properties.forEach((propName, property) {
|
|
||||||
final dartType = getDartPropertyType(property);
|
|
||||||
// 判断是否可空:
|
|
||||||
// 1. String 类型(非 date-time/date)强制为非空,忽略 Swagger 的 nullable 标记
|
|
||||||
// 2. 如果有 defaultValue,则不可空(因为 json_annotation 会保证有值)
|
|
||||||
// 3. 否则根据 nullable 标记决定
|
|
||||||
final isNormalString = property.type == PropertyType.string &&
|
|
||||||
property.format != 'date-time' &&
|
|
||||||
property.format != 'date';
|
|
||||||
final hasDefaultValue = property.defaultValue != null || isNormalString;
|
|
||||||
final nullable = hasDefaultValue ? '' : (property.nullable ? '?' : '');
|
|
||||||
final dartPropName = StringUtils.toDartPropertyName(propName);
|
|
||||||
|
|
||||||
if (property.description.isNotEmpty) {
|
|
||||||
buffer.writeln(
|
|
||||||
' ${StringUtils.generateComment(property.description)}',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加JsonKey注解
|
|
||||||
final needsJsonKey =
|
|
||||||
_needsJsonKeyAnnotation(dartPropName, propName, property, model);
|
|
||||||
if (needsJsonKey.isNotEmpty) {
|
|
||||||
buffer.writeln(' @JsonKey($needsJsonKey)');
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.writeln(' final $dartType$nullable $dartPropName;');
|
|
||||||
buffer.writeln('');
|
|
||||||
});
|
|
||||||
|
|
||||||
// 生成构造函数
|
|
||||||
if (model.properties.isEmpty) {
|
|
||||||
buffer.writeln(' const $className();');
|
|
||||||
} else {
|
|
||||||
buffer.writeln(' const $className({');
|
|
||||||
model.properties.forEach((propName, property) {
|
|
||||||
final dartPropName = StringUtils.toDartPropertyName(propName);
|
|
||||||
// 判断是否需要 required 修饰符:
|
|
||||||
// 1. String 类型(非 date-time/date)强制需要 required,忽略 Swagger 的 nullable 标记
|
|
||||||
// 2. 其他非可空字段需要 required
|
|
||||||
// 3. 可空字段不需要 required
|
|
||||||
final isNormalString = property.type == PropertyType.string &&
|
|
||||||
property.format != 'date-time' &&
|
|
||||||
property.format != 'date';
|
|
||||||
final shouldBeRequired = isNormalString || !property.nullable;
|
|
||||||
final required = shouldBeRequired ? 'required ' : '';
|
|
||||||
buffer.writeln(' ${required}this.$dartPropName,');
|
|
||||||
});
|
|
||||||
buffer.writeln(' });');
|
|
||||||
}
|
|
||||||
buffer.writeln('');
|
|
||||||
|
|
||||||
// 生成 fromJson 工厂方法
|
|
||||||
buffer.writeln(
|
|
||||||
' factory $className.fromJson(Map<String, dynamic> json) =>',
|
|
||||||
);
|
);
|
||||||
buffer.writeln(' _\$${className}FromJson(json);');
|
|
||||||
buffer.writeln('');
|
|
||||||
|
|
||||||
// 生成 toJson 方法
|
|
||||||
buffer.writeln(
|
|
||||||
' Map<String, dynamic> toJson() => _\$${className}ToJson(this);');
|
|
||||||
buffer.writeln('');
|
|
||||||
|
|
||||||
buffer.writeln('}');
|
|
||||||
|
|
||||||
return buffer.toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取模型应该存放的子目录
|
/// 获取模型应该存放的子目录
|
||||||
|
|
@ -259,33 +137,38 @@ class ModelCodeGenerator extends ModelGenerator {
|
||||||
final buffer = StringBuffer();
|
final buffer = StringBuffer();
|
||||||
|
|
||||||
// 生成文件头
|
// 生成文件头
|
||||||
buffer.writeln(generateFileHeader(
|
buffer.writeln(
|
||||||
'${model.name} 模型定义',
|
generateFileHeader(
|
||||||
fileName: fileName ?? StringUtils.generateFileName(model.name),
|
'${model.name} 模型定义',
|
||||||
));
|
fileName: fileName ?? StringUtils.generateFileName(model.name),
|
||||||
buffer.writeln('');
|
),
|
||||||
|
);
|
||||||
|
buffer.writeln();
|
||||||
|
|
||||||
// 枚举类需要导入 json_annotation 以使用 @JsonEnum 注解
|
// Freezed 模型需要导入 freezed_annotation
|
||||||
if (!useSimpleModels && model.isEnum) {
|
if (!model.isEnum) {
|
||||||
buffer.writeln(
|
buffer.writeln(
|
||||||
'import \'package:json_annotation/json_annotation.dart\';',
|
"import 'package:freezed_annotation/freezed_annotation.dart';",
|
||||||
);
|
);
|
||||||
buffer.writeln('');
|
// json_annotation is already exported by freezed_annotation, so we don't need to import it explicitly
|
||||||
|
// unless we are using specific features not covered by freezed (which is rare for standard usage)
|
||||||
|
// buffer.writeln('import \'package:json_annotation/json_annotation.dart\';');
|
||||||
|
buffer.writeln();
|
||||||
}
|
}
|
||||||
// 普通类且非简洁模式时导入 json_annotation
|
// 枚举类需要导入 json_annotation 以使用 @JsonEnum 注解
|
||||||
else if (!useSimpleModels && !model.isEnum) {
|
else if (model.isEnum) {
|
||||||
buffer.writeln(
|
buffer.writeln(
|
||||||
'import \'package:json_annotation/json_annotation.dart\';',
|
"import 'package:json_annotation/json_annotation.dart';",
|
||||||
);
|
);
|
||||||
buffer.writeln('');
|
buffer.writeln();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成导入依赖 - 统一使用父目录的 index.dart
|
// 生成导入依赖 - 统一使用父目录的 index.dart
|
||||||
// 因为模型现在在子目录中(如 result/user_result.dart),需要导入 '../index.dart'
|
// 因为模型现在在子目录中(如 result/user_result.dart),需要导入 '../index.dart'
|
||||||
final importedTypes = getImportedTypes(model);
|
final importedTypes = getImportedTypes(model);
|
||||||
if (importedTypes.isNotEmpty) {
|
if (importedTypes.isNotEmpty) {
|
||||||
buffer.writeln('import \'../index.dart\';');
|
buffer.writeln("import '../index.dart';");
|
||||||
buffer.writeln('');
|
buffer.writeln();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成模型代码,但不包含导入语句和文件头(因为已经在上面生成了)
|
// 生成模型代码,但不包含导入语句和文件头(因为已经在上面生成了)
|
||||||
|
|
@ -320,14 +203,14 @@ class ModelCodeGenerator extends ModelGenerator {
|
||||||
buffer.writeln('enum $className {');
|
buffer.writeln('enum $className {');
|
||||||
|
|
||||||
// 生成枚举值
|
// 生成枚举值
|
||||||
for (int 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 = StringUtils.generateEnumValueName(value, i);
|
final enumName = StringUtils.generateEnumValueName(value, i);
|
||||||
|
|
||||||
if (enumType == 'integer' || enumType == 'number') {
|
if (enumType == 'integer' || enumType == 'number') {
|
||||||
buffer.writeln(' $enumName($value),');
|
buffer.writeln(' $enumName($value),');
|
||||||
} else {
|
} else {
|
||||||
buffer.writeln(' $enumName(\'$value\'),');
|
buffer.writeln(" $enumName('$value'),");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -336,14 +219,14 @@ class ModelCodeGenerator extends ModelGenerator {
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
buffer.writeln(content.substring(0, content.lastIndexOf(',')));
|
buffer.writeln(content.substring(0, content.lastIndexOf(',')));
|
||||||
buffer.writeln(';');
|
buffer.writeln(';');
|
||||||
buffer.writeln('');
|
buffer.writeln();
|
||||||
|
|
||||||
// 生成构造函数和方法
|
// 生成构造函数和方法
|
||||||
buffer.writeln(' const $className(this.value);');
|
buffer.writeln(' const $className(this.value);');
|
||||||
buffer.writeln(
|
buffer.writeln(
|
||||||
' final ${enumType == 'integer' || enumType == 'number' ? 'int' : 'String'} value;',
|
' final ${enumType == 'integer' || enumType == 'number' ? 'int' : 'String'} value;',
|
||||||
);
|
);
|
||||||
buffer.writeln('');
|
buffer.writeln();
|
||||||
|
|
||||||
// 生成 fromValue 方法
|
// 生成 fromValue 方法
|
||||||
buffer.writeln(' static $className fromValue(dynamic value) {');
|
buffer.writeln(' static $className fromValue(dynamic value) {');
|
||||||
|
|
@ -352,19 +235,19 @@ class ModelCodeGenerator extends ModelGenerator {
|
||||||
buffer.writeln(' return enumValue;');
|
buffer.writeln(' return enumValue;');
|
||||||
buffer.writeln(' }');
|
buffer.writeln(' }');
|
||||||
buffer.writeln(' }');
|
buffer.writeln(' }');
|
||||||
buffer.writeln(' throw ArgumentError(\'Unknown enum value: \$value\');');
|
buffer.writeln(r" throw ArgumentError('Unknown enum value: $value');");
|
||||||
buffer.writeln(' }');
|
buffer.writeln(' }');
|
||||||
buffer.writeln('');
|
buffer.writeln();
|
||||||
|
|
||||||
// 生成 fromJson 方法
|
// 生成 fromJson 方法
|
||||||
buffer.writeln(' factory $className.fromJson(dynamic json) {');
|
buffer.writeln(' factory $className.fromJson(dynamic json) {');
|
||||||
buffer.writeln(' return fromValue(json);');
|
buffer.writeln(' return fromValue(json);');
|
||||||
buffer.writeln(' }');
|
buffer.writeln(' }');
|
||||||
buffer.writeln('');
|
buffer.writeln();
|
||||||
|
|
||||||
// 生成 toJson 方法
|
// 生成 toJson 方法
|
||||||
buffer.writeln(' dynamic toJson() => value;');
|
buffer.writeln(' dynamic toJson() => value;');
|
||||||
buffer.writeln('');
|
buffer.writeln();
|
||||||
|
|
||||||
buffer.writeln('}');
|
buffer.writeln('}');
|
||||||
|
|
||||||
|
|
@ -380,25 +263,26 @@ class ModelCodeGenerator extends ModelGenerator {
|
||||||
|
|
||||||
// 生成 part 声明
|
// 生成 part 声明
|
||||||
final partFileName = StringUtils.generateFileName(model.name);
|
final partFileName = StringUtils.generateFileName(model.name);
|
||||||
|
final freezedPart = partFileName.replaceAll('.dart', '.freezed.dart');
|
||||||
final generatedPart = partFileName.replaceAll('.dart', '.g.dart');
|
final generatedPart = partFileName.replaceAll('.dart', '.g.dart');
|
||||||
buffer.writeln('part \'$generatedPart\';');
|
buffer.writeln("part '$freezedPart';");
|
||||||
buffer.writeln('');
|
buffer.writeln("part '$generatedPart';");
|
||||||
|
buffer.writeln();
|
||||||
|
|
||||||
// 生成类注释
|
// 生成类注释
|
||||||
if (model.description.isNotEmpty) {
|
if (model.description.isNotEmpty) {
|
||||||
buffer.writeln(StringUtils.generateComment(model.description));
|
buffer.writeln(StringUtils.generateComment(model.description));
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.writeln('@JsonSerializable(checked: true, includeIfNull: false)');
|
buffer.writeln('@freezed');
|
||||||
buffer.writeln('class $className {');
|
buffer.writeln('abstract class $className with _\$$className {');
|
||||||
|
|
||||||
|
// 生成 factory 构造函数
|
||||||
|
buffer.writeln(' const factory $className({');
|
||||||
|
|
||||||
// 生成属性
|
// 生成属性
|
||||||
model.properties.forEach((propName, property) {
|
model.properties.forEach((propName, property) {
|
||||||
final dartType = getDartPropertyType(property);
|
final dartType = getDartPropertyType(property);
|
||||||
// 判断是否可空:
|
|
||||||
// 1. String 类型(非 date-time/date)强制为非空,忽略 Swagger 的 nullable 标记
|
|
||||||
// 2. 如果有 defaultValue,则不可空(因为 json_annotation 会保证有值)
|
|
||||||
// 3. 否则根据 nullable 标记决定
|
|
||||||
final isNormalString = property.type == PropertyType.string &&
|
final isNormalString = property.type == PropertyType.string &&
|
||||||
property.format != 'date-time' &&
|
property.format != 'date-time' &&
|
||||||
property.format != 'date';
|
property.format != 'date';
|
||||||
|
|
@ -408,55 +292,32 @@ class ModelCodeGenerator extends ModelGenerator {
|
||||||
|
|
||||||
if (property.description.isNotEmpty) {
|
if (property.description.isNotEmpty) {
|
||||||
buffer.writeln(
|
buffer.writeln(
|
||||||
' ${StringUtils.generateComment(property.description)}',
|
' ${StringUtils.generateComment(property.description)}',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加JsonKey注解
|
// 添加JsonKey注解
|
||||||
final needsJsonKey =
|
final jsonKeyAnnotations =
|
||||||
_needsJsonKeyAnnotation(dartPropName, propName, property, model);
|
_needsJsonKeyAnnotation(dartPropName, propName, property, model);
|
||||||
if (needsJsonKey.isNotEmpty) {
|
if (jsonKeyAnnotations.isNotEmpty) {
|
||||||
buffer.writeln(' @JsonKey($needsJsonKey)');
|
buffer.writeln(' @JsonKey($jsonKeyAnnotations)');
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.writeln(' final $dartType$nullable $dartPropName;');
|
// 判断是否需要 required 修饰符
|
||||||
buffer.writeln('');
|
final shouldBeRequired = isNormalString || !property.nullable;
|
||||||
|
final required = shouldBeRequired ? 'required ' : '';
|
||||||
|
|
||||||
|
buffer.writeln(' $required$dartType$nullable $dartPropName,');
|
||||||
});
|
});
|
||||||
|
|
||||||
// 生成构造函数
|
buffer.writeln(' }) = _$className;');
|
||||||
if (model.properties.isEmpty) {
|
buffer.writeln();
|
||||||
buffer.writeln(' const $className();');
|
|
||||||
} else {
|
|
||||||
buffer.writeln(' const $className({');
|
|
||||||
model.properties.forEach((propName, property) {
|
|
||||||
final dartPropName = StringUtils.toDartPropertyName(propName);
|
|
||||||
// 判断是否需要 required 修饰符:
|
|
||||||
// 1. String 类型(非 date-time/date)强制需要 required,忽略 Swagger 的 nullable 标记
|
|
||||||
// 2. 其他非可空字段需要 required
|
|
||||||
// 3. 可空字段不需要 required
|
|
||||||
final isNormalString = property.type == PropertyType.string &&
|
|
||||||
property.format != 'date-time' &&
|
|
||||||
property.format != 'date';
|
|
||||||
final shouldBeRequired = isNormalString || !property.nullable;
|
|
||||||
final required = shouldBeRequired ? 'required ' : '';
|
|
||||||
buffer.writeln(' ${required}this.$dartPropName,');
|
|
||||||
});
|
|
||||||
buffer.writeln(' });');
|
|
||||||
}
|
|
||||||
buffer.writeln('');
|
|
||||||
|
|
||||||
// 生成 fromJson 工厂方法
|
// 生成 fromJson 工厂方法
|
||||||
buffer.writeln(
|
buffer.writeln(
|
||||||
' factory $className.fromJson(Map<String, dynamic> json) =>',
|
' factory $className.fromJson(Map<String, dynamic> json) =>',
|
||||||
);
|
);
|
||||||
buffer.writeln(' _\$${className}FromJson(json);');
|
buffer.writeln(' _\$${className}FromJson(json);');
|
||||||
buffer.writeln('');
|
|
||||||
|
|
||||||
// 生成 toJson 方法
|
|
||||||
buffer.writeln(
|
|
||||||
' Map<String, dynamic> toJson() => _\$${className}ToJson(this);');
|
|
||||||
buffer.writeln('');
|
|
||||||
|
|
||||||
buffer.writeln('}');
|
buffer.writeln('}');
|
||||||
|
|
||||||
return buffer.toString();
|
return buffer.toString();
|
||||||
|
|
@ -468,33 +329,33 @@ class ModelCodeGenerator extends ModelGenerator {
|
||||||
final buffer = StringBuffer();
|
final buffer = StringBuffer();
|
||||||
|
|
||||||
buffer.writeln(generateFileHeader('API 模型导出文件'));
|
buffer.writeln(generateFileHeader('API 模型导出文件'));
|
||||||
buffer.writeln('');
|
buffer.writeln();
|
||||||
|
|
||||||
// 添加 library 声明
|
// 添加 library 声明
|
||||||
buffer.writeln('library;');
|
buffer.writeln('library;');
|
||||||
buffer.writeln('');
|
buffer.writeln();
|
||||||
|
|
||||||
// 导出 base_result 和 base_page_result(如果配置了)
|
// 导出 base_result 和 base_page_result(如果配置了)
|
||||||
final baseResultImport = SwaggerConfig.baseResultImport;
|
final baseResultImport = SwaggerConfig.baseResultImport;
|
||||||
final basePageResultImport = SwaggerConfig.basePageResultImport;
|
final basePageResultImport = SwaggerConfig.basePageResultImport;
|
||||||
|
|
||||||
if (baseResultImport.isNotEmpty) {
|
if (baseResultImport.isNotEmpty) {
|
||||||
buffer.writeln('export \'$baseResultImport\';');
|
buffer.writeln("export '$baseResultImport';");
|
||||||
}
|
}
|
||||||
if (basePageResultImport.isNotEmpty) {
|
if (basePageResultImport.isNotEmpty) {
|
||||||
buffer.writeln('export \'$basePageResultImport\';');
|
buffer.writeln("export '$basePageResultImport';");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((baseResultImport.isNotEmpty || basePageResultImport.isNotEmpty) &&
|
if ((baseResultImport.isNotEmpty || basePageResultImport.isNotEmpty) &&
|
||||||
modelsByDirectory.isNotEmpty) {
|
modelsByDirectory.isNotEmpty) {
|
||||||
buffer.writeln('');
|
buffer.writeln();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 导出所有子目录的 index.dart
|
// 导出所有子目录的 index.dart
|
||||||
final sortedDirs = modelsByDirectory.keys.toList()..sort();
|
final sortedDirs = modelsByDirectory.keys.toList()..sort();
|
||||||
|
|
||||||
for (final dir in sortedDirs) {
|
for (final dir in sortedDirs) {
|
||||||
buffer.writeln('export \'$dir/index.dart\';');
|
buffer.writeln("export '$dir/index.dart';");
|
||||||
}
|
}
|
||||||
|
|
||||||
return generateTypeCheckedCode(buffer.toString());
|
return generateTypeCheckedCode(buffer.toString());
|
||||||
|
|
@ -505,11 +366,11 @@ class ModelCodeGenerator extends ModelGenerator {
|
||||||
final buffer = StringBuffer();
|
final buffer = StringBuffer();
|
||||||
|
|
||||||
buffer.writeln(generateFileHeader('模型导出文件'));
|
buffer.writeln(generateFileHeader('模型导出文件'));
|
||||||
buffer.writeln('');
|
buffer.writeln();
|
||||||
|
|
||||||
// 添加 library 声明
|
// 添加 library 声明
|
||||||
buffer.writeln('library;');
|
buffer.writeln('library;');
|
||||||
buffer.writeln('');
|
buffer.writeln();
|
||||||
|
|
||||||
// 按模型名排序并导出
|
// 按模型名排序并导出
|
||||||
final sortedModels = List<ApiModel>.from(models)
|
final sortedModels = List<ApiModel>.from(models)
|
||||||
|
|
@ -517,7 +378,7 @@ class ModelCodeGenerator extends ModelGenerator {
|
||||||
|
|
||||||
for (final model in sortedModels) {
|
for (final model in sortedModels) {
|
||||||
final fileName = StringUtils.generateFileName(model.name);
|
final fileName = StringUtils.generateFileName(model.name);
|
||||||
buffer.writeln('export \'$fileName\';');
|
buffer.writeln("export '$fileName';");
|
||||||
}
|
}
|
||||||
|
|
||||||
return generateTypeCheckedCode(buffer.toString());
|
return generateTypeCheckedCode(buffer.toString());
|
||||||
|
|
@ -527,33 +388,33 @@ class ModelCodeGenerator extends ModelGenerator {
|
||||||
final buffer = StringBuffer();
|
final buffer = StringBuffer();
|
||||||
|
|
||||||
buffer.writeln(generateFileHeader('API 模型导出文件'));
|
buffer.writeln(generateFileHeader('API 模型导出文件'));
|
||||||
buffer.writeln('');
|
buffer.writeln();
|
||||||
|
|
||||||
// 添加 library 声明
|
// 添加 library 声明
|
||||||
buffer.writeln('library;');
|
buffer.writeln('library;');
|
||||||
buffer.writeln('');
|
buffer.writeln();
|
||||||
|
|
||||||
// 导出 base_result 和 base_page_result(如果配置了)
|
// 导出 base_result 和 base_page_result(如果配置了)
|
||||||
final baseResultImport = SwaggerConfig.baseResultImport;
|
final baseResultImport = SwaggerConfig.baseResultImport;
|
||||||
final basePageResultImport = SwaggerConfig.basePageResultImport;
|
final basePageResultImport = SwaggerConfig.basePageResultImport;
|
||||||
|
|
||||||
if (baseResultImport.isNotEmpty) {
|
if (baseResultImport.isNotEmpty) {
|
||||||
buffer.writeln('export \'$baseResultImport\';');
|
buffer.writeln("export '$baseResultImport';");
|
||||||
}
|
}
|
||||||
if (basePageResultImport.isNotEmpty) {
|
if (basePageResultImport.isNotEmpty) {
|
||||||
buffer.writeln('export \'$basePageResultImport\';');
|
buffer.writeln("export '$basePageResultImport';");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((baseResultImport.isNotEmpty || basePageResultImport.isNotEmpty) &&
|
if ((baseResultImport.isNotEmpty || basePageResultImport.isNotEmpty) &&
|
||||||
modelFileNames.isNotEmpty) {
|
modelFileNames.isNotEmpty) {
|
||||||
buffer.writeln('');
|
buffer.writeln();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按文件名排序并导出所有模型
|
// 按文件名排序并导出所有模型
|
||||||
final sortedFiles = List<String>.from(modelFileNames)..sort();
|
final sortedFiles = List<String>.from(modelFileNames)..sort();
|
||||||
|
|
||||||
for (final fileName in sortedFiles) {
|
for (final fileName in sortedFiles) {
|
||||||
buffer.writeln('export \'$fileName\';');
|
buffer.writeln("export '$fileName';");
|
||||||
}
|
}
|
||||||
|
|
||||||
return generateTypeCheckedCode(buffer.toString());
|
return generateTypeCheckedCode(buffer.toString());
|
||||||
|
|
@ -570,7 +431,7 @@ class ModelCodeGenerator extends ModelGenerator {
|
||||||
|
|
||||||
// 属性名与JSON字段名不同时需要name参数
|
// 属性名与JSON字段名不同时需要name参数
|
||||||
if (dartPropName != propName) {
|
if (dartPropName != propName) {
|
||||||
annotations.add('name: \'$propName\'');
|
annotations.add("name: '$propName'");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✨ 使用模型的 usageType 判断,而不是基于名字判断
|
// ✨ 使用模型的 usageType 判断,而不是基于名字判断
|
||||||
|
|
@ -587,10 +448,10 @@ class ModelCodeGenerator extends ModelGenerator {
|
||||||
if (property.defaultValue != null) {
|
if (property.defaultValue != null) {
|
||||||
// 如果OpenAPI文档中有明确的默认值,使用它
|
// 如果OpenAPI文档中有明确的默认值,使用它
|
||||||
final defaultVal = property.defaultValue.toString();
|
final defaultVal = property.defaultValue.toString();
|
||||||
annotations.add('defaultValue: \'$defaultVal\'');
|
annotations.add("defaultValue: '$defaultVal'");
|
||||||
} else {
|
} else {
|
||||||
// 如果没有默认值,使用空字符串作为默认值
|
// 如果没有默认值,使用空字符串作为默认值
|
||||||
annotations.add('defaultValue: \'\'');
|
annotations.add("defaultValue: ''");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -620,7 +481,7 @@ class ModelCodeGenerator extends ModelGenerator {
|
||||||
annotations.add('defaultValue: $defaultVal');
|
annotations.add('defaultValue: $defaultVal');
|
||||||
} else {
|
} else {
|
||||||
// 对于其他类型,将默认值作为字符串处理
|
// 对于其他类型,将默认值作为字符串处理
|
||||||
annotations.add('defaultValue: \'$defaultVal\'');
|
annotations.add("defaultValue: '$defaultVal'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -2,27 +2,28 @@ import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
import '../core/config.dart';
|
import 'package:swagger_generator_flutter/core/config.dart';
|
||||||
import '../core/exceptions.dart';
|
import 'package:swagger_generator_flutter/core/exceptions.dart';
|
||||||
import '../core/models.dart';
|
import 'package:swagger_generator_flutter/core/models.dart';
|
||||||
import '../utils/cache_manager.dart';
|
import 'package:swagger_generator_flutter/utils/cache_manager.dart';
|
||||||
import '../utils/performance_monitor.dart';
|
import 'package:swagger_generator_flutter/utils/performance_monitor.dart';
|
||||||
import '../utils/reference_resolver.dart';
|
import 'package:swagger_generator_flutter/utils/reference_resolver.dart';
|
||||||
import '../utils/string_utils.dart';
|
import 'package:swagger_generator_flutter/utils/string_utils.dart';
|
||||||
|
|
||||||
/// Swagger数据解析器
|
/// Swagger数据解析器
|
||||||
/// 负责解析Swagger JSON文档并提取相关信息
|
/// 负责解析Swagger JSON文档并提取相关信息
|
||||||
class SwaggerDataParser {
|
class SwaggerDataParser {
|
||||||
final CacheManager _cacheManager;
|
|
||||||
final PerformanceMonitor _performanceMonitor;
|
|
||||||
|
|
||||||
// 缓存解析结果
|
|
||||||
final Map<String, SwaggerDocument> _cachedDocuments = {};
|
|
||||||
|
|
||||||
SwaggerDataParser()
|
SwaggerDataParser()
|
||||||
: _cacheManager = CacheManager(),
|
: _cacheManager = CacheManager(),
|
||||||
_performanceMonitor = PerformanceMonitor();
|
_performanceMonitor = PerformanceMonitor();
|
||||||
|
final CacheManager _cacheManager;
|
||||||
|
final PerformanceMonitor _performanceMonitor;
|
||||||
|
static final Logger _logger = Logger('SwaggerDataParser');
|
||||||
|
|
||||||
|
// 缓存解析结果
|
||||||
|
final Map<String, SwaggerDocument> _cachedDocuments = {};
|
||||||
|
|
||||||
/// 获取并解析Swagger JSON文档
|
/// 获取并解析Swagger JSON文档
|
||||||
/// [url] 可选参数,如果不传则使用配置中的第一个 URL
|
/// [url] 可选参数,如果不传则使用配置中的第一个 URL
|
||||||
|
|
@ -31,7 +32,7 @@ class SwaggerDataParser {
|
||||||
|
|
||||||
// 如果有缓存,直接返回缓存结果
|
// 如果有缓存,直接返回缓存结果
|
||||||
if (_cachedDocuments.containsKey(swaggerUrl)) {
|
if (_cachedDocuments.containsKey(swaggerUrl)) {
|
||||||
print('📦 使用缓存的文档: $swaggerUrl');
|
_logger.info('📦 使用缓存的文档: $swaggerUrl');
|
||||||
return _cachedDocuments[swaggerUrl]!;
|
return _cachedDocuments[swaggerUrl]!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,8 +48,8 @@ class SwaggerDataParser {
|
||||||
// 处理本地文件
|
// 处理本地文件
|
||||||
final filePath = swaggerUrl.replaceFirst('file://', '');
|
final filePath = swaggerUrl.replaceFirst('file://', '');
|
||||||
final file = File(filePath);
|
final file = File(filePath);
|
||||||
if (await file.exists()) {
|
if (file.existsSync()) {
|
||||||
final content = await file.readAsString();
|
final content = file.readAsStringSync();
|
||||||
jsonData = json.decode(content) as Map<String, dynamic>;
|
jsonData = json.decode(content) as Map<String, dynamic>;
|
||||||
} else {
|
} else {
|
||||||
throw SwaggerParseException(
|
throw SwaggerParseException(
|
||||||
|
|
@ -78,9 +79,9 @@ class SwaggerDataParser {
|
||||||
|
|
||||||
final document = await parseSwaggerDocument(jsonData);
|
final document = await parseSwaggerDocument(jsonData);
|
||||||
_cachedDocuments[swaggerUrl] = document;
|
_cachedDocuments[swaggerUrl] = document;
|
||||||
print('✅ Swagger文档解析完成');
|
_logger.info('✅ Swagger文档解析完成');
|
||||||
return document;
|
return document;
|
||||||
} catch (e) {
|
} on Object catch (e) {
|
||||||
if (e is SwaggerParseException) {
|
if (e is SwaggerParseException) {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
|
|
@ -173,8 +174,8 @@ class SwaggerDataParser {
|
||||||
if (servers.isEmpty) {
|
if (servers.isEmpty) {
|
||||||
servers.add(const ApiServer(url: '/'));
|
servers.add(const ApiServer(url: '/'));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} on Object catch (e) {
|
||||||
print('⚠️ 解析servers配置时发生错误: $e');
|
_logger.warning('⚠️ 解析servers配置时发生错误: $e');
|
||||||
// 提供默认服务器配置
|
// 提供默认服务器配置
|
||||||
servers.add(const ApiServer(url: '/'));
|
servers.add(const ApiServer(url: '/'));
|
||||||
}
|
}
|
||||||
|
|
@ -226,8 +227,8 @@ class SwaggerDataParser {
|
||||||
callbacks: components.callbacks,
|
callbacks: components.callbacks,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} on Object catch (e) {
|
||||||
print('⚠️ 解析components配置时发生错误: $e');
|
_logger.warning('⚠️ 解析components配置时发生错误: $e');
|
||||||
}
|
}
|
||||||
|
|
||||||
return const ApiComponents();
|
return const ApiComponents();
|
||||||
|
|
@ -250,8 +251,8 @@ class SwaggerDataParser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} on Object catch (e) {
|
||||||
print('⚠️ 解析tags信息时发生错误: $e');
|
_logger.warning('⚠️ 解析tags信息时发生错误: $e');
|
||||||
}
|
}
|
||||||
|
|
||||||
return tagsInfo;
|
return tagsInfo;
|
||||||
|
|
@ -274,11 +275,11 @@ class SwaggerDataParser {
|
||||||
final method = HttpMethod.fromString(methodKey);
|
final method = HttpMethod.fromString(methodKey);
|
||||||
final apiPath = ApiPath.fromJson(
|
final apiPath = ApiPath.fromJson(
|
||||||
pathKey,
|
pathKey,
|
||||||
methodKey, // 传入字符串而不是HttpMethod对象
|
method,
|
||||||
methodValue,
|
methodValue,
|
||||||
);
|
);
|
||||||
final key =
|
final key = '${method.value.toUpperCase()}_'
|
||||||
'${method.value.toUpperCase()}_${pathKey.replaceAll('/', '_')}';
|
'${pathKey.replaceAll('/', '_')}';
|
||||||
paths[key] = apiPath;
|
paths[key] = apiPath;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -340,8 +341,8 @@ class SwaggerDataParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 引用类型 ($ref)
|
// 引用类型 ($ref)
|
||||||
if (propData['\$ref'] != null) {
|
if (propData[r'$ref'] != null) {
|
||||||
final ref = propData['\$ref'] as String;
|
final ref = propData[r'$ref'] as String;
|
||||||
// 从 #/components/schemas/ModelName 或 #/definitions/ModelName 中提取类型名
|
// 从 #/components/schemas/ModelName 或 #/definitions/ModelName 中提取类型名
|
||||||
final parts = ref.split('/');
|
final parts = ref.split('/');
|
||||||
if (parts.isNotEmpty) {
|
if (parts.isNotEmpty) {
|
||||||
|
|
@ -540,7 +541,7 @@ class SwaggerDataParser {
|
||||||
ModelUsageType usageType,
|
ModelUsageType usageType,
|
||||||
) {
|
) {
|
||||||
// 处理 $ref
|
// 处理 $ref
|
||||||
final ref = schema['\$ref'] as String?;
|
final ref = schema[r'$ref'] as String?;
|
||||||
if (ref != null) {
|
if (ref != null) {
|
||||||
// 从 #/components/schemas/ModelName 提取 ModelName
|
// 从 #/components/schemas/ModelName 提取 ModelName
|
||||||
final schemaName = ref.split('/').last;
|
final schemaName = ref.split('/').last;
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,20 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'commands/base_command.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'commands/generate_command.dart';
|
import 'package:swagger_generator_flutter/commands/base_command.dart';
|
||||||
import 'core/config.dart';
|
import 'package:swagger_generator_flutter/commands/generate_command.dart';
|
||||||
|
import 'package:swagger_generator_flutter/core/config.dart';
|
||||||
import 'utils/string_utils.dart';
|
import 'package:swagger_generator_flutter/utils/string_utils.dart';
|
||||||
|
|
||||||
/// Swagger CLI 应用程序
|
/// Swagger CLI 应用程序
|
||||||
/// 使用命令模式架构的新版本CLI工具
|
/// 使用命令模式架构的新版本CLI工具
|
||||||
class SwaggerCLI {
|
class SwaggerCLI {
|
||||||
final Map<String, BaseCommand> _commands = {};
|
|
||||||
|
|
||||||
SwaggerCLI() {
|
SwaggerCLI() {
|
||||||
_registerCommands();
|
_registerCommands();
|
||||||
}
|
}
|
||||||
|
static final Logger _logger = Logger('SwaggerCLI');
|
||||||
|
final Map<String, BaseCommand> _commands = {};
|
||||||
|
|
||||||
/// 注册所有命令
|
/// 注册所有命令
|
||||||
void _registerCommands() {
|
void _registerCommands() {
|
||||||
|
|
@ -56,8 +57,9 @@ class SwaggerCLI {
|
||||||
// 查找并执行命令
|
// 查找并执行命令
|
||||||
final command = _commands[commandName];
|
final command = _commands[commandName];
|
||||||
if (command == null) {
|
if (command == null) {
|
||||||
print('❌ 未知命令: $commandName');
|
_logger
|
||||||
print('');
|
..severe('❌ 未知命令: $commandName')
|
||||||
|
..info('');
|
||||||
_showAvailableCommands();
|
_showAvailableCommands();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
@ -75,33 +77,37 @@ class SwaggerCLI {
|
||||||
|
|
||||||
// 显示执行时间
|
// 显示执行时间
|
||||||
if (exitCode == 0) {
|
if (exitCode == 0) {
|
||||||
print('');
|
_logger
|
||||||
print('⏱️ 执行时间: ${StringUtils.formatDuration(stopwatch.elapsed)}');
|
..info('')
|
||||||
|
..info('⏱️ 执行时间: ${StringUtils.formatDuration(stopwatch.elapsed)}');
|
||||||
}
|
}
|
||||||
|
|
||||||
return exitCode;
|
return exitCode;
|
||||||
} catch (error, stackTrace) {
|
} catch (error, stackTrace) {
|
||||||
print('❌ 应用程序错误: $error');
|
_logger
|
||||||
print('堆栈跟踪: $stackTrace');
|
..severe('❌ 应用程序错误: $error')
|
||||||
|
..severe('堆栈跟踪: $stackTrace');
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 显示应用程序横幅
|
/// 显示应用程序横幅
|
||||||
void _showBanner() {
|
void _showBanner() {
|
||||||
print('');
|
_logger
|
||||||
print('🚀 Swagger API 代码生成器 v2.0');
|
..info('')
|
||||||
print('=====================================');
|
..info('🚀 Swagger API 代码生成器 v2.0')
|
||||||
print('强大的 Swagger API 代码生成工具');
|
..info('=====================================')
|
||||||
print('');
|
..info('强大的 Swagger API 代码生成工具')
|
||||||
|
..info('');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 显示帮助信息
|
/// 显示帮助信息
|
||||||
void _showHelp() {
|
void _showHelp() {
|
||||||
print('用法: dart swagger_cli_new.dart <命令> [选项]');
|
_logger
|
||||||
print('');
|
..info('用法: dart swagger_cli_new.dart <命令> [选项]')
|
||||||
print('全新的命令式架构,提供更好的可扩展性和用户体验。');
|
..info('')
|
||||||
print('');
|
..info('全新的命令式架构,提供更好的可扩展性和用户体验。')
|
||||||
|
..info('');
|
||||||
_showAvailableCommands();
|
_showAvailableCommands();
|
||||||
_showGlobalOptions();
|
_showGlobalOptions();
|
||||||
_showExamples();
|
_showExamples();
|
||||||
|
|
@ -110,68 +116,74 @@ class SwaggerCLI {
|
||||||
|
|
||||||
/// 显示可用命令
|
/// 显示可用命令
|
||||||
void _showAvailableCommands() {
|
void _showAvailableCommands() {
|
||||||
print('📋 可用命令:');
|
_logger
|
||||||
print('');
|
..info('📋 可用命令:')
|
||||||
|
..info('');
|
||||||
|
|
||||||
for (final command in _commands.values) {
|
for (final command in _commands.values) {
|
||||||
print(' ${command.name.padRight(12)} ${command.description}');
|
_logger.info(' ${command.name.padRight(12)} ${command.description}');
|
||||||
}
|
}
|
||||||
|
|
||||||
print(' help 显示帮助信息');
|
_logger
|
||||||
print(' version 显示版本信息');
|
..info(' help 显示帮助信息')
|
||||||
print('');
|
..info(' version 显示版本信息')
|
||||||
|
..info('');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 显示全局选项
|
/// 显示全局选项
|
||||||
void _showGlobalOptions() {
|
void _showGlobalOptions() {
|
||||||
print('🔧 全局选项:');
|
_logger
|
||||||
print(' -h, --help 显示帮助信息');
|
..info('🔧 全局选项:')
|
||||||
print(' --version 显示版本信息');
|
..info(' -h, --help 显示帮助信息')
|
||||||
print('');
|
..info(' --version 显示版本信息')
|
||||||
|
..info('');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 显示使用示例
|
/// 显示使用示例
|
||||||
void _showExamples() {
|
void _showExamples() {
|
||||||
print('💡 使用示例:');
|
_logger
|
||||||
print('');
|
..info('💡 使用示例:')
|
||||||
print(' # 生成所有文件');
|
..info('')
|
||||||
print(' dart swagger_cli_new.dart generate --all');
|
..info(' # 生成所有文件')
|
||||||
print('');
|
..info(' dart swagger_cli_new.dart generate --all')
|
||||||
print(' # 只生成模型文件(简洁版本)');
|
..info('')
|
||||||
print(' dart swagger_cli_new.dart generate --models --simple');
|
..info(' # 只生成模型文件(简洁版本)')
|
||||||
print('');
|
..info(' dart swagger_cli_new.dart generate --models --simple')
|
||||||
print(' # 生成到指定目录并启用性能监控');
|
..info('')
|
||||||
print(
|
..info(' # 生成到指定目录并启用性能监控')
|
||||||
' dart swagger_cli_new.dart generate --all --output-dir lib/generated --performance',
|
..info(
|
||||||
);
|
' dart swagger_cli_new.dart generate --all --output-dir lib/generated --performance',
|
||||||
print('');
|
)
|
||||||
print(' # 查看具体命令的帮助');
|
..info('')
|
||||||
print(' dart swagger_cli_new.dart generate --help');
|
..info(' # 查看具体命令的帮助')
|
||||||
print('');
|
..info(' dart swagger_cli_new.dart generate --help')
|
||||||
|
..info('');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 显示联系信息
|
/// 显示联系信息
|
||||||
void _showContact() {
|
void _showContact() {
|
||||||
print('🌐 更多信息:');
|
_logger
|
||||||
print(' API文档: ${SwaggerConfig.swaggerJsonUrls.first}');
|
..info('🌐 更多信息:')
|
||||||
print(' 基础URL: ${SwaggerConfig.baseUrl}');
|
..info(' API文档: ${SwaggerConfig.swaggerJsonUrls.first}')
|
||||||
print('');
|
..info(' 基础URL: ${SwaggerConfig.baseUrl}')
|
||||||
|
..info('');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 显示版本信息
|
/// 显示版本信息
|
||||||
void _showVersion() {
|
void _showVersion() {
|
||||||
print('Swagger CLI v2.0.0');
|
_logger
|
||||||
print('构建于: ${DateTime.now().toIso8601String()}');
|
..info('Swagger CLI v2.0.0')
|
||||||
print('Dart SDK: ${Platform.version}');
|
..info('构建于: ${DateTime.now().toIso8601String()}')
|
||||||
print('');
|
..info('Dart SDK: ${Platform.version}')
|
||||||
print('功能特性:');
|
..info('')
|
||||||
print('- 🏗️ 模块化命令架构');
|
..info('功能特性:')
|
||||||
print('- 🚀 性能监控和优化');
|
..info('- 🏗️ 模块化命令架构')
|
||||||
print('- 🔍 智能类型验证');
|
..info('- 🚀 性能监控和优化')
|
||||||
print('- 📋 详细的错误报告');
|
..info('- 🔍 智能类型验证')
|
||||||
print('- 💾 智能缓存机制');
|
..info('- 📋 详细的错误报告')
|
||||||
print('- 📚 丰富的文档生成');
|
..info('- 💾 智能缓存机制')
|
||||||
print('');
|
..info('- 📚 丰富的文档生成')
|
||||||
|
..info('');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 格式化持续时间
|
/// 格式化持续时间
|
||||||
|
|
@ -186,6 +198,16 @@ class SwaggerCLI {
|
||||||
|
|
||||||
/// CLI应用程序入口点
|
/// CLI应用程序入口点
|
||||||
Future<void> main(List<String> arguments) async {
|
Future<void> main(List<String> arguments) async {
|
||||||
|
// 配置日志
|
||||||
|
Logger.root.level = Level.ALL;
|
||||||
|
Logger.root.onRecord.listen((record) {
|
||||||
|
if (record.level >= Level.SEVERE) {
|
||||||
|
stderr.writeln(record.message);
|
||||||
|
} else {
|
||||||
|
stdout.writeln(record.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
final cli = SwaggerCLI();
|
final cli = SwaggerCLI();
|
||||||
final exitCode = await cli.run(arguments);
|
final exitCode = await cli.run(arguments);
|
||||||
exit(exitCode);
|
exit(exitCode);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
/// Swagger Generator Flutter
|
/// Swagger Generator Flutter
|
||||||
///
|
///
|
||||||
/// 一个强大的 Flutter OpenAPI 3.0 代码生成器,专门为 Dio + Retrofit 架构优化。
|
/// 一个强大的 Flutter OpenAPI 3.0 代码生成器,专门为 Dio + Retrofit 架构优化。
|
||||||
library swagger_generator_flutter;
|
library;
|
||||||
|
|
||||||
export 'core/error_reporter.dart';
|
export 'core/error_reporter.dart';
|
||||||
// 核心模型
|
// 核心模型
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,17 @@
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
|
|
||||||
import 'string_utils.dart';
|
import 'package:swagger_generator_flutter/utils/string_utils.dart';
|
||||||
|
|
||||||
/// 缓存管理器
|
/// 缓存管理器
|
||||||
/// 提供简单的内存缓存功能
|
/// 提供简单的内存缓存功能
|
||||||
class CacheManager {
|
class CacheManager {
|
||||||
|
CacheManager();
|
||||||
static const int _maxMemoryItems = 100;
|
static const int _maxMemoryItems = 100;
|
||||||
|
|
||||||
// 内存缓存
|
// 内存缓存
|
||||||
final Map<String, _CacheEntry> _memoryCache = {};
|
final Map<String, _CacheEntry> _memoryCache = {};
|
||||||
final Queue<String> _accessOrder = Queue<String>();
|
final Queue<String> _accessOrder = Queue<String>();
|
||||||
|
|
||||||
CacheManager();
|
|
||||||
|
|
||||||
/// 从缓存获取数据
|
/// 从缓存获取数据
|
||||||
T? get<T>(String key) {
|
T? get<T>(String key) {
|
||||||
final entry = _memoryCache[key];
|
final entry = _memoryCache[key];
|
||||||
|
|
@ -64,15 +63,16 @@ class CacheManager {
|
||||||
return CacheStats(
|
return CacheStats(
|
||||||
memoryItems: _memoryCache.length,
|
memoryItems: _memoryCache.length,
|
||||||
diskItems: 0,
|
diskItems: 0,
|
||||||
hitRate: 0.0,
|
hitRate: 0,
|
||||||
totalSize: 0,
|
totalSize: 0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 更新访问顺序
|
/// 更新访问顺序
|
||||||
void _updateAccessOrder(String key) {
|
void _updateAccessOrder(String key) {
|
||||||
_accessOrder.removeWhere((k) => k == key);
|
_accessOrder
|
||||||
_accessOrder.addLast(key);
|
..removeWhere((k) => k == key)
|
||||||
|
..addLast(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 驱逐最旧的项
|
/// 驱逐最旧的项
|
||||||
|
|
@ -86,30 +86,28 @@ class CacheManager {
|
||||||
|
|
||||||
/// 缓存条目
|
/// 缓存条目
|
||||||
class _CacheEntry {
|
class _CacheEntry {
|
||||||
final dynamic value;
|
|
||||||
final DateTime expiresAt;
|
|
||||||
|
|
||||||
_CacheEntry({
|
_CacheEntry({
|
||||||
required this.value,
|
required this.value,
|
||||||
required this.expiresAt,
|
required this.expiresAt,
|
||||||
});
|
});
|
||||||
|
final dynamic value;
|
||||||
|
final DateTime expiresAt;
|
||||||
|
|
||||||
bool get isExpired => DateTime.now().isAfter(expiresAt);
|
bool get isExpired => DateTime.now().isAfter(expiresAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 缓存统计信息
|
/// 缓存统计信息
|
||||||
class CacheStats {
|
class CacheStats {
|
||||||
final int memoryItems;
|
|
||||||
final int diskItems;
|
|
||||||
final double hitRate;
|
|
||||||
final int totalSize;
|
|
||||||
|
|
||||||
const CacheStats({
|
const CacheStats({
|
||||||
required this.memoryItems,
|
required this.memoryItems,
|
||||||
required this.diskItems,
|
required this.diskItems,
|
||||||
required this.hitRate,
|
required this.hitRate,
|
||||||
required this.totalSize,
|
required this.totalSize,
|
||||||
});
|
});
|
||||||
|
final int memoryItems;
|
||||||
|
final int diskItems;
|
||||||
|
final double hitRate;
|
||||||
|
final int totalSize;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
/// 文件工具类
|
/// 文件工具类
|
||||||
/// 提供文件操作、目录管理和代码格式化功能
|
/// 提供文件操作、目录管理和代码格式化功能
|
||||||
class FileUtils {
|
class FileUtils {
|
||||||
|
static final Logger _logger = Logger('FileUtils');
|
||||||
|
|
||||||
/// 解析路径(支持相对路径和绝对路径)
|
/// 解析路径(支持相对路径和绝对路径)
|
||||||
/// 如果是相对路径,相对于项目根目录(配置文件所在目录)
|
/// 如果是相对路径,相对于项目根目录(配置文件所在目录)
|
||||||
static String resolvePath(String filePath) {
|
static String resolvePath(String filePath) {
|
||||||
|
|
@ -27,7 +30,7 @@ class FileUtils {
|
||||||
/// 查找配置文件
|
/// 查找配置文件
|
||||||
static String? _findConfigFile() {
|
static String? _findConfigFile() {
|
||||||
var currentDir = Directory.current;
|
var currentDir = Directory.current;
|
||||||
final maxDepth = 10;
|
const maxDepth = 10;
|
||||||
var depth = 0;
|
var depth = 0;
|
||||||
|
|
||||||
while (depth < maxDepth) {
|
while (depth < maxDepth) {
|
||||||
|
|
@ -52,7 +55,7 @@ class FileUtils {
|
||||||
static Future<Directory> ensureDirectoryExists(String dirPath) async {
|
static Future<Directory> ensureDirectoryExists(String dirPath) async {
|
||||||
final resolvedPath = resolvePath(dirPath);
|
final resolvedPath = resolvePath(dirPath);
|
||||||
final directory = Directory(resolvedPath);
|
final directory = Directory(resolvedPath);
|
||||||
if (!await directory.exists()) {
|
if (!directory.existsSync()) {
|
||||||
await directory.create(recursive: true);
|
await directory.create(recursive: true);
|
||||||
}
|
}
|
||||||
return directory;
|
return directory;
|
||||||
|
|
@ -66,13 +69,15 @@ class FileUtils {
|
||||||
final directory = file.parent;
|
final directory = file.parent;
|
||||||
|
|
||||||
// 确保目录存在
|
// 确保目录存在
|
||||||
if (!await directory.exists()) {
|
if (!directory.existsSync()) {
|
||||||
await directory.create(recursive: true);
|
await directory.create(recursive: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 写入文件
|
// 写入文件
|
||||||
await file.writeAsString(content);
|
await file.writeAsString(content);
|
||||||
} catch (e) {
|
} on FileSystemException {
|
||||||
|
rethrow;
|
||||||
|
} on Object {
|
||||||
throw FileSystemException('写入文件失败: $filePath', filePath);
|
throw FileSystemException('写入文件失败: $filePath', filePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -81,30 +86,32 @@ class FileUtils {
|
||||||
static Future<String> safeReadFile(String filePath) async {
|
static Future<String> safeReadFile(String filePath) async {
|
||||||
try {
|
try {
|
||||||
final file = File(filePath);
|
final file = File(filePath);
|
||||||
if (!await file.exists()) {
|
if (!file.existsSync()) {
|
||||||
throw FileSystemException('文件不存在: $filePath', filePath);
|
throw FileSystemException('文件不存在: $filePath', filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await file.readAsString();
|
return await file.readAsString();
|
||||||
} catch (e) {
|
} on FileSystemException {
|
||||||
|
rethrow;
|
||||||
|
} on Object {
|
||||||
throw FileSystemException('读取文件失败: $filePath', filePath);
|
throw FileSystemException('读取文件失败: $filePath', filePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 检查文件是否存在
|
/// 检查文件是否存在
|
||||||
static Future<bool> fileExists(String filePath) async {
|
static Future<bool> fileExists(String filePath) async {
|
||||||
return await File(filePath).exists();
|
return File(filePath).existsSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 检查目录是否存在
|
/// 检查目录是否存在
|
||||||
static Future<bool> directoryExists(String dirPath) async {
|
static Future<bool> directoryExists(String dirPath) async {
|
||||||
return await Directory(dirPath).exists();
|
return Directory(dirPath).existsSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 删除文件(如果存在)
|
/// 删除文件(如果存在)
|
||||||
static Future<void> deleteFileIfExists(String filePath) async {
|
static Future<void> deleteFileIfExists(String filePath) async {
|
||||||
final file = File(filePath);
|
final file = File(filePath);
|
||||||
if (await file.exists()) {
|
if (file.existsSync()) {
|
||||||
await file.delete();
|
await file.delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -112,56 +119,66 @@ class FileUtils {
|
||||||
/// 删除目录(如果存在)
|
/// 删除目录(如果存在)
|
||||||
static Future<void> deleteDirectoryIfExists(String dirPath) async {
|
static Future<void> deleteDirectoryIfExists(String dirPath) async {
|
||||||
final directory = Directory(dirPath);
|
final directory = Directory(dirPath);
|
||||||
if (await directory.exists()) {
|
if (directory.existsSync()) {
|
||||||
await directory.delete(recursive: true);
|
await directory.delete(recursive: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 复制文件
|
/// 复制文件
|
||||||
static Future<void> copyFile(
|
static Future<void> copyFile(
|
||||||
String sourcePath, String destinationPath) async {
|
String sourcePath,
|
||||||
|
String destinationPath,
|
||||||
|
) async {
|
||||||
try {
|
try {
|
||||||
final sourceFile = File(sourcePath);
|
final sourceFile = File(sourcePath);
|
||||||
final destinationFile = File(destinationPath);
|
final destinationFile = File(destinationPath);
|
||||||
|
|
||||||
if (!await sourceFile.exists()) {
|
if (!sourceFile.existsSync()) {
|
||||||
throw FileSystemException('源文件不存在: $sourcePath', sourcePath);
|
throw FileSystemException('源文件不存在: $sourcePath', sourcePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保目标目录存在
|
// 确保目标目录存在
|
||||||
final destinationDir = destinationFile.parent;
|
final destinationDir = destinationFile.parent;
|
||||||
if (!await destinationDir.exists()) {
|
if (!destinationDir.existsSync()) {
|
||||||
await destinationDir.create(recursive: true);
|
await destinationDir.create(recursive: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
await sourceFile.copy(destinationPath);
|
await sourceFile.copy(destinationPath);
|
||||||
} catch (e) {
|
} on Object catch (e) {
|
||||||
throw FileSystemException('复制文件失败: $sourcePath -> $destinationPath',
|
throw FileSystemException(
|
||||||
sourcePath, e is OSError ? e : null);
|
'复制文件失败: $sourcePath -> $destinationPath',
|
||||||
|
sourcePath,
|
||||||
|
e is OSError ? e : null,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 移动文件
|
/// 移动文件
|
||||||
static Future<void> moveFile(
|
static Future<void> moveFile(
|
||||||
String sourcePath, String destinationPath) async {
|
String sourcePath,
|
||||||
|
String destinationPath,
|
||||||
|
) async {
|
||||||
try {
|
try {
|
||||||
final sourceFile = File(sourcePath);
|
final sourceFile = File(sourcePath);
|
||||||
final destinationFile = File(destinationPath);
|
final destinationFile = File(destinationPath);
|
||||||
|
|
||||||
if (!await sourceFile.exists()) {
|
if (!sourceFile.existsSync()) {
|
||||||
throw FileSystemException('源文件不存在: $sourcePath', sourcePath);
|
throw FileSystemException('源文件不存在: $sourcePath', sourcePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保目标目录存在
|
// 确保目标目录存在
|
||||||
final destinationDir = destinationFile.parent;
|
final destinationDir = destinationFile.parent;
|
||||||
if (!await destinationDir.exists()) {
|
if (!destinationDir.existsSync()) {
|
||||||
await destinationDir.create(recursive: true);
|
await destinationDir.create(recursive: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
await sourceFile.rename(destinationPath);
|
await sourceFile.rename(destinationPath);
|
||||||
} catch (e) {
|
} on Object catch (e) {
|
||||||
throw FileSystemException('移动文件失败: $sourcePath -> $destinationPath',
|
throw FileSystemException(
|
||||||
sourcePath, e is OSError ? e : null);
|
'移动文件失败: $sourcePath -> $destinationPath',
|
||||||
|
sourcePath,
|
||||||
|
e is OSError ? e : null,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -169,11 +186,11 @@ class FileUtils {
|
||||||
static Future<int> getFileSize(String filePath) async {
|
static Future<int> getFileSize(String filePath) async {
|
||||||
try {
|
try {
|
||||||
final file = File(filePath);
|
final file = File(filePath);
|
||||||
if (!await file.exists()) {
|
if (!file.existsSync()) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return await file.length();
|
return await file.length();
|
||||||
} catch (e) {
|
} on Object {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -186,21 +203,23 @@ class FileUtils {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int totalSize = 0;
|
var totalSize = 0;
|
||||||
await for (final entity in directory.list(recursive: true)) {
|
for (final entity in directory.listSync(recursive: true)) {
|
||||||
if (entity is File) {
|
if (entity is File) {
|
||||||
totalSize += await entity.length();
|
totalSize += entity.lengthSync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return totalSize;
|
return totalSize;
|
||||||
} catch (e) {
|
} on Object {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 列出目录中的文件
|
/// 列出目录中的文件
|
||||||
static Future<List<String>> listFiles(String dirPath,
|
static Future<List<String>> listFiles(
|
||||||
{String? extension}) async {
|
String dirPath, {
|
||||||
|
String? extension,
|
||||||
|
}) async {
|
||||||
try {
|
try {
|
||||||
final directory = Directory(dirPath);
|
final directory = Directory(dirPath);
|
||||||
if (!await directory.exists()) {
|
if (!await directory.exists()) {
|
||||||
|
|
@ -208,7 +227,7 @@ class FileUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
final files = <String>[];
|
final files = <String>[];
|
||||||
await for (final entity in directory.list()) {
|
for (final entity in directory.listSync()) {
|
||||||
if (entity is File) {
|
if (entity is File) {
|
||||||
if (extension == null || entity.path.endsWith(extension)) {
|
if (extension == null || entity.path.endsWith(extension)) {
|
||||||
files.add(entity.path);
|
files.add(entity.path);
|
||||||
|
|
@ -216,7 +235,7 @@ class FileUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return files;
|
return files;
|
||||||
} catch (e) {
|
} on Object {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -230,13 +249,13 @@ class FileUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
final directories = <String>[];
|
final directories = <String>[];
|
||||||
await for (final entity in directory.list()) {
|
for (final entity in directory.listSync()) {
|
||||||
if (entity is Directory) {
|
if (entity is Directory) {
|
||||||
directories.add(entity.path);
|
directories.add(entity.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return directories;
|
return directories;
|
||||||
} catch (e) {
|
} on Object {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -245,34 +264,42 @@ class FileUtils {
|
||||||
static Future<String> createBackup(String filePath) async {
|
static Future<String> createBackup(String filePath) async {
|
||||||
try {
|
try {
|
||||||
final file = File(filePath);
|
final file = File(filePath);
|
||||||
if (!await file.exists()) {
|
if (!file.existsSync()) {
|
||||||
throw FileSystemException('文件不存在: $filePath', filePath);
|
throw FileSystemException('文件不存在: $filePath', filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
final timestamp = DateTime.now().toIso8601String().replaceAll(':', '-');
|
final timestamp = DateTime.now().toIso8601String().replaceAll(':', '-');
|
||||||
final backupPath = '${filePath}.backup.$timestamp';
|
final backupPath = '$filePath.backup.$timestamp';
|
||||||
|
|
||||||
await file.copy(backupPath);
|
await file.copy(backupPath);
|
||||||
return backupPath;
|
return backupPath;
|
||||||
} catch (e) {
|
} on Object catch (e) {
|
||||||
throw FileSystemException(
|
throw FileSystemException(
|
||||||
'创建备份失败: $filePath', filePath, e is OSError ? e : null);
|
'创建备份失败: $filePath',
|
||||||
|
filePath,
|
||||||
|
e is OSError ? e : null,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 恢复备份文件
|
/// 恢复备份文件
|
||||||
static Future<void> restoreBackup(
|
static Future<void> restoreBackup(
|
||||||
String backupPath, String originalPath) async {
|
String backupPath,
|
||||||
|
String originalPath,
|
||||||
|
) async {
|
||||||
try {
|
try {
|
||||||
final backupFile = File(backupPath);
|
final backupFile = File(backupPath);
|
||||||
if (!await backupFile.exists()) {
|
if (!backupFile.existsSync()) {
|
||||||
throw FileSystemException('备份文件不存在: $backupPath', backupPath);
|
throw FileSystemException('备份文件不存在: $backupPath', backupPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
await backupFile.copy(originalPath);
|
await backupFile.copy(originalPath);
|
||||||
} catch (e) {
|
} on Object catch (e) {
|
||||||
throw FileSystemException('恢复备份失败: $backupPath -> $originalPath',
|
throw FileSystemException(
|
||||||
backupPath, e is OSError ? e : null);
|
'恢复备份失败: $backupPath -> $originalPath',
|
||||||
|
backupPath,
|
||||||
|
e is OSError ? e : null,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -327,20 +354,22 @@ class FileUtils {
|
||||||
return fileName
|
return fileName
|
||||||
.replaceAll(RegExp(r'[<>:"/\\|?*]'), '_')
|
.replaceAll(RegExp(r'[<>:"/\\|?*]'), '_')
|
||||||
.replaceAll(RegExp(r'\s+'), '_')
|
.replaceAll(RegExp(r'\s+'), '_')
|
||||||
.replaceAll(RegExp(r'_{2,}'), '_')
|
.replaceAll(RegExp('_{2,}'), '_')
|
||||||
.replaceAll(RegExp(r'^_|_$'), '');
|
.replaceAll(RegExp(r'^_|_$'), '');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 生成唯一文件名
|
/// 生成唯一文件名
|
||||||
static Future<String> generateUniqueFileName(
|
static Future<String> generateUniqueFileName(
|
||||||
String basePath, String fileName) async {
|
String basePath,
|
||||||
|
String fileName,
|
||||||
|
) async {
|
||||||
final extension = getFileExtension(fileName);
|
final extension = getFileExtension(fileName);
|
||||||
final nameWithoutExt = getFileNameWithoutExtension(fileName);
|
final nameWithoutExt = getFileNameWithoutExtension(fileName);
|
||||||
|
|
||||||
String uniqueName = fileName;
|
var uniqueName = fileName;
|
||||||
int counter = 1;
|
var counter = 1;
|
||||||
|
|
||||||
while (await File(path.join(basePath, uniqueName)).exists()) {
|
while (File(path.join(basePath, uniqueName)).existsSync()) {
|
||||||
uniqueName = '${nameWithoutExt}_$counter$extension';
|
uniqueName = '${nameWithoutExt}_$counter$extension';
|
||||||
counter++;
|
counter++;
|
||||||
}
|
}
|
||||||
|
|
@ -356,8 +385,8 @@ class FileUtils {
|
||||||
for (final filePath in filePaths) {
|
for (final filePath in filePaths) {
|
||||||
try {
|
try {
|
||||||
await operation(filePath);
|
await operation(filePath);
|
||||||
} catch (e) {
|
} on Object catch (e) {
|
||||||
print('批量操作失败: $filePath - $e');
|
_logger.warning('批量操作失败: $filePath - $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -377,7 +406,7 @@ class FileUtils {
|
||||||
final regex = RegExp(pattern);
|
final regex = RegExp(pattern);
|
||||||
final foundFiles = <String>[];
|
final foundFiles = <String>[];
|
||||||
|
|
||||||
await for (final entity in directory.list(recursive: recursive)) {
|
for (final entity in directory.listSync(recursive: recursive)) {
|
||||||
if (entity is File) {
|
if (entity is File) {
|
||||||
final fileName = getFileName(entity.path);
|
final fileName = getFileName(entity.path);
|
||||||
if (regex.hasMatch(fileName)) {
|
if (regex.hasMatch(fileName)) {
|
||||||
|
|
@ -387,7 +416,7 @@ class FileUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
return foundFiles;
|
return foundFiles;
|
||||||
} catch (e) {
|
} on Object {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -396,13 +425,13 @@ class FileUtils {
|
||||||
static Future<DateTime?> getFileModifiedTime(String filePath) async {
|
static Future<DateTime?> getFileModifiedTime(String filePath) async {
|
||||||
try {
|
try {
|
||||||
final file = File(filePath);
|
final file = File(filePath);
|
||||||
if (!await file.exists()) {
|
if (!file.existsSync()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final stat = await file.stat();
|
final stat = file.statSync();
|
||||||
return stat.modified;
|
return stat.modified;
|
||||||
} catch (e) {
|
} on Object {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -423,13 +452,13 @@ class FileUtils {
|
||||||
static Future<String?> calculateFileHash(String filePath) async {
|
static Future<String?> calculateFileHash(String filePath) async {
|
||||||
try {
|
try {
|
||||||
final file = File(filePath);
|
final file = File(filePath);
|
||||||
if (!await file.exists()) {
|
if (!file.existsSync()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final bytes = await file.readAsBytes();
|
final bytes = await file.readAsBytes();
|
||||||
return bytes.hashCode.toString();
|
return bytes.hashCode.toString();
|
||||||
} catch (e) {
|
} on Object {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -470,8 +499,8 @@ class FileUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} on Object catch (e) {
|
||||||
print('清理临时文件失败: $e');
|
_logger.warning('清理临时文件失败: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -490,11 +519,11 @@ class FileUtils {
|
||||||
static Future<List<FileSystemEntity>> listDirectory(String path) async {
|
static Future<List<FileSystemEntity>> listDirectory(String path) async {
|
||||||
try {
|
try {
|
||||||
final directory = Directory(path);
|
final directory = Directory(path);
|
||||||
if (!await directory.exists()) {
|
if (!directory.existsSync()) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return await directory.list().toList();
|
return await directory.list().toList();
|
||||||
} catch (e) {
|
} on Object {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,23 +6,24 @@ import 'package:logging/logging.dart';
|
||||||
/// 性能监控器
|
/// 性能监控器
|
||||||
/// 用于监控和记录应用程序性能指标
|
/// 用于监控和记录应用程序性能指标
|
||||||
class PerformanceMonitor {
|
class PerformanceMonitor {
|
||||||
|
PerformanceMonitor({bool enabled = true}) : _enabled = enabled {
|
||||||
|
if (_enabled) {
|
||||||
|
_logger.info('性能监控器已启用');
|
||||||
|
}
|
||||||
|
}
|
||||||
static final Logger _logger = Logger('PerformanceMonitor');
|
static final Logger _logger = Logger('PerformanceMonitor');
|
||||||
|
|
||||||
final Map<String, PerformanceMetric> _metrics = {};
|
final Map<String, PerformanceMetric> _metrics = {};
|
||||||
final Map<String, List<Duration>> _measurements = {};
|
final Map<String, List<Duration>> _measurements = {};
|
||||||
final bool _enabled;
|
final bool _enabled;
|
||||||
|
|
||||||
PerformanceMonitor({bool enabled = true}) : _enabled = enabled {
|
|
||||||
if (_enabled) {
|
|
||||||
_logger.info('性能监控器已启用');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 测量异步操作的执行时间
|
/// 测量异步操作的执行时间
|
||||||
Future<T> measure<T>(
|
Future<T> measure<T>(
|
||||||
String operationName, Future<T> Function() operation) async {
|
String operationName,
|
||||||
|
Future<T> Function() operation,
|
||||||
|
) async {
|
||||||
if (!_enabled) {
|
if (!_enabled) {
|
||||||
return await operation();
|
return operation();
|
||||||
}
|
}
|
||||||
|
|
||||||
final stopwatch = Stopwatch()..start();
|
final stopwatch = Stopwatch()..start();
|
||||||
|
|
@ -34,7 +35,7 @@ class PerformanceMonitor {
|
||||||
_recordMeasurement(operationName, stopwatch.elapsed);
|
_recordMeasurement(operationName, stopwatch.elapsed);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (e) {
|
} on Object catch (e) {
|
||||||
stopwatch.stop();
|
stopwatch.stop();
|
||||||
_recordMeasurement(operationName, stopwatch.elapsed, error: e);
|
_recordMeasurement(operationName, stopwatch.elapsed, error: e);
|
||||||
rethrow;
|
rethrow;
|
||||||
|
|
@ -56,7 +57,7 @@ class PerformanceMonitor {
|
||||||
_recordMeasurement(operationName, stopwatch.elapsed);
|
_recordMeasurement(operationName, stopwatch.elapsed);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (e) {
|
} on Object catch (e) {
|
||||||
stopwatch.stop();
|
stopwatch.stop();
|
||||||
_recordMeasurement(operationName, stopwatch.elapsed, error: e);
|
_recordMeasurement(operationName, stopwatch.elapsed, error: e);
|
||||||
rethrow;
|
rethrow;
|
||||||
|
|
@ -69,8 +70,11 @@ class PerformanceMonitor {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 记录测量结果
|
/// 记录测量结果
|
||||||
void _recordMeasurement(String operationName, Duration duration,
|
void _recordMeasurement(
|
||||||
{dynamic error}) {
|
String operationName,
|
||||||
|
Duration duration, {
|
||||||
|
dynamic error,
|
||||||
|
}) {
|
||||||
if (!_enabled) return;
|
if (!_enabled) return;
|
||||||
|
|
||||||
// 记录到测量历史中
|
// 记录到测量历史中
|
||||||
|
|
@ -124,18 +128,21 @@ class PerformanceMonitor {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取慢操作(执行时间超过阈值)
|
/// 获取慢操作(执行时间超过阈值)
|
||||||
List<SlowOperation> getSlowOperations(
|
List<SlowOperation> getSlowOperations([
|
||||||
[Duration threshold = const Duration(milliseconds: 500)]) {
|
Duration threshold = const Duration(milliseconds: 500),
|
||||||
|
]) {
|
||||||
final slowOps = <SlowOperation>[];
|
final slowOps = <SlowOperation>[];
|
||||||
|
|
||||||
for (final metric in _metrics.values) {
|
for (final metric in _metrics.values) {
|
||||||
if (metric.maxTime >= threshold) {
|
if (metric.maxTime >= threshold) {
|
||||||
slowOps.add(SlowOperation(
|
slowOps.add(
|
||||||
name: metric.operationName,
|
SlowOperation(
|
||||||
maxTime: metric.maxTime,
|
name: metric.operationName,
|
||||||
avgTime: metric.averageTime,
|
maxTime: metric.maxTime,
|
||||||
count: metric.count,
|
avgTime: metric.averageTime,
|
||||||
));
|
count: metric.count,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,16 +164,18 @@ class PerformanceMonitor {
|
||||||
'totalOperations': report.totalOperations,
|
'totalOperations': report.totalOperations,
|
||||||
'totalTime': report.totalTime.inMilliseconds,
|
'totalTime': report.totalTime.inMilliseconds,
|
||||||
},
|
},
|
||||||
'metrics': report.metrics.map((key, value) => MapEntry(key, {
|
'metrics': report.metrics.map(
|
||||||
'operationName': value.operationName,
|
(key, value) => MapEntry(key, {
|
||||||
'count': value.count,
|
'operationName': value.operationName,
|
||||||
'totalTime': value.totalTime.inMilliseconds,
|
'count': value.count,
|
||||||
'averageTime': value.averageTime.inMilliseconds,
|
'totalTime': value.totalTime.inMilliseconds,
|
||||||
'minTime': value.minTime.inMilliseconds,
|
'averageTime': value.averageTime.inMilliseconds,
|
||||||
'maxTime': value.maxTime.inMilliseconds,
|
'minTime': value.minTime.inMilliseconds,
|
||||||
'errorCount': value.errorCount,
|
'maxTime': value.maxTime.inMilliseconds,
|
||||||
'lastExecuted': value.lastExecuted?.toIso8601String(),
|
'errorCount': value.errorCount,
|
||||||
})),
|
'lastExecuted': value.lastExecuted?.toIso8601String(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
final file = File(filePath);
|
final file = File(filePath);
|
||||||
|
|
@ -181,53 +190,56 @@ class PerformanceMonitor {
|
||||||
/// 打印性能摘要
|
/// 打印性能摘要
|
||||||
void printSummary() {
|
void printSummary() {
|
||||||
if (!_enabled) {
|
if (!_enabled) {
|
||||||
print('性能监控器已禁用');
|
_logger.info('性能监控器已禁用');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_metrics.isEmpty) {
|
if (_metrics.isEmpty) {
|
||||||
print('没有性能数据');
|
_logger.info('没有性能数据');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
print('\n🔍 性能监控摘要:');
|
final buffer = StringBuffer()
|
||||||
print('=' * 50);
|
..writeln('\n🔍 性能监控摘要:')
|
||||||
|
..writeln('=' * 50);
|
||||||
|
|
||||||
final sortedMetrics = _metrics.values.toList()
|
final sortedMetrics = _metrics.values.toList()
|
||||||
..sort((a, b) => b.totalTime.compareTo(a.totalTime));
|
..sort((a, b) => b.totalTime.compareTo(a.totalTime));
|
||||||
|
|
||||||
for (final metric in sortedMetrics) {
|
for (final metric in sortedMetrics) {
|
||||||
print('${metric.operationName}:');
|
buffer
|
||||||
print(' 执行次数: ${metric.count}');
|
..writeln('${metric.operationName}:')
|
||||||
print(' 总时间: ${metric.totalTime.inMilliseconds}ms');
|
..writeln(' 执行次数: ${metric.count}')
|
||||||
print(' 平均时间: ${metric.averageTime.inMilliseconds}ms');
|
..writeln(' 总时间: ${metric.totalTime.inMilliseconds}ms')
|
||||||
print(' 最小时间: ${metric.minTime.inMilliseconds}ms');
|
..writeln(' 平均时间: ${metric.averageTime.inMilliseconds}ms')
|
||||||
print(' 最大时间: ${metric.maxTime.inMilliseconds}ms');
|
..writeln(' 最小时间: ${metric.minTime.inMilliseconds}ms')
|
||||||
|
..writeln(' 最大时间: ${metric.maxTime.inMilliseconds}ms');
|
||||||
if (metric.errorCount > 0) {
|
if (metric.errorCount > 0) {
|
||||||
print(' 错误次数: ${metric.errorCount}');
|
buffer.writeln(' 错误次数: ${metric.errorCount}');
|
||||||
}
|
}
|
||||||
print('');
|
buffer.writeln();
|
||||||
}
|
}
|
||||||
|
|
||||||
final slowOps = getSlowOperations();
|
final slowOps = getSlowOperations();
|
||||||
if (slowOps.isNotEmpty) {
|
if (slowOps.isNotEmpty) {
|
||||||
print('🐌 慢操作 (>500ms):');
|
buffer.writeln('🐌 慢操作 (>500ms):');
|
||||||
for (final op in slowOps.take(5)) {
|
for (final op in slowOps.take(5)) {
|
||||||
print(' ${op.name}: ${op.maxTime.inMilliseconds}ms (最大)');
|
buffer.writeln(' ${op.name}: ${op.maxTime.inMilliseconds}ms (最大)');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_logger.info(buffer.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 性能计时器
|
/// 性能计时器
|
||||||
class PerformanceTimer {
|
class PerformanceTimer {
|
||||||
|
PerformanceTimer(this.operationName, this.monitor)
|
||||||
|
: _stopwatch = Stopwatch()..start();
|
||||||
final String operationName;
|
final String operationName;
|
||||||
final PerformanceMonitor monitor;
|
final PerformanceMonitor monitor;
|
||||||
final Stopwatch _stopwatch;
|
final Stopwatch _stopwatch;
|
||||||
|
|
||||||
PerformanceTimer(this.operationName, this.monitor)
|
|
||||||
: _stopwatch = Stopwatch()..start();
|
|
||||||
|
|
||||||
/// 停止计时并记录结果
|
/// 停止计时并记录结果
|
||||||
void stop({dynamic error}) {
|
void stop({dynamic error}) {
|
||||||
if (!_stopwatch.isRunning) return;
|
if (!_stopwatch.isRunning) return;
|
||||||
|
|
@ -242,6 +254,7 @@ class PerformanceTimer {
|
||||||
|
|
||||||
/// 性能指标
|
/// 性能指标
|
||||||
class PerformanceMetric {
|
class PerformanceMetric {
|
||||||
|
PerformanceMetric(this.operationName);
|
||||||
final String operationName;
|
final String operationName;
|
||||||
int count = 0;
|
int count = 0;
|
||||||
Duration totalTime = Duration.zero;
|
Duration totalTime = Duration.zero;
|
||||||
|
|
@ -250,8 +263,6 @@ class PerformanceMetric {
|
||||||
int errorCount = 0;
|
int errorCount = 0;
|
||||||
DateTime? lastExecuted;
|
DateTime? lastExecuted;
|
||||||
|
|
||||||
PerformanceMetric(this.operationName);
|
|
||||||
|
|
||||||
/// 添加测量结果
|
/// 添加测量结果
|
||||||
void addMeasurement(Duration duration, {dynamic error}) {
|
void addMeasurement(Duration duration, {dynamic error}) {
|
||||||
count++;
|
count++;
|
||||||
|
|
@ -279,24 +290,23 @@ class PerformanceMetric {
|
||||||
|
|
||||||
/// 成功率
|
/// 成功率
|
||||||
double get successRate {
|
double get successRate {
|
||||||
if (count == 0) return 0.0;
|
if (count == 0) return 0;
|
||||||
return (count - errorCount) / count;
|
return (count - errorCount) / count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 性能报告
|
/// 性能报告
|
||||||
class PerformanceReport {
|
class PerformanceReport {
|
||||||
final int totalOperations;
|
|
||||||
final Duration totalTime;
|
|
||||||
final Map<String, PerformanceMetric> metrics;
|
|
||||||
final DateTime generatedAt;
|
|
||||||
|
|
||||||
const PerformanceReport({
|
const PerformanceReport({
|
||||||
required this.totalOperations,
|
required this.totalOperations,
|
||||||
required this.totalTime,
|
required this.totalTime,
|
||||||
required this.metrics,
|
required this.metrics,
|
||||||
required this.generatedAt,
|
required this.generatedAt,
|
||||||
});
|
});
|
||||||
|
final int totalOperations;
|
||||||
|
final Duration totalTime;
|
||||||
|
final Map<String, PerformanceMetric> metrics;
|
||||||
|
final DateTime generatedAt;
|
||||||
|
|
||||||
/// 获取最慢的操作
|
/// 获取最慢的操作
|
||||||
PerformanceMetric? get slowestOperation {
|
PerformanceMetric? get slowestOperation {
|
||||||
|
|
@ -321,15 +331,14 @@ class PerformanceReport {
|
||||||
|
|
||||||
/// 慢操作信息
|
/// 慢操作信息
|
||||||
class SlowOperation {
|
class SlowOperation {
|
||||||
final String name;
|
|
||||||
final Duration maxTime;
|
|
||||||
final Duration avgTime;
|
|
||||||
final int count;
|
|
||||||
|
|
||||||
const SlowOperation({
|
const SlowOperation({
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.maxTime,
|
required this.maxTime,
|
||||||
required this.avgTime,
|
required this.avgTime,
|
||||||
required this.count,
|
required this.count,
|
||||||
});
|
});
|
||||||
|
final String name;
|
||||||
|
final Duration maxTime;
|
||||||
|
final Duration avgTime;
|
||||||
|
final int count;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,17 @@
|
||||||
/// 引用解析器
|
/// 引用解析器
|
||||||
/// 处理复杂嵌套类型和循环引用检测
|
/// 处理复杂嵌套类型和循环引用检测
|
||||||
library reference_resolver;
|
library;
|
||||||
|
|
||||||
import '../core/models.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:swagger_generator_flutter/core/models.dart';
|
||||||
|
|
||||||
/// 引用解析器
|
/// 引用解析器
|
||||||
/// 负责处理 OpenAPI 文档中的复杂引用关系
|
/// 负责处理 OpenAPI 文档中的复杂引用关系
|
||||||
class ReferenceResolver {
|
class ReferenceResolver {
|
||||||
|
ReferenceResolver({this.maxDepth = 50});
|
||||||
|
|
||||||
|
static final Logger _logger = Logger('ReferenceResolver');
|
||||||
|
|
||||||
/// 已解析的模型缓存
|
/// 已解析的模型缓存
|
||||||
final Map<String, ApiModel> _resolvedModels = {};
|
final Map<String, ApiModel> _resolvedModels = {};
|
||||||
|
|
||||||
|
|
@ -22,8 +27,6 @@ class ReferenceResolver {
|
||||||
/// 当前解析深度
|
/// 当前解析深度
|
||||||
int _currentDepth = 0;
|
int _currentDepth = 0;
|
||||||
|
|
||||||
ReferenceResolver({this.maxDepth = 50});
|
|
||||||
|
|
||||||
/// 解析所有模型,处理复杂引用关系
|
/// 解析所有模型,处理复杂引用关系
|
||||||
Map<String, ApiModel> resolveModels(Map<String, dynamic> componentsJson) {
|
Map<String, ApiModel> resolveModels(Map<String, dynamic> componentsJson) {
|
||||||
final schemasJson =
|
final schemasJson =
|
||||||
|
|
@ -40,8 +43,8 @@ class ReferenceResolver {
|
||||||
if (model != null) {
|
if (model != null) {
|
||||||
resolvedModels[schemaName] = model;
|
resolvedModels[schemaName] = model;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} on Object catch (e) {
|
||||||
print('⚠️ 解析模型 $schemaName 时发生错误: $e');
|
_logger.warning('⚠️ 解析模型 $schemaName 时发生错误: $e');
|
||||||
// 创建一个基本的模型作为后备
|
// 创建一个基本的模型作为后备
|
||||||
resolvedModels[schemaName] = ApiModel(
|
resolvedModels[schemaName] = ApiModel(
|
||||||
name: schemaName,
|
name: schemaName,
|
||||||
|
|
@ -73,20 +76,21 @@ class ReferenceResolver {
|
||||||
|
|
||||||
// 检查循环引用
|
// 检查循环引用
|
||||||
if (_resolutionPath.contains(modelName)) {
|
if (_resolutionPath.contains(modelName)) {
|
||||||
print('🔄 检测到循环引用: ${_resolutionPath.join(' -> ')} -> $modelName');
|
_logger
|
||||||
|
.warning('🔄 检测到循环引用: ${_resolutionPath.join(' -> ')} -> $modelName');
|
||||||
return _createCircularReferenceModel(modelName);
|
return _createCircularReferenceModel(modelName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查解析深度
|
// 检查解析深度
|
||||||
if (_currentDepth >= maxDepth) {
|
if (_currentDepth >= maxDepth) {
|
||||||
print('⚠️ 达到最大解析深度 $maxDepth,停止解析 $modelName');
|
_logger.warning('⚠️ 达到最大解析深度 $maxDepth,停止解析 $modelName');
|
||||||
return _createDepthLimitModel(modelName);
|
return _createDepthLimitModel(modelName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取原始数据
|
// 获取原始数据
|
||||||
final schemaData = _rawSchemas[modelName];
|
final schemaData = _rawSchemas[modelName];
|
||||||
if (schemaData == null) {
|
if (schemaData == null) {
|
||||||
print('⚠️ 未找到模型定义: $modelName');
|
_logger.warning('⚠️ 未找到模型定义: $modelName');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,7 +129,7 @@ class ReferenceResolver {
|
||||||
|
|
||||||
/// 解析枚举模型
|
/// 解析枚举模型
|
||||||
ApiModel _parseEnumModel(String name, Map<String, dynamic> json) {
|
ApiModel _parseEnumModel(String name, Map<String, dynamic> json) {
|
||||||
final enumValues = List<dynamic>.from(json['enum'] ?? []);
|
final enumValues = List<dynamic>.from((json['enum'] as List?) ?? []);
|
||||||
final enumType =
|
final enumType =
|
||||||
PropertyType.fromString(json['type'] as String? ?? 'string');
|
PropertyType.fromString(json['type'] as String? ?? 'string');
|
||||||
|
|
||||||
|
|
@ -169,10 +173,11 @@ class ReferenceResolver {
|
||||||
if (json['properties'] != null) {
|
if (json['properties'] != null) {
|
||||||
final directProperties = _parseProperties(
|
final directProperties = _parseProperties(
|
||||||
json['properties'] as Map<String, dynamic>,
|
json['properties'] as Map<String, dynamic>,
|
||||||
List<String>.from(json['required'] ?? []),
|
List<String>.from((json['required'] as List?) ?? []),
|
||||||
);
|
);
|
||||||
mergedProperties.addAll(directProperties);
|
mergedProperties.addAll(directProperties);
|
||||||
mergedRequired.addAll(List<String>.from(json['required'] ?? []));
|
mergedRequired
|
||||||
|
.addAll(List<String>.from((json['required'] as List?) ?? []));
|
||||||
}
|
}
|
||||||
|
|
||||||
return ApiModel(
|
return ApiModel(
|
||||||
|
|
@ -192,10 +197,10 @@ class ReferenceResolver {
|
||||||
ApiModel _parseObjectModel(String name, Map<String, dynamic> json) {
|
ApiModel _parseObjectModel(String name, Map<String, dynamic> json) {
|
||||||
final properties = _parseProperties(
|
final properties = _parseProperties(
|
||||||
json['properties'] as Map<String, dynamic>? ?? {},
|
json['properties'] as Map<String, dynamic>? ?? {},
|
||||||
List<String>.from(json['required'] ?? []),
|
List<String>.from((json['required'] as List?) ?? []),
|
||||||
);
|
);
|
||||||
|
|
||||||
final required = List<String>.from(json['required'] ?? []);
|
final required = List<String>.from((json['required'] as List?) ?? []);
|
||||||
|
|
||||||
return ApiModel(
|
return ApiModel(
|
||||||
name: name,
|
name: name,
|
||||||
|
|
@ -245,8 +250,8 @@ class ReferenceResolver {
|
||||||
final property =
|
final property =
|
||||||
_parsePropertyWithContext(propName, propData, requiredFields);
|
_parsePropertyWithContext(propName, propData, requiredFields);
|
||||||
properties[propName] = property;
|
properties[propName] = property;
|
||||||
} catch (e) {
|
} on Object catch (e) {
|
||||||
print('⚠️ 解析属性 $propName 时发生错误: $e');
|
_logger.warning('⚠️ 解析属性 $propName 时发生错误: $e');
|
||||||
// 创建一个基本属性作为后备
|
// 创建一个基本属性作为后备
|
||||||
properties[propName] = ApiProperty(
|
properties[propName] = ApiProperty(
|
||||||
name: propName,
|
name: propName,
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,13 @@
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// This utility provides string conversion helpers for code generation, such as
|
/// This utility provides string conversion helpers for code generation, such as
|
||||||
/// converting snake_case to camelCase, generating Dart class names, and cleaning descriptions.
|
/// converting snake_case to camelCase, generating Dart class names, and
|
||||||
|
/// cleaning descriptions.
|
||||||
///
|
///
|
||||||
library;
|
library;
|
||||||
|
|
||||||
import '../core/config_loader.dart';
|
import 'package:swagger_generator_flutter/core/config_loader.dart';
|
||||||
import '../core/models.dart';
|
import 'package:swagger_generator_flutter/core/models.dart';
|
||||||
|
|
||||||
class StringUtils {
|
class StringUtils {
|
||||||
/// 转换为camelCase
|
/// 转换为camelCase
|
||||||
|
|
@ -41,8 +42,8 @@ class StringUtils {
|
||||||
final parts = input.split('_').where((p) => p.isNotEmpty).toList();
|
final parts = input.split('_').where((p) => p.isNotEmpty).toList();
|
||||||
if (parts.isEmpty) return input;
|
if (parts.isEmpty) return input;
|
||||||
|
|
||||||
String result = parts.first.toLowerCase();
|
var result = parts.first.toLowerCase();
|
||||||
for (int i = 1; i < parts.length; i++) {
|
for (var i = 1; i < parts.length; i++) {
|
||||||
final part = parts[i];
|
final part = parts[i];
|
||||||
if (part.isNotEmpty) {
|
if (part.isNotEmpty) {
|
||||||
result += part[0].toUpperCase() + part.substring(1).toLowerCase();
|
result += part[0].toUpperCase() + part.substring(1).toLowerCase();
|
||||||
|
|
@ -59,7 +60,7 @@ class StringUtils {
|
||||||
// 如果输入包含下划线,先按下划线分割并转换每个部分
|
// 如果输入包含下划线,先按下划线分割并转换每个部分
|
||||||
if (input.contains('_')) {
|
if (input.contains('_')) {
|
||||||
final parts = input.split('_');
|
final parts = input.split('_');
|
||||||
String result = '';
|
var result = '';
|
||||||
for (final part in parts) {
|
for (final part in parts) {
|
||||||
if (part.isNotEmpty) {
|
if (part.isNotEmpty) {
|
||||||
// 保持每个部分的原始大小写,只确保首字母大写
|
// 保持每个部分的原始大小写,只确保首字母大写
|
||||||
|
|
@ -94,7 +95,7 @@ class StringUtils {
|
||||||
|
|
||||||
// 处理PascalCase和camelCase
|
// 处理PascalCase和camelCase
|
||||||
final result =
|
final result =
|
||||||
input.replaceAllMapped(RegExp(r'([A-Z]+)([A-Z][a-z])'), (match) {
|
input.replaceAllMapped(RegExp('([A-Z]+)([A-Z][a-z])'), (match) {
|
||||||
return '${match[1]!.substring(0, match[1]!.length - 1)}_${match[2]}';
|
return '${match[1]!.substring(0, match[1]!.length - 1)}_${match[2]}';
|
||||||
}).replaceAllMapped(RegExp(r'([a-z\d])([A-Z])'), (match) {
|
}).replaceAllMapped(RegExp(r'([a-z\d])([A-Z])'), (match) {
|
||||||
return '${match[1]}_${match[2]}';
|
return '${match[1]}_${match[2]}';
|
||||||
|
|
@ -130,13 +131,13 @@ class StringUtils {
|
||||||
return propName[0].toLowerCase() + propName.substring(1);
|
return propName[0].toLowerCase() + propName.substring(1);
|
||||||
}
|
}
|
||||||
// 处理特殊字符和数字开头的情况
|
// 处理特殊字符和数字开头的情况
|
||||||
String result = propName;
|
var result = propName;
|
||||||
// 如果以数字开头,添加前缀
|
// 如果以数字开头,添加前缀
|
||||||
if (RegExp(r'^[0-9]').hasMatch(result)) {
|
if (RegExp('^[0-9]').hasMatch(result)) {
|
||||||
result = 'n$result';
|
result = 'n$result';
|
||||||
}
|
}
|
||||||
// 替换特殊字符为下划线
|
// 替换特殊字符为下划线
|
||||||
result = result.replaceAll(RegExp(r'[^a-zA-Z0-9_]'), '_');
|
result = result.replaceAll(RegExp('[^a-zA-Z0-9_]'), '_');
|
||||||
// 转换为camelCase
|
// 转换为camelCase
|
||||||
result = toCamelCase(result);
|
result = toCamelCase(result);
|
||||||
// 确保不为空
|
// 确保不为空
|
||||||
|
|
@ -151,7 +152,7 @@ class StringUtils {
|
||||||
if (description.isEmpty) return description;
|
if (description.isEmpty) return description;
|
||||||
|
|
||||||
// 移除多余的空白字符和换行符
|
// 移除多余的空白字符和换行符
|
||||||
String cleaned = description
|
var cleaned = description
|
||||||
.replaceAll(RegExp(r'\s+'), ' ')
|
.replaceAll(RegExp(r'\s+'), ' ')
|
||||||
.replaceAll(RegExp(r'[\r\n]+'), ' ')
|
.replaceAll(RegExp(r'[\r\n]+'), ' ')
|
||||||
.trim();
|
.trim();
|
||||||
|
|
@ -178,7 +179,7 @@ class StringUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 移除路径中的版本前缀
|
// 移除路径中的版本前缀
|
||||||
String cleanPath = path.replaceFirst('/api/v1', '');
|
var cleanPath = path.replaceFirst('/api/v1', '');
|
||||||
|
|
||||||
// 移除开头的斜杠
|
// 移除开头的斜杠
|
||||||
if (cleanPath.startsWith('/')) {
|
if (cleanPath.startsWith('/')) {
|
||||||
|
|
@ -201,14 +202,14 @@ class StringUtils {
|
||||||
/// 生成Dart类名
|
/// 生成Dart类名
|
||||||
static String generateClassName(String name) {
|
static String generateClassName(String name) {
|
||||||
// 确保类名以大写字母开头
|
// 确保类名以大写字母开头
|
||||||
final cleanName = name.replaceAll(RegExp(r'[^a-zA-Z0-9_]'), '_');
|
final cleanName = name.replaceAll(RegExp('[^a-zA-Z0-9_]'), '_');
|
||||||
return toPascalCase(cleanName);
|
return toPascalCase(cleanName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 生成常量名称 (UPPER_SNAKE_CASE)
|
/// 生成常量名称 (UPPER_SNAKE_CASE)
|
||||||
static String generateConstantName(String name) {
|
static String generateConstantName(String name) {
|
||||||
// 清理特殊字符
|
// 清理特殊字符
|
||||||
final cleanName = name.replaceAll(RegExp(r'[^a-zA-Z0-9_]'), '_');
|
final cleanName = name.replaceAll(RegExp('[^a-zA-Z0-9_]'), '_');
|
||||||
// 转换为 snake_case 然后转为大写
|
// 转换为 snake_case 然后转为大写
|
||||||
return toSnakeCase(cleanName).toUpperCase();
|
return toSnakeCase(cleanName).toUpperCase();
|
||||||
}
|
}
|
||||||
|
|
@ -232,7 +233,7 @@ class StringUtils {
|
||||||
static String generateEnumValueName(dynamic value, int index) {
|
static String generateEnumValueName(dynamic value, int index) {
|
||||||
if (value is String) {
|
if (value is String) {
|
||||||
// 尝试从字符串生成合法的枚举名
|
// 尝试从字符串生成合法的枚举名
|
||||||
final cleanValue = value.replaceAll(RegExp(r'[^a-zA-Z0-9_]'), '');
|
final cleanValue = value.replaceAll(RegExp('[^a-zA-Z0-9_]'), '');
|
||||||
if (cleanValue.isNotEmpty && isValidDartIdentifier(cleanValue)) {
|
if (cleanValue.isNotEmpty && isValidDartIdentifier(cleanValue)) {
|
||||||
return toCamelCase(cleanValue);
|
return toCamelCase(cleanValue);
|
||||||
}
|
}
|
||||||
|
|
@ -286,11 +287,11 @@ class StringUtils {
|
||||||
/// 转义字符串中的特殊字符
|
/// 转义字符串中的特殊字符
|
||||||
String escapeString(String input) {
|
String escapeString(String input) {
|
||||||
return input
|
return input
|
||||||
.replaceAll('\\', '\\\\')
|
.replaceAll(r'\', r'\\')
|
||||||
.replaceAll('"', '\\"')
|
.replaceAll('"', r'\"')
|
||||||
.replaceAll('\n', '\\n')
|
.replaceAll('\n', r'\n')
|
||||||
.replaceAll('\r', '\\r')
|
.replaceAll('\r', r'\r')
|
||||||
.replaceAll('\t', '\\t');
|
.replaceAll('\t', r'\t');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 缩进文本
|
/// 缩进文本
|
||||||
|
|
@ -379,14 +380,16 @@ class StringUtils {
|
||||||
final author = ConfigLoader.getAuthor();
|
final author = ConfigLoader.getAuthor();
|
||||||
final copyright = ConfigLoader.getCopyright();
|
final copyright = ConfigLoader.getCopyright();
|
||||||
|
|
||||||
return '''// $description
|
return '''
|
||||||
|
// $description
|
||||||
// 由 $generatorName by $author 生成
|
// 由 $generatorName by $author 生成
|
||||||
// $copyright
|
// $copyright
|
||||||
''';
|
''';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 应用文件头模板
|
/// 应用文件头模板
|
||||||
/// 支持变量: {fileName}, {fileType}, {swaggerUrl}, {generatorName}, {author}, {copyright}
|
/// 支持变量: {fileName}, {fileType}, {swaggerUrl}, {generatorName}, {author},
|
||||||
|
/// {copyright}
|
||||||
static String _applyFileHeaderTemplate(
|
static String _applyFileHeaderTemplate(
|
||||||
String template, {
|
String template, {
|
||||||
required String description,
|
required String description,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import '../core/models.dart';
|
import 'package:swagger_generator_flutter/core/models.dart';
|
||||||
|
|
||||||
/// 类型验证器
|
/// 类型验证器
|
||||||
/// 提供严格的类型检查和验证功能
|
/// 提供严格的类型检查和验证功能
|
||||||
|
|
@ -248,12 +248,10 @@ class TypeValidator {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case CodeType.documentation:
|
case CodeType.documentation:
|
||||||
if (!code.contains('#')) {
|
if (!code.contains('#')) {
|
||||||
warnings.add('文档代码似乎不包含Markdown标题');
|
warnings.add('文档代码似乎不包含Markdown标题');
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查潜在的命名冲突
|
// 检查潜在的命名冲突
|
||||||
|
|
@ -387,8 +385,8 @@ class TypeValidator {
|
||||||
errors.add(
|
errors.add(
|
||||||
ValidationError(
|
ValidationError(
|
||||||
field: 'reference',
|
field: 'reference',
|
||||||
message:
|
message: '模型 ${model.name} 中的属性 ${property.name} '
|
||||||
'模型 ${model.name} 中的属性 ${property.name} 引用了不存在的类型: ${property.reference}',
|
'引用了不存在的类型: ${property.reference}',
|
||||||
severity: ErrorSeverity.error,
|
severity: ErrorSeverity.error,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -424,7 +422,7 @@ class TypeValidator {
|
||||||
try {
|
try {
|
||||||
DateTime.parse(dateString);
|
DateTime.parse(dateString);
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} on Object {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -546,15 +544,14 @@ class TypeValidator {
|
||||||
|
|
||||||
/// 验证结果
|
/// 验证结果
|
||||||
class ValidationResult {
|
class ValidationResult {
|
||||||
final bool isValid;
|
|
||||||
final List<ValidationError> errors;
|
|
||||||
final List<String> warnings;
|
|
||||||
|
|
||||||
const ValidationResult({
|
const ValidationResult({
|
||||||
required this.isValid,
|
required this.isValid,
|
||||||
required this.errors,
|
required this.errors,
|
||||||
required this.warnings,
|
required this.warnings,
|
||||||
});
|
});
|
||||||
|
final bool isValid;
|
||||||
|
final List<ValidationError> errors;
|
||||||
|
final List<String> warnings;
|
||||||
|
|
||||||
/// 是否有错误
|
/// 是否有错误
|
||||||
bool get hasErrors => errors.isNotEmpty;
|
bool get hasErrors => errors.isNotEmpty;
|
||||||
|
|
@ -580,7 +577,8 @@ class ValidationResult {
|
||||||
buffer.writeln('\n🚨 错误:');
|
buffer.writeln('\n🚨 错误:');
|
||||||
for (final error in errors) {
|
for (final error in errors) {
|
||||||
buffer.writeln(
|
buffer.writeln(
|
||||||
'- [${error.severity.name.toUpperCase()}] ${error.field}: ${error.message}',
|
'- [${error.severity.name.toUpperCase()}] '
|
||||||
|
'${error.field}: ${error.message}',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -598,19 +596,19 @@ class ValidationResult {
|
||||||
|
|
||||||
/// 验证错误
|
/// 验证错误
|
||||||
class ValidationError {
|
class ValidationError {
|
||||||
final String field;
|
|
||||||
final String message;
|
|
||||||
final ErrorSeverity severity;
|
|
||||||
|
|
||||||
const ValidationError({
|
const ValidationError({
|
||||||
required this.field,
|
required this.field,
|
||||||
required this.message,
|
required this.message,
|
||||||
required this.severity,
|
required this.severity,
|
||||||
});
|
});
|
||||||
|
final String field;
|
||||||
|
final String message;
|
||||||
|
final ErrorSeverity severity;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'ValidationError(field: $field, message: $message, severity: $severity)';
|
return 'ValidationError(field: $field, message: $message, '
|
||||||
|
'severity: $severity)';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,17 @@
|
||||||
/// 集成详细的错误报告和修复建议
|
/// 集成详细的错误报告和修复建议
|
||||||
library;
|
library;
|
||||||
|
|
||||||
import '../core/error_reporter.dart';
|
import 'package:swagger_generator_flutter/core/error_reporter.dart';
|
||||||
import '../core/models.dart';
|
import 'package:swagger_generator_flutter/core/models.dart';
|
||||||
|
|
||||||
/// 增强的 OpenAPI 验证器
|
/// 增强的 OpenAPI 验证器
|
||||||
class EnhancedValidator {
|
class EnhancedValidator {
|
||||||
final ErrorReporter _errorReporter;
|
|
||||||
final bool _includeWarnings;
|
|
||||||
|
|
||||||
EnhancedValidator({
|
EnhancedValidator({
|
||||||
bool includeWarnings = true,
|
bool includeWarnings = true,
|
||||||
}) : _errorReporter = ErrorReporter(),
|
}) : _errorReporter = ErrorReporter(),
|
||||||
_includeWarnings = includeWarnings;
|
_includeWarnings = includeWarnings;
|
||||||
|
final ErrorReporter _errorReporter;
|
||||||
|
final bool _includeWarnings;
|
||||||
|
|
||||||
/// 获取错误报告器
|
/// 获取错误报告器
|
||||||
ErrorReporter get errorReporter => _errorReporter;
|
ErrorReporter get errorReporter => _errorReporter;
|
||||||
|
|
@ -54,7 +53,7 @@ class EnhancedValidator {
|
||||||
category: ErrorCategory.validation,
|
category: ErrorCategory.validation,
|
||||||
jsonPath: 'info.title',
|
jsonPath: 'info.title',
|
||||||
suggestions: [
|
suggestions: [
|
||||||
FixSuggestion(
|
const FixSuggestion(
|
||||||
description: 'Add a descriptive title for your API',
|
description: 'Add a descriptive title for your API',
|
||||||
codeExample: '"title": "My API"',
|
codeExample: '"title": "My API"',
|
||||||
),
|
),
|
||||||
|
|
@ -72,7 +71,7 @@ class EnhancedValidator {
|
||||||
category: ErrorCategory.validation,
|
category: ErrorCategory.validation,
|
||||||
jsonPath: 'info.version',
|
jsonPath: 'info.version',
|
||||||
suggestions: [
|
suggestions: [
|
||||||
FixSuggestion(
|
const FixSuggestion(
|
||||||
description: 'Add a version number using semantic versioning',
|
description: 'Add a version number using semantic versioning',
|
||||||
codeExample: '"version": "1.0.0"',
|
codeExample: '"version": "1.0.0"',
|
||||||
documentationUrl: 'https://semver.org/',
|
documentationUrl: 'https://semver.org/',
|
||||||
|
|
@ -92,7 +91,7 @@ class EnhancedValidator {
|
||||||
category: ErrorCategory.bestPractice,
|
category: ErrorCategory.bestPractice,
|
||||||
jsonPath: 'info.description',
|
jsonPath: 'info.description',
|
||||||
suggestions: [
|
suggestions: [
|
||||||
FixSuggestion(
|
const FixSuggestion(
|
||||||
description: 'Add a description explaining what your API does',
|
description: 'Add a description explaining what your API does',
|
||||||
codeExample:
|
codeExample:
|
||||||
'"description": "This API provides user management functionality"',
|
'"description": "This API provides user management functionality"',
|
||||||
|
|
@ -112,10 +111,10 @@ class EnhancedValidator {
|
||||||
category: ErrorCategory.bestPractice,
|
category: ErrorCategory.bestPractice,
|
||||||
jsonPath: 'servers',
|
jsonPath: 'servers',
|
||||||
suggestions: [
|
suggestions: [
|
||||||
FixSuggestion(
|
const FixSuggestion(
|
||||||
description: 'Add at least one server configuration',
|
description: 'Add at least one server configuration',
|
||||||
codeExample:
|
codeExample: '"servers": [{"url": "https://api.example.com", '
|
||||||
'"servers": [{"url": "https://api.example.com", "description": "Production server"}]',
|
'"description": "Production server"}]',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
@ -133,19 +132,17 @@ class EnhancedValidator {
|
||||||
category: ErrorCategory.validation,
|
category: ErrorCategory.validation,
|
||||||
jsonPath: 'paths',
|
jsonPath: 'paths',
|
||||||
suggestions: [
|
suggestions: [
|
||||||
FixSuggestion(
|
const FixSuggestion(
|
||||||
description: 'Add at least one API endpoint',
|
description: 'Add at least one API endpoint',
|
||||||
codeExample:
|
codeExample: '"/users": { "get": { "responses": { "200": '
|
||||||
'"/users": { "get": { "responses": { "200": { "description": "Success" } } } }',
|
'{ "description": "Success" } } } }',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
document.paths.forEach((pathPattern, apiPath) {
|
document.paths.forEach(_validatePath);
|
||||||
_validatePath(pathPattern, apiPath);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 验证单个路径
|
/// 验证单个路径
|
||||||
|
|
@ -181,7 +178,7 @@ class EnhancedValidator {
|
||||||
category: ErrorCategory.validation,
|
category: ErrorCategory.validation,
|
||||||
jsonPath: '$pathKey.responses',
|
jsonPath: '$pathKey.responses',
|
||||||
suggestions: [
|
suggestions: [
|
||||||
FixSuggestion(
|
const FixSuggestion(
|
||||||
description: 'Add at least a default response',
|
description: 'Add at least a default response',
|
||||||
codeExample: '"responses": { "200": { "description": "Success" } }',
|
codeExample: '"responses": { "200": { "description": "Success" } }',
|
||||||
),
|
),
|
||||||
|
|
@ -202,8 +199,8 @@ class EnhancedValidator {
|
||||||
suggestions: [
|
suggestions: [
|
||||||
FixSuggestion(
|
FixSuggestion(
|
||||||
description: 'Add a unique operationId',
|
description: 'Add a unique operationId',
|
||||||
codeExample:
|
codeExample: '"operationId": '
|
||||||
'"operationId": "${_generateOperationId(pathPattern, apiPath.method)}"',
|
'"${_generateOperationId(pathPattern, apiPath.method)}"',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
@ -220,7 +217,7 @@ class EnhancedValidator {
|
||||||
category: ErrorCategory.bestPractice,
|
category: ErrorCategory.bestPractice,
|
||||||
jsonPath: '$pathKey.summary',
|
jsonPath: '$pathKey.summary',
|
||||||
suggestions: [
|
suggestions: [
|
||||||
FixSuggestion(
|
const FixSuggestion(
|
||||||
description: 'Add a brief summary',
|
description: 'Add a brief summary',
|
||||||
codeExample: '"summary": "Get all users"',
|
codeExample: '"summary": "Get all users"',
|
||||||
),
|
),
|
||||||
|
|
@ -237,7 +234,10 @@ class EnhancedValidator {
|
||||||
|
|
||||||
/// 验证参数
|
/// 验证参数
|
||||||
void _validateParameters(
|
void _validateParameters(
|
||||||
List<ApiParameter> parameters, String pathKey, String pathPattern) {
|
List<ApiParameter> parameters,
|
||||||
|
String pathKey,
|
||||||
|
String pathPattern,
|
||||||
|
) {
|
||||||
// 提取路径参数
|
// 提取路径参数
|
||||||
final pathParams = _extractPathParameters(pathPattern);
|
final pathParams = _extractPathParameters(pathPattern);
|
||||||
final declaredPathParams = parameters
|
final declaredPathParams = parameters
|
||||||
|
|
@ -259,8 +259,8 @@ class EnhancedValidator {
|
||||||
suggestions: [
|
suggestions: [
|
||||||
FixSuggestion(
|
FixSuggestion(
|
||||||
description: 'Add parameter declaration',
|
description: 'Add parameter declaration',
|
||||||
codeExample:
|
codeExample: '{"name": "$param", "in": "path", "required": true, '
|
||||||
'{"name": "$param", "in": "path", "required": true, "schema": {"type": "string"}}',
|
'"schema": {"type": "string"}}',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
@ -268,7 +268,7 @@ class EnhancedValidator {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证每个参数
|
// 验证每个参数
|
||||||
for (int i = 0; i < parameters.length; i++) {
|
for (var i = 0; i < parameters.length; i++) {
|
||||||
final param = parameters[i];
|
final param = parameters[i];
|
||||||
final paramPath = '$pathKey.parameters[$i]';
|
final paramPath = '$pathKey.parameters[$i]';
|
||||||
|
|
||||||
|
|
@ -282,7 +282,7 @@ class EnhancedValidator {
|
||||||
category: ErrorCategory.validation,
|
category: ErrorCategory.validation,
|
||||||
jsonPath: '$paramPath.name',
|
jsonPath: '$paramPath.name',
|
||||||
suggestions: [
|
suggestions: [
|
||||||
FixSuggestion(
|
const FixSuggestion(
|
||||||
description: 'Add a name for the parameter',
|
description: 'Add a name for the parameter',
|
||||||
codeExample: '"name": "userId"',
|
codeExample: '"name": "userId"',
|
||||||
),
|
),
|
||||||
|
|
@ -300,7 +300,7 @@ class EnhancedValidator {
|
||||||
category: ErrorCategory.validation,
|
category: ErrorCategory.validation,
|
||||||
jsonPath: '$paramPath.required',
|
jsonPath: '$paramPath.required',
|
||||||
suggestions: [
|
suggestions: [
|
||||||
FixSuggestion(
|
const FixSuggestion(
|
||||||
description: 'Set required: true for path parameters',
|
description: 'Set required: true for path parameters',
|
||||||
codeExample: '"required": true',
|
codeExample: '"required": true',
|
||||||
),
|
),
|
||||||
|
|
@ -312,8 +312,8 @@ class EnhancedValidator {
|
||||||
|
|
||||||
/// 验证响应
|
/// 验证响应
|
||||||
void _validateResponses(Map<String, ApiResponse> responses, String pathKey) {
|
void _validateResponses(Map<String, ApiResponse> responses, String pathKey) {
|
||||||
bool hasSuccessResponse = false;
|
var hasSuccessResponse = false;
|
||||||
bool hasErrorResponse = false;
|
var hasErrorResponse = false;
|
||||||
|
|
||||||
responses.forEach((code, response) {
|
responses.forEach((code, response) {
|
||||||
final responsePath = '$pathKey.responses["$code"]';
|
final responsePath = '$pathKey.responses["$code"]';
|
||||||
|
|
@ -339,7 +339,7 @@ class EnhancedValidator {
|
||||||
category: ErrorCategory.bestPractice,
|
category: ErrorCategory.bestPractice,
|
||||||
jsonPath: '$responsePath.description',
|
jsonPath: '$responsePath.description',
|
||||||
suggestions: [
|
suggestions: [
|
||||||
FixSuggestion(
|
const FixSuggestion(
|
||||||
description: 'Add a description for the response',
|
description: 'Add a description for the response',
|
||||||
codeExample: '"description": "Successful operation"',
|
codeExample: '"description": "Successful operation"',
|
||||||
),
|
),
|
||||||
|
|
@ -359,7 +359,7 @@ class EnhancedValidator {
|
||||||
category: ErrorCategory.bestPractice,
|
category: ErrorCategory.bestPractice,
|
||||||
jsonPath: '$pathKey.responses',
|
jsonPath: '$pathKey.responses',
|
||||||
suggestions: [
|
suggestions: [
|
||||||
FixSuggestion(
|
const FixSuggestion(
|
||||||
description: 'Add a success response',
|
description: 'Add a success response',
|
||||||
codeExample: '"200": { "description": "Success" }',
|
codeExample: '"200": { "description": "Success" }',
|
||||||
),
|
),
|
||||||
|
|
@ -378,10 +378,10 @@ class EnhancedValidator {
|
||||||
category: ErrorCategory.bestPractice,
|
category: ErrorCategory.bestPractice,
|
||||||
jsonPath: '$pathKey.responses',
|
jsonPath: '$pathKey.responses',
|
||||||
suggestions: [
|
suggestions: [
|
||||||
FixSuggestion(
|
const FixSuggestion(
|
||||||
description: 'Add common error responses',
|
description: 'Add common error responses',
|
||||||
codeExample:
|
codeExample: '"400": { "description": "Bad Request" }, '
|
||||||
'"400": { "description": "Bad Request" }, "404": { "description": "Not Found" }',
|
'"404": { "description": "Not Found" }',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
@ -391,14 +391,10 @@ class EnhancedValidator {
|
||||||
/// 验证组件
|
/// 验证组件
|
||||||
void _validateComponents(SwaggerDocument document) {
|
void _validateComponents(SwaggerDocument document) {
|
||||||
// 验证 schemas
|
// 验证 schemas
|
||||||
document.components.schemas.forEach((name, model) {
|
document.components.schemas.forEach(_validateSchema);
|
||||||
_validateSchema(name, model);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 验证安全方案
|
// 验证安全方案
|
||||||
document.components.securitySchemes.forEach((name, scheme) {
|
document.components.securitySchemes.forEach(_validateSecurityScheme);
|
||||||
_validateSecurityScheme(name, scheme);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 验证 Schema
|
/// 验证 Schema
|
||||||
|
|
@ -415,7 +411,7 @@ class EnhancedValidator {
|
||||||
category: ErrorCategory.validation,
|
category: ErrorCategory.validation,
|
||||||
jsonPath: schemaPath,
|
jsonPath: schemaPath,
|
||||||
suggestions: [
|
suggestions: [
|
||||||
FixSuggestion(
|
const FixSuggestion(
|
||||||
description: 'Ensure schema has a valid name',
|
description: 'Ensure schema has a valid name',
|
||||||
codeExample:
|
codeExample:
|
||||||
'Schema name should match the key in components.schemas',
|
'Schema name should match the key in components.schemas',
|
||||||
|
|
@ -435,10 +431,11 @@ class EnhancedValidator {
|
||||||
category: ErrorCategory.performance,
|
category: ErrorCategory.performance,
|
||||||
jsonPath: schemaPath,
|
jsonPath: schemaPath,
|
||||||
suggestions: [
|
suggestions: [
|
||||||
FixSuggestion(
|
const FixSuggestion(
|
||||||
description: 'Consider using composition with allOf',
|
description: 'Consider using composition with allOf',
|
||||||
codeExample:
|
codeExample:
|
||||||
'"allOf": [{ "\$ref": "#/components/schemas/BaseModel" }, { "type": "object", "properties": {...} }]',
|
r'"allOf": [{ "$ref": "#/components/schemas/BaseModel" }, '
|
||||||
|
'{ "type": "object", "properties": {...} }]',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
@ -461,14 +458,13 @@ class EnhancedValidator {
|
||||||
category: ErrorCategory.security,
|
category: ErrorCategory.security,
|
||||||
jsonPath: '$schemePath.name',
|
jsonPath: '$schemePath.name',
|
||||||
suggestions: [
|
suggestions: [
|
||||||
FixSuggestion(
|
const FixSuggestion(
|
||||||
description: 'Add name field for API key parameter',
|
description: 'Add name field for API key parameter',
|
||||||
codeExample: '"name": "X-API-Key"',
|
codeExample: '"name": "X-API-Key"',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
|
|
||||||
case SecuritySchemeType.http:
|
case SecuritySchemeType.http:
|
||||||
if (scheme.scheme == null || scheme.scheme!.isEmpty) {
|
if (scheme.scheme == null || scheme.scheme!.isEmpty) {
|
||||||
|
|
@ -481,14 +477,13 @@ class EnhancedValidator {
|
||||||
category: ErrorCategory.security,
|
category: ErrorCategory.security,
|
||||||
jsonPath: '$schemePath.scheme',
|
jsonPath: '$schemePath.scheme',
|
||||||
suggestions: [
|
suggestions: [
|
||||||
FixSuggestion(
|
const FixSuggestion(
|
||||||
description: 'Add scheme field',
|
description: 'Add scheme field',
|
||||||
codeExample: '"scheme": "bearer"',
|
codeExample: '"scheme": "bearer"',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
|
|
||||||
case SecuritySchemeType.oauth2:
|
case SecuritySchemeType.oauth2:
|
||||||
if (scheme.flows == null) {
|
if (scheme.flows == null) {
|
||||||
|
|
@ -500,15 +495,14 @@ class EnhancedValidator {
|
||||||
category: ErrorCategory.security,
|
category: ErrorCategory.security,
|
||||||
jsonPath: '$schemePath.flows',
|
jsonPath: '$schemePath.flows',
|
||||||
suggestions: [
|
suggestions: [
|
||||||
FixSuggestion(
|
const FixSuggestion(
|
||||||
description: 'Add flows configuration',
|
description: 'Add flows configuration',
|
||||||
codeExample:
|
codeExample: '"flows": { "authorizationCode": '
|
||||||
'"flows": { "authorizationCode": { "authorizationUrl": "...", "tokenUrl": "..." } }',
|
'{ "authorizationUrl": "...", "tokenUrl": "..." } }',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
|
|
||||||
case SecuritySchemeType.openIdConnect:
|
case SecuritySchemeType.openIdConnect:
|
||||||
if (scheme.openIdConnectUrl == null ||
|
if (scheme.openIdConnectUrl == null ||
|
||||||
|
|
@ -521,15 +515,14 @@ class EnhancedValidator {
|
||||||
category: ErrorCategory.security,
|
category: ErrorCategory.security,
|
||||||
jsonPath: '$schemePath.openIdConnectUrl',
|
jsonPath: '$schemePath.openIdConnectUrl',
|
||||||
suggestions: [
|
suggestions: [
|
||||||
FixSuggestion(
|
const FixSuggestion(
|
||||||
description: 'Add OpenID Connect URL',
|
description: 'Add OpenID Connect URL',
|
||||||
codeExample:
|
codeExample: '"openIdConnectUrl": '
|
||||||
'"openIdConnectUrl": "https://example.com/.well-known/openid_configuration"',
|
'"https://example.com/.well-known/openid_configuration"',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -552,7 +545,7 @@ class EnhancedValidator {
|
||||||
category: ErrorCategory.bestPractice,
|
category: ErrorCategory.bestPractice,
|
||||||
jsonPath: 'paths',
|
jsonPath: 'paths',
|
||||||
suggestions: [
|
suggestions: [
|
||||||
FixSuggestion(
|
const FixSuggestion(
|
||||||
description: 'Add tags to operations',
|
description: 'Add tags to operations',
|
||||||
codeExample: '"tags": ["users"]',
|
codeExample: '"tags": ["users"]',
|
||||||
),
|
),
|
||||||
|
|
@ -581,7 +574,7 @@ class EnhancedValidator {
|
||||||
pathParts.removeRange(0, 2);
|
pathParts.removeRange(0, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
final nameParts = pathParts.map((part) => _toPascalCase(part)).join('');
|
final nameParts = pathParts.map(_toPascalCase).join();
|
||||||
return '$methodPrefix$nameParts';
|
return '$methodPrefix$nameParts';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -589,9 +582,11 @@ class EnhancedValidator {
|
||||||
String _toPascalCase(String input) {
|
String _toPascalCase(String input) {
|
||||||
return input
|
return input
|
||||||
.split('_')
|
.split('_')
|
||||||
.map((word) => word.isEmpty
|
.map(
|
||||||
? ''
|
(word) => word.isEmpty
|
||||||
: word[0].toUpperCase() + word.substring(1).toLowerCase())
|
? ''
|
||||||
.join('');
|
: word[0].toUpperCase() + word.substring(1).toLowerCase(),
|
||||||
|
)
|
||||||
|
.join();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,10 @@
|
||||||
/// 验证 OpenAPI 3.0 文档的完整性和正确性
|
/// 验证 OpenAPI 3.0 文档的完整性和正确性
|
||||||
library;
|
library;
|
||||||
|
|
||||||
import '../core/models.dart';
|
import 'package:swagger_generator_flutter/core/models.dart';
|
||||||
|
|
||||||
/// Schema 验证结果
|
/// Schema 验证结果
|
||||||
class ValidationResult {
|
class ValidationResult {
|
||||||
final bool isValid;
|
|
||||||
final List<ValidationError> errors;
|
|
||||||
final List<ValidationWarning> warnings;
|
|
||||||
|
|
||||||
const ValidationResult({
|
const ValidationResult({
|
||||||
required this.isValid,
|
required this.isValid,
|
||||||
this.errors = const [],
|
this.errors = const [],
|
||||||
|
|
@ -17,8 +13,9 @@ class ValidationResult {
|
||||||
});
|
});
|
||||||
|
|
||||||
/// 创建成功的验证结果
|
/// 创建成功的验证结果
|
||||||
factory ValidationResult.success(
|
factory ValidationResult.success({
|
||||||
{List<ValidationWarning> warnings = const []}) {
|
List<ValidationWarning> warnings = const [],
|
||||||
|
}) {
|
||||||
return ValidationResult(
|
return ValidationResult(
|
||||||
isValid: true,
|
isValid: true,
|
||||||
warnings: warnings,
|
warnings: warnings,
|
||||||
|
|
@ -26,14 +23,19 @@ class ValidationResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 创建失败的验证结果
|
/// 创建失败的验证结果
|
||||||
factory ValidationResult.failure(List<ValidationError> errors,
|
factory ValidationResult.failure(
|
||||||
{List<ValidationWarning> warnings = const []}) {
|
List<ValidationError> errors, {
|
||||||
|
List<ValidationWarning> warnings = const [],
|
||||||
|
}) {
|
||||||
return ValidationResult(
|
return ValidationResult(
|
||||||
isValid: false,
|
isValid: false,
|
||||||
errors: errors,
|
errors: errors,
|
||||||
warnings: warnings,
|
warnings: warnings,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
final bool isValid;
|
||||||
|
final List<ValidationError> errors;
|
||||||
|
final List<ValidationWarning> warnings;
|
||||||
|
|
||||||
/// 是否有警告
|
/// 是否有警告
|
||||||
bool get hasWarnings => warnings.isNotEmpty;
|
bool get hasWarnings => warnings.isNotEmpty;
|
||||||
|
|
@ -44,22 +46,20 @@ class ValidationResult {
|
||||||
|
|
||||||
/// 验证错误
|
/// 验证错误
|
||||||
class ValidationError {
|
class ValidationError {
|
||||||
final String path;
|
|
||||||
final String message;
|
|
||||||
final ValidationErrorType type;
|
|
||||||
final String? suggestion;
|
|
||||||
|
|
||||||
const ValidationError({
|
const ValidationError({
|
||||||
required this.path,
|
required this.path,
|
||||||
required this.message,
|
required this.message,
|
||||||
required this.type,
|
required this.type,
|
||||||
this.suggestion,
|
this.suggestion,
|
||||||
});
|
});
|
||||||
|
final String path;
|
||||||
|
final String message;
|
||||||
|
final ValidationErrorType type;
|
||||||
|
final String? suggestion;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
final buffer = StringBuffer();
|
final buffer = StringBuffer()..write('[$type] $path: $message');
|
||||||
buffer.write('[$type] $path: $message');
|
|
||||||
if (suggestion != null) {
|
if (suggestion != null) {
|
||||||
buffer.write(' (建议: $suggestion)');
|
buffer.write(' (建议: $suggestion)');
|
||||||
}
|
}
|
||||||
|
|
@ -69,20 +69,18 @@ class ValidationError {
|
||||||
|
|
||||||
/// 验证警告
|
/// 验证警告
|
||||||
class ValidationWarning {
|
class ValidationWarning {
|
||||||
final String path;
|
|
||||||
final String message;
|
|
||||||
final String? suggestion;
|
|
||||||
|
|
||||||
const ValidationWarning({
|
const ValidationWarning({
|
||||||
required this.path,
|
required this.path,
|
||||||
required this.message,
|
required this.message,
|
||||||
this.suggestion,
|
this.suggestion,
|
||||||
});
|
});
|
||||||
|
final String path;
|
||||||
|
final String message;
|
||||||
|
final String? suggestion;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
final buffer = StringBuffer();
|
final buffer = StringBuffer()..write('[WARNING] $path: $message');
|
||||||
buffer.write('[WARNING] $path: $message');
|
|
||||||
if (suggestion != null) {
|
if (suggestion != null) {
|
||||||
buffer.write(' (建议: $suggestion)');
|
buffer.write(' (建议: $suggestion)');
|
||||||
}
|
}
|
||||||
|
|
@ -136,70 +134,84 @@ class SchemaValidator {
|
||||||
/// 验证基本信息
|
/// 验证基本信息
|
||||||
void _validateInfo(SwaggerDocument document) {
|
void _validateInfo(SwaggerDocument document) {
|
||||||
if (document.title.isEmpty) {
|
if (document.title.isEmpty) {
|
||||||
_errors.add(const ValidationError(
|
_errors.add(
|
||||||
path: 'info.title',
|
const ValidationError(
|
||||||
message: 'API 标题不能为空',
|
path: 'info.title',
|
||||||
type: ValidationErrorType.required,
|
message: 'API 标题不能为空',
|
||||||
suggestion: '请提供有意义的 API 标题',
|
type: ValidationErrorType.required,
|
||||||
));
|
suggestion: '请提供有意义的 API 标题',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.version.isEmpty) {
|
if (document.version.isEmpty) {
|
||||||
_errors.add(const ValidationError(
|
_errors.add(
|
||||||
path: 'info.version',
|
const ValidationError(
|
||||||
message: 'API 版本不能为空',
|
path: 'info.version',
|
||||||
type: ValidationErrorType.required,
|
message: 'API 版本不能为空',
|
||||||
suggestion: '请使用语义化版本号,如 "1.0.0"',
|
type: ValidationErrorType.required,
|
||||||
));
|
suggestion: '请使用语义化版本号,如 "1.0.0"',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.description.isEmpty) {
|
if (document.description.isEmpty) {
|
||||||
_warnings.add(const ValidationWarning(
|
_warnings.add(
|
||||||
path: 'info.description',
|
const ValidationWarning(
|
||||||
message: 'API 描述为空',
|
path: 'info.description',
|
||||||
suggestion: '建议添加 API 的详细描述',
|
message: 'API 描述为空',
|
||||||
));
|
suggestion: '建议添加 API 的详细描述',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 验证服务器配置
|
/// 验证服务器配置
|
||||||
void _validateServers(List<ApiServer> servers) {
|
void _validateServers(List<ApiServer> servers) {
|
||||||
if (servers.isEmpty) {
|
if (servers.isEmpty) {
|
||||||
_warnings.add(const ValidationWarning(
|
_warnings.add(
|
||||||
path: 'servers',
|
const ValidationWarning(
|
||||||
message: '未定义服务器配置',
|
path: 'servers',
|
||||||
suggestion: '建议添加至少一个服务器配置',
|
message: '未定义服务器配置',
|
||||||
));
|
suggestion: '建议添加至少一个服务器配置',
|
||||||
|
),
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < servers.length; i++) {
|
for (var i = 0; i < servers.length; i++) {
|
||||||
final server = servers[i];
|
final server = servers[i];
|
||||||
final path = 'servers[$i]';
|
final path = 'servers[$i]';
|
||||||
|
|
||||||
if (server.url.isEmpty) {
|
if (server.url.isEmpty) {
|
||||||
_errors.add(ValidationError(
|
_errors.add(
|
||||||
path: '$path.url',
|
ValidationError(
|
||||||
message: '服务器 URL 不能为空',
|
path: '$path.url',
|
||||||
type: ValidationErrorType.required,
|
message: '服务器 URL 不能为空',
|
||||||
));
|
type: ValidationErrorType.required,
|
||||||
|
),
|
||||||
|
);
|
||||||
} else if (!_isValidUrl(server.url)) {
|
} else if (!_isValidUrl(server.url)) {
|
||||||
_errors.add(ValidationError(
|
_errors.add(
|
||||||
path: '$path.url',
|
ValidationError(
|
||||||
message: '服务器 URL 格式无效: ${server.url}',
|
path: '$path.url',
|
||||||
type: ValidationErrorType.format,
|
message: '服务器 URL 格式无效: ${server.url}',
|
||||||
suggestion: '请使用有效的 URL 格式,如 "https://api.example.com"',
|
type: ValidationErrorType.format,
|
||||||
));
|
suggestion: '请使用有效的 URL 格式,如 "https://api.example.com"',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证服务器变量
|
// 验证服务器变量
|
||||||
server.variables.forEach((name, variable) {
|
server.variables.forEach((name, variable) {
|
||||||
if (variable.defaultValue.isEmpty) {
|
if (variable.defaultValue.isEmpty) {
|
||||||
_errors.add(ValidationError(
|
_errors.add(
|
||||||
path: '$path.variables.$name.default',
|
ValidationError(
|
||||||
message: '服务器变量必须有默认值',
|
path: '$path.variables.$name.default',
|
||||||
type: ValidationErrorType.required,
|
message: '服务器变量必须有默认值',
|
||||||
));
|
type: ValidationErrorType.required,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -208,11 +220,13 @@ class SchemaValidator {
|
||||||
/// 验证路径
|
/// 验证路径
|
||||||
void _validatePaths(Map<String, ApiPath> paths) {
|
void _validatePaths(Map<String, ApiPath> paths) {
|
||||||
if (paths.isEmpty) {
|
if (paths.isEmpty) {
|
||||||
_errors.add(const ValidationError(
|
_errors.add(
|
||||||
path: 'paths',
|
const ValidationError(
|
||||||
message: 'API 文档必须包含至少一个路径',
|
path: 'paths',
|
||||||
type: ValidationErrorType.required,
|
message: 'API 文档必须包含至少一个路径',
|
||||||
));
|
type: ValidationErrorType.required,
|
||||||
|
),
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -226,24 +240,28 @@ class SchemaValidator {
|
||||||
void _validatePath(ApiPath path, String pathKey) {
|
void _validatePath(ApiPath path, String pathKey) {
|
||||||
// 验证操作 ID
|
// 验证操作 ID
|
||||||
if (path.operationId.isEmpty) {
|
if (path.operationId.isEmpty) {
|
||||||
_warnings.add(ValidationWarning(
|
_warnings.add(
|
||||||
path: '$pathKey.operationId',
|
ValidationWarning(
|
||||||
message: '缺少操作 ID',
|
path: '$pathKey.operationId',
|
||||||
suggestion: '建议为每个操作添加唯一的 operationId',
|
message: '缺少操作 ID',
|
||||||
));
|
suggestion: '建议为每个操作添加唯一的 operationId',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证摘要和描述
|
// 验证摘要和描述
|
||||||
if (path.summary.isEmpty) {
|
if (path.summary.isEmpty) {
|
||||||
_warnings.add(ValidationWarning(
|
_warnings.add(
|
||||||
path: '$pathKey.summary',
|
ValidationWarning(
|
||||||
message: '缺少操作摘要',
|
path: '$pathKey.summary',
|
||||||
suggestion: '建议添加简短的操作描述',
|
message: '缺少操作摘要',
|
||||||
));
|
suggestion: '建议添加简短的操作描述',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证参数
|
// 验证参数
|
||||||
for (int i = 0; i < path.parameters.length; i++) {
|
for (var i = 0; i < path.parameters.length; i++) {
|
||||||
_validateParameter(path.parameters[i], '$pathKey.parameters[$i]');
|
_validateParameter(path.parameters[i], '$pathKey.parameters[$i]');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -254,11 +272,13 @@ class SchemaValidator {
|
||||||
|
|
||||||
// 验证响应
|
// 验证响应
|
||||||
if (path.responses.isEmpty) {
|
if (path.responses.isEmpty) {
|
||||||
_errors.add(ValidationError(
|
_errors.add(
|
||||||
path: '$pathKey.responses',
|
ValidationError(
|
||||||
message: '操作必须定义至少一个响应',
|
path: '$pathKey.responses',
|
||||||
type: ValidationErrorType.required,
|
message: '操作必须定义至少一个响应',
|
||||||
));
|
type: ValidationErrorType.required,
|
||||||
|
),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
path.responses.forEach((code, response) {
|
path.responses.forEach((code, response) {
|
||||||
_validateResponse(response, '$pathKey.responses["$code"]');
|
_validateResponse(response, '$pathKey.responses["$code"]');
|
||||||
|
|
@ -266,7 +286,7 @@ class SchemaValidator {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证安全要求
|
// 验证安全要求
|
||||||
for (int i = 0; i < path.security.length; i++) {
|
for (var i = 0; i < path.security.length; i++) {
|
||||||
_validateSecurityRequirement(path.security[i], '$pathKey.security[$i]');
|
_validateSecurityRequirement(path.security[i], '$pathKey.security[$i]');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -274,40 +294,48 @@ class SchemaValidator {
|
||||||
/// 验证参数
|
/// 验证参数
|
||||||
void _validateParameter(ApiParameter parameter, String path) {
|
void _validateParameter(ApiParameter parameter, String path) {
|
||||||
if (parameter.name.isEmpty) {
|
if (parameter.name.isEmpty) {
|
||||||
_errors.add(ValidationError(
|
_errors.add(
|
||||||
path: '$path.name',
|
ValidationError(
|
||||||
message: '参数名称不能为空',
|
path: '$path.name',
|
||||||
type: ValidationErrorType.required,
|
message: '参数名称不能为空',
|
||||||
));
|
type: ValidationErrorType.required,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证路径参数必须是必需的
|
// 验证路径参数必须是必需的
|
||||||
if (parameter.location == ParameterLocation.path && !parameter.required) {
|
if (parameter.location == ParameterLocation.path && !parameter.required) {
|
||||||
_errors.add(ValidationError(
|
_errors.add(
|
||||||
path: '$path.required',
|
ValidationError(
|
||||||
message: '路径参数必须是必需的',
|
path: '$path.required',
|
||||||
type: ValidationErrorType.constraint,
|
message: '路径参数必须是必需的',
|
||||||
));
|
type: ValidationErrorType.constraint,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证参数类型
|
// 验证参数类型
|
||||||
if (parameter.type == PropertyType.unknown) {
|
if (parameter.type == PropertyType.unknown) {
|
||||||
_warnings.add(ValidationWarning(
|
_warnings.add(
|
||||||
path: '$path.type',
|
ValidationWarning(
|
||||||
message: '参数类型未知',
|
path: '$path.type',
|
||||||
suggestion: '建议明确指定参数类型',
|
message: '参数类型未知',
|
||||||
));
|
suggestion: '建议明确指定参数类型',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 验证请求体
|
/// 验证请求体
|
||||||
void _validateRequestBody(ApiRequestBody requestBody, String path) {
|
void _validateRequestBody(ApiRequestBody requestBody, String path) {
|
||||||
if (requestBody.content.isEmpty) {
|
if (requestBody.content.isEmpty) {
|
||||||
_errors.add(ValidationError(
|
_errors.add(
|
||||||
path: '$path.content',
|
ValidationError(
|
||||||
message: '请求体必须定义至少一种内容类型',
|
path: '$path.content',
|
||||||
type: ValidationErrorType.required,
|
message: '请求体必须定义至少一种内容类型',
|
||||||
));
|
type: ValidationErrorType.required,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
requestBody.content.forEach((mediaType, content) {
|
requestBody.content.forEach((mediaType, content) {
|
||||||
|
|
@ -318,11 +346,13 @@ class SchemaValidator {
|
||||||
/// 验证响应
|
/// 验证响应
|
||||||
void _validateResponse(ApiResponse response, String path) {
|
void _validateResponse(ApiResponse response, String path) {
|
||||||
if (response.description.isEmpty) {
|
if (response.description.isEmpty) {
|
||||||
_warnings.add(ValidationWarning(
|
_warnings.add(
|
||||||
path: '$path.description',
|
ValidationWarning(
|
||||||
message: '响应缺少描述',
|
path: '$path.description',
|
||||||
suggestion: '建议为响应添加描述',
|
message: '响应缺少描述',
|
||||||
));
|
suggestion: '建议为响应添加描述',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
response.content.forEach((mediaType, content) {
|
response.content.forEach((mediaType, content) {
|
||||||
|
|
@ -332,24 +362,31 @@ class SchemaValidator {
|
||||||
|
|
||||||
/// 验证媒体类型
|
/// 验证媒体类型
|
||||||
void _validateMediaType(
|
void _validateMediaType(
|
||||||
ApiMediaType mediaType, String path, String contentType) {
|
ApiMediaType mediaType,
|
||||||
|
String path,
|
||||||
|
String contentType,
|
||||||
|
) {
|
||||||
// 验证 schema
|
// 验证 schema
|
||||||
if (mediaType.schema == null) {
|
if (mediaType.schema == null) {
|
||||||
_warnings.add(ValidationWarning(
|
_warnings.add(
|
||||||
path: '$path.schema',
|
ValidationWarning(
|
||||||
message: '媒体类型缺少 schema 定义',
|
path: '$path.schema',
|
||||||
suggestion: '建议为媒体类型添加 schema',
|
message: '媒体类型缺少 schema 定义',
|
||||||
));
|
suggestion: '建议为媒体类型添加 schema',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证编码(仅适用于 multipart 和 form data)
|
// 验证编码(仅适用于 multipart 和 form data)
|
||||||
if (contentType.startsWith('multipart/') || contentType.contains('form')) {
|
if (contentType.startsWith('multipart/') || contentType.contains('form')) {
|
||||||
if (mediaType.encoding.isEmpty) {
|
if (mediaType.encoding.isEmpty) {
|
||||||
_warnings.add(ValidationWarning(
|
_warnings.add(
|
||||||
path: '$path.encoding',
|
ValidationWarning(
|
||||||
message: '表单数据建议定义编码信息',
|
path: '$path.encoding',
|
||||||
suggestion: '为文件上传字段添加 contentType 等编码信息',
|
message: '表单数据建议定义编码信息',
|
||||||
));
|
suggestion: '为文件上传字段添加 contentType 等编码信息',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -372,11 +409,13 @@ class SchemaValidator {
|
||||||
/// 验证模型
|
/// 验证模型
|
||||||
void _validateModel(ApiModel model, String path) {
|
void _validateModel(ApiModel model, String path) {
|
||||||
if (model.name.isEmpty) {
|
if (model.name.isEmpty) {
|
||||||
_errors.add(ValidationError(
|
_errors.add(
|
||||||
path: '$path.name',
|
ValidationError(
|
||||||
message: '模型名称不能为空',
|
path: '$path.name',
|
||||||
type: ValidationErrorType.required,
|
message: '模型名称不能为空',
|
||||||
));
|
type: ValidationErrorType.required,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证属性
|
// 验证属性
|
||||||
|
|
@ -387,11 +426,13 @@ class SchemaValidator {
|
||||||
// 验证必需字段
|
// 验证必需字段
|
||||||
for (final requiredField in model.required) {
|
for (final requiredField in model.required) {
|
||||||
if (!model.properties.containsKey(requiredField)) {
|
if (!model.properties.containsKey(requiredField)) {
|
||||||
_errors.add(ValidationError(
|
_errors.add(
|
||||||
path: '$path.required',
|
ValidationError(
|
||||||
message: '必需字段 "$requiredField" 在属性中未定义',
|
path: '$path.required',
|
||||||
type: ValidationErrorType.reference,
|
message: '必需字段 "$requiredField" 在属性中未定义',
|
||||||
));
|
type: ValidationErrorType.reference,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -399,42 +440,52 @@ class SchemaValidator {
|
||||||
/// 验证属性
|
/// 验证属性
|
||||||
void _validateProperty(ApiProperty property, String path) {
|
void _validateProperty(ApiProperty property, String path) {
|
||||||
if (property.name.isEmpty) {
|
if (property.name.isEmpty) {
|
||||||
_errors.add(ValidationError(
|
_errors.add(
|
||||||
path: '$path.name',
|
ValidationError(
|
||||||
message: '属性名称不能为空',
|
path: '$path.name',
|
||||||
type: ValidationErrorType.required,
|
message: '属性名称不能为空',
|
||||||
));
|
type: ValidationErrorType.required,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (property.type == PropertyType.unknown) {
|
if (property.type == PropertyType.unknown) {
|
||||||
_warnings.add(ValidationWarning(
|
_warnings.add(
|
||||||
path: '$path.type',
|
ValidationWarning(
|
||||||
message: '属性类型未知',
|
path: '$path.type',
|
||||||
suggestion: '建议明确指定属性类型',
|
message: '属性类型未知',
|
||||||
));
|
suggestion: '建议明确指定属性类型',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 验证安全方案
|
/// 验证安全方案
|
||||||
void _validateSecurity(List<ApiSecurityRequirement> security,
|
void _validateSecurity(
|
||||||
Map<String, ApiSecurityScheme> schemes) {
|
List<ApiSecurityRequirement> security,
|
||||||
for (int i = 0; i < security.length; i++) {
|
Map<String, ApiSecurityScheme> schemes,
|
||||||
|
) {
|
||||||
|
for (var i = 0; i < security.length; i++) {
|
||||||
_validateSecurityRequirement(security[i], 'security[$i]');
|
_validateSecurityRequirement(security[i], 'security[$i]');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 验证安全要求
|
/// 验证安全要求
|
||||||
void _validateSecurityRequirement(
|
void _validateSecurityRequirement(
|
||||||
ApiSecurityRequirement requirement, String path) {
|
ApiSecurityRequirement requirement,
|
||||||
|
String path,
|
||||||
|
) {
|
||||||
for (final schemeName in requirement.schemeNames) {
|
for (final schemeName in requirement.schemeNames) {
|
||||||
// 这里应该验证安全方案是否在 components.securitySchemes 中定义
|
// 这里应该验证安全方案是否在 components.securitySchemes 中定义
|
||||||
// 但由于当前模型结构限制,我们只能添加警告
|
// 但由于当前模型结构限制,我们只能添加警告
|
||||||
if (schemeName.isEmpty) {
|
if (schemeName.isEmpty) {
|
||||||
_warnings.add(ValidationWarning(
|
_warnings.add(
|
||||||
path: path,
|
ValidationWarning(
|
||||||
message: '安全方案名称为空',
|
path: path,
|
||||||
suggestion: '请确保安全方案名称有效',
|
message: '安全方案名称为空',
|
||||||
));
|
suggestion: '请确保安全方案名称有效',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -444,41 +495,45 @@ class SchemaValidator {
|
||||||
switch (scheme.type) {
|
switch (scheme.type) {
|
||||||
case SecuritySchemeType.apiKey:
|
case SecuritySchemeType.apiKey:
|
||||||
if (scheme.name == null || scheme.name!.isEmpty) {
|
if (scheme.name == null || scheme.name!.isEmpty) {
|
||||||
_errors.add(ValidationError(
|
_errors.add(
|
||||||
path: '$path.name',
|
ValidationError(
|
||||||
message: 'API Key 安全方案必须指定参数名称',
|
path: '$path.name',
|
||||||
type: ValidationErrorType.required,
|
message: 'API Key 安全方案必须指定参数名称',
|
||||||
));
|
type: ValidationErrorType.required,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case SecuritySchemeType.http:
|
case SecuritySchemeType.http:
|
||||||
if (scheme.scheme == null || scheme.scheme!.isEmpty) {
|
if (scheme.scheme == null || scheme.scheme!.isEmpty) {
|
||||||
_errors.add(ValidationError(
|
_errors.add(
|
||||||
path: '$path.scheme',
|
ValidationError(
|
||||||
message: 'HTTP 安全方案必须指定认证方案',
|
path: '$path.scheme',
|
||||||
type: ValidationErrorType.required,
|
message: 'HTTP 安全方案必须指定认证方案',
|
||||||
));
|
type: ValidationErrorType.required,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case SecuritySchemeType.oauth2:
|
case SecuritySchemeType.oauth2:
|
||||||
if (scheme.flows == null) {
|
if (scheme.flows == null) {
|
||||||
_errors.add(ValidationError(
|
_errors.add(
|
||||||
path: '$path.flows',
|
ValidationError(
|
||||||
message: 'OAuth2 安全方案必须定义流程',
|
path: '$path.flows',
|
||||||
type: ValidationErrorType.required,
|
message: 'OAuth2 安全方案必须定义流程',
|
||||||
));
|
type: ValidationErrorType.required,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case SecuritySchemeType.openIdConnect:
|
case SecuritySchemeType.openIdConnect:
|
||||||
if (scheme.openIdConnectUrl == null ||
|
if (scheme.openIdConnectUrl == null ||
|
||||||
scheme.openIdConnectUrl!.isEmpty) {
|
scheme.openIdConnectUrl!.isEmpty) {
|
||||||
_errors.add(ValidationError(
|
_errors.add(
|
||||||
path: '$path.openIdConnectUrl',
|
ValidationError(
|
||||||
message: 'OpenID Connect 安全方案必须指定 URL',
|
path: '$path.openIdConnectUrl',
|
||||||
type: ValidationErrorType.required,
|
message: 'OpenID Connect 安全方案必须指定 URL',
|
||||||
));
|
type: ValidationErrorType.required,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -487,7 +542,7 @@ class SchemaValidator {
|
||||||
try {
|
try {
|
||||||
final uri = Uri.parse(url);
|
final uri = Uri.parse(url);
|
||||||
return uri.hasScheme && (uri.scheme == 'http' || uri.scheme == 'https');
|
return uri.hasScheme && (uri.scheme == 'http' || uri.scheme == 'https');
|
||||||
} catch (e) {
|
} on Object {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -507,11 +562,13 @@ class SchemaValidator {
|
||||||
void _validateOpenApiVersion(SwaggerDocument document) {
|
void _validateOpenApiVersion(SwaggerDocument document) {
|
||||||
// SwaggerDocument 没有直接的 openApiVersion 属性
|
// SwaggerDocument 没有直接的 openApiVersion 属性
|
||||||
// 这里我们假设它是 OpenAPI 3.0 兼容的
|
// 这里我们假设它是 OpenAPI 3.0 兼容的
|
||||||
_warnings.add(const ValidationWarning(
|
_warnings.add(
|
||||||
path: 'openapi',
|
const ValidationWarning(
|
||||||
message: '无法验证 OpenAPI 版本',
|
path: 'openapi',
|
||||||
suggestion: '确保使用 OpenAPI 3.0.x 或 3.1.x 版本',
|
message: '无法验证 OpenAPI 版本',
|
||||||
));
|
suggestion: '确保使用 OpenAPI 3.0.x 或 3.1.x 版本',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 验证路径结构
|
/// 验证路径结构
|
||||||
|
|
@ -519,15 +576,17 @@ class SchemaValidator {
|
||||||
final pathPatterns = document.paths.keys.toList();
|
final pathPatterns = document.paths.keys.toList();
|
||||||
|
|
||||||
// 检查路径冲突
|
// 检查路径冲突
|
||||||
for (int i = 0; i < pathPatterns.length; i++) {
|
for (var i = 0; i < pathPatterns.length; i++) {
|
||||||
for (int j = i + 1; j < pathPatterns.length; j++) {
|
for (var j = i + 1; j < pathPatterns.length; j++) {
|
||||||
if (_pathsConflict(pathPatterns[i], pathPatterns[j])) {
|
if (_pathsConflict(pathPatterns[i], pathPatterns[j])) {
|
||||||
_errors.add(ValidationError(
|
_errors.add(
|
||||||
path: 'paths',
|
ValidationError(
|
||||||
message: '路径冲突: "${pathPatterns[i]}" 与 "${pathPatterns[j]}"',
|
path: 'paths',
|
||||||
type: ValidationErrorType.constraint,
|
message: '路径冲突: "${pathPatterns[i]}" 与 "${pathPatterns[j]}"',
|
||||||
suggestion: '确保路径模式不会产生歧义',
|
type: ValidationErrorType.constraint,
|
||||||
));
|
suggestion: '确保路径模式不会产生歧义',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -544,23 +603,27 @@ class SchemaValidator {
|
||||||
// 检查路径中的参数是否都有声明
|
// 检查路径中的参数是否都有声明
|
||||||
for (final param in pathParams) {
|
for (final param in pathParams) {
|
||||||
if (!declaredParams.contains(param)) {
|
if (!declaredParams.contains(param)) {
|
||||||
_errors.add(ValidationError(
|
_errors.add(
|
||||||
path: 'paths["$pathPattern"][${path.method.value}].parameters',
|
ValidationError(
|
||||||
message: '路径参数 "$param" 未在参数列表中声明',
|
path: 'paths["$pathPattern"][${path.method.value}].parameters',
|
||||||
type: ValidationErrorType.reference,
|
message: '路径参数 "$param" 未在参数列表中声明',
|
||||||
suggestion: '添加路径参数的声明',
|
type: ValidationErrorType.reference,
|
||||||
));
|
suggestion: '添加路径参数的声明',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查声明的路径参数是否都在路径中使用
|
// 检查声明的路径参数是否都在路径中使用
|
||||||
for (final param in declaredParams) {
|
for (final param in declaredParams) {
|
||||||
if (!pathParams.contains(param)) {
|
if (!pathParams.contains(param)) {
|
||||||
_warnings.add(ValidationWarning(
|
_warnings.add(
|
||||||
path: 'paths["$pathPattern"][${path.method.value}].parameters',
|
ValidationWarning(
|
||||||
message: '声明的路径参数 "$param" 未在路径中使用',
|
path: 'paths["$pathPattern"][${path.method.value}].parameters',
|
||||||
suggestion: '移除未使用的参数声明或修正路径',
|
message: '声明的路径参数 "$param" 未在路径中使用',
|
||||||
));
|
suggestion: '移除未使用的参数声明或修正路径',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -580,34 +643,40 @@ class SchemaValidator {
|
||||||
// 检查未定义的引用
|
// 检查未定义的引用
|
||||||
for (final ref in schemaRefs) {
|
for (final ref in schemaRefs) {
|
||||||
if (!schemas.contains(ref)) {
|
if (!schemas.contains(ref)) {
|
||||||
_errors.add(ValidationError(
|
_errors.add(
|
||||||
path: 'components.schemas',
|
ValidationError(
|
||||||
message: '引用的 schema "$ref" 未定义',
|
path: 'components.schemas',
|
||||||
type: ValidationErrorType.reference,
|
message: '引用的 schema "$ref" 未定义',
|
||||||
suggestion: '定义缺失的 schema 或修正引用',
|
type: ValidationErrorType.reference,
|
||||||
));
|
suggestion: '定义缺失的 schema 或修正引用',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final ref in securityRefs) {
|
for (final ref in securityRefs) {
|
||||||
if (!securitySchemes.contains(ref)) {
|
if (!securitySchemes.contains(ref)) {
|
||||||
_errors.add(ValidationError(
|
_errors.add(
|
||||||
path: 'components.securitySchemes',
|
ValidationError(
|
||||||
message: '引用的安全方案 "$ref" 未定义',
|
path: 'components.securitySchemes',
|
||||||
type: ValidationErrorType.reference,
|
message: '引用的安全方案 "$ref" 未定义',
|
||||||
suggestion: '定义缺失的安全方案或修正引用',
|
type: ValidationErrorType.reference,
|
||||||
));
|
suggestion: '定义缺失的安全方案或修正引用',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查未使用的组件
|
// 检查未使用的组件
|
||||||
for (final schema in schemas) {
|
for (final schema in schemas) {
|
||||||
if (!schemaRefs.contains(schema)) {
|
if (!schemaRefs.contains(schema)) {
|
||||||
_warnings.add(ValidationWarning(
|
_warnings.add(
|
||||||
path: 'components.schemas["$schema"]',
|
ValidationWarning(
|
||||||
message: 'Schema "$schema" 已定义但未被使用',
|
path: 'components.schemas["$schema"]',
|
||||||
suggestion: '移除未使用的 schema 或添加引用',
|
message: 'Schema "$schema" 已定义但未被使用',
|
||||||
));
|
suggestion: '移除未使用的 schema 或添加引用',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -617,32 +686,37 @@ class SchemaValidator {
|
||||||
final definedSchemes = document.components.securitySchemes.keys.toSet();
|
final definedSchemes = document.components.securitySchemes.keys.toSet();
|
||||||
|
|
||||||
// 检查全局安全要求
|
// 检查全局安全要求
|
||||||
for (int i = 0; i < document.security.length; i++) {
|
for (var i = 0; i < document.security.length; i++) {
|
||||||
final requirement = document.security[i];
|
final requirement = document.security[i];
|
||||||
for (final schemeName in requirement.schemeNames) {
|
for (final schemeName in requirement.schemeNames) {
|
||||||
if (!definedSchemes.contains(schemeName)) {
|
if (!definedSchemes.contains(schemeName)) {
|
||||||
_errors.add(ValidationError(
|
_errors.add(
|
||||||
path: 'security[$i]',
|
ValidationError(
|
||||||
message: '引用的安全方案 "$schemeName" 未定义',
|
path: 'security[$i]',
|
||||||
type: ValidationErrorType.reference,
|
message: '引用的安全方案 "$schemeName" 未定义',
|
||||||
suggestion: '在 components.securitySchemes 中定义该安全方案',
|
type: ValidationErrorType.reference,
|
||||||
));
|
suggestion: '在 components.securitySchemes 中定义该安全方案',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查操作级别的安全要求
|
// 检查操作级别的安全要求
|
||||||
document.paths.forEach((pathPattern, path) {
|
document.paths.forEach((pathPattern, path) {
|
||||||
for (int i = 0; i < path.security.length; i++) {
|
for (var i = 0; i < path.security.length; i++) {
|
||||||
final requirement = path.security[i];
|
final requirement = path.security[i];
|
||||||
for (final schemeName in requirement.schemeNames) {
|
for (final schemeName in requirement.schemeNames) {
|
||||||
if (!definedSchemes.contains(schemeName)) {
|
if (!definedSchemes.contains(schemeName)) {
|
||||||
_errors.add(ValidationError(
|
_errors.add(
|
||||||
path: 'paths["$pathPattern"][${path.method.value}].security[$i]',
|
ValidationError(
|
||||||
message: '引用的安全方案 "$schemeName" 未定义',
|
path:
|
||||||
type: ValidationErrorType.reference,
|
'paths["$pathPattern"][${path.method.value}].security[$i]',
|
||||||
suggestion: '在 components.securitySchemes 中定义该安全方案',
|
message: '引用的安全方案 "$schemeName" 未定义',
|
||||||
));
|
type: ValidationErrorType.reference,
|
||||||
|
suggestion: '在 components.securitySchemes 中定义该安全方案',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -655,16 +729,22 @@ class SchemaValidator {
|
||||||
// 验证请求体示例
|
// 验证请求体示例
|
||||||
if (path.requestBody != null) {
|
if (path.requestBody != null) {
|
||||||
path.requestBody!.content.forEach((mediaType, content) {
|
path.requestBody!.content.forEach((mediaType, content) {
|
||||||
_validateMediaTypeExamples(content,
|
_validateMediaTypeExamples(
|
||||||
'$pathPattern[${path.method.value}].requestBody.content["$mediaType"]');
|
content,
|
||||||
|
'$pathPattern[${path.method.value}]'
|
||||||
|
'.requestBody.content["$mediaType"]',
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证响应示例
|
// 验证响应示例
|
||||||
path.responses.forEach((code, response) {
|
path.responses.forEach((code, response) {
|
||||||
response.content.forEach((mediaType, content) {
|
response.content.forEach((mediaType, content) {
|
||||||
_validateMediaTypeExamples(content,
|
_validateMediaTypeExamples(
|
||||||
'$pathPattern[${path.method.value}].responses["$code"].content["$mediaType"]');
|
content,
|
||||||
|
'$pathPattern[${path.method.value}]'
|
||||||
|
'.responses["$code"].content["$mediaType"]',
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -674,16 +754,18 @@ class SchemaValidator {
|
||||||
void _validateMediaTypeExamples(ApiMediaType mediaType, String path) {
|
void _validateMediaTypeExamples(ApiMediaType mediaType, String path) {
|
||||||
// 检查 example 和 examples 不能同时存在
|
// 检查 example 和 examples 不能同时存在
|
||||||
if (mediaType.example != null && mediaType.examples.isNotEmpty) {
|
if (mediaType.example != null && mediaType.examples.isNotEmpty) {
|
||||||
_warnings.add(ValidationWarning(
|
_warnings.add(
|
||||||
path: path,
|
ValidationWarning(
|
||||||
message: 'example 和 examples 不应同时存在',
|
path: path,
|
||||||
suggestion: '使用 examples 对象来提供多个示例',
|
message: 'example 和 examples 不应同时存在',
|
||||||
));
|
suggestion: '使用 examples 对象来提供多个示例',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证示例格式
|
// 验证示例格式
|
||||||
if (mediaType.example != null && mediaType.schema != null) {
|
if (mediaType.example != null && mediaType.schema != null) {
|
||||||
// TODO: 根据 schema 验证 example 的格式
|
// TODO(max): 根据 schema 验证 example 的格式
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -697,11 +779,13 @@ class SchemaValidator {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!hasSuccessResponse) {
|
if (!hasSuccessResponse) {
|
||||||
_warnings.add(ValidationWarning(
|
_warnings.add(
|
||||||
path: 'paths["$pathPattern"][${path.method.value}].responses',
|
ValidationWarning(
|
||||||
message: '缺少成功响应 (2xx)',
|
path: 'paths["$pathPattern"][${path.method.value}].responses',
|
||||||
suggestion: '添加至少一个成功响应',
|
message: '缺少成功响应 (2xx)',
|
||||||
));
|
suggestion: '添加至少一个成功响应',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查错误响应
|
// 检查错误响应
|
||||||
|
|
@ -711,11 +795,13 @@ class SchemaValidator {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!hasErrorResponse) {
|
if (!hasErrorResponse) {
|
||||||
_warnings.add(ValidationWarning(
|
_warnings.add(
|
||||||
path: 'paths["$pathPattern"][${path.method.value}].responses',
|
ValidationWarning(
|
||||||
message: '建议添加错误响应 (4xx/5xx)',
|
path: 'paths["$pathPattern"][${path.method.value}].responses',
|
||||||
suggestion: '添加常见的错误响应,如 400、401、404、500',
|
message: '建议添加错误响应 (4xx/5xx)',
|
||||||
));
|
suggestion: '添加常见的错误响应,如 400、401、404、500',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -730,12 +816,14 @@ class SchemaValidator {
|
||||||
parameterNames.putIfAbsent(pathPattern, () => <String>{});
|
parameterNames.putIfAbsent(pathPattern, () => <String>{});
|
||||||
|
|
||||||
if (parameterNames[pathPattern]!.contains(key)) {
|
if (parameterNames[pathPattern]!.contains(key)) {
|
||||||
_errors.add(ValidationError(
|
_errors.add(
|
||||||
path: 'paths["$pathPattern"][${path.method.value}].parameters',
|
ValidationError(
|
||||||
message: '重复的参数: ${param.name} (${param.location.name})',
|
path: 'paths["$pathPattern"][${path.method.value}].parameters',
|
||||||
type: ValidationErrorType.constraint,
|
message: '重复的参数: ${param.name} (${param.location.name})',
|
||||||
suggestion: '确保参数名称在同一位置类型中唯一',
|
type: ValidationErrorType.constraint,
|
||||||
));
|
suggestion: '确保参数名称在同一位置类型中唯一',
|
||||||
|
),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
parameterNames[pathPattern]!.add(key);
|
parameterNames[pathPattern]!.add(key);
|
||||||
}
|
}
|
||||||
|
|
@ -762,13 +850,16 @@ class SchemaValidator {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 收集所有引用
|
/// 收集所有引用
|
||||||
void _collectReferences(SwaggerDocument document, Set<String> schemaRefs,
|
void _collectReferences(
|
||||||
Set<String> securityRefs) {
|
SwaggerDocument document,
|
||||||
|
Set<String> schemaRefs,
|
||||||
|
Set<String> securityRefs,
|
||||||
|
) {
|
||||||
// 从路径中收集引用
|
// 从路径中收集引用
|
||||||
document.paths.forEach((pathPattern, path) {
|
document.paths.forEach((pathPattern, path) {
|
||||||
// 从参数中收集引用
|
// 从参数中收集引用
|
||||||
for (final _ in path.parameters) {
|
for (final _ in path.parameters) {
|
||||||
// TODO: 收集参数 schema 引用
|
// TODO(max): 收集参数 schema 引用
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从请求体中收集引用
|
// 从请求体中收集引用
|
||||||
|
|
@ -799,18 +890,20 @@ class SchemaValidator {
|
||||||
// 从组件中收集引用
|
// 从组件中收集引用
|
||||||
document.components.schemas.forEach((name, model) {
|
document.components.schemas.forEach((name, model) {
|
||||||
for (final _ in model.properties.values) {
|
for (final _ in model.properties.values) {
|
||||||
// TODO: 收集属性 schema 引用
|
// TODO(max): 收集属性 schema 引用
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 收集 Schema 引用
|
/// 收集 Schema 引用
|
||||||
void _collectSchemaReferences(
|
void _collectSchemaReferences(
|
||||||
Map<String, dynamic>? schema, Set<String> refs) {
|
Map<String, dynamic>? schema,
|
||||||
|
Set<String> refs,
|
||||||
|
) {
|
||||||
if (schema == null) return;
|
if (schema == null) return;
|
||||||
|
|
||||||
// 检查 $ref
|
// 检查 $ref
|
||||||
final ref = schema['\$ref'] as String?;
|
final ref = schema[r'$ref'] as String?;
|
||||||
if (ref != null && ref.startsWith('#/components/schemas/')) {
|
if (ref != null && ref.startsWith('#/components/schemas/')) {
|
||||||
final refName = ref.substring('#/components/schemas/'.length);
|
final refName = ref.substring('#/components/schemas/'.length);
|
||||||
refs.add(refName);
|
refs.add(refName);
|
||||||
|
|
|
||||||
222
pubspec.lock
222
pubspec.lock
|
|
@ -5,18 +5,26 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _fe_analyzer_shared
|
name: _fe_analyzer_shared
|
||||||
sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7"
|
sha256: f0bb5d1648339c8308cc0b9838d8456b3cfe5c91f9dc1a735b4d003269e5da9a
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "67.0.0"
|
version: "88.0.0"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d"
|
sha256: "0b7b9c329d2879f8f05d6c05b32ee9ec025f39b077864bdb5ac9a7b63418a98f"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.4.1"
|
version: "8.1.1"
|
||||||
|
ansicolor:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ansicolor
|
||||||
|
sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.3"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -45,50 +53,34 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build
|
name: build
|
||||||
sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
|
sha256: c1668065e9ba04752570ad7e038288559d1e2ca5c6d0131c0f5f55e39e777413
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.1"
|
version: "4.0.3"
|
||||||
build_config:
|
build_config:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_config
|
name: build_config
|
||||||
sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33"
|
sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
version: "1.2.0"
|
||||||
build_daemon:
|
build_daemon:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_daemon
|
name: build_daemon
|
||||||
sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa"
|
sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.4"
|
version: "4.1.1"
|
||||||
build_resolvers:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: build_resolvers
|
|
||||||
sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a"
|
|
||||||
url: "https://pub.flutter-io.cn"
|
|
||||||
source: hosted
|
|
||||||
version: "2.4.2"
|
|
||||||
build_runner:
|
build_runner:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: build_runner
|
name: build_runner
|
||||||
sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d"
|
sha256: "110c56ef29b5eb367b4d17fc79375fa8c18a6cd7acd92c05bb3986c17a079057"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.13"
|
version: "2.10.4"
|
||||||
build_runner_core:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: build_runner_core
|
|
||||||
sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0
|
|
||||||
url: "https://pub.flutter-io.cn"
|
|
||||||
source: hosted
|
|
||||||
version: "7.3.2"
|
|
||||||
built_collection:
|
built_collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -101,10 +93,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: built_value
|
name: built_value
|
||||||
sha256: "082001b5c3dc495d4a42f1d5789990505df20d8547d42507c29050af6933ee27"
|
sha256: "426cf75afdb23aa74bd4e471704de3f9393f3c7b04c1e2d9c6f1073ae0b8b139"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.10.1"
|
version: "8.12.1"
|
||||||
checked_yaml:
|
checked_yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -125,10 +117,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: code_builder
|
name: code_builder
|
||||||
sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e"
|
sha256: "11654819532ba94c34de52ff5feb52bd81cba1de00ef2ed622fd50295f9d4243"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.10.1"
|
version: "4.11.0"
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -157,26 +149,26 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: crypto
|
name: crypto
|
||||||
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
|
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.6"
|
version: "3.0.7"
|
||||||
dart_style:
|
dart_style:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dart_style
|
name: dart_style
|
||||||
sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9"
|
sha256: c87dfe3d56f183ffe9106a18aebc6db431fc7c98c31a54b952a77f3d54a85697
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.6"
|
version: "3.1.2"
|
||||||
dio:
|
dio:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: dio
|
name: dio
|
||||||
sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9"
|
sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.8.0+1"
|
version: "5.9.0"
|
||||||
dio_web_adapter:
|
dio_web_adapter:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -201,6 +193,30 @@ packages:
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.1.1"
|
||||||
|
flutter_lints:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: flutter_lints
|
||||||
|
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.0"
|
||||||
|
freezed:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: freezed
|
||||||
|
sha256: "13065f10e135263a4f5a4391b79a8efc5fb8106f8dd555a9e49b750b45393d77"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.3"
|
||||||
|
freezed_annotation:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: freezed_annotation
|
||||||
|
sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.0"
|
||||||
frontend_server_client:
|
frontend_server_client:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -225,14 +241,22 @@ packages:
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.2"
|
version: "2.3.2"
|
||||||
|
hotreloader:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: hotreloader
|
||||||
|
sha256: bc167a1163807b03bada490bfe2df25b0d744df359227880220a5cbd04e5734b
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "4.3.0"
|
||||||
http:
|
http:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: http
|
name: http
|
||||||
sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b"
|
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.6.0"
|
||||||
http_multi_server:
|
http_multi_server:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -257,14 +281,6 @@ packages:
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.5"
|
version: "1.0.5"
|
||||||
js:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: js
|
|
||||||
sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
|
|
||||||
url: "https://pub.flutter-io.cn"
|
|
||||||
source: hosted
|
|
||||||
version: "0.7.2"
|
|
||||||
json_annotation:
|
json_annotation:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -277,10 +293,26 @@ packages:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: json_serializable
|
name: json_serializable
|
||||||
sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b
|
sha256: c5b2ee75210a0f263c6c7b9eeea80553dbae96ea1bf57f02484e806a3ffdffa3
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.8.0"
|
version: "6.11.2"
|
||||||
|
lean_builder:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: lean_builder
|
||||||
|
sha256: ef5cd5f907157eb7aa87d1704504b5a6386d2cbff88a3c2b3344477bab323ee9
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.2"
|
||||||
|
lints:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: lints
|
||||||
|
sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.0"
|
||||||
logging:
|
logging:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -293,18 +325,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.17"
|
version: "0.12.18"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.16.0"
|
version: "1.17.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -341,18 +373,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: pool
|
name: pool
|
||||||
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
|
sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.1"
|
version: "1.5.2"
|
||||||
protobuf:
|
protobuf:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: protobuf
|
name: protobuf
|
||||||
sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d"
|
sha256: "2fcc8a202ca7ec17dab7c97d6b6d91cf03aa07fe6f65f8afbb6dfa52cc5bd902"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "5.1.0"
|
||||||
pub_semver:
|
pub_semver:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -373,18 +405,18 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: retrofit
|
name: retrofit
|
||||||
sha256: "84d70114a5b6bae5f4c1302335f9cb610ebeb1b02023d5e7e87697aaff52926a"
|
sha256: "84063c18a00d55af41d6b8401edf8473e8c215bd7068ef7ec5e34c60657ffdbe"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.6.0"
|
version: "4.9.1"
|
||||||
retrofit_generator:
|
retrofit_generator:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: retrofit_generator
|
name: retrofit_generator
|
||||||
sha256: "8dfc406cdfa171f33cbd21bf5bd8b6763548cc217de19cdeaa07a76727fac4ca"
|
sha256: "7ec323f3329ad2ca0bcdc96fe02ec7f2486ecfac6cd2d035b03c398ef6f42308"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.2.1"
|
version: "10.2.0"
|
||||||
shelf:
|
shelf:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -413,26 +445,26 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shelf_web_socket
|
name: shelf_web_socket
|
||||||
sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67
|
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "3.0.0"
|
||||||
source_gen:
|
source_gen:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: source_gen
|
name: source_gen
|
||||||
sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832"
|
sha256: "07b277b67e0096c45196cbddddf2d8c6ffc49342e88bf31d460ce04605ddac75"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.0"
|
version: "4.1.1"
|
||||||
source_helper:
|
source_helper:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: source_helper
|
name: source_helper
|
||||||
sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c"
|
sha256: "6a3c6cc82073a8797f8c4dc4572146114a39652851c157db37e964d9c7038723"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.5"
|
version: "1.3.8"
|
||||||
source_map_stack_trace:
|
source_map_stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -501,42 +533,26 @@ packages:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: test
|
name: test
|
||||||
sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb"
|
sha256: "77cc98ea27006c84e71a7356cf3daf9ddbde2d91d84f77dbfe64cf0e4d9611ae"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.26.2"
|
version: "1.28.0"
|
||||||
test_api:
|
test_api:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
|
sha256: "19a78f63e83d3a61f00826d09bc2f60e191bf3504183c001262be6ac75589fb8"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.6"
|
version: "0.7.8"
|
||||||
test_core:
|
test_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_core
|
name: test_core
|
||||||
sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a"
|
sha256: f1072617a6657e5fc09662e721307f7fb009b4ed89b19f47175d11d5254a62d4
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.11"
|
version: "0.6.14"
|
||||||
timing:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: timing
|
|
||||||
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
|
|
||||||
url: "https://pub.flutter-io.cn"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.2"
|
|
||||||
tuple:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: tuple
|
|
||||||
sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
|
|
||||||
url: "https://pub.flutter-io.cn"
|
|
||||||
source: hosted
|
|
||||||
version: "2.0.2"
|
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -545,22 +561,30 @@ packages:
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.0"
|
||||||
|
very_good_analysis:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: very_good_analysis
|
||||||
|
sha256: "96245839dbcc45dfab1af5fa551603b5c7a282028a64746c19c547d21a7f1e3a"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "10.0.0"
|
||||||
vm_service:
|
vm_service:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vm_service
|
name: vm_service
|
||||||
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
|
sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "15.0.0"
|
version: "15.0.2"
|
||||||
watcher:
|
watcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: watcher
|
name: watcher
|
||||||
sha256: "0b7fd4a0bbc4b92641dbf20adfd7e3fd1398fe17102d94b674234563e110088a"
|
sha256: "592ab6e2892f67760543fb712ff0177f4ec76c031f02f5b4ff8d3fc5eb9fb61a"
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
version: "1.1.4"
|
||||||
web:
|
web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -593,6 +617,14 @@ packages:
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.1"
|
version: "1.2.1"
|
||||||
|
xxh3:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: xxh3
|
||||||
|
sha256: "399a0438f5d426785723c99da6b16e136f4953fb1e9db0bf270bd41dd4619916"
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.0"
|
||||||
yaml:
|
yaml:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -602,4 +634,4 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.8.0 <4.0.0"
|
dart: ">=3.9.0 <4.0.0"
|
||||||
|
|
|
||||||
36
pubspec.yaml
36
pubspec.yaml
|
|
@ -15,27 +15,27 @@ executables:
|
||||||
swagger_generator_flutter: swagger_generator_flutter
|
swagger_generator_flutter: swagger_generator_flutter
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
# Flutter SDK(可选,仅当需要 Flutter 特性时)
|
# HTTP 客户端
|
||||||
# 注释掉以支持纯 Dart 项目
|
dio: ^5.9.0
|
||||||
# flutter:
|
# JSON 序列化
|
||||||
# sdk: flutter
|
freezed_annotation: ^3.1.0
|
||||||
|
|
||||||
# 核心依赖
|
|
||||||
path: ^1.8.0
|
|
||||||
logging: ^1.1.0
|
|
||||||
yaml: ^3.1.0
|
|
||||||
|
|
||||||
# HTTP 和 API 相关(仅用于类型引用,不是运行时依赖)
|
# HTTP 和 API 相关(仅用于类型引用,不是运行时依赖)
|
||||||
http: ^1.1.0
|
http: ^1.1.0
|
||||||
dio: ^5.0.0
|
json_annotation: ^4.9.0
|
||||||
retrofit: ^4.0.0
|
# 核心依赖
|
||||||
json_annotation: ^4.8.1
|
logging: ^1.3.0
|
||||||
|
path: ^1.9.1
|
||||||
|
# API 客户端
|
||||||
|
retrofit: ^4.9.1
|
||||||
|
yaml: ^3.1.3
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
# 代码生成工具(仅用于测试/示例)
|
||||||
|
build_runner: ^2.10.4
|
||||||
|
flutter_lints: 6.0.0
|
||||||
|
freezed: ^3.2.3
|
||||||
|
json_serializable: ^6.11.2
|
||||||
|
retrofit_generator: ^10.2.0
|
||||||
# 测试框架
|
# 测试框架
|
||||||
test: ^1.24.0
|
test: ^1.24.0
|
||||||
|
very_good_analysis: ^10.0.0
|
||||||
# 代码生成工具(仅用于测试/示例)
|
|
||||||
build_runner: ^2.4.7
|
|
||||||
json_serializable: ^6.7.1
|
|
||||||
retrofit_generator: ^8.0.0
|
|
||||||
|
|
|
||||||
|
|
@ -18,15 +18,14 @@ void main() {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
components: ApiComponents(
|
components: ApiComponents(
|
||||||
schemas: {},
|
|
||||||
securitySchemes: {
|
securitySchemes: {
|
||||||
'bearerAuth': const ApiSecurityScheme(
|
'bearerAuth': ApiSecurityScheme(
|
||||||
type: SecuritySchemeType.http,
|
type: SecuritySchemeType.http,
|
||||||
description: 'Bearer token',
|
description: 'Bearer token',
|
||||||
scheme: 'bearer',
|
scheme: 'bearer',
|
||||||
bearerFormat: 'JWT',
|
bearerFormat: 'JWT',
|
||||||
),
|
),
|
||||||
'apiKey': const ApiSecurityScheme(
|
'apiKey': ApiSecurityScheme(
|
||||||
type: SecuritySchemeType.apiKey,
|
type: SecuritySchemeType.apiKey,
|
||||||
description: 'API Key',
|
description: 'API Key',
|
||||||
name: 'X-API-Key',
|
name: 'X-API-Key',
|
||||||
|
|
@ -35,7 +34,7 @@ void main() {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
paths: {
|
paths: {
|
||||||
'/users': const ApiPath(
|
'/users': ApiPath(
|
||||||
path: '/users',
|
path: '/users',
|
||||||
method: HttpMethod.get,
|
method: HttpMethod.get,
|
||||||
summary: 'Get all users',
|
summary: 'Get all users',
|
||||||
|
|
@ -59,25 +58,25 @@ void main() {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
responses: {
|
responses: {
|
||||||
'200': const ApiResponse(
|
'200': ApiResponse(
|
||||||
code: '200',
|
code: '200',
|
||||||
description: 'Successful response',
|
description: 'Successful response',
|
||||||
content: {
|
content: {
|
||||||
'application/json': const ApiMediaType(
|
'application/json': ApiMediaType(
|
||||||
schema: {
|
schema: {
|
||||||
'type': 'array',
|
'type': 'array',
|
||||||
'items': {
|
'items': {
|
||||||
'\$ref': '#/components/schemas/User',
|
r'$ref': '#/components/schemas/User',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
'400': const ApiResponse(
|
'400': ApiResponse(
|
||||||
code: '400',
|
code: '400',
|
||||||
description: 'Bad request',
|
description: 'Bad request',
|
||||||
),
|
),
|
||||||
'401': const ApiResponse(
|
'401': ApiResponse(
|
||||||
code: '401',
|
code: '401',
|
||||||
description: 'Unauthorized',
|
description: 'Unauthorized',
|
||||||
),
|
),
|
||||||
|
|
@ -88,7 +87,7 @@ void main() {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
'/users/{id}': const ApiPath(
|
'/users/{id}': ApiPath(
|
||||||
path: '/users/{id}',
|
path: '/users/{id}',
|
||||||
method: HttpMethod.get,
|
method: HttpMethod.get,
|
||||||
summary: 'Get user by ID',
|
summary: 'Get user by ID',
|
||||||
|
|
@ -105,24 +104,24 @@ void main() {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
responses: {
|
responses: {
|
||||||
'200': const ApiResponse(
|
'200': ApiResponse(
|
||||||
code: '200',
|
code: '200',
|
||||||
description: 'User found',
|
description: 'User found',
|
||||||
content: {
|
content: {
|
||||||
'application/json': const ApiMediaType(
|
'application/json': ApiMediaType(
|
||||||
schema: {
|
schema: {
|
||||||
'\$ref': '#/components/schemas/User',
|
r'$ref': '#/components/schemas/User',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
'404': const ApiResponse(
|
'404': ApiResponse(
|
||||||
code: '404',
|
code: '404',
|
||||||
description: 'User not found',
|
description: 'User not found',
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
'/users/create': const ApiPath(
|
'/users/create': ApiPath(
|
||||||
path: '/users/create',
|
path: '/users/create',
|
||||||
method: HttpMethod.post,
|
method: HttpMethod.post,
|
||||||
summary: 'Create user',
|
summary: 'Create user',
|
||||||
|
|
@ -134,32 +133,32 @@ void main() {
|
||||||
description: 'User data',
|
description: 'User data',
|
||||||
required: true,
|
required: true,
|
||||||
content: {
|
content: {
|
||||||
'application/json': const ApiMediaType(
|
'application/json': ApiMediaType(
|
||||||
schema: {
|
schema: {
|
||||||
'\$ref': '#/components/schemas/CreateUserRequest',
|
r'$ref': '#/components/schemas/CreateUserRequest',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
responses: {
|
responses: {
|
||||||
'201': const ApiResponse(
|
'201': ApiResponse(
|
||||||
code: '201',
|
code: '201',
|
||||||
description: 'User created',
|
description: 'User created',
|
||||||
content: {
|
content: {
|
||||||
'application/json': const ApiMediaType(
|
'application/json': ApiMediaType(
|
||||||
schema: {
|
schema: {
|
||||||
'\$ref': '#/components/schemas/User',
|
r'$ref': '#/components/schemas/User',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
'400': const ApiResponse(
|
'400': ApiResponse(
|
||||||
code: '400',
|
code: '400',
|
||||||
description: 'Invalid input',
|
description: 'Invalid input',
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
'/files/upload': const ApiPath(
|
'/files/upload': ApiPath(
|
||||||
path: '/files/upload',
|
path: '/files/upload',
|
||||||
method: HttpMethod.post,
|
method: HttpMethod.post,
|
||||||
summary: 'Upload file',
|
summary: 'Upload file',
|
||||||
|
|
@ -171,7 +170,7 @@ void main() {
|
||||||
description: 'File to upload',
|
description: 'File to upload',
|
||||||
required: true,
|
required: true,
|
||||||
content: {
|
content: {
|
||||||
'multipart/form-data': const ApiMediaType(
|
'multipart/form-data': ApiMediaType(
|
||||||
schema: {
|
schema: {
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
'properties': {
|
'properties': {
|
||||||
|
|
@ -188,13 +187,13 @@ void main() {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
responses: {
|
responses: {
|
||||||
'200': const ApiResponse(
|
'200': ApiResponse(
|
||||||
code: '200',
|
code: '200',
|
||||||
description: 'File uploaded successfully',
|
description: 'File uploaded successfully',
|
||||||
content: {
|
content: {
|
||||||
'application/json': const ApiMediaType(
|
'application/json': ApiMediaType(
|
||||||
schema: {
|
schema: {
|
||||||
'\$ref': '#/components/schemas/FileUploadResult',
|
r'$ref': '#/components/schemas/FileUploadResult',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
@ -203,29 +202,29 @@ void main() {
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
models: {
|
models: {
|
||||||
'User': const ApiModel(
|
'User': ApiModel(
|
||||||
name: 'User',
|
name: 'User',
|
||||||
description: 'User model',
|
description: 'User model',
|
||||||
properties: {
|
properties: {
|
||||||
'id': const ApiProperty(
|
'id': ApiProperty(
|
||||||
name: 'id',
|
name: 'id',
|
||||||
type: PropertyType.integer,
|
type: PropertyType.integer,
|
||||||
description: 'User ID',
|
description: 'User ID',
|
||||||
required: true,
|
required: true,
|
||||||
),
|
),
|
||||||
'name': const ApiProperty(
|
'name': ApiProperty(
|
||||||
name: 'name',
|
name: 'name',
|
||||||
type: PropertyType.string,
|
type: PropertyType.string,
|
||||||
description: 'User name',
|
description: 'User name',
|
||||||
required: true,
|
required: true,
|
||||||
),
|
),
|
||||||
'email': const ApiProperty(
|
'email': ApiProperty(
|
||||||
name: 'email',
|
name: 'email',
|
||||||
type: PropertyType.string,
|
type: PropertyType.string,
|
||||||
description: 'User email',
|
description: 'User email',
|
||||||
required: true,
|
required: true,
|
||||||
),
|
),
|
||||||
'createdAt': const ApiProperty(
|
'createdAt': ApiProperty(
|
||||||
name: 'createdAt',
|
name: 'createdAt',
|
||||||
type: PropertyType.string,
|
type: PropertyType.string,
|
||||||
description: 'Creation timestamp',
|
description: 'Creation timestamp',
|
||||||
|
|
@ -234,17 +233,17 @@ void main() {
|
||||||
},
|
},
|
||||||
required: ['id', 'name', 'email'],
|
required: ['id', 'name', 'email'],
|
||||||
),
|
),
|
||||||
'CreateUserRequest': const ApiModel(
|
'CreateUserRequest': ApiModel(
|
||||||
name: 'CreateUserRequest',
|
name: 'CreateUserRequest',
|
||||||
description: 'Request model for creating a user',
|
description: 'Request model for creating a user',
|
||||||
properties: {
|
properties: {
|
||||||
'name': const ApiProperty(
|
'name': ApiProperty(
|
||||||
name: 'name',
|
name: 'name',
|
||||||
type: PropertyType.string,
|
type: PropertyType.string,
|
||||||
description: 'User name',
|
description: 'User name',
|
||||||
required: true,
|
required: true,
|
||||||
),
|
),
|
||||||
'email': const ApiProperty(
|
'email': ApiProperty(
|
||||||
name: 'email',
|
name: 'email',
|
||||||
type: PropertyType.string,
|
type: PropertyType.string,
|
||||||
description: 'User email',
|
description: 'User email',
|
||||||
|
|
@ -253,23 +252,23 @@ void main() {
|
||||||
},
|
},
|
||||||
required: ['name', 'email'],
|
required: ['name', 'email'],
|
||||||
),
|
),
|
||||||
'FileUploadResult': const ApiModel(
|
'FileUploadResult': ApiModel(
|
||||||
name: 'FileUploadResult',
|
name: 'FileUploadResult',
|
||||||
description: 'Result of file upload',
|
description: 'Result of file upload',
|
||||||
properties: {
|
properties: {
|
||||||
'url': const ApiProperty(
|
'url': ApiProperty(
|
||||||
name: 'url',
|
name: 'url',
|
||||||
type: PropertyType.string,
|
type: PropertyType.string,
|
||||||
description: 'File URL',
|
description: 'File URL',
|
||||||
required: true,
|
required: true,
|
||||||
),
|
),
|
||||||
'filename': const ApiProperty(
|
'filename': ApiProperty(
|
||||||
name: 'filename',
|
name: 'filename',
|
||||||
type: PropertyType.string,
|
type: PropertyType.string,
|
||||||
description: 'Original filename',
|
description: 'Original filename',
|
||||||
required: true,
|
required: true,
|
||||||
),
|
),
|
||||||
'size': const ApiProperty(
|
'size': ApiProperty(
|
||||||
name: 'size',
|
name: 'size',
|
||||||
type: PropertyType.integer,
|
type: PropertyType.integer,
|
||||||
description: 'File size in bytes',
|
description: 'File size in bytes',
|
||||||
|
|
@ -301,87 +300,87 @@ void main() {
|
||||||
expect(result, contains('abstract class TestApiService'));
|
expect(result, contains('abstract class TestApiService'));
|
||||||
expect(result, contains('@RestApi()'));
|
expect(result, contains('@RestApi()'));
|
||||||
expect(result, contains('factory TestApiService(Dio dio'));
|
expect(result, contains('factory TestApiService(Dio dio'));
|
||||||
expect(result, contains('@GET(\'/users\')'));
|
expect(result, contains("@GET('/users')"));
|
||||||
expect(result, contains('@POST(\'/users\')'));
|
expect(result, contains("@POST('/users')"));
|
||||||
expect(result, contains('@Path(\'id\')'));
|
expect(result, contains("@Path('id')"));
|
||||||
expect(result, contains('@Query(\'page\')'));
|
expect(result, contains("@Query('page')"));
|
||||||
expect(result, contains('@Body()'));
|
expect(result, contains('@Body()'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('generates split APIs by tags', () {
|
test('generates split APIs by tags', () {
|
||||||
final generator = RetrofitApiGenerator(
|
final generator = RetrofitApiGenerator(
|
||||||
className: 'ApiService',
|
className: 'ApiService',
|
||||||
splitByTags: true,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final result = generator.generateFromDocument(testDocument);
|
final result = generator.generateFromDocument(testDocument);
|
||||||
|
|
||||||
expect(result, isNotEmpty);
|
expect(result, isNotEmpty);
|
||||||
expect(result, contains('UsersApi'));
|
// The main file should contain the aggregator class
|
||||||
expect(result, contains('FilesApi'));
|
|
||||||
expect(result, contains('class ApiService'));
|
expect(result, contains('class ApiService'));
|
||||||
expect(result, contains('late final UsersApi users'));
|
// It should have getters for the individual API services
|
||||||
expect(result, contains('late final FilesApi files'));
|
expect(result, contains('UsersApi get users => _usersApi;'));
|
||||||
|
expect(result, contains('FilesApi get files => _filesApi;'));
|
||||||
|
// It should import the tag-based API files
|
||||||
|
expect(result, contains("import 'users_api.dart';"));
|
||||||
|
expect(result, contains("import 'files_api.dart';"));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handles file upload endpoints', () {
|
test('handles file upload endpoints', () {
|
||||||
final generator = RetrofitApiGenerator();
|
final generator = RetrofitApiGenerator(splitByTags: false);
|
||||||
|
|
||||||
final result = generator.generateFromDocument(testDocument);
|
final result = generator.generateFromDocument(testDocument);
|
||||||
|
|
||||||
expect(result, contains('@POST(\'/files/upload\')'));
|
expect(result, contains("@POST('/files/upload')"));
|
||||||
expect(result, contains('@MultiPart()'));
|
expect(result, contains('@MultiPart()'));
|
||||||
expect(result, contains('MultipartFile'));
|
expect(result, contains('MultipartFile'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('generates proper parameter annotations', () {
|
test('generates proper parameter annotations', () {
|
||||||
final generator = RetrofitApiGenerator();
|
final generator = RetrofitApiGenerator(splitByTags: false);
|
||||||
|
|
||||||
final result = generator.generateFromDocument(testDocument);
|
final result = generator.generateFromDocument(testDocument);
|
||||||
|
|
||||||
// Path parameters
|
// Path parameters
|
||||||
expect(result, contains('@Path(\'id\') int id'));
|
expect(result, contains("@Path('id') int id"));
|
||||||
|
|
||||||
// Query parameters
|
// Query parameters
|
||||||
expect(result, contains('@Query(\'page\') int? page'));
|
expect(result, contains("@Query('page') int? page"));
|
||||||
expect(result, contains('@Query(\'limit\') int? limit'));
|
expect(result, contains("@Query('limit') int? limit"));
|
||||||
|
|
||||||
// Body parameters
|
// Body parameters
|
||||||
expect(result, contains('@Body() CreateUserRequest body'));
|
expect(result, contains('@Body() CreateUserRequest request'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('generates security annotations', () {
|
test('generates security annotations', () {
|
||||||
final generator = RetrofitApiGenerator();
|
final generator = RetrofitApiGenerator(splitByTags: false);
|
||||||
|
|
||||||
final result = generator.generateFromDocument(testDocument);
|
final result = generator.generateFromDocument(testDocument);
|
||||||
|
|
||||||
// Should include headers for authentication
|
// Should include headers for authentication
|
||||||
expect(
|
expect(
|
||||||
result, anyOf([contains('Authorization'), contains('X-API-Key')]));
|
result, anyOf([contains('Authorization'), contains('X-API-Key')]),);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group('Code Quality', () {
|
group('Code Quality', () {
|
||||||
test('generated code is valid Dart syntax', () {
|
test('generated code is valid Dart syntax', () {
|
||||||
final generator = RetrofitApiGenerator();
|
final generator = RetrofitApiGenerator(splitByTags: false);
|
||||||
final result = generator.generateFromDocument(testDocument);
|
final result = generator.generateFromDocument(testDocument);
|
||||||
|
|
||||||
// Basic syntax checks
|
// Basic syntax checks
|
||||||
expect(result, isNot(contains(';;'))); // No double semicolons
|
expect(result, isNot(contains(';;'))); // No double semicolons
|
||||||
expect(result, isNot(contains(',,'))); // No double commas
|
expect(result, isNot(contains(',,'))); // No double commas
|
||||||
expect(result,
|
|
||||||
isNot(contains(' '))); // No double spaces (basic formatting)
|
|
||||||
|
|
||||||
// Check for proper imports
|
// Check for proper imports
|
||||||
expect(result, contains('import \'package:dio/dio.dart\';'));
|
expect(result, contains("import 'package:dio/dio.dart';"));
|
||||||
expect(result, contains('import \'package:retrofit/retrofit.dart\';'));
|
expect(result, contains("import 'package:retrofit/retrofit.dart';"));
|
||||||
|
|
||||||
// Check for proper class structure
|
// Check for proper class structure
|
||||||
final classMatches = RegExp(r'class \w+').allMatches(result);
|
final classMatches = RegExp(r'class \w+').allMatches(result);
|
||||||
final abstractClassMatches =
|
final abstractClassMatches =
|
||||||
RegExp(r'abstract class \w+').allMatches(result);
|
RegExp(r'abstract class \w+').allMatches(result);
|
||||||
expect(
|
expect(
|
||||||
classMatches.length + abstractClassMatches.length, greaterThan(0));
|
classMatches.length + abstractClassMatches.length, greaterThan(0),);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handles special characters in names', () {
|
test('handles special characters in names', () {
|
||||||
|
|
@ -389,10 +388,8 @@ void main() {
|
||||||
title: 'API with Special-Characters_and.dots',
|
title: 'API with Special-Characters_and.dots',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
description: 'Test API',
|
description: 'Test API',
|
||||||
servers: [],
|
|
||||||
components: ApiComponents(schemas: {}, securitySchemes: {}),
|
|
||||||
paths: {
|
paths: {
|
||||||
'/special-endpoint_with.dots': const ApiPath(
|
'/special-endpoint_with.dots': ApiPath(
|
||||||
path: '/special-endpoint_with.dots',
|
path: '/special-endpoint_with.dots',
|
||||||
method: HttpMethod.get,
|
method: HttpMethod.get,
|
||||||
summary: 'Special endpoint',
|
summary: 'Special endpoint',
|
||||||
|
|
@ -401,7 +398,7 @@ void main() {
|
||||||
tags: ['special-tag_with.dots'],
|
tags: ['special-tag_with.dots'],
|
||||||
parameters: [],
|
parameters: [],
|
||||||
responses: {
|
responses: {
|
||||||
'200': const ApiResponse(
|
'200': ApiResponse(
|
||||||
code: '200',
|
code: '200',
|
||||||
description: 'Success',
|
description: 'Success',
|
||||||
),
|
),
|
||||||
|
|
@ -410,10 +407,9 @@ void main() {
|
||||||
},
|
},
|
||||||
models: {},
|
models: {},
|
||||||
controllers: {},
|
controllers: {},
|
||||||
security: [],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final generator = RetrofitApiGenerator();
|
final generator = RetrofitApiGenerator(splitByTags: false);
|
||||||
final result = generator.generateFromDocument(specialDocument);
|
final result = generator.generateFromDocument(specialDocument);
|
||||||
|
|
||||||
expect(result, isNotEmpty);
|
expect(result, isNotEmpty);
|
||||||
|
|
@ -422,15 +418,15 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handles nullable and required fields correctly', () {
|
test('handles nullable and required fields correctly', () {
|
||||||
final generator = RetrofitApiGenerator();
|
final generator = RetrofitApiGenerator(splitByTags: false);
|
||||||
final result = generator.generateFromDocument(testDocument);
|
final result = generator.generateFromDocument(testDocument);
|
||||||
|
|
||||||
// Required path parameters should not be nullable
|
// Required path parameters should not be nullable
|
||||||
expect(result, contains('@Path(\'id\') int id'));
|
expect(result, contains("@Path('id') int id"));
|
||||||
|
|
||||||
// Optional query parameters should be nullable
|
// Optional query parameters should be nullable
|
||||||
expect(result, contains('@Query(\'page\') int? page'));
|
expect(result, contains("@Query('page') int? page"));
|
||||||
expect(result, contains('@Query(\'limit\') int? limit'));
|
expect(result, contains("@Query('limit') int? limit"));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -440,19 +436,16 @@ void main() {
|
||||||
title: 'Empty API',
|
title: 'Empty API',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
description: 'Empty test API',
|
description: 'Empty test API',
|
||||||
servers: [],
|
|
||||||
components: ApiComponents(schemas: {}, securitySchemes: {}),
|
|
||||||
paths: {},
|
paths: {},
|
||||||
models: {},
|
models: {},
|
||||||
controllers: {},
|
controllers: {},
|
||||||
security: [],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final generator = RetrofitApiGenerator();
|
final generator = RetrofitApiGenerator();
|
||||||
final result = generator.generateFromDocument(emptyDocument);
|
final result = generator.generateFromDocument(emptyDocument);
|
||||||
|
|
||||||
expect(result, isNotEmpty);
|
expect(result, isNotEmpty);
|
||||||
expect(result, contains('Empty API'));
|
|
||||||
// Should still generate basic structure even with no paths
|
// Should still generate basic structure even with no paths
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -461,10 +454,8 @@ void main() {
|
||||||
title: 'Test API',
|
title: 'Test API',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
description: 'Test',
|
description: 'Test',
|
||||||
servers: [],
|
|
||||||
components: ApiComponents(schemas: {}, securitySchemes: {}),
|
|
||||||
paths: {
|
paths: {
|
||||||
'/test': const ApiPath(
|
'/test': ApiPath(
|
||||||
path: '/test',
|
path: '/test',
|
||||||
method: HttpMethod.get,
|
method: HttpMethod.get,
|
||||||
summary: 'Test endpoint',
|
summary: 'Test endpoint',
|
||||||
|
|
@ -473,7 +464,7 @@ void main() {
|
||||||
tags: [],
|
tags: [],
|
||||||
parameters: [],
|
parameters: [],
|
||||||
responses: {
|
responses: {
|
||||||
'200': const ApiResponse(
|
'200': ApiResponse(
|
||||||
code: '200',
|
code: '200',
|
||||||
description: 'Success',
|
description: 'Success',
|
||||||
),
|
),
|
||||||
|
|
@ -482,13 +473,12 @@ void main() {
|
||||||
},
|
},
|
||||||
models: {},
|
models: {},
|
||||||
controllers: {},
|
controllers: {},
|
||||||
security: [],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final generator = RetrofitApiGenerator();
|
final generator = RetrofitApiGenerator(splitByTags: false);
|
||||||
expect(
|
expect(
|
||||||
() => generator.generateFromDocument(documentWithoutOperationIds),
|
() => generator.generateFromDocument(documentWithoutOperationIds),
|
||||||
returnsNormally);
|
returnsNormally,);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ void main() {
|
||||||
group('Comprehensive Parser Tests', () {
|
group('Comprehensive Parser Tests', () {
|
||||||
group('OpenAPI 3.0 Core Features', () {
|
group('OpenAPI 3.0 Core Features', () {
|
||||||
test('parses basic OpenAPI 3.0 document', () {
|
test('parses basic OpenAPI 3.0 document', () {
|
||||||
final json = {
|
final json = <String, dynamic>{
|
||||||
'openapi': '3.0.3',
|
'openapi': '3.0.3',
|
||||||
'info': {
|
'info': {
|
||||||
'title': 'Test API',
|
'title': 'Test API',
|
||||||
|
|
@ -31,7 +31,7 @@ void main() {
|
||||||
'schema': {
|
'schema': {
|
||||||
'type': 'array',
|
'type': 'array',
|
||||||
'items': {
|
'items': {
|
||||||
'\$ref': '#/components/schemas/User',
|
r'$ref': '#/components/schemas/User',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -73,7 +73,7 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('parses servers with variables', () {
|
test('parses servers with variables', () {
|
||||||
final json = {
|
final json = <String, dynamic>{
|
||||||
'openapi': '3.0.3',
|
'openapi': '3.0.3',
|
||||||
'info': {'title': 'Test', 'version': '1.0.0'},
|
'info': {'title': 'Test', 'version': '1.0.0'},
|
||||||
'servers': [
|
'servers': [
|
||||||
|
|
@ -100,7 +100,7 @@ void main() {
|
||||||
final server = document.servers.first;
|
final server = document.servers.first;
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
server.url, equals('https://{environment}.example.com/{basePath}'));
|
server.url, equals('https://{environment}.example.com/{basePath}'),);
|
||||||
expect(server.variables, hasLength(2));
|
expect(server.variables, hasLength(2));
|
||||||
expect(server.variables.containsKey('environment'), isTrue);
|
expect(server.variables.containsKey('environment'), isTrue);
|
||||||
expect(server.variables['environment']!.defaultValue, equals('api'));
|
expect(server.variables['environment']!.defaultValue, equals('api'));
|
||||||
|
|
@ -108,7 +108,7 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('parses complex request body', () {
|
test('parses complex request body', () {
|
||||||
final json = {
|
final json = <String, dynamic>{
|
||||||
'openapi': '3.0.3',
|
'openapi': '3.0.3',
|
||||||
'info': {'title': 'Test', 'version': '1.0.0'},
|
'info': {'title': 'Test', 'version': '1.0.0'},
|
||||||
'paths': {
|
'paths': {
|
||||||
|
|
@ -121,7 +121,7 @@ void main() {
|
||||||
'content': {
|
'content': {
|
||||||
'application/json': {
|
'application/json': {
|
||||||
'schema': {
|
'schema': {
|
||||||
'\$ref': '#/components/schemas/User',
|
r'$ref': '#/components/schemas/User',
|
||||||
},
|
},
|
||||||
'examples': {
|
'examples': {
|
||||||
'user1': {
|
'user1': {
|
||||||
|
|
@ -135,7 +135,7 @@ void main() {
|
||||||
},
|
},
|
||||||
'application/xml': {
|
'application/xml': {
|
||||||
'schema': {
|
'schema': {
|
||||||
'\$ref': '#/components/schemas/User',
|
r'$ref': '#/components/schemas/User',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -166,9 +166,9 @@ void main() {
|
||||||
expect(path.requestBody!.required, isTrue);
|
expect(path.requestBody!.required, isTrue);
|
||||||
expect(path.requestBody!.content, hasLength(2));
|
expect(path.requestBody!.content, hasLength(2));
|
||||||
expect(
|
expect(
|
||||||
path.requestBody!.content.containsKey('application/json'), isTrue);
|
path.requestBody!.content.containsKey('application/json'), isTrue,);
|
||||||
expect(
|
expect(
|
||||||
path.requestBody!.content.containsKey('application/xml'), isTrue);
|
path.requestBody!.content.containsKey('application/xml'), isTrue,);
|
||||||
|
|
||||||
final jsonContent = path.requestBody!.content['application/json']!;
|
final jsonContent = path.requestBody!.content['application/json']!;
|
||||||
expect(jsonContent.examples, hasLength(1));
|
expect(jsonContent.examples, hasLength(1));
|
||||||
|
|
@ -176,7 +176,7 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('parses complex responses with headers and links', () {
|
test('parses complex responses with headers and links', () {
|
||||||
final json = {
|
final json = <String, dynamic>{
|
||||||
'openapi': '3.0.3',
|
'openapi': '3.0.3',
|
||||||
'info': {'title': 'Test', 'version': '1.0.0'},
|
'info': {'title': 'Test', 'version': '1.0.0'},
|
||||||
'paths': {
|
'paths': {
|
||||||
|
|
@ -203,7 +203,7 @@ void main() {
|
||||||
'content': {
|
'content': {
|
||||||
'application/json': {
|
'application/json': {
|
||||||
'schema': {
|
'schema': {
|
||||||
'\$ref': '#/components/schemas/User',
|
r'$ref': '#/components/schemas/User',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -211,7 +211,7 @@ void main() {
|
||||||
'getUserPosts': {
|
'getUserPosts': {
|
||||||
'operationId': 'getUserPosts',
|
'operationId': 'getUserPosts',
|
||||||
'parameters': {
|
'parameters': {
|
||||||
'userId': '\$response.body#/id',
|
'userId': r'$response.body#/id',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -253,7 +253,7 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('parses security schemes and requirements', () {
|
test('parses security schemes and requirements', () {
|
||||||
final json = {
|
final json = <String, dynamic>{
|
||||||
'openapi': '3.0.3',
|
'openapi': '3.0.3',
|
||||||
'info': {'title': 'Test', 'version': '1.0.0'},
|
'info': {'title': 'Test', 'version': '1.0.0'},
|
||||||
'security': [
|
'security': [
|
||||||
|
|
@ -266,7 +266,7 @@ void main() {
|
||||||
'summary': 'Protected endpoint',
|
'summary': 'Protected endpoint',
|
||||||
'security': [
|
'security': [
|
||||||
{
|
{
|
||||||
'bearerAuth': ['read:users']
|
'bearerAuth': ['read:users'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
'responses': {
|
'responses': {
|
||||||
|
|
@ -333,7 +333,7 @@ void main() {
|
||||||
|
|
||||||
group('Schema Validation', () {
|
group('Schema Validation', () {
|
||||||
test('parses allOf composition', () {
|
test('parses allOf composition', () {
|
||||||
final json = {
|
final json = <String, dynamic>{
|
||||||
'openapi': '3.0.3',
|
'openapi': '3.0.3',
|
||||||
'info': {'title': 'Test', 'version': '1.0.0'},
|
'info': {'title': 'Test', 'version': '1.0.0'},
|
||||||
'paths': {},
|
'paths': {},
|
||||||
|
|
@ -348,7 +348,7 @@ void main() {
|
||||||
},
|
},
|
||||||
'Dog': {
|
'Dog': {
|
||||||
'allOf': [
|
'allOf': [
|
||||||
{'\$ref': '#/components/schemas/Pet'},
|
{r'$ref': '#/components/schemas/Pet'},
|
||||||
{
|
{
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
'properties': {
|
'properties': {
|
||||||
|
|
@ -373,7 +373,7 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('parses oneOf and anyOf', () {
|
test('parses oneOf and anyOf', () {
|
||||||
final json = {
|
final json = <String, dynamic>{
|
||||||
'openapi': '3.0.3',
|
'openapi': '3.0.3',
|
||||||
'info': {'title': 'Test', 'version': '1.0.0'},
|
'info': {'title': 'Test', 'version': '1.0.0'},
|
||||||
'paths': {},
|
'paths': {},
|
||||||
|
|
@ -403,7 +403,7 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('parses discriminator', () {
|
test('parses discriminator', () {
|
||||||
final json = {
|
final json = <String, dynamic>{
|
||||||
'openapi': '3.0.3',
|
'openapi': '3.0.3',
|
||||||
'info': {'title': 'Test', 'version': '1.0.0'},
|
'info': {'title': 'Test', 'version': '1.0.0'},
|
||||||
'paths': {},
|
'paths': {},
|
||||||
|
|
@ -426,7 +426,7 @@ void main() {
|
||||||
},
|
},
|
||||||
'Dog': {
|
'Dog': {
|
||||||
'allOf': [
|
'allOf': [
|
||||||
{'\$ref': '#/components/schemas/Pet'},
|
{r'$ref': '#/components/schemas/Pet'},
|
||||||
{
|
{
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
'properties': {
|
'properties': {
|
||||||
|
|
@ -437,7 +437,7 @@ void main() {
|
||||||
},
|
},
|
||||||
'Cat': {
|
'Cat': {
|
||||||
'allOf': [
|
'allOf': [
|
||||||
{'\$ref': '#/components/schemas/Pet'},
|
{r'$ref': '#/components/schemas/Pet'},
|
||||||
{
|
{
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
'properties': {
|
'properties': {
|
||||||
|
|
@ -461,18 +461,18 @@ void main() {
|
||||||
|
|
||||||
group('Error Handling', () {
|
group('Error Handling', () {
|
||||||
test('handles missing required fields gracefully', () {
|
test('handles missing required fields gracefully', () {
|
||||||
final json = {
|
final json = <String, dynamic>{
|
||||||
'openapi': '3.0.3',
|
'openapi': '3.0.3',
|
||||||
// Missing info object
|
// Missing info object
|
||||||
'paths': {},
|
'paths': {},
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(() => SwaggerDocument.fromJson(json),
|
expect(() => SwaggerDocument.fromJson(json),
|
||||||
throwsA(isA<FormatException>()));
|
throwsA(isA<FormatException>()),);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handles invalid OpenAPI version', () {
|
test('handles invalid OpenAPI version', () {
|
||||||
final json = {
|
final json = <String, dynamic>{
|
||||||
'openapi': '2.0', // Invalid version
|
'openapi': '2.0', // Invalid version
|
||||||
'info': {'title': 'Test', 'version': '1.0.0'},
|
'info': {'title': 'Test', 'version': '1.0.0'},
|
||||||
'paths': {},
|
'paths': {},
|
||||||
|
|
@ -483,21 +483,21 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handles malformed paths', () {
|
test('handles malformed paths', () {
|
||||||
final json = {
|
final json = <String, dynamic>{
|
||||||
'openapi': '3.0.3',
|
'openapi': '3.0.3',
|
||||||
'info': {'title': 'Test', 'version': '1.0.0'},
|
'info': {'title': 'Test', 'version': '1.0.0'},
|
||||||
'paths': {
|
'paths': {
|
||||||
'/valid': {
|
'/valid': {
|
||||||
'get': {
|
'get': {
|
||||||
'responses': {
|
'responses': {
|
||||||
'200': {'description': 'OK'}
|
'200': {'description': 'OK'},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'/invalid': {
|
'/invalid': {
|
||||||
'invalidMethod': {
|
'invalidMethod': {
|
||||||
'responses': {
|
'responses': {
|
||||||
'200': {'description': 'OK'}
|
'200': {'description': 'OK'},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -510,7 +510,7 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handles circular references', () {
|
test('handles circular references', () {
|
||||||
final json = {
|
final json = <String, dynamic>{
|
||||||
'openapi': '3.0.3',
|
'openapi': '3.0.3',
|
||||||
'info': {'title': 'Test', 'version': '1.0.0'},
|
'info': {'title': 'Test', 'version': '1.0.0'},
|
||||||
'paths': {},
|
'paths': {},
|
||||||
|
|
@ -522,7 +522,7 @@ void main() {
|
||||||
'value': {'type': 'string'},
|
'value': {'type': 'string'},
|
||||||
'children': {
|
'children': {
|
||||||
'type': 'array',
|
'type': 'array',
|
||||||
'items': {'\$ref': '#/components/schemas/Node'},
|
'items': {r'$ref': '#/components/schemas/Node'},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -539,7 +539,7 @@ void main() {
|
||||||
|
|
||||||
group('Edge Cases', () {
|
group('Edge Cases', () {
|
||||||
test('handles empty document', () {
|
test('handles empty document', () {
|
||||||
final json = {
|
final json = <String, dynamic>{
|
||||||
'openapi': '3.0.3',
|
'openapi': '3.0.3',
|
||||||
'info': {'title': 'Empty API', 'version': '1.0.0'},
|
'info': {'title': 'Empty API', 'version': '1.0.0'},
|
||||||
'paths': {},
|
'paths': {},
|
||||||
|
|
@ -556,12 +556,12 @@ void main() {
|
||||||
final schemas = <String, dynamic>{};
|
final schemas = <String, dynamic>{};
|
||||||
|
|
||||||
// Create a large number of paths and schemas
|
// Create a large number of paths and schemas
|
||||||
for (int i = 0; i < 1000; i++) {
|
for (var i = 0; i < 1000; i++) {
|
||||||
paths['/resource$i'] = {
|
paths['/resource$i'] = {
|
||||||
'get': {
|
'get': {
|
||||||
'summary': 'Get resource $i',
|
'summary': 'Get resource $i',
|
||||||
'responses': {
|
'responses': {
|
||||||
'200': {'description': 'Success'}
|
'200': {'description': 'Success'},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -575,7 +575,7 @@ void main() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
final json = {
|
final json = <String, dynamic>{
|
||||||
'openapi': '3.0.3',
|
'openapi': '3.0.3',
|
||||||
'info': {'title': 'Large API', 'version': '1.0.0'},
|
'info': {'title': 'Large API', 'version': '1.0.0'},
|
||||||
'paths': paths,
|
'paths': paths,
|
||||||
|
|
@ -589,11 +589,11 @@ void main() {
|
||||||
expect(document.paths.length, greaterThan(500));
|
expect(document.paths.length, greaterThan(500));
|
||||||
expect(document.models.length, greaterThan(500));
|
expect(document.models.length, greaterThan(500));
|
||||||
expect(stopwatch.elapsedMilliseconds,
|
expect(stopwatch.elapsedMilliseconds,
|
||||||
lessThan(10000)); // Should complete within 10 seconds
|
lessThan(10000),); // Should complete within 10 seconds
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handles unicode and special characters', () {
|
test('handles unicode and special characters', () {
|
||||||
final json = {
|
final json = <String, dynamic>{
|
||||||
'openapi': '3.0.3',
|
'openapi': '3.0.3',
|
||||||
'info': {
|
'info': {
|
||||||
'title': 'API with 中文 and émojis 🚀',
|
'title': 'API with 中文 and émojis 🚀',
|
||||||
|
|
@ -605,7 +605,7 @@ void main() {
|
||||||
'get': {
|
'get': {
|
||||||
'summary': 'Test with unicode path',
|
'summary': 'Test with unicode path',
|
||||||
'responses': {
|
'responses': {
|
||||||
'200': {'description': 'Success'}
|
'200': {'description': 'Success'},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ void main() {
|
||||||
expect(
|
expect(
|
||||||
encoded.length,
|
encoded.length,
|
||||||
greaterThan(
|
greaterThan(
|
||||||
testString.length)); // UTF-8 uses multiple bytes for non-ASCII
|
testString.length,),); // UTF-8 uses multiple bytes for non-ASCII
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handles ASCII encoding', () {
|
test('handles ASCII encoding', () {
|
||||||
|
|
@ -23,7 +23,7 @@ void main() {
|
||||||
|
|
||||||
expect(decoded, testString);
|
expect(decoded, testString);
|
||||||
expect(
|
expect(
|
||||||
encoded.length, testString.length); // ASCII is 1 byte per character
|
encoded.length, testString.length,); // ASCII is 1 byte per character
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handles Latin1 encoding', () {
|
test('handles Latin1 encoding', () {
|
||||||
|
|
@ -46,7 +46,7 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handles URL encoding and decoding', () {
|
test('handles URL encoding and decoding', () {
|
||||||
const testString = 'Hello World & Special Characters!@#\$%^&*()';
|
const testString = r'Hello World & Special Characters!@#$%^&*()';
|
||||||
final encoded = Uri.encodeComponent(testString);
|
final encoded = Uri.encodeComponent(testString);
|
||||||
final decoded = Uri.decodeComponent(encoded);
|
final decoded = Uri.decodeComponent(encoded);
|
||||||
|
|
||||||
|
|
@ -60,7 +60,7 @@ void main() {
|
||||||
final testBytes = utf8Bom + utf8.encode('Hello');
|
final testBytes = utf8Bom + utf8.encode('Hello');
|
||||||
|
|
||||||
// 检测 BOM
|
// 检测 BOM
|
||||||
final bool hasUtf8Bom = testBytes.length >= 3 &&
|
final hasUtf8Bom = testBytes.length >= 3 &&
|
||||||
testBytes[0] == 0xEF &&
|
testBytes[0] == 0xEF &&
|
||||||
testBytes[1] == 0xBB &&
|
testBytes[1] == 0xBB &&
|
||||||
testBytes[2] == 0xBF;
|
testBytes[2] == 0xBF;
|
||||||
|
|
@ -71,7 +71,7 @@ void main() {
|
||||||
test('detects BOM for UTF-16LE', () {
|
test('detects BOM for UTF-16LE', () {
|
||||||
final utf16leBom = [0xFF, 0xFE];
|
final utf16leBom = [0xFF, 0xFE];
|
||||||
|
|
||||||
final bool hasUtf16LeBom = utf16leBom.length >= 2 &&
|
final hasUtf16LeBom = utf16leBom.length >= 2 &&
|
||||||
utf16leBom[0] == 0xFF &&
|
utf16leBom[0] == 0xFF &&
|
||||||
utf16leBom[1] == 0xFE;
|
utf16leBom[1] == 0xFE;
|
||||||
|
|
||||||
|
|
@ -81,7 +81,7 @@ void main() {
|
||||||
test('detects BOM for UTF-16BE', () {
|
test('detects BOM for UTF-16BE', () {
|
||||||
final utf16beBom = [0xFE, 0xFF];
|
final utf16beBom = [0xFE, 0xFF];
|
||||||
|
|
||||||
final bool hasUtf16BeBom = utf16beBom.length >= 2 &&
|
final hasUtf16BeBom = utf16beBom.length >= 2 &&
|
||||||
utf16beBom[0] == 0xFE &&
|
utf16beBom[0] == 0xFE &&
|
||||||
utf16beBom[1] == 0xFF;
|
utf16beBom[1] == 0xFF;
|
||||||
|
|
||||||
|
|
@ -167,7 +167,7 @@ void main() {
|
||||||
final encodedPairs = <String>[];
|
final encodedPairs = <String>[];
|
||||||
formData.forEach((key, value) {
|
formData.forEach((key, value) {
|
||||||
final encodedKey = Uri.encodeComponent(key);
|
final encodedKey = Uri.encodeComponent(key);
|
||||||
final encodedValue = Uri.encodeComponent(value.toString());
|
final encodedValue = Uri.encodeComponent(value);
|
||||||
encodedPairs.add('$encodedKey=$encodedValue');
|
encodedPairs.add('$encodedKey=$encodedValue');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ void main() {
|
||||||
|
|
||||||
setUp(() {
|
setUp(() {
|
||||||
validator = EnhancedValidator(
|
validator = EnhancedValidator(
|
||||||
includeWarnings: true,
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -24,12 +24,8 @@ void main() {
|
||||||
description: 'Production server',
|
description: 'Production server',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
components: ApiComponents(
|
|
||||||
schemas: {},
|
|
||||||
securitySchemes: {},
|
|
||||||
),
|
|
||||||
paths: {
|
paths: {
|
||||||
'/users': const ApiPath(
|
'/users': ApiPath(
|
||||||
path: '/users',
|
path: '/users',
|
||||||
method: HttpMethod.get,
|
method: HttpMethod.get,
|
||||||
summary: 'Get users',
|
summary: 'Get users',
|
||||||
|
|
@ -38,11 +34,11 @@ void main() {
|
||||||
tags: ['users'],
|
tags: ['users'],
|
||||||
parameters: [],
|
parameters: [],
|
||||||
responses: {
|
responses: {
|
||||||
'200': const ApiResponse(
|
'200': ApiResponse(
|
||||||
code: '200',
|
code: '200',
|
||||||
description: 'Success',
|
description: 'Success',
|
||||||
content: {
|
content: {
|
||||||
'application/json': const ApiMediaType(
|
'application/json': ApiMediaType(
|
||||||
schema: {'type': 'array'},
|
schema: {'type': 'array'},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
@ -52,7 +48,6 @@ void main() {
|
||||||
},
|
},
|
||||||
models: {},
|
models: {},
|
||||||
controllers: {},
|
controllers: {},
|
||||||
security: [],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final isValid = validator.validateDocument(document);
|
final isValid = validator.validateDocument(document);
|
||||||
|
|
@ -65,12 +60,9 @@ void main() {
|
||||||
title: '', // Missing title
|
title: '', // Missing title
|
||||||
version: '', // Missing version
|
version: '', // Missing version
|
||||||
description: '',
|
description: '',
|
||||||
servers: [],
|
|
||||||
components: ApiComponents(schemas: {}, securitySchemes: {}),
|
|
||||||
paths: {}, // Empty paths
|
paths: {}, // Empty paths
|
||||||
models: {},
|
models: {},
|
||||||
controllers: {},
|
controllers: {},
|
||||||
security: [],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final isValid = validator.validateDocument(document);
|
final isValid = validator.validateDocument(document);
|
||||||
|
|
@ -88,10 +80,8 @@ void main() {
|
||||||
title: 'Test API',
|
title: 'Test API',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
description: 'Test',
|
description: 'Test',
|
||||||
servers: [],
|
|
||||||
components: ApiComponents(schemas: {}, securitySchemes: {}),
|
|
||||||
paths: {
|
paths: {
|
||||||
'/users/{id}': const ApiPath(
|
'/users/{id}': ApiPath(
|
||||||
path: '/users/{id}',
|
path: '/users/{id}',
|
||||||
method: HttpMethod.get,
|
method: HttpMethod.get,
|
||||||
summary: 'Get user',
|
summary: 'Get user',
|
||||||
|
|
@ -102,7 +92,7 @@ void main() {
|
||||||
// Missing path parameter declaration for 'id'
|
// Missing path parameter declaration for 'id'
|
||||||
],
|
],
|
||||||
responses: {
|
responses: {
|
||||||
'200': const ApiResponse(
|
'200': ApiResponse(
|
||||||
code: '200',
|
code: '200',
|
||||||
description: 'Success',
|
description: 'Success',
|
||||||
),
|
),
|
||||||
|
|
@ -111,7 +101,6 @@ void main() {
|
||||||
},
|
},
|
||||||
models: {},
|
models: {},
|
||||||
controllers: {},
|
controllers: {},
|
||||||
security: [],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final isValid = validator.validateDocument(document);
|
final isValid = validator.validateDocument(document);
|
||||||
|
|
@ -126,10 +115,8 @@ void main() {
|
||||||
title: 'Test API',
|
title: 'Test API',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
description: 'Test',
|
description: 'Test',
|
||||||
servers: [],
|
|
||||||
components: ApiComponents(schemas: {}, securitySchemes: {}),
|
|
||||||
paths: {
|
paths: {
|
||||||
'/users/{id}': const ApiPath(
|
'/users/{id}': ApiPath(
|
||||||
path: '/users/{id}',
|
path: '/users/{id}',
|
||||||
method: HttpMethod.get,
|
method: HttpMethod.get,
|
||||||
summary: 'Get user',
|
summary: 'Get user',
|
||||||
|
|
@ -146,7 +133,7 @@ void main() {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
responses: {
|
responses: {
|
||||||
'200': const ApiResponse(
|
'200': ApiResponse(
|
||||||
code: '200',
|
code: '200',
|
||||||
description: 'Success',
|
description: 'Success',
|
||||||
),
|
),
|
||||||
|
|
@ -155,7 +142,6 @@ void main() {
|
||||||
},
|
},
|
||||||
models: {},
|
models: {},
|
||||||
controllers: {},
|
controllers: {},
|
||||||
security: [],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final isValid = validator.validateDocument(document);
|
final isValid = validator.validateDocument(document);
|
||||||
|
|
@ -170,17 +156,15 @@ void main() {
|
||||||
title: 'Test API',
|
title: 'Test API',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
description: 'Test',
|
description: 'Test',
|
||||||
servers: [],
|
|
||||||
components: ApiComponents(
|
components: ApiComponents(
|
||||||
schemas: {},
|
|
||||||
securitySchemes: {
|
securitySchemes: {
|
||||||
'apiKey': const ApiSecurityScheme(
|
'apiKey': ApiSecurityScheme(
|
||||||
type: SecuritySchemeType.apiKey,
|
type: SecuritySchemeType.apiKey,
|
||||||
description: 'API Key',
|
description: 'API Key',
|
||||||
name: '', // Missing name
|
name: '', // Missing name
|
||||||
location: ApiKeyLocation.header,
|
location: ApiKeyLocation.header,
|
||||||
),
|
),
|
||||||
'bearer': const ApiSecurityScheme(
|
'bearer': ApiSecurityScheme(
|
||||||
type: SecuritySchemeType.http,
|
type: SecuritySchemeType.http,
|
||||||
description: 'Bearer token',
|
description: 'Bearer token',
|
||||||
scheme: '', // Missing scheme
|
scheme: '', // Missing scheme
|
||||||
|
|
@ -188,7 +172,7 @@ void main() {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
paths: {
|
paths: {
|
||||||
'/test': const ApiPath(
|
'/test': ApiPath(
|
||||||
path: '/test',
|
path: '/test',
|
||||||
method: HttpMethod.get,
|
method: HttpMethod.get,
|
||||||
summary: 'Test',
|
summary: 'Test',
|
||||||
|
|
@ -197,7 +181,7 @@ void main() {
|
||||||
tags: [],
|
tags: [],
|
||||||
parameters: [],
|
parameters: [],
|
||||||
responses: {
|
responses: {
|
||||||
'200': const ApiResponse(
|
'200': ApiResponse(
|
||||||
code: '200',
|
code: '200',
|
||||||
description: 'Success',
|
description: 'Success',
|
||||||
),
|
),
|
||||||
|
|
@ -206,7 +190,6 @@ void main() {
|
||||||
},
|
},
|
||||||
models: {},
|
models: {},
|
||||||
controllers: {},
|
controllers: {},
|
||||||
security: [],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final isValid = validator.validateDocument(document);
|
final isValid = validator.validateDocument(document);
|
||||||
|
|
@ -222,10 +205,8 @@ void main() {
|
||||||
title: 'Test API',
|
title: 'Test API',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
description: '', // Missing description
|
description: '', // Missing description
|
||||||
servers: [], // Missing servers
|
|
||||||
components: ApiComponents(schemas: {}, securitySchemes: {}),
|
|
||||||
paths: {
|
paths: {
|
||||||
'/test': const ApiPath(
|
'/test': ApiPath(
|
||||||
path: '/test',
|
path: '/test',
|
||||||
method: HttpMethod.get,
|
method: HttpMethod.get,
|
||||||
summary: '', // Missing summary
|
summary: '', // Missing summary
|
||||||
|
|
@ -234,7 +215,7 @@ void main() {
|
||||||
tags: [],
|
tags: [],
|
||||||
parameters: [],
|
parameters: [],
|
||||||
responses: {
|
responses: {
|
||||||
'200': const ApiResponse(
|
'200': ApiResponse(
|
||||||
code: '200',
|
code: '200',
|
||||||
description: '', // Missing response description
|
description: '', // Missing response description
|
||||||
),
|
),
|
||||||
|
|
@ -243,7 +224,6 @@ void main() {
|
||||||
},
|
},
|
||||||
models: {},
|
models: {},
|
||||||
controllers: {},
|
controllers: {},
|
||||||
security: [],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final isValid = validator.validateDocument(document);
|
final isValid = validator.validateDocument(document);
|
||||||
|
|
@ -263,10 +243,8 @@ void main() {
|
||||||
title: 'Test API',
|
title: 'Test API',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
description: 'Test',
|
description: 'Test',
|
||||||
servers: [],
|
|
||||||
components: ApiComponents(schemas: {}, securitySchemes: {}),
|
|
||||||
paths: {
|
paths: {
|
||||||
'/test': const ApiPath(
|
'/test': ApiPath(
|
||||||
path: '/test',
|
path: '/test',
|
||||||
method: HttpMethod.get,
|
method: HttpMethod.get,
|
||||||
summary: 'Test',
|
summary: 'Test',
|
||||||
|
|
@ -279,7 +257,6 @@ void main() {
|
||||||
},
|
},
|
||||||
models: {},
|
models: {},
|
||||||
controllers: {},
|
controllers: {},
|
||||||
security: [],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final isValid = validator.validateDocument(document);
|
final isValid = validator.validateDocument(document);
|
||||||
|
|
@ -295,9 +272,8 @@ void main() {
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
description: 'Test API',
|
description: 'Test API',
|
||||||
servers: [ApiServer(url: 'https://api.example.com')],
|
servers: [ApiServer(url: 'https://api.example.com')],
|
||||||
components: ApiComponents(schemas: {}, securitySchemes: {}),
|
|
||||||
paths: {
|
paths: {
|
||||||
'/test': const ApiPath(
|
'/test': ApiPath(
|
||||||
path: '/test',
|
path: '/test',
|
||||||
method: HttpMethod.get,
|
method: HttpMethod.get,
|
||||||
summary: 'Test',
|
summary: 'Test',
|
||||||
|
|
@ -306,7 +282,7 @@ void main() {
|
||||||
tags: [], // No tags
|
tags: [], // No tags
|
||||||
parameters: [],
|
parameters: [],
|
||||||
responses: {
|
responses: {
|
||||||
'200': const ApiResponse(
|
'200': ApiResponse(
|
||||||
code: '200',
|
code: '200',
|
||||||
description: 'Success',
|
description: 'Success',
|
||||||
),
|
),
|
||||||
|
|
@ -316,7 +292,6 @@ void main() {
|
||||||
},
|
},
|
||||||
models: {},
|
models: {},
|
||||||
controllers: {},
|
controllers: {},
|
||||||
security: [],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final isValid = validator.validateDocument(document);
|
final isValid = validator.validateDocument(document);
|
||||||
|
|
@ -331,7 +306,7 @@ void main() {
|
||||||
test('validates large schemas', () {
|
test('validates large schemas', () {
|
||||||
// Create a model with many properties
|
// Create a model with many properties
|
||||||
final properties = <String, ApiProperty>{};
|
final properties = <String, ApiProperty>{};
|
||||||
for (int i = 0; i < 25; i++) {
|
for (var i = 0; i < 25; i++) {
|
||||||
properties['property$i'] = ApiProperty(
|
properties['property$i'] = ApiProperty(
|
||||||
name: 'property$i',
|
name: 'property$i',
|
||||||
type: PropertyType.string,
|
type: PropertyType.string,
|
||||||
|
|
@ -366,7 +341,7 @@ void main() {
|
||||||
tags: ['test'],
|
tags: ['test'],
|
||||||
parameters: [],
|
parameters: [],
|
||||||
responses: {
|
responses: {
|
||||||
'200': const ApiResponse(
|
'200': ApiResponse(
|
||||||
code: '200',
|
code: '200',
|
||||||
description: 'Success',
|
description: 'Success',
|
||||||
),
|
),
|
||||||
|
|
@ -391,12 +366,9 @@ void main() {
|
||||||
title: '',
|
title: '',
|
||||||
version: '',
|
version: '',
|
||||||
description: '',
|
description: '',
|
||||||
servers: [],
|
|
||||||
components: ApiComponents(schemas: {}, securitySchemes: {}),
|
|
||||||
paths: {},
|
paths: {},
|
||||||
models: {},
|
models: {},
|
||||||
controllers: {},
|
controllers: {},
|
||||||
security: [],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
validator.validateDocument(document);
|
validator.validateDocument(document);
|
||||||
|
|
@ -414,12 +386,9 @@ void main() {
|
||||||
title: '',
|
title: '',
|
||||||
version: '',
|
version: '',
|
||||||
description: '',
|
description: '',
|
||||||
servers: [],
|
|
||||||
components: ApiComponents(schemas: {}, securitySchemes: {}),
|
|
||||||
paths: {},
|
paths: {},
|
||||||
models: {},
|
models: {},
|
||||||
controllers: {},
|
controllers: {},
|
||||||
security: [],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
validator.validateDocument(document);
|
validator.validateDocument(document);
|
||||||
|
|
@ -440,10 +409,8 @@ void main() {
|
||||||
title: 'Test API',
|
title: 'Test API',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
description: '', // Missing description
|
description: '', // Missing description
|
||||||
servers: [], // Missing servers
|
|
||||||
components: ApiComponents(schemas: {}, securitySchemes: {}),
|
|
||||||
paths: {
|
paths: {
|
||||||
'/test': const ApiPath(
|
'/test': ApiPath(
|
||||||
path: '/test',
|
path: '/test',
|
||||||
method: HttpMethod.get,
|
method: HttpMethod.get,
|
||||||
summary: 'Test',
|
summary: 'Test',
|
||||||
|
|
@ -452,7 +419,7 @@ void main() {
|
||||||
tags: ['test'],
|
tags: ['test'],
|
||||||
parameters: [],
|
parameters: [],
|
||||||
responses: {
|
responses: {
|
||||||
'200': const ApiResponse(
|
'200': ApiResponse(
|
||||||
code: '200',
|
code: '200',
|
||||||
description: 'Success',
|
description: 'Success',
|
||||||
),
|
),
|
||||||
|
|
@ -461,7 +428,6 @@ void main() {
|
||||||
},
|
},
|
||||||
models: {},
|
models: {},
|
||||||
controllers: {},
|
controllers: {},
|
||||||
security: [],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final isValid = strictValidator.validateDocument(document);
|
final isValid = strictValidator.validateDocument(document);
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ void main() {
|
||||||
'data': {
|
'data': {
|
||||||
'type': 'array',
|
'type': 'array',
|
||||||
'items': {
|
'items': {
|
||||||
'\$ref': '#/components/schemas/User',
|
r'$ref': '#/components/schemas/User',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'total': {'type': 'integer'},
|
'total': {'type': 'integer'},
|
||||||
|
|
@ -83,7 +83,7 @@ void main() {
|
||||||
'content': {
|
'content': {
|
||||||
'application/json': {
|
'application/json': {
|
||||||
'schema': {
|
'schema': {
|
||||||
'\$ref': '#/components/schemas/CreateUserRequest',
|
r'$ref': '#/components/schemas/CreateUserRequest',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -94,7 +94,7 @@ void main() {
|
||||||
'content': {
|
'content': {
|
||||||
'application/json': {
|
'application/json': {
|
||||||
'schema': {
|
'schema': {
|
||||||
'\$ref': '#/components/schemas/User',
|
r'$ref': '#/components/schemas/User',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -125,7 +125,7 @@ void main() {
|
||||||
'content': {
|
'content': {
|
||||||
'application/json': {
|
'application/json': {
|
||||||
'schema': {
|
'schema': {
|
||||||
'\$ref': '#/components/schemas/User',
|
r'$ref': '#/components/schemas/User',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -167,7 +167,7 @@ void main() {
|
||||||
'content': {
|
'content': {
|
||||||
'application/json': {
|
'application/json': {
|
||||||
'schema': {
|
'schema': {
|
||||||
'\$ref': '#/components/schemas/FileUploadResult',
|
r'$ref': '#/components/schemas/FileUploadResult',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -275,7 +275,7 @@ void main() {
|
||||||
|
|
||||||
// 3. 验证文档
|
// 3. 验证文档
|
||||||
final validator = EnhancedValidator(
|
final validator = EnhancedValidator(
|
||||||
includeWarnings: true,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final isValid = validator.validateDocument(document);
|
final isValid = validator.validateDocument(document);
|
||||||
|
|
@ -285,17 +285,16 @@ void main() {
|
||||||
final criticalErrors = errors
|
final criticalErrors = errors
|
||||||
.where((e) =>
|
.where((e) =>
|
||||||
e.severity == ErrorSeverity.error ||
|
e.severity == ErrorSeverity.error ||
|
||||||
e.severity == ErrorSeverity.critical)
|
e.severity == ErrorSeverity.critical,)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
expect(criticalErrors, isEmpty,
|
expect(criticalErrors, isEmpty,
|
||||||
reason:
|
reason:
|
||||||
'Document should not have critical errors: ${criticalErrors.map((e) => e.title).join(", ")}');
|
'Document should not have critical errors: ${criticalErrors.map((e) => e.title).join(", ")}',);
|
||||||
|
|
||||||
// 4. 生成 Retrofit API 代码
|
// 4. 生成 Retrofit API 代码
|
||||||
final retrofitGenerator = RetrofitApiGenerator(
|
final retrofitGenerator = RetrofitApiGenerator(
|
||||||
className: 'IntegrationTestApi',
|
className: 'IntegrationTestApi',
|
||||||
splitByTags: true,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final retrofitCode = retrofitGenerator.generateFromDocument(document);
|
final retrofitCode = retrofitGenerator.generateFromDocument(document);
|
||||||
|
|
@ -303,27 +302,27 @@ void main() {
|
||||||
// 验证生成的代码
|
// 验证生成的代码
|
||||||
expect(retrofitCode, isNotEmpty);
|
expect(retrofitCode, isNotEmpty);
|
||||||
expect(retrofitCode, contains('IntegrationTestApi'));
|
expect(retrofitCode, contains('IntegrationTestApi'));
|
||||||
expect(retrofitCode, contains('@GET(\'/users\')'));
|
expect(retrofitCode, contains("@GET('/users')"));
|
||||||
expect(retrofitCode, contains('@POST(\'/users\')'));
|
expect(retrofitCode, contains("@POST('/users')"));
|
||||||
expect(retrofitCode, contains('@GET(\'/users/{id}\')'));
|
expect(retrofitCode, contains("@GET('/users/{id}')"));
|
||||||
expect(retrofitCode, contains('@POST(\'/files/upload\')'));
|
expect(retrofitCode, contains("@POST('/files/upload')"));
|
||||||
expect(retrofitCode, contains('@Path(\'id\')'));
|
expect(retrofitCode, contains("@Path('id')"));
|
||||||
expect(retrofitCode, contains('@Query(\'page\')'));
|
expect(retrofitCode, contains("@Query('page')"));
|
||||||
expect(retrofitCode, contains('@MultiPart()'));
|
expect(retrofitCode, contains('@MultiPart()'));
|
||||||
|
|
||||||
// 5. 性能验证
|
// 5. 性能验证
|
||||||
print('Integration Test Performance Summary:');
|
print('Integration Test Performance Summary:');
|
||||||
print(' Parse Time: ${parseStats.totalTime.inMilliseconds}ms');
|
print(' Parse Time: ${parseStats.totalTime.inMilliseconds}ms');
|
||||||
print(
|
print(
|
||||||
' Document Size: ${(jsonString.length / 1024).toStringAsFixed(2)}KB');
|
' Document Size: ${(jsonString.length / 1024).toStringAsFixed(2)}KB',);
|
||||||
print(' Paths Parsed: ${parseStats.pathCount}');
|
print(' Paths Parsed: ${parseStats.pathCount}');
|
||||||
print(' Schemas Parsed: ${parseStats.schemaCount}');
|
print(' Schemas Parsed: ${parseStats.schemaCount}');
|
||||||
print(
|
print(
|
||||||
' Retrofit Code Size: ${(retrofitCode.length / 1024).toStringAsFixed(2)}KB');
|
' Retrofit Code Size: ${(retrofitCode.length / 1024).toStringAsFixed(2)}KB',);
|
||||||
|
|
||||||
// 验证性能指标
|
// 验证性能指标
|
||||||
expect(
|
expect(
|
||||||
parseStats.totalTime.inMilliseconds, lessThan(2000)); // 解析应在2秒内完成
|
parseStats.totalTime.inMilliseconds, lessThan(2000),); // 解析应在2秒内完成
|
||||||
expect(retrofitCode.length, greaterThan(1000)); // 应生成足够的代码
|
expect(retrofitCode.length, greaterThan(1000)); // 应生成足够的代码
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -331,20 +330,19 @@ void main() {
|
||||||
final file = File('swagger.json');
|
final file = File('swagger.json');
|
||||||
if (!file.existsSync()) {
|
if (!file.existsSync()) {
|
||||||
print(
|
print(
|
||||||
'swagger.json not found, skipping real project integration test');
|
'swagger.json not found, skipping real project integration test',);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final jsonString = await file.readAsString();
|
final jsonString = await file.readAsString();
|
||||||
print(
|
print(
|
||||||
'Real project swagger.json size: ${(jsonString.length / 1024).toStringAsFixed(2)}KB');
|
'Real project swagger.json size: ${(jsonString.length / 1024).toStringAsFixed(2)}KB',);
|
||||||
|
|
||||||
// 解析
|
// 解析
|
||||||
final parser = PerformanceParser(
|
final parser = PerformanceParser(
|
||||||
config: const ParseConfig(
|
config: const ParseConfig(
|
||||||
enablePerformanceStats: true,
|
enablePerformanceStats: true,
|
||||||
enableParallelParsing: false, // 禁用并行解析
|
enableParallelParsing: false, // 禁用并行解析
|
||||||
maxConcurrency: 4,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -378,7 +376,6 @@ void main() {
|
||||||
// 生成代码(使用 RetrofitApiGenerator)
|
// 生成代码(使用 RetrofitApiGenerator)
|
||||||
final generator = RetrofitApiGenerator(
|
final generator = RetrofitApiGenerator(
|
||||||
className: 'OAMobileApiService',
|
className: 'OAMobileApiService',
|
||||||
splitByTags: true,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final genStopwatch = Stopwatch()..start();
|
final genStopwatch = Stopwatch()..start();
|
||||||
|
|
@ -391,7 +388,7 @@ void main() {
|
||||||
print('Code generation results:');
|
print('Code generation results:');
|
||||||
print(' Generation Time: ${genStopwatch.elapsedMilliseconds}ms');
|
print(' Generation Time: ${genStopwatch.elapsedMilliseconds}ms');
|
||||||
print(
|
print(
|
||||||
' Generated Code Size: ${(generatedCode.length / 1024).toStringAsFixed(2)}KB');
|
' Generated Code Size: ${(generatedCode.length / 1024).toStringAsFixed(2)}KB',);
|
||||||
print(' Generated Lines: ${generatedCode.split('\n').length}');
|
print(' Generated Lines: ${generatedCode.split('\n').length}');
|
||||||
|
|
||||||
// 性能要求
|
// 性能要求
|
||||||
|
|
@ -409,7 +406,7 @@ void main() {
|
||||||
final parser = PerformanceParser();
|
final parser = PerformanceParser();
|
||||||
|
|
||||||
expect(() => parser.parseDocument(malformedJson),
|
expect(() => parser.parseDocument(malformedJson),
|
||||||
throwsA(isA<FormatException>()));
|
throwsA(isA<FormatException>()),);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handles invalid OpenAPI document', () async {
|
test('handles invalid OpenAPI document', () async {
|
||||||
|
|
@ -426,7 +423,7 @@ void main() {
|
||||||
final parser = PerformanceParser();
|
final parser = PerformanceParser();
|
||||||
|
|
||||||
expect(() => parser.parseDocument(jsonString),
|
expect(() => parser.parseDocument(jsonString),
|
||||||
throwsA(isA<FormatException>()));
|
throwsA(isA<FormatException>()),);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('validation catches common errors', () async {
|
test('validation catches common errors', () async {
|
||||||
|
|
@ -471,7 +468,7 @@ void main() {
|
||||||
final paths = <String, dynamic>{};
|
final paths = <String, dynamic>{};
|
||||||
final schemas = <String, dynamic>{};
|
final schemas = <String, dynamic>{};
|
||||||
|
|
||||||
for (int i = 0; i < 200; i++) {
|
for (var i = 0; i < 200; i++) {
|
||||||
paths['/resource$i'] = {
|
paths['/resource$i'] = {
|
||||||
'get': {
|
'get': {
|
||||||
'summary': 'Get resource $i',
|
'summary': 'Get resource $i',
|
||||||
|
|
@ -483,7 +480,7 @@ void main() {
|
||||||
'content': {
|
'content': {
|
||||||
'application/json': {
|
'application/json': {
|
||||||
'schema': {
|
'schema': {
|
||||||
'\$ref': '#/components/schemas/Resource$i',
|
r'$ref': '#/components/schemas/Resource$i',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -517,14 +514,13 @@ void main() {
|
||||||
|
|
||||||
final jsonString = jsonEncode(largeDoc);
|
final jsonString = jsonEncode(largeDoc);
|
||||||
print(
|
print(
|
||||||
'Large document size: ${(jsonString.length / 1024).toStringAsFixed(2)}KB');
|
'Large document size: ${(jsonString.length / 1024).toStringAsFixed(2)}KB',);
|
||||||
|
|
||||||
// 测试解析性能
|
// 测试解析性能
|
||||||
final parser = PerformanceParser(
|
final parser = PerformanceParser(
|
||||||
config: const ParseConfig(
|
config: const ParseConfig(
|
||||||
enablePerformanceStats: true,
|
enablePerformanceStats: true,
|
||||||
enableParallelParsing: false, // 禁用并行解析
|
enableParallelParsing: false, // 禁用并行解析
|
||||||
maxConcurrency: 4,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -538,7 +534,7 @@ void main() {
|
||||||
|
|
||||||
// 测试生成性能(使用 RetrofitApiGenerator)
|
// 测试生成性能(使用 RetrofitApiGenerator)
|
||||||
final generator = RetrofitApiGenerator(
|
final generator = RetrofitApiGenerator(
|
||||||
splitByTags: true,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final genStopwatch = Stopwatch()..start();
|
final genStopwatch = Stopwatch()..start();
|
||||||
|
|
@ -554,7 +550,7 @@ void main() {
|
||||||
print(' Paths: ${document.paths.length}');
|
print(' Paths: ${document.paths.length}');
|
||||||
print(' Models: ${document.models.length}');
|
print(' Models: ${document.models.length}');
|
||||||
print(
|
print(
|
||||||
' Generated Code: ${(generatedCode.length / 1024).toStringAsFixed(2)}KB');
|
' Generated Code: ${(generatedCode.length / 1024).toStringAsFixed(2)}KB',);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,12 @@ void main() {
|
||||||
expect(MediaType.xml.value, 'application/xml');
|
expect(MediaType.xml.value, 'application/xml');
|
||||||
expect(MediaType.multipartFormData.value, 'multipart/form-data');
|
expect(MediaType.multipartFormData.value, 'multipart/form-data');
|
||||||
expect(
|
expect(
|
||||||
MediaType.formUrlEncoded.value, 'application/x-www-form-urlencoded');
|
MediaType.formUrlEncoded.value, 'application/x-www-form-urlencoded',);
|
||||||
expect(MediaType.textPlain.value, 'text/plain');
|
expect(MediaType.textPlain.value, 'text/plain');
|
||||||
expect(MediaType.textHtml.value, 'text/html');
|
expect(MediaType.textHtml.value, 'text/html');
|
||||||
expect(MediaType.textCsv.value, 'text/csv');
|
expect(MediaType.textCsv.value, 'text/csv');
|
||||||
expect(
|
expect(
|
||||||
MediaType.applicationOctetStream.value, 'application/octet-stream');
|
MediaType.applicationOctetStream.value, 'application/octet-stream',);
|
||||||
expect(MediaType.applicationPdf.value, 'application/pdf');
|
expect(MediaType.applicationPdf.value, 'application/pdf');
|
||||||
expect(MediaType.imagePng.value, 'image/png');
|
expect(MediaType.imagePng.value, 'image/png');
|
||||||
expect(MediaType.imageJpeg.value, 'image/jpeg');
|
expect(MediaType.imageJpeg.value, 'image/jpeg');
|
||||||
|
|
@ -28,22 +28,22 @@ void main() {
|
||||||
expect(MediaTypeExtension.fromString('application/xml'), MediaType.xml);
|
expect(MediaTypeExtension.fromString('application/xml'), MediaType.xml);
|
||||||
expect(MediaTypeExtension.fromString('text/xml'), MediaType.xml);
|
expect(MediaTypeExtension.fromString('text/xml'), MediaType.xml);
|
||||||
expect(MediaTypeExtension.fromString('multipart/form-data'),
|
expect(MediaTypeExtension.fromString('multipart/form-data'),
|
||||||
MediaType.multipartFormData);
|
MediaType.multipartFormData,);
|
||||||
expect(MediaTypeExtension.fromString('application/x-www-form-urlencoded'),
|
expect(MediaTypeExtension.fromString('application/x-www-form-urlencoded'),
|
||||||
MediaType.formUrlEncoded);
|
MediaType.formUrlEncoded,);
|
||||||
expect(MediaTypeExtension.fromString('text/plain'), MediaType.textPlain);
|
expect(MediaTypeExtension.fromString('text/plain'), MediaType.textPlain);
|
||||||
expect(MediaTypeExtension.fromString('text/html'), MediaType.textHtml);
|
expect(MediaTypeExtension.fromString('text/html'), MediaType.textHtml);
|
||||||
expect(MediaTypeExtension.fromString('text/csv'), MediaType.textCsv);
|
expect(MediaTypeExtension.fromString('text/csv'), MediaType.textCsv);
|
||||||
expect(MediaTypeExtension.fromString('application/octet-stream'),
|
expect(MediaTypeExtension.fromString('application/octet-stream'),
|
||||||
MediaType.applicationOctetStream);
|
MediaType.applicationOctetStream,);
|
||||||
expect(MediaTypeExtension.fromString('application/pdf'),
|
expect(MediaTypeExtension.fromString('application/pdf'),
|
||||||
MediaType.applicationPdf);
|
MediaType.applicationPdf,);
|
||||||
expect(MediaTypeExtension.fromString('image/png'), MediaType.imagePng);
|
expect(MediaTypeExtension.fromString('image/png'), MediaType.imagePng);
|
||||||
expect(MediaTypeExtension.fromString('image/jpeg'), MediaType.imageJpeg);
|
expect(MediaTypeExtension.fromString('image/jpeg'), MediaType.imageJpeg);
|
||||||
expect(MediaTypeExtension.fromString('image/jpg'), MediaType.imageJpeg);
|
expect(MediaTypeExtension.fromString('image/jpg'), MediaType.imageJpeg);
|
||||||
expect(MediaTypeExtension.fromString('image/gif'), MediaType.imageGif);
|
expect(MediaTypeExtension.fromString('image/gif'), MediaType.imageGif);
|
||||||
expect(
|
expect(
|
||||||
MediaTypeExtension.fromString('image/svg+xml'), MediaType.imageSvg);
|
MediaTypeExtension.fromString('image/svg+xml'), MediaType.imageSvg,);
|
||||||
expect(MediaTypeExtension.fromString('audio/mpeg'), MediaType.audioMp3);
|
expect(MediaTypeExtension.fromString('audio/mpeg'), MediaType.audioMp3);
|
||||||
expect(MediaTypeExtension.fromString('audio/mp3'), MediaType.audioMp3);
|
expect(MediaTypeExtension.fromString('audio/mp3'), MediaType.audioMp3);
|
||||||
expect(MediaTypeExtension.fromString('video/mp4'), MediaType.videoMp4);
|
expect(MediaTypeExtension.fromString('video/mp4'), MediaType.videoMp4);
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ void main() {
|
||||||
tags: ['User'],
|
tags: ['User'],
|
||||||
parameters: [],
|
parameters: [],
|
||||||
responses: {},
|
responses: {},
|
||||||
requestBody: null,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(path.path, '/api/users');
|
expect(path.path, '/api/users');
|
||||||
|
|
@ -195,7 +194,7 @@ void main() {
|
||||||
'links': {
|
'links': {
|
||||||
'GetUserByName': {
|
'GetUserByName': {
|
||||||
'operationId': 'getUserByName',
|
'operationId': 'getUserByName',
|
||||||
'parameters': {'username': '\$response.body#/username'},
|
'parameters': {'username': r'$response.body#/username'},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -248,7 +247,7 @@ void main() {
|
||||||
|
|
||||||
expect(requestBody.content.length, 1);
|
expect(requestBody.content.length, 1);
|
||||||
expect(
|
expect(
|
||||||
requestBody.content['application/json']?.schema?['type'], 'object');
|
requestBody.content['application/json']?.schema?['type'], 'object',);
|
||||||
expect(requestBody.supportedMediaTypes, contains('application/json'));
|
expect(requestBody.supportedMediaTypes, contains('application/json'));
|
||||||
expect(requestBody.supportsMediaType('application/json'), true);
|
expect(requestBody.supportsMediaType('application/json'), true);
|
||||||
});
|
});
|
||||||
|
|
@ -433,7 +432,7 @@ void main() {
|
||||||
final json = {
|
final json = {
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
'description': 'User object',
|
'description': 'User object',
|
||||||
'\$ref': '#/components/schemas/User',
|
r'$ref': '#/components/schemas/User',
|
||||||
};
|
};
|
||||||
|
|
||||||
final property = ApiProperty.fromJson('user', json, []);
|
final property = ApiProperty.fromJson('user', json, []);
|
||||||
|
|
@ -448,7 +447,7 @@ void main() {
|
||||||
'description': 'User list',
|
'description': 'User list',
|
||||||
'items': {
|
'items': {
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
'\$ref': '#/components/schemas/User',
|
r'$ref': '#/components/schemas/User',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -633,7 +632,7 @@ void main() {
|
||||||
expect(document.components.schemas['User']?.name, 'User');
|
expect(document.components.schemas['User']?.name, 'User');
|
||||||
expect(document.components.responses.length, 1);
|
expect(document.components.responses.length, 1);
|
||||||
expect(document.components.responses['NotFound']?.description,
|
expect(document.components.responses['NotFound']?.description,
|
||||||
'Resource not found');
|
'Resource not found',);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('creates SwaggerDocument with composition schemas', () {
|
test('creates SwaggerDocument with composition schemas', () {
|
||||||
|
|
@ -654,7 +653,7 @@ void main() {
|
||||||
},
|
},
|
||||||
'Dog': {
|
'Dog': {
|
||||||
'allOf': [
|
'allOf': [
|
||||||
{'\$ref': '#/components/schemas/Pet'},
|
{r'$ref': '#/components/schemas/Pet'},
|
||||||
{
|
{
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
'properties': {
|
'properties': {
|
||||||
|
|
@ -665,7 +664,7 @@ void main() {
|
||||||
},
|
},
|
||||||
'Animal': {
|
'Animal': {
|
||||||
'oneOf': [
|
'oneOf': [
|
||||||
{'\$ref': '#/components/schemas/Pet'},
|
{r'$ref': '#/components/schemas/Pet'},
|
||||||
{
|
{
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
'properties': {
|
'properties': {
|
||||||
|
|
@ -743,7 +742,6 @@ void main() {
|
||||||
tags: ['User'],
|
tags: ['User'],
|
||||||
parameters: [],
|
parameters: [],
|
||||||
responses: {},
|
responses: {},
|
||||||
requestBody: null,
|
|
||||||
),
|
),
|
||||||
const ApiPath(
|
const ApiPath(
|
||||||
path: '/api/users/{id}',
|
path: '/api/users/{id}',
|
||||||
|
|
@ -754,7 +752,6 @@ void main() {
|
||||||
tags: ['User'],
|
tags: ['User'],
|
||||||
parameters: [],
|
parameters: [],
|
||||||
responses: {},
|
responses: {},
|
||||||
requestBody: null,
|
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -780,7 +777,6 @@ void main() {
|
||||||
tags: ['User'],
|
tags: ['User'],
|
||||||
parameters: [],
|
parameters: [],
|
||||||
responses: {},
|
responses: {},
|
||||||
requestBody: null,
|
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -812,7 +808,7 @@ void main() {
|
||||||
'200': <String, dynamic>{
|
'200': <String, dynamic>{
|
||||||
'description': 'Success',
|
'description': 'Success',
|
||||||
'schema': <String, dynamic>{'type': 'object'},
|
'schema': <String, dynamic>{'type': 'object'},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
'requestBody': <String, dynamic>{
|
'requestBody': <String, dynamic>{
|
||||||
'description': 'User data',
|
'description': 'User data',
|
||||||
|
|
@ -826,7 +822,7 @@ void main() {
|
||||||
'deprecated': true,
|
'deprecated': true,
|
||||||
};
|
};
|
||||||
|
|
||||||
final path = ApiPath.fromJson('/api/users/{id}', 'PUT', json);
|
final path = ApiPath.fromJson('/api/users/{id}', HttpMethod.put, json);
|
||||||
|
|
||||||
expect(path.path, '/api/users/{id}');
|
expect(path.path, '/api/users/{id}');
|
||||||
expect(path.method, HttpMethod.put);
|
expect(path.method, HttpMethod.put);
|
||||||
|
|
@ -843,7 +839,7 @@ void main() {
|
||||||
test('creates ApiPath from JSON with minimal fields', () {
|
test('creates ApiPath from JSON with minimal fields', () {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
|
|
||||||
final path = ApiPath.fromJson('/api/users', 'GET', json);
|
final path = ApiPath.fromJson('/api/users', HttpMethod.get, json);
|
||||||
|
|
||||||
expect(path.path, '/api/users');
|
expect(path.path, '/api/users');
|
||||||
expect(path.method, HttpMethod.get);
|
expect(path.method, HttpMethod.get);
|
||||||
|
|
@ -952,7 +948,7 @@ void main() {
|
||||||
'description': 'User list',
|
'description': 'User list',
|
||||||
'items': {
|
'items': {
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
'\$ref': '#/components/schemas/User',
|
r'$ref': '#/components/schemas/User',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1057,7 +1053,7 @@ void main() {
|
||||||
expect(ParameterLocation.fromString('unknown'), ParameterLocation.query);
|
expect(ParameterLocation.fromString('unknown'), ParameterLocation.query);
|
||||||
expect(ParameterLocation.fromString(''), ParameterLocation.query);
|
expect(ParameterLocation.fromString(''), ParameterLocation.query);
|
||||||
expect(ParameterLocation.fromString('CUSTOM_LOCATION'),
|
expect(ParameterLocation.fromString('CUSTOM_LOCATION'),
|
||||||
ParameterLocation.query);
|
ParameterLocation.query,);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('HttpMethod fromString handles unknown methods', () {
|
test('HttpMethod fromString handles unknown methods', () {
|
||||||
|
|
@ -1121,7 +1117,7 @@ void main() {
|
||||||
test('creates ApiSchema from JSON with allOf', () {
|
test('creates ApiSchema from JSON with allOf', () {
|
||||||
final json = {
|
final json = {
|
||||||
'allOf': [
|
'allOf': [
|
||||||
{'\$ref': '#/components/schemas/Pet'},
|
{r'$ref': '#/components/schemas/Pet'},
|
||||||
{
|
{
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
'properties': {
|
'properties': {
|
||||||
|
|
@ -1146,8 +1142,8 @@ void main() {
|
||||||
test('creates ApiSchema from JSON with oneOf', () {
|
test('creates ApiSchema from JSON with oneOf', () {
|
||||||
final json = {
|
final json = {
|
||||||
'oneOf': [
|
'oneOf': [
|
||||||
{'\$ref': '#/components/schemas/Cat'},
|
{r'$ref': '#/components/schemas/Cat'},
|
||||||
{'\$ref': '#/components/schemas/Dog'},
|
{r'$ref': '#/components/schemas/Dog'},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1165,7 +1161,7 @@ void main() {
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'minLength': 1,
|
'minLength': 1,
|
||||||
'maxLength': 100,
|
'maxLength': 100,
|
||||||
'pattern': '^[a-zA-Z]+\$',
|
'pattern': r'^[a-zA-Z]+$',
|
||||||
'example': 'example',
|
'example': 'example',
|
||||||
'nullable': true,
|
'nullable': true,
|
||||||
};
|
};
|
||||||
|
|
@ -1175,7 +1171,7 @@ void main() {
|
||||||
expect(schema.type, 'string');
|
expect(schema.type, 'string');
|
||||||
expect(schema.minLength, 1);
|
expect(schema.minLength, 1);
|
||||||
expect(schema.maxLength, 100);
|
expect(schema.maxLength, 100);
|
||||||
expect(schema.pattern, '^[a-zA-Z]+\$');
|
expect(schema.pattern, r'^[a-zA-Z]+$');
|
||||||
expect(schema.example, 'example');
|
expect(schema.example, 'example');
|
||||||
expect(schema.nullable, true);
|
expect(schema.nullable, true);
|
||||||
});
|
});
|
||||||
|
|
@ -1183,8 +1179,8 @@ void main() {
|
||||||
test('creates ApiSchema from JSON with discriminator', () {
|
test('creates ApiSchema from JSON with discriminator', () {
|
||||||
final json = {
|
final json = {
|
||||||
'oneOf': [
|
'oneOf': [
|
||||||
{'\$ref': '#/components/schemas/Cat'},
|
{r'$ref': '#/components/schemas/Cat'},
|
||||||
{'\$ref': '#/components/schemas/Dog'},
|
{r'$ref': '#/components/schemas/Dog'},
|
||||||
],
|
],
|
||||||
'discriminator': {
|
'discriminator': {
|
||||||
'propertyName': 'petType',
|
'propertyName': 'petType',
|
||||||
|
|
@ -1204,9 +1200,9 @@ void main() {
|
||||||
expect(schema.discriminator?.hasMapping, true);
|
expect(schema.discriminator?.hasMapping, true);
|
||||||
expect(schema.discriminator?.mapping.length, 2);
|
expect(schema.discriminator?.mapping.length, 2);
|
||||||
expect(schema.discriminator?.getSchemaForValue('cat'),
|
expect(schema.discriminator?.getSchemaForValue('cat'),
|
||||||
'#/components/schemas/Cat');
|
'#/components/schemas/Cat',);
|
||||||
expect(schema.discriminator?.getSchemaForValue('dog'),
|
expect(schema.discriminator?.getSchemaForValue('dog'),
|
||||||
'#/components/schemas/Dog');
|
'#/components/schemas/Dog',);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -1234,9 +1230,9 @@ void main() {
|
||||||
expect(discriminator.mapping.length, 2);
|
expect(discriminator.mapping.length, 2);
|
||||||
expect(discriminator.hasMapping, true);
|
expect(discriminator.hasMapping, true);
|
||||||
expect(
|
expect(
|
||||||
discriminator.getSchemaForValue('cat'), '#/components/schemas/Cat');
|
discriminator.getSchemaForValue('cat'), '#/components/schemas/Cat',);
|
||||||
expect(
|
expect(
|
||||||
discriminator.getSchemaForValue('dog'), '#/components/schemas/Dog');
|
discriminator.getSchemaForValue('dog'), '#/components/schemas/Dog',);
|
||||||
expect(discriminator.getSchemaForValue('bird'), isNull);
|
expect(discriminator.getSchemaForValue('bird'), isNull);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -1255,9 +1251,9 @@ void main() {
|
||||||
expect(discriminator.mapping.length, 2);
|
expect(discriminator.mapping.length, 2);
|
||||||
expect(discriminator.hasMapping, true);
|
expect(discriminator.hasMapping, true);
|
||||||
expect(
|
expect(
|
||||||
discriminator.getSchemaForValue('user'), '#/components/schemas/User');
|
discriminator.getSchemaForValue('user'), '#/components/schemas/User',);
|
||||||
expect(discriminator.getSchemaForValue('admin'),
|
expect(discriminator.getSchemaForValue('admin'),
|
||||||
'#/components/schemas/Admin');
|
'#/components/schemas/Admin',);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('creates ApiDiscriminator from JSON with minimal fields', () {
|
test('creates ApiDiscriminator from JSON with minimal fields', () {
|
||||||
|
|
@ -1313,15 +1309,15 @@ void main() {
|
||||||
final addressProperty = property.nestedProperties['address']!;
|
final addressProperty = property.nestedProperties['address']!;
|
||||||
expect(addressProperty.nestedProperties.length, 3);
|
expect(addressProperty.nestedProperties.length, 3);
|
||||||
expect(addressProperty.nestedProperties['coordinates']?.type,
|
expect(addressProperty.nestedProperties['coordinates']?.type,
|
||||||
PropertyType.object);
|
PropertyType.object,);
|
||||||
|
|
||||||
final coordinatesProperty =
|
final coordinatesProperty =
|
||||||
addressProperty.nestedProperties['coordinates']!;
|
addressProperty.nestedProperties['coordinates']!;
|
||||||
expect(coordinatesProperty.nestedProperties.length, 2);
|
expect(coordinatesProperty.nestedProperties.length, 2);
|
||||||
expect(coordinatesProperty.nestedProperties['lat']?.type,
|
expect(coordinatesProperty.nestedProperties['lat']?.type,
|
||||||
PropertyType.number);
|
PropertyType.number,);
|
||||||
expect(coordinatesProperty.nestedProperties['lng']?.type,
|
expect(coordinatesProperty.nestedProperties['lng']?.type,
|
||||||
PropertyType.number);
|
PropertyType.number,);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('creates ApiProperty with array of nested objects', () {
|
test('creates ApiProperty with array of nested objects', () {
|
||||||
|
|
@ -1438,7 +1434,7 @@ void main() {
|
||||||
},
|
},
|
||||||
'additionalProperties': {
|
'additionalProperties': {
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'pattern': '^[a-zA-Z]+\$',
|
'pattern': r'^[a-zA-Z]+$',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1448,7 +1444,7 @@ void main() {
|
||||||
expect(schema.allowsAdditionalProperties, true);
|
expect(schema.allowsAdditionalProperties, true);
|
||||||
expect(schema.additionalPropertiesSchema, isNotNull);
|
expect(schema.additionalPropertiesSchema, isNotNull);
|
||||||
expect(schema.additionalPropertiesSchema?.type, 'string');
|
expect(schema.additionalPropertiesSchema?.type, 'string');
|
||||||
expect(schema.additionalPropertiesSchema?.pattern, '^[a-zA-Z]+\$');
|
expect(schema.additionalPropertiesSchema?.pattern, r'^[a-zA-Z]+$');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('creates ApiSchema with patternProperties', () {
|
test('creates ApiSchema with patternProperties', () {
|
||||||
|
|
@ -1472,14 +1468,14 @@ void main() {
|
||||||
final json = {
|
final json = {
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
'propertyNames': {
|
'propertyNames': {
|
||||||
'pattern': '^[A-Za-z_][A-Za-z0-9_]*\$',
|
'pattern': r'^[A-Za-z_][A-Za-z0-9_]*$',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
final schema = ApiSchema.fromJson(json);
|
final schema = ApiSchema.fromJson(json);
|
||||||
|
|
||||||
expect(schema.hasPropertyNames, true);
|
expect(schema.hasPropertyNames, true);
|
||||||
expect(schema.propertyNames?.pattern, '^[A-Za-z_][A-Za-z0-9_]*\$');
|
expect(schema.propertyNames?.pattern, r'^[A-Za-z_][A-Za-z0-9_]*$');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('creates ApiSchema with dependencies', () {
|
test('creates ApiSchema with dependencies', () {
|
||||||
|
|
@ -1567,7 +1563,7 @@ void main() {
|
||||||
'^prefix_': {'type': 'integer'},
|
'^prefix_': {'type': 'integer'},
|
||||||
},
|
},
|
||||||
'propertyNames': {
|
'propertyNames': {
|
||||||
'pattern': '^[a-z]+\$',
|
'pattern': r'^[a-z]+$',
|
||||||
},
|
},
|
||||||
'dependencies': {
|
'dependencies': {
|
||||||
'name': ['id'],
|
'name': ['id'],
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ void main() {
|
||||||
},
|
},
|
||||||
'User': {
|
'User': {
|
||||||
'allOf': [
|
'allOf': [
|
||||||
{'\$ref': '#/components/schemas/BaseEntity'},
|
{r'$ref': '#/components/schemas/BaseEntity'},
|
||||||
{
|
{
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
'properties': {
|
'properties': {
|
||||||
|
|
@ -89,7 +89,7 @@ void main() {
|
||||||
|
|
||||||
// 检查合并的属性
|
// 检查合并的属性
|
||||||
expect(
|
expect(
|
||||||
models['User']!.properties.length, 4); // id, createdAt, name, email
|
models['User']!.properties.length, 4,); // id, createdAt, name, email
|
||||||
expect(models['User']!.properties['id'], isNotNull);
|
expect(models['User']!.properties['id'], isNotNull);
|
||||||
expect(models['User']!.properties['name'], isNotNull);
|
expect(models['User']!.properties['name'], isNotNull);
|
||||||
expect(models['User']!.properties['email'], isNotNull);
|
expect(models['User']!.properties['email'], isNotNull);
|
||||||
|
|
@ -103,10 +103,10 @@ void main() {
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
'properties': {
|
'properties': {
|
||||||
'id': {'type': 'integer'},
|
'id': {'type': 'integer'},
|
||||||
'parent': {'\$ref': '#/components/schemas/Node'},
|
'parent': {r'$ref': '#/components/schemas/Node'},
|
||||||
'children': {
|
'children': {
|
||||||
'type': 'array',
|
'type': 'array',
|
||||||
'items': {'\$ref': '#/components/schemas/Node'},
|
'items': {r'$ref': '#/components/schemas/Node'},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'required': ['id'],
|
'required': ['id'],
|
||||||
|
|
@ -152,7 +152,7 @@ void main() {
|
||||||
'name': {'type': 'string'},
|
'name': {'type': 'string'},
|
||||||
'addresses': {
|
'addresses': {
|
||||||
'type': 'array',
|
'type': 'array',
|
||||||
'items': {'\$ref': '#/components/schemas/Address'},
|
'items': {r'$ref': '#/components/schemas/Address'},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'required': ['id', 'name'],
|
'required': ['id', 'name'],
|
||||||
|
|
@ -211,8 +211,8 @@ void main() {
|
||||||
'schemas': {
|
'schemas': {
|
||||||
'Pet': {
|
'Pet': {
|
||||||
'oneOf': [
|
'oneOf': [
|
||||||
{'\$ref': '#/components/schemas/Cat'},
|
{r'$ref': '#/components/schemas/Cat'},
|
||||||
{'\$ref': '#/components/schemas/Dog'},
|
{r'$ref': '#/components/schemas/Dog'},
|
||||||
],
|
],
|
||||||
'discriminator': {
|
'discriminator': {
|
||||||
'propertyName': 'petType',
|
'propertyName': 'petType',
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
|
|
||||||
|
|
@ -79,10 +79,10 @@ void main() {
|
||||||
expect(flows.hasAnyFlow, true);
|
expect(flows.hasAnyFlow, true);
|
||||||
expect(flows.availableFlows.length, 3);
|
expect(flows.availableFlows.length, 3);
|
||||||
expect(flows.availableFlows.contains(OAuth2FlowType.authorizationCode),
|
expect(flows.availableFlows.contains(OAuth2FlowType.authorizationCode),
|
||||||
true);
|
true,);
|
||||||
expect(flows.availableFlows.contains(OAuth2FlowType.implicit), true);
|
expect(flows.availableFlows.contains(OAuth2FlowType.implicit), true);
|
||||||
expect(flows.availableFlows.contains(OAuth2FlowType.clientCredentials),
|
expect(flows.availableFlows.contains(OAuth2FlowType.clientCredentials),
|
||||||
true);
|
true,);
|
||||||
expect(flows.availableFlows.contains(OAuth2FlowType.password), false);
|
expect(flows.availableFlows.contains(OAuth2FlowType.password), false);
|
||||||
|
|
||||||
expect(flows.authorizationCode, isNotNull);
|
expect(flows.authorizationCode, isNotNull);
|
||||||
|
|
@ -206,7 +206,7 @@ void main() {
|
||||||
expect(scheme.type, SecuritySchemeType.openIdConnect);
|
expect(scheme.type, SecuritySchemeType.openIdConnect);
|
||||||
expect(scheme.description, 'OpenID Connect authentication');
|
expect(scheme.description, 'OpenID Connect authentication');
|
||||||
expect(scheme.openIdConnectUrl,
|
expect(scheme.openIdConnectUrl,
|
||||||
'https://example.com/.well-known/openid_configuration');
|
'https://example.com/.well-known/openid_configuration',);
|
||||||
expect(scheme.isOpenIdConnect, true);
|
expect(scheme.isOpenIdConnect, true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -251,15 +251,15 @@ void main() {
|
||||||
|
|
||||||
test('converts string to security scheme type', () {
|
test('converts string to security scheme type', () {
|
||||||
expect(SecuritySchemeTypeExtension.fromString('apiKey'),
|
expect(SecuritySchemeTypeExtension.fromString('apiKey'),
|
||||||
SecuritySchemeType.apiKey);
|
SecuritySchemeType.apiKey,);
|
||||||
expect(SecuritySchemeTypeExtension.fromString('http'),
|
expect(SecuritySchemeTypeExtension.fromString('http'),
|
||||||
SecuritySchemeType.http);
|
SecuritySchemeType.http,);
|
||||||
expect(SecuritySchemeTypeExtension.fromString('oauth2'),
|
expect(SecuritySchemeTypeExtension.fromString('oauth2'),
|
||||||
SecuritySchemeType.oauth2);
|
SecuritySchemeType.oauth2,);
|
||||||
expect(SecuritySchemeTypeExtension.fromString('openIdConnect'),
|
expect(SecuritySchemeTypeExtension.fromString('openIdConnect'),
|
||||||
SecuritySchemeType.openIdConnect);
|
SecuritySchemeType.openIdConnect,);
|
||||||
expect(SecuritySchemeTypeExtension.fromString('unknown'),
|
expect(SecuritySchemeTypeExtension.fromString('unknown'),
|
||||||
SecuritySchemeType.apiKey);
|
SecuritySchemeType.apiKey,);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('converts API key location to string', () {
|
test('converts API key location to string', () {
|
||||||
|
|
@ -271,11 +271,11 @@ void main() {
|
||||||
test('converts string to API key location', () {
|
test('converts string to API key location', () {
|
||||||
expect(ApiKeyLocationExtension.fromString('query'), ApiKeyLocation.query);
|
expect(ApiKeyLocationExtension.fromString('query'), ApiKeyLocation.query);
|
||||||
expect(
|
expect(
|
||||||
ApiKeyLocationExtension.fromString('header'), ApiKeyLocation.header);
|
ApiKeyLocationExtension.fromString('header'), ApiKeyLocation.header,);
|
||||||
expect(
|
expect(
|
||||||
ApiKeyLocationExtension.fromString('cookie'), ApiKeyLocation.cookie);
|
ApiKeyLocationExtension.fromString('cookie'), ApiKeyLocation.cookie,);
|
||||||
expect(
|
expect(
|
||||||
ApiKeyLocationExtension.fromString('unknown'), ApiKeyLocation.header);
|
ApiKeyLocationExtension.fromString('unknown'), ApiKeyLocation.header,);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('converts OAuth2 flow type to string', () {
|
test('converts OAuth2 flow type to string', () {
|
||||||
|
|
@ -287,15 +287,15 @@ void main() {
|
||||||
|
|
||||||
test('converts string to OAuth2 flow type', () {
|
test('converts string to OAuth2 flow type', () {
|
||||||
expect(OAuth2FlowTypeExtension.fromString('authorizationCode'),
|
expect(OAuth2FlowTypeExtension.fromString('authorizationCode'),
|
||||||
OAuth2FlowType.authorizationCode);
|
OAuth2FlowType.authorizationCode,);
|
||||||
expect(OAuth2FlowTypeExtension.fromString('implicit'),
|
expect(OAuth2FlowTypeExtension.fromString('implicit'),
|
||||||
OAuth2FlowType.implicit);
|
OAuth2FlowType.implicit,);
|
||||||
expect(OAuth2FlowTypeExtension.fromString('password'),
|
expect(OAuth2FlowTypeExtension.fromString('password'),
|
||||||
OAuth2FlowType.password);
|
OAuth2FlowType.password,);
|
||||||
expect(OAuth2FlowTypeExtension.fromString('clientCredentials'),
|
expect(OAuth2FlowTypeExtension.fromString('clientCredentials'),
|
||||||
OAuth2FlowType.clientCredentials);
|
OAuth2FlowType.clientCredentials,);
|
||||||
expect(OAuth2FlowTypeExtension.fromString('unknown'),
|
expect(OAuth2FlowTypeExtension.fromString('unknown'),
|
||||||
OAuth2FlowType.authorizationCode);
|
OAuth2FlowType.authorizationCode,);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:swagger_generator_flutter/core/models.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/generators/retrofit_api_generator.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
|
@ -7,7 +8,7 @@ void main() {
|
||||||
late SwaggerDocument simpleDocument;
|
late SwaggerDocument simpleDocument;
|
||||||
|
|
||||||
setUp(() {
|
setUp(() {
|
||||||
simpleDocument = SwaggerDocument(
|
simpleDocument = const SwaggerDocument(
|
||||||
title: 'Simple Test API',
|
title: 'Simple Test API',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
description: 'A simple test API',
|
description: 'A simple test API',
|
||||||
|
|
@ -17,10 +18,6 @@ void main() {
|
||||||
description: 'Test server',
|
description: 'Test server',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
components: ApiComponents(
|
|
||||||
schemas: {},
|
|
||||||
securitySchemes: {},
|
|
||||||
),
|
|
||||||
paths: {
|
paths: {
|
||||||
'/users': ApiPath(
|
'/users': ApiPath(
|
||||||
path: '/users',
|
path: '/users',
|
||||||
|
|
@ -93,7 +90,6 @@ void main() {
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
controllers: {},
|
controllers: {},
|
||||||
security: [],
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -122,7 +118,7 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('generates path annotations', () {
|
test('generates path annotations', () {
|
||||||
final generator = RetrofitApiGenerator();
|
final generator = RetrofitApiGenerator(splitByTags: false);
|
||||||
|
|
||||||
final result = generator.generateFromDocument(simpleDocument);
|
final result = generator.generateFromDocument(simpleDocument);
|
||||||
|
|
||||||
|
|
@ -132,7 +128,7 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('generates parameter annotations', () {
|
test('generates parameter annotations', () {
|
||||||
final generator = RetrofitApiGenerator();
|
final generator = RetrofitApiGenerator(splitByTags: false);
|
||||||
|
|
||||||
final result = generator.generateFromDocument(simpleDocument);
|
final result = generator.generateFromDocument(simpleDocument);
|
||||||
|
|
||||||
|
|
@ -142,7 +138,7 @@ void main() {
|
||||||
|
|
||||||
test('handles split by tags', () {
|
test('handles split by tags', () {
|
||||||
final generator = RetrofitApiGenerator(
|
final generator = RetrofitApiGenerator(
|
||||||
splitByTags: true,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final result = generator.generateFromDocument(simpleDocument);
|
final result = generator.generateFromDocument(simpleDocument);
|
||||||
|
|
@ -154,7 +150,7 @@ void main() {
|
||||||
|
|
||||||
group('Code Quality Tests', () {
|
group('Code Quality Tests', () {
|
||||||
test('generated code has proper structure', () {
|
test('generated code has proper structure', () {
|
||||||
final generator = RetrofitApiGenerator();
|
final generator = RetrofitApiGenerator(splitByTags: false);
|
||||||
final result = generator.generateFromDocument(simpleDocument);
|
final result = generator.generateFromDocument(simpleDocument);
|
||||||
|
|
||||||
// Check for basic Dart syntax
|
// Check for basic Dart syntax
|
||||||
|
|
@ -168,32 +164,26 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handles empty paths gracefully', () {
|
test('handles empty paths gracefully', () {
|
||||||
final emptyDocument = SwaggerDocument(
|
const emptyDocument = SwaggerDocument(
|
||||||
title: 'Empty API',
|
title: 'Empty API',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
description: 'Empty API',
|
description: 'Empty API',
|
||||||
servers: [],
|
|
||||||
components: ApiComponents(schemas: {}, securitySchemes: {}),
|
|
||||||
paths: {},
|
paths: {},
|
||||||
models: {},
|
models: {},
|
||||||
controllers: {},
|
controllers: {},
|
||||||
security: [],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final generator = RetrofitApiGenerator();
|
final generator = RetrofitApiGenerator(splitByTags: false);
|
||||||
final result = generator.generateFromDocument(emptyDocument);
|
final result = generator.generateFromDocument(emptyDocument);
|
||||||
|
|
||||||
expect(result, isNotEmpty);
|
expect(result, isNotEmpty);
|
||||||
expect(result, contains('Empty API'));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handles special characters in API names', () {
|
test('handles special characters in API names', () {
|
||||||
final specialDocument = SwaggerDocument(
|
const specialDocument = SwaggerDocument(
|
||||||
title: 'API-with_Special.Characters',
|
title: 'API-with_Special.Characters',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
description: 'Test',
|
description: 'Test',
|
||||||
servers: [],
|
|
||||||
components: ApiComponents(schemas: {}, securitySchemes: {}),
|
|
||||||
paths: {
|
paths: {
|
||||||
'/special-endpoint': ApiPath(
|
'/special-endpoint': ApiPath(
|
||||||
path: '/special-endpoint',
|
path: '/special-endpoint',
|
||||||
|
|
@ -213,21 +203,18 @@ void main() {
|
||||||
},
|
},
|
||||||
models: {},
|
models: {},
|
||||||
controllers: {},
|
controllers: {},
|
||||||
security: [],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final generator = RetrofitApiGenerator();
|
final generator = RetrofitApiGenerator(splitByTags: false);
|
||||||
expect(() => generator.generateFromDocument(specialDocument),
|
expect(() => generator.generateFromDocument(specialDocument),
|
||||||
returnsNormally);
|
returnsNormally,);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handles nullable parameters correctly', () {
|
test('handles nullable parameters correctly', () {
|
||||||
final documentWithOptionalParams = SwaggerDocument(
|
const documentWithOptionalParams = SwaggerDocument(
|
||||||
title: 'Test API',
|
title: 'Test API',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
description: 'Test',
|
description: 'Test',
|
||||||
servers: [],
|
|
||||||
components: ApiComponents(schemas: {}, securitySchemes: {}),
|
|
||||||
paths: {
|
paths: {
|
||||||
'/search': ApiPath(
|
'/search': ApiPath(
|
||||||
path: '/search',
|
path: '/search',
|
||||||
|
|
@ -262,10 +249,9 @@ void main() {
|
||||||
},
|
},
|
||||||
models: {},
|
models: {},
|
||||||
controllers: {},
|
controllers: {},
|
||||||
security: [],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final generator = RetrofitApiGenerator();
|
final generator = RetrofitApiGenerator(splitByTags: false);
|
||||||
final result =
|
final result =
|
||||||
generator.generateFromDocument(documentWithOptionalParams);
|
generator.generateFromDocument(documentWithOptionalParams);
|
||||||
|
|
||||||
|
|
@ -281,7 +267,7 @@ void main() {
|
||||||
test('handles medium-sized documents efficiently', () {
|
test('handles medium-sized documents efficiently', () {
|
||||||
// Create a document with multiple paths
|
// Create a document with multiple paths
|
||||||
final paths = <String, ApiPath>{};
|
final paths = <String, ApiPath>{};
|
||||||
for (int i = 0; i < 50; i++) {
|
for (var i = 0; i < 50; i++) {
|
||||||
paths['/resource$i'] = ApiPath(
|
paths['/resource$i'] = ApiPath(
|
||||||
path: '/resource$i',
|
path: '/resource$i',
|
||||||
method: HttpMethod.get,
|
method: HttpMethod.get,
|
||||||
|
|
@ -291,7 +277,7 @@ void main() {
|
||||||
tags: ['resources'],
|
tags: ['resources'],
|
||||||
parameters: [],
|
parameters: [],
|
||||||
responses: {
|
responses: {
|
||||||
'200': ApiResponse(
|
'200': const ApiResponse(
|
||||||
code: '200',
|
code: '200',
|
||||||
description: 'Success',
|
description: 'Success',
|
||||||
),
|
),
|
||||||
|
|
@ -304,14 +290,13 @@ void main() {
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
description: 'Large API',
|
description: 'Large API',
|
||||||
servers: [],
|
servers: [],
|
||||||
components: ApiComponents(schemas: {}, securitySchemes: {}),
|
|
||||||
paths: paths,
|
paths: paths,
|
||||||
models: {},
|
models: {},
|
||||||
controllers: {},
|
controllers: {},
|
||||||
security: [],
|
security: [],
|
||||||
);
|
);
|
||||||
|
|
||||||
final generator = RetrofitApiGenerator();
|
final generator = RetrofitApiGenerator(splitByTags: false);
|
||||||
final stopwatch = Stopwatch()..start();
|
final stopwatch = Stopwatch()..start();
|
||||||
|
|
||||||
final result = generator.generateFromDocument(largeDocument);
|
final result = generator.generateFromDocument(largeDocument);
|
||||||
|
|
@ -320,9 +305,9 @@ void main() {
|
||||||
|
|
||||||
expect(result, isNotEmpty);
|
expect(result, isNotEmpty);
|
||||||
expect(stopwatch.elapsedMilliseconds,
|
expect(stopwatch.elapsedMilliseconds,
|
||||||
lessThan(5000)); // Should complete within 5 seconds
|
lessThan(5000),); // Should complete within 5 seconds
|
||||||
expect(result.length,
|
expect(result.length,
|
||||||
greaterThan(1000)); // Should generate substantial code
|
greaterThan(1000),); // Should generate substantial code
|
||||||
});
|
});
|
||||||
|
|
||||||
test('memory usage is reasonable', () {
|
test('memory usage is reasonable', () {
|
||||||
|
|
@ -331,7 +316,22 @@ void main() {
|
||||||
|
|
||||||
// Basic memory usage check - result should not be excessively large
|
// Basic memory usage check - result should not be excessively large
|
||||||
expect(result.length,
|
expect(result.length,
|
||||||
lessThan(100000)); // Less than 100KB for simple document
|
lessThan(100000),); // Less than 100KB for simple document
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('ModelCodeGenerator Tests', () {
|
||||||
|
test('generates Freezed model structure', () {
|
||||||
|
final generator = ModelCodeGenerator(simpleDocument);
|
||||||
|
final result =
|
||||||
|
generator.generateSingleModelFile(simpleDocument.models['User']!);
|
||||||
|
|
||||||
|
expect(result, contains('@freezed'));
|
||||||
|
expect(result, contains("part 'user.freezed.dart';"));
|
||||||
|
expect(result, contains("part 'user.g.dart';"));
|
||||||
|
expect(result, contains('const factory User({'));
|
||||||
|
expect(result,
|
||||||
|
contains('factory User.fromJson(Map<String, dynamic> json)'),);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -33,17 +33,17 @@ void main() {
|
||||||
|
|
||||||
test('converts PascalCase to camelCase', () {
|
test('converts PascalCase to camelCase', () {
|
||||||
expect(StringUtils.toCamelCase('GetClassesTaskChecklistUsers'),
|
expect(StringUtils.toCamelCase('GetClassesTaskChecklistUsers'),
|
||||||
'getClassesTaskChecklistUsers');
|
'getClassesTaskChecklistUsers',);
|
||||||
expect(StringUtils.toCamelCase('GetUserInfo'), 'getUserInfo');
|
expect(StringUtils.toCamelCase('GetUserInfo'), 'getUserInfo');
|
||||||
expect(StringUtils.toCamelCase('CreateTask'), 'createTask');
|
expect(StringUtils.toCamelCase('CreateTask'), 'createTask');
|
||||||
expect(
|
expect(
|
||||||
StringUtils.toCamelCase('UpdateUserProfile'), 'updateUserProfile');
|
StringUtils.toCamelCase('UpdateUserProfile'), 'updateUserProfile',);
|
||||||
expect(StringUtils.toCamelCase('DeleteTaskById'), 'deleteTaskById');
|
expect(StringUtils.toCamelCase('DeleteTaskById'), 'deleteTaskById');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('preserves existing camelCase', () {
|
test('preserves existing camelCase', () {
|
||||||
expect(StringUtils.toCamelCase('getClassesTaskChecklistUsers'),
|
expect(StringUtils.toCamelCase('getClassesTaskChecklistUsers'),
|
||||||
'getClassesTaskChecklistUsers');
|
'getClassesTaskChecklistUsers',);
|
||||||
expect(StringUtils.toCamelCase('getUserInfo'), 'getUserInfo');
|
expect(StringUtils.toCamelCase('getUserInfo'), 'getUserInfo');
|
||||||
expect(StringUtils.toCamelCase('createTask'), 'createTask');
|
expect(StringUtils.toCamelCase('createTask'), 'createTask');
|
||||||
});
|
});
|
||||||
|
|
@ -146,7 +146,7 @@ void main() {
|
||||||
test('generates valid enum names from strings', () {
|
test('generates valid enum names from strings', () {
|
||||||
expect(StringUtils.generateEnumValueName('active', 0), 'active');
|
expect(StringUtils.generateEnumValueName('active', 0), 'active');
|
||||||
expect(
|
expect(
|
||||||
StringUtils.generateEnumValueName('user_status', 1), 'userStatus');
|
StringUtils.generateEnumValueName('user_status', 1), 'userStatus',);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handles invalid strings', () {
|
test('handles invalid strings', () {
|
||||||
|
|
@ -163,14 +163,14 @@ void main() {
|
||||||
group('cleanDescription', () {
|
group('cleanDescription', () {
|
||||||
test('cleans basic descriptions', () {
|
test('cleans basic descriptions', () {
|
||||||
expect(StringUtils.cleanDescription(' test description '),
|
expect(StringUtils.cleanDescription(' test description '),
|
||||||
'test description');
|
'test description',);
|
||||||
expect(StringUtils.cleanDescription('line1\nline2'), 'line1 line2');
|
expect(StringUtils.cleanDescription('line1\nline2'), 'line1 line2');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('removes special characters', () {
|
test('removes special characters', () {
|
||||||
expect(StringUtils.cleanDescription('test@#\$%'), 'test');
|
expect(StringUtils.cleanDescription(r'test@#$%'), 'test');
|
||||||
expect(StringUtils.cleanDescription('test[description]'),
|
expect(StringUtils.cleanDescription('test[description]'),
|
||||||
'testdescription');
|
'testdescription',);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('truncates long descriptions', () {
|
test('truncates long descriptions', () {
|
||||||
|
|
@ -211,7 +211,7 @@ void main() {
|
||||||
group('formatDuration', () {
|
group('formatDuration', () {
|
||||||
test('formats duration correctly', () {
|
test('formats duration correctly', () {
|
||||||
expect(StringUtils.formatDuration(const Duration(milliseconds: 500)),
|
expect(StringUtils.formatDuration(const Duration(milliseconds: 500)),
|
||||||
'500毫秒');
|
'500毫秒',);
|
||||||
expect(StringUtils.formatDuration(const Duration(seconds: 1)), '1.00秒');
|
expect(StringUtils.formatDuration(const Duration(seconds: 1)), '1.00秒');
|
||||||
expect(StringUtils.formatDuration(const Duration(seconds: 2)), '2.00秒');
|
expect(StringUtils.formatDuration(const Duration(seconds: 2)), '2.00秒');
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import '../lib/utils/string_utils.dart';
|
import 'package:swagger_generator_flutter/utils/string_utils.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
print('Testing function name generation:');
|
print('Testing function name generation:');
|
||||||
print(
|
print(
|
||||||
'GetClassesTaskChecklistUsers -> ${StringUtils.toCamelCase('GetClassesTaskChecklistUsers')}');
|
'GetClassesTaskChecklistUsers -> ${StringUtils.toCamelCase('GetClassesTaskChecklistUsers')}',);
|
||||||
print('GetUserInfo -> ${StringUtils.toCamelCase('GetUserInfo')}');
|
print('GetUserInfo -> ${StringUtils.toCamelCase('GetUserInfo')}');
|
||||||
print('CreateTask -> ${StringUtils.toCamelCase('CreateTask')}');
|
print('CreateTask -> ${StringUtils.toCamelCase('CreateTask')}');
|
||||||
print('UpdateUserProfile -> ${StringUtils.toCamelCase('UpdateUserProfile')}');
|
print('UpdateUserProfile -> ${StringUtils.toCamelCase('UpdateUserProfile')}');
|
||||||
|
|
@ -11,11 +11,11 @@ void main() {
|
||||||
|
|
||||||
print('\nTesting existing camelCase:');
|
print('\nTesting existing camelCase:');
|
||||||
print(
|
print(
|
||||||
'getClassesTaskChecklistUsers -> ${StringUtils.toCamelCase('getClassesTaskChecklistUsers')}');
|
'getClassesTaskChecklistUsers -> ${StringUtils.toCamelCase('getClassesTaskChecklistUsers')}',);
|
||||||
print('getUserInfo -> ${StringUtils.toCamelCase('getUserInfo')}');
|
print('getUserInfo -> ${StringUtils.toCamelCase('getUserInfo')}');
|
||||||
|
|
||||||
print('\nTesting snake_case:');
|
print('\nTesting snake_case:');
|
||||||
print(
|
print(
|
||||||
'get_classes_task_checklist_users -> ${StringUtils.toCamelCase('get_classes_task_checklist_users')}');
|
'get_classes_task_checklist_users -> ${StringUtils.toCamelCase('get_classes_task_checklist_users')}',);
|
||||||
print('get_user_info -> ${StringUtils.toCamelCase('get_user_info')}');
|
print('get_user_info -> ${StringUtils.toCamelCase('get_user_info')}');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import '../lib/utils/string_utils.dart';
|
import 'package:swagger_generator_flutter/utils/string_utils.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
print('Testing property name conversion:');
|
print('Testing property name conversion:');
|
||||||
|
|
@ -6,13 +6,13 @@ void main() {
|
||||||
print('meetingTitle -> ${StringUtils.toDartPropertyName('meetingTitle')}');
|
print('meetingTitle -> ${StringUtils.toDartPropertyName('meetingTitle')}');
|
||||||
print('taskInfo -> ${StringUtils.toDartPropertyName('taskInfo')}');
|
print('taskInfo -> ${StringUtils.toDartPropertyName('taskInfo')}');
|
||||||
print(
|
print(
|
||||||
'sunTaskUserResults -> ${StringUtils.toDartPropertyName('sunTaskUserResults')}');
|
'sunTaskUserResults -> ${StringUtils.toDartPropertyName('sunTaskUserResults')}',);
|
||||||
print(
|
print(
|
||||||
'sunTaskFileResults -> ${StringUtils.toDartPropertyName('sunTaskFileResults')}');
|
'sunTaskFileResults -> ${StringUtils.toDartPropertyName('sunTaskFileResults')}',);
|
||||||
|
|
||||||
print('\nTesting snake_case conversion:');
|
print('\nTesting snake_case conversion:');
|
||||||
print(
|
print(
|
||||||
'class_cadre_id -> ${StringUtils.toDartPropertyName('class_cadre_id')}');
|
'class_cadre_id -> ${StringUtils.toDartPropertyName('class_cadre_id')}',);
|
||||||
print('meeting_title -> ${StringUtils.toDartPropertyName('meeting_title')}');
|
print('meeting_title -> ${StringUtils.toDartPropertyName('meeting_title')}');
|
||||||
print('task_info -> ${StringUtils.toDartPropertyName('task_info')}');
|
print('task_info -> ${StringUtils.toDartPropertyName('task_info')}');
|
||||||
|
|
||||||
|
|
@ -21,10 +21,10 @@ void main() {
|
||||||
print('ProblemTitle -> ${StringUtils.toDartPropertyName('ProblemTitle')}');
|
print('ProblemTitle -> ${StringUtils.toDartPropertyName('ProblemTitle')}');
|
||||||
print('ProblemObj -> ${StringUtils.toDartPropertyName('ProblemObj')}');
|
print('ProblemObj -> ${StringUtils.toDartPropertyName('ProblemObj')}');
|
||||||
print(
|
print(
|
||||||
'ProblemPhenomenon -> ${StringUtils.toDartPropertyName('ProblemPhenomenon')}');
|
'ProblemPhenomenon -> ${StringUtils.toDartPropertyName('ProblemPhenomenon')}',);
|
||||||
print('ClassesId -> ${StringUtils.toDartPropertyName('ClassesId')}');
|
print('ClassesId -> ${StringUtils.toDartPropertyName('ClassesId')}');
|
||||||
print(
|
print(
|
||||||
'ProblemTaskType -> ${StringUtils.toDartPropertyName('ProblemTaskType')}');
|
'ProblemTaskType -> ${StringUtils.toDartPropertyName('ProblemTaskType')}',);
|
||||||
print('PageSize -> ${StringUtils.toDartPropertyName('PageSize')}');
|
print('PageSize -> ${StringUtils.toDartPropertyName('PageSize')}');
|
||||||
|
|
||||||
print('\nTesting parameter name conversion:');
|
print('\nTesting parameter name conversion:');
|
||||||
|
|
@ -41,20 +41,20 @@ void main() {
|
||||||
|
|
||||||
print('\nTesting tag names:');
|
print('\nTesting tag names:');
|
||||||
print(
|
print(
|
||||||
'Follow Manager -> ${StringUtils.toDartPropertyName('Follow Manager')}');
|
'Follow Manager -> ${StringUtils.toDartPropertyName('Follow Manager')}',);
|
||||||
print('Health Check -> ${StringUtils.toDartPropertyName('Health Check')}');
|
print('Health Check -> ${StringUtils.toDartPropertyName('Health Check')}');
|
||||||
print(
|
print(
|
||||||
'Mobile Manager -> ${StringUtils.toDartPropertyName('Mobile Manager')}');
|
'Mobile Manager -> ${StringUtils.toDartPropertyName('Mobile Manager')}',);
|
||||||
print('My Info -> ${StringUtils.toDartPropertyName('My Info')}');
|
print('My Info -> ${StringUtils.toDartPropertyName('My Info')}');
|
||||||
print(
|
print(
|
||||||
'Task Class Cadre Meeting -> ${StringUtils.toDartPropertyName('Task Class Cadre Meeting')}');
|
'Task Class Cadre Meeting -> ${StringUtils.toDartPropertyName('Task Class Cadre Meeting')}',);
|
||||||
print(
|
print(
|
||||||
'Task Class Meeting -> ${StringUtils.toDartPropertyName('Task Class Meeting')}');
|
'Task Class Meeting -> ${StringUtils.toDartPropertyName('Task Class Meeting')}',);
|
||||||
print(
|
print(
|
||||||
'Task Coach Sub -> ${StringUtils.toDartPropertyName('Task Coach Sub')}');
|
'Task Coach Sub -> ${StringUtils.toDartPropertyName('Task Coach Sub')}',);
|
||||||
print('Task Cultural -> ${StringUtils.toDartPropertyName('Task Cultural')}');
|
print('Task Cultural -> ${StringUtils.toDartPropertyName('Task Cultural')}');
|
||||||
print(
|
print(
|
||||||
'Task Data Collect -> ${StringUtils.toDartPropertyName('Task Data Collect')}');
|
'Task Data Collect -> ${StringUtils.toDartPropertyName('Task Data Collect')}',);
|
||||||
print('Task Follow -> ${StringUtils.toDartPropertyName('Task Follow')}');
|
print('Task Follow -> ${StringUtils.toDartPropertyName('Task Follow')}');
|
||||||
print('Task Info -> ${StringUtils.toDartPropertyName('Task Info')}');
|
print('Task Info -> ${StringUtils.toDartPropertyName('Task Info')}');
|
||||||
print('Task Meeting -> ${StringUtils.toDartPropertyName('Task Meeting')}');
|
print('Task Meeting -> ${StringUtils.toDartPropertyName('Task Meeting')}');
|
||||||
|
|
@ -62,15 +62,15 @@ void main() {
|
||||||
print('Task Solution -> ${StringUtils.toDartPropertyName('Task Solution')}');
|
print('Task Solution -> ${StringUtils.toDartPropertyName('Task Solution')}');
|
||||||
print('Task Spot -> ${StringUtils.toDartPropertyName('Task Spot')}');
|
print('Task Spot -> ${StringUtils.toDartPropertyName('Task Spot')}');
|
||||||
print(
|
print(
|
||||||
'Task Summarize -> ${StringUtils.toDartPropertyName('Task Summarize')}');
|
'Task Summarize -> ${StringUtils.toDartPropertyName('Task Summarize')}',);
|
||||||
print('Task Talk -> ${StringUtils.toDartPropertyName('Task Talk')}');
|
print('Task Talk -> ${StringUtils.toDartPropertyName('Task Talk')}');
|
||||||
print(
|
print(
|
||||||
'Task Teacher Behavior -> ${StringUtils.toDartPropertyName('Task Teacher Behavior')}');
|
'Task Teacher Behavior -> ${StringUtils.toDartPropertyName('Task Teacher Behavior')}',);
|
||||||
print(
|
print(
|
||||||
'Task Teacher Talk -> ${StringUtils.toDartPropertyName('Task Teacher Talk')}');
|
'Task Teacher Talk -> ${StringUtils.toDartPropertyName('Task Teacher Talk')}',);
|
||||||
|
|
||||||
print('\nTesting comment cleaning:');
|
print('\nTesting comment cleaning:');
|
||||||
print('部长新增工作任务指标(会删除所有管理的班级任务指标)-删除所有管理的学习官的通用任务指标');
|
print('部长新增工作任务指标(会删除所有管理的班级任务指标)-删除所有管理的学习官的通用任务指标');
|
||||||
print(
|
print(
|
||||||
'Cleaned: ${StringUtils.cleanDescription('部长新增工作任务指标(会删除所有管理的班级任务指标)-删除所有管理的学习官的通用任务指标')}');
|
'Cleaned: ${StringUtils.cleanDescription('部长新增工作任务指标(会删除所有管理的班级任务指标)-删除所有管理的学习官的通用任务指标')}',);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue