This commit is contained in:
Max 2025-07-13 07:01:51 +08:00
parent f2faffebfc
commit 5c9d2c8d36
25 changed files with 9868 additions and 0 deletions

82
bin/main.dart Normal file
View File

@ -0,0 +1,82 @@
#!/usr/bin/env dart
import 'dart:io';
import '../lib/swagger_cli_new.dart';
/// Swagger CLI
///
/// Swagger API
/// - Swagger/OpenAPI
/// - Dart
/// - API
/// - API
/// -
///
/// 使
/// dart run swagger_cli <command> [options]
///
///
/// - generate:
/// - help:
/// - version:
Future<void> main(List<String> arguments) async {
//
if (arguments.isEmpty) {
_showWelcome();
arguments = ['help'];
}
//
if (arguments.contains('--version') || arguments.contains('-v')) {
_showVersion();
return;
}
// 使CLI
final cli = SwaggerCLI();
final exitCode = await cli.run(arguments);
// 退
exit(exitCode);
}
///
void _showWelcome() {
print('');
print('🚀 欢迎使用 Swagger CLI 工具!');
print('');
print('这是一个强大的 Swagger API 代码生成工具,可以帮助您:');
print('');
print(' 📋 解析 Swagger/OpenAPI 文档');
print(' 🛠️ 生成 Dart 模型类');
print(' 📡 生成 API 端点常量');
print(' 📚 生成完整的 API 文档');
print(' 🔒 提供类型安全的代码生成');
print('');
print('使用 --help 查看详细帮助信息');
print('');
}
///
void _showVersion() {
print('');
print('🚀 Swagger CLI 工具 v2.0.0');
print('');
print('构建信息:');
print(' - Dart SDK: ${Platform.version}');
print(' - 平台: ${Platform.operatingSystem}');
print(' - 架构: ${Platform.version}');
print('');
print('特性:');
print(' ✨ 现代化的命令行界面');
print(' 🏗️ 模块化架构设计');
print(' 🚀 高性能代码生成');
print(' 🔍 智能类型验证');
print(' 📊 性能监控和分析');
print(' 💾 智能缓存机制');
print(' 📝 丰富的文档生成');
print('');
print('更多信息请访问: https://github.com/yourorg/swagger_cli');
print('');
}

8
build.yaml Normal file
View File

@ -0,0 +1,8 @@
targets:
$default:
sources:
- "lib/**"
builders:
json_serializable:
generate_for:
- "lib/**"

View File

@ -0,0 +1,356 @@
import '../core/exceptions.dart';
///
///
abstract class BaseCommand {
///
String get name;
///
String get description;
///
String get usage;
///
List<CommandOption> get options => [];
///
List<CommandArgument> get arguments => [];
///
Future<int> execute(List<String> args);
///
void showHelp() {
print('');
print('命令: $name');
print('描述: $description');
print('用法: $usage');
if (arguments.isNotEmpty) {
print('');
print('参数:');
for (final arg in arguments) {
final required = arg.required ? '(必填)' : '(可选)';
print(' ${arg.name} ${arg.description} $required');
}
}
if (options.isNotEmpty) {
print('');
print('选项:');
for (final option in options) {
final short = option.shortName != null ? '-${option.shortName}, ' : '';
final defaultValue = option.defaultValue != null
? ' (默认: ${option.defaultValue})'
: '';
print(' $short--${option.name} ${option.description}$defaultValue');
}
}
print('');
}
///
ParsedArguments parseArguments(List<String> args) {
final parser = ArgumentParser(this);
return parser.parse(args);
}
///
void validateArguments(ParsedArguments parsedArgs) {
//
for (final arg in arguments.where((a) => a.required)) {
if (!parsedArgs.hasArgument(arg.name)) {
throw CommandException('缺少必填参数: ${arg.name}');
}
}
//
for (final option in options.where((o) => o.required)) {
if (!parsedArgs.hasOption(option.name)) {
throw CommandException('缺少必填选项: --${option.name}');
}
}
}
///
Future<void> prepare(ParsedArguments parsedArgs) async {
//
}
///
Future<void> cleanup(ParsedArguments parsedArgs) async {
//
}
///
void handleError(dynamic error, StackTrace stackTrace) {
if (error is CommandException) {
print('❌ 错误: ${error.message}');
if (error.details != null) {
print('详细信息: ${error.details}');
}
} else if (error is SwaggerException) {
print('❌ Swagger错误: ${error.message}');
if (error.details != null) {
print('详细信息: ${error.details}');
}
} else {
print('❌ 未知错误: $error');
print('堆栈跟踪: $stackTrace');
}
}
///
void success(String message) {
print('$message');
}
///
void info(String message) {
print(' $message');
}
///
void warning(String message) {
print('⚠️ $message');
}
///
void progress(String message) {
print('🔄 $message');
}
}
///
class CommandOption {
final String name;
final String? shortName;
final String description;
final bool required;
final dynamic defaultValue;
final OptionType type;
const CommandOption({
required this.name,
this.shortName,
required this.description,
this.required = false,
this.defaultValue,
this.type = OptionType.flag,
});
}
///
class CommandArgument {
final String name;
final String description;
final bool required;
final dynamic defaultValue;
const CommandArgument({
required this.name,
required this.description,
this.required = true,
this.defaultValue,
});
}
///
enum OptionType { flag, string, int, double, list }
///
class ParsedArguments {
final Map<String, dynamic> _options = {};
final Map<String, dynamic> _arguments = {};
///
void setOption(String name, dynamic value) {
_options[name] = value;
}
///
void setArgument(String name, dynamic value) {
_arguments[name] = value;
}
///
T? getOption<T>(String name) {
return _options[name] as T?;
}
///
T? getArgument<T>(String name) {
return _arguments[name] as T?;
}
///
bool hasOption(String name) {
return _options.containsKey(name);
}
///
bool hasArgument(String name) {
return _arguments.containsKey(name);
}
///
Map<String, dynamic> get options => Map.unmodifiable(_options);
///
Map<String, dynamic> get arguments => Map.unmodifiable(_arguments);
}
///
class ArgumentParser {
final BaseCommand command;
ArgumentParser(this.command);
///
ParsedArguments parse(List<String> args) {
final result = ParsedArguments();
final argQueue = List<String>.from(args);
int argumentIndex = 0;
while (argQueue.isNotEmpty) {
final current = argQueue.removeAt(0);
if (current.startsWith('--')) {
//
_parseLongOption(current, argQueue, result);
} else if (current.startsWith('-') && current.length > 1) {
//
_parseShortOption(current, argQueue, result);
} else {
//
if (argumentIndex < command.arguments.length) {
final arg = command.arguments[argumentIndex];
result.setArgument(arg.name, current);
argumentIndex++;
} else {
throw CommandException('未知参数: $current');
}
}
}
//
_setDefaultValues(result);
return result;
}
///
void _parseLongOption(
String current,
List<String> argQueue,
ParsedArguments result,
) {
String optionName;
String? optionValue;
if (current.contains('=')) {
final parts = current.split('=');
optionName = parts[0].substring(2);
optionValue = parts.sublist(1).join('=');
} else {
optionName = current.substring(2);
}
final option = command.options.firstWhere(
(o) => o.name == optionName,
orElse: () => throw CommandException('未知选项: --$optionName'),
);
if (option.type == OptionType.flag) {
result.setOption(optionName, true);
} else {
if (optionValue == null) {
if (argQueue.isEmpty) {
throw CommandException('选项 --$optionName 需要一个值');
}
optionValue = argQueue.removeAt(0);
}
final convertedValue = _convertValue(optionValue, option.type);
result.setOption(optionName, convertedValue);
}
}
///
void _parseShortOption(
String current,
List<String> argQueue,
ParsedArguments result,
) {
final shortName = current.substring(1);
final option = command.options.firstWhere(
(o) => o.shortName == shortName,
orElse: () => throw CommandException('未知选项: -$shortName'),
);
if (option.type == OptionType.flag) {
result.setOption(option.name, true);
} else {
if (argQueue.isEmpty) {
throw CommandException('选项 -$shortName 需要一个值');
}
final optionValue = argQueue.removeAt(0);
final convertedValue = _convertValue(optionValue, option.type);
result.setOption(option.name, convertedValue);
}
}
///
dynamic _convertValue(String value, OptionType type) {
switch (type) {
case OptionType.string:
return value;
case OptionType.int:
return int.tryParse(value) ??
(throw CommandException('无效的整数值: $value'));
case OptionType.double:
return double.tryParse(value) ??
(throw CommandException('无效的浮点数值: $value'));
case OptionType.list:
return value.split(',').map((s) => s.trim()).toList();
case OptionType.flag:
return true;
}
}
///
void _setDefaultValues(ParsedArguments result) {
//
for (final option in command.options) {
if (!result.hasOption(option.name) && option.defaultValue != null) {
result.setOption(option.name, option.defaultValue);
}
}
//
for (final argument in command.arguments) {
if (!result.hasArgument(argument.name) && argument.defaultValue != null) {
result.setArgument(argument.name, argument.defaultValue);
}
}
}
}
///
class CommandException implements Exception {
final String message;
final String? details;
const CommandException(this.message, {this.details});
@override
String toString() {
return 'CommandException: $message${details != null ? ' ($details)' : ''}';
}
}

View File

@ -0,0 +1,312 @@
import '../core/models.dart';
import '../generators/documentation_generator.dart';
import '../generators/endpoint_code_generator.dart';
import '../generators/model_code_generator.dart';
import '../generators/retrofit_api_generator.dart';
import '../parsers/swagger_data_parser.dart';
import '../utils/file_utils.dart';
import 'base_command.dart';
/// Generate命令
///
class GenerateCommand extends BaseCommand {
@override
String get name => 'generate';
@override
String get description => '生成API代码文件模型、端点、文档等';
@override
String get usage => 'dart swagger_cli.dart generate [options]';
@override
List<CommandOption> get options => [
const CommandOption(
name: 'endpoints',
shortName: 'e',
description: '生成API端点常量',
type: OptionType.flag,
),
const CommandOption(
name: 'models',
shortName: 'm',
description: '生成数据模型',
type: OptionType.flag,
),
const CommandOption(
name: 'docs',
shortName: 'd',
description: '生成API文档',
type: OptionType.flag,
),
const CommandOption(
name: 'api',
shortName: 'r',
description: '生成Retrofit风格API接口',
type: OptionType.flag,
),
const CommandOption(
name: 'split-by-tags',
shortName: 't',
description: '按tags分组生成多个API文件',
type: OptionType.flag,
),
const CommandOption(
name: 'all',
shortName: 'a',
description: '生成所有文件(默认)',
type: OptionType.flag,
),
const CommandOption(
name: 'simple',
shortName: 's',
description: '使用简洁版模型生成',
type: OptionType.flag,
),
const CommandOption(
name: 'output-dir',
shortName: 'o',
description: '输出目录',
type: OptionType.string,
defaultValue: 'generator',
),
];
@override
Future<int> execute(List<String> args) async {
try {
final parsedArgs = parseArguments(args);
validateArguments(parsedArgs);
await prepare(parsedArgs);
// Swagger文档
progress('正在解析Swagger文档...');
final parser = SwaggerDataParser();
final document = await parser.fetchAndParseSwaggerDocument();
//
final options = _parseGenerateOptions(parsedArgs);
final outputDir =
parsedArgs.getOption<String>('output-dir') ?? 'generator';
final fullOutputDir = FileUtils.getProjectRootGeneratorDir();
progress('输出目录: $fullOutputDir');
//
await FileUtils.ensureDirectoryExists(fullOutputDir);
int generatedFiles = 0;
//
if (options.generateEndpoints) {
progress('正在生成API端点常量...');
final generator = EndpointCodeGenerator(document);
final code = generator.generate();
final filePath = '$fullOutputDir/api_paths.dart';
await FileUtils.writeFile(filePath, code);
success('API端点常量已保存到: $filePath');
generatedFiles++;
}
//
if (options.generateModels) {
progress('正在生成数据模型...');
final generator = ModelCodeGenerator(
document,
useSimpleModels: options.useSimpleModels,
);
final modelsDir = '$fullOutputDir/api_models';
await FileUtils.ensureDirectoryExists(modelsDir);
final modelFiles = generator.generateSeparateModelFiles();
for (final entry in modelFiles.entries) {
final filePath = '$modelsDir/${entry.key}';
await FileUtils.writeFile(filePath, entry.value);
success('模型文件已保存到: $filePath');
generatedFiles++;
}
}
// Retrofit API
if (options.generateApi) {
if (options.splitByTags) {
progress('正在按tags分组生成Retrofit风格API接口...');
final generator = RetrofitApiGenerator(
document,
className: 'ApiClient',
useRetrofit: true,
useDio: true,
splitByTags: true,
);
//
generator.ensureParameterEntitiesGenerated();
// tags API
final tagApiFiles = generator.generateApiFilesByTags();
final apiDir = '$fullOutputDir/api';
await FileUtils.ensureDirectoryExists(apiDir);
for (final entry in tagApiFiles.entries) {
final fileName = entry.key;
final code = entry.value;
final filePath = '$apiDir/$fileName';
await FileUtils.writeFile(filePath, code);
success('API接口文件已保存到: $filePath');
generatedFiles++;
}
// API
final mainCode = generator.generateMainApiFile();
final mainFilePath = '$apiDir/api_client.dart';
await FileUtils.writeFile(mainFilePath, mainCode);
success('主API接口文件已保存到: $mainFilePath');
generatedFiles++;
//
final parameterEntityFiles = generator.generateParameterEntityFiles();
if (parameterEntityFiles.isNotEmpty) {
final modelsDir = '$fullOutputDir/api_models';
await FileUtils.ensureDirectoryExists(modelsDir);
for (final entry in parameterEntityFiles.entries) {
final filePath = '$modelsDir/${entry.key}';
await FileUtils.writeFile(filePath, entry.value);
success('参数实体类文件已保存到: $filePath');
generatedFiles++;
}
}
} else {
progress('正在生成Retrofit风格API接口...');
final generator = RetrofitApiGenerator(
document,
className: 'ApiClient',
useRetrofit: true,
useDio: true,
splitByTags: false,
);
//
generator.ensureParameterEntitiesGenerated();
final code = generator.generate();
final apiDir = '$fullOutputDir/api';
await FileUtils.ensureDirectoryExists(apiDir);
final filePath = '$apiDir/api.dart';
await FileUtils.writeFile(filePath, code);
success('Retrofit API接口已保存到: $filePath');
generatedFiles++;
//
final parameterEntityFiles = generator.generateParameterEntityFiles();
if (parameterEntityFiles.isNotEmpty) {
final modelsDir = '$fullOutputDir/api_models';
await FileUtils.ensureDirectoryExists(modelsDir);
for (final entry in parameterEntityFiles.entries) {
final filePath = '$modelsDir/${entry.key}';
await FileUtils.writeFile(filePath, entry.value);
success('参数实体类文件已保存到: $filePath');
generatedFiles++;
}
}
}
}
//
if (options.generateDocs) {
progress('正在生成API文档...');
final generator = DocumentationGenerator(document);
final code = generator.generate();
final filePath = '$fullOutputDir/api_documentation.md';
await FileUtils.writeFile(filePath, code);
success('API文档已保存到: $filePath');
generatedFiles++;
}
//
_generateSummary(document, fullOutputDir);
success('代码生成完成!共生成 $generatedFiles 个文件');
return 0;
} catch (e) {
print('❌ 生成失败: $e');
return 1;
}
}
///
GenerateOptions _parseGenerateOptions(ParsedArguments args) {
final hasAnyFlag = args.hasOption('endpoints') ||
args.hasOption('models') ||
args.hasOption('docs') ||
args.hasOption('api');
return GenerateOptions(
generateEndpoints: hasAnyFlag
? (args.getOption<bool>('endpoints') ?? false)
: (args.getOption<bool>('all') ?? true),
generateModels: hasAnyFlag
? (args.getOption<bool>('models') ?? false)
: (args.getOption<bool>('all') ?? true),
generateDocs: hasAnyFlag
? (args.getOption<bool>('docs') ?? false)
: (args.getOption<bool>('all') ?? true),
generateApi: hasAnyFlag
? (args.getOption<bool>('api') ?? false)
: (args.getOption<bool>('all') ?? true),
useSimpleModels: args.getOption<bool>('simple') ?? false,
splitByTags: args.getOption<bool>('split-by-tags') ?? false,
);
}
///
void _generateSummary(SwaggerDocument document, String outputDir) {
final summary = StringBuffer();
summary.writeln('# 代码生成摘要');
summary.writeln('');
summary.writeln('**API标题**: ${document.title}');
summary.writeln('**API版本**: ${document.version}');
summary.writeln('**生成时间**: ${DateTime.now().toIso8601String()}');
summary.writeln('');
summary.writeln('## 统计信息');
summary.writeln('- 控制器数量: ${document.controllers.length}');
summary.writeln('- API路径数量: ${document.paths.length}');
summary.writeln('- 数据模型数量: ${document.models.length}');
summary.writeln('');
summary.writeln('## 控制器列表');
document.controllers.forEach((name, controller) {
summary.writeln(
'- **$name**: ${controller.description} (${controller.paths.length} 个路径)');
});
FileUtils.writeFile('$outputDir/SUMMARY.md', summary.toString());
}
}
///
class GenerateOptions {
final bool generateEndpoints;
final bool generateModels;
final bool generateDocs;
final bool generateApi;
final bool useSimpleModels;
final bool splitByTags;
const GenerateOptions({
required this.generateEndpoints,
required this.generateModels,
required this.generateDocs,
required this.generateApi,
required this.useSimpleModels,
required this.splitByTags,
});
}

62
lib/core/config.dart Normal file
View File

@ -0,0 +1,62 @@
/// Swagger配置管理
/// Swagger相关的配置项
class SwaggerConfig {
/// Swagger JSON文档的URL
static const String swaggerJsonUrl =
'https://quanxue-test-api.w.23544.com:8843/swagger/v1/swagger.json';
/// API URL
static const String baseUrl = 'https://quanxue-test-api.w.23544.com:8843';
/// API版本
static const String apiVersion = '/api/v1';
///
static const String defaultGeneratorDir = 'generator';
/// API文件目录
static const String defaultApiDir = 'api';
///
static const String defaultModelsDir = 'api_models';
///
static const String defaultEndpointsFile = 'generated_api_paths.dart';
///
static const String defaultDocumentationFile =
'generated_api_documentation.md';
/// HTTP请求头配置
static const Map<String, String> httpHeaders = {
'Accept': 'application/json',
'User-Agent': 'Flutter-SwaggerParser/1.0',
};
///
static const Map<String, dynamic> defaultGenerateOptions = {
'generateEndpoints': true,
'generateModels': true,
'generateDocs': true,
'useSimpleModels': false,
'separateModelFiles': true,
};
/// API基础URL
static String get fullApiUrl => '$baseUrl$apiVersion';
///
/// 使Swagger文档中的描述使
static String getControllerDescription(
String controllerName, {
String? swaggerDescription,
}) {
// 1. 使Swagger文档中的描述
if (swaggerDescription != null && swaggerDescription.isNotEmpty) {
return swaggerDescription;
}
// 2. 使
return controllerName;
}
}

608
lib/core/exceptions.dart Normal file
View File

@ -0,0 +1,608 @@
import 'dart:io';
/// Swagger CLI
abstract class SwaggerException implements Exception {
final String message;
final String? details;
final DateTime timestamp;
SwaggerException(this.message, {this.details}) : timestamp = DateTime.now();
@override
String toString() {
if (details != null) {
return '$runtimeType: $message\n详细信息: $details';
}
return '$runtimeType: $message';
}
}
/// Swagger解析异常
class SwaggerParseException extends SwaggerException {
final String? url;
final int? statusCode;
final String? operation;
SwaggerParseException(
super.message, {
super.details,
this.url,
this.statusCode,
this.operation,
});
@override
String toString() {
final buffer = StringBuffer();
buffer.writeln('SwaggerParseException: $message');
if (url != null) {
buffer.writeln('URL: $url');
}
if (statusCode != null) {
buffer.writeln('状态码: $statusCode');
}
if (operation != null) {
buffer.writeln('操作: $operation');
}
if (details != null) {
buffer.writeln('详细信息: $details');
}
return buffer.toString().trim();
}
}
///
class CodeGenerationException extends SwaggerException {
final String? generatorType;
final String? modelName;
final String? phase;
CodeGenerationException(
super.message, {
super.details,
this.generatorType,
this.modelName,
this.phase,
});
@override
String toString() {
final buffer = StringBuffer();
buffer.writeln('CodeGenerationException: $message');
if (generatorType != null) {
buffer.writeln('生成器类型: $generatorType');
}
if (modelName != null) {
buffer.writeln('模型名称: $modelName');
}
if (phase != null) {
buffer.writeln('生成阶段: $phase');
}
if (details != null) {
buffer.writeln('详细信息: $details');
}
return buffer.toString().trim();
}
}
///
class FileOperationException extends SwaggerException {
final String? filePath;
final String? operation;
final int? errorCode;
FileOperationException(
super.message, {
super.details,
this.filePath,
this.operation,
this.errorCode,
});
@override
String toString() {
final buffer = StringBuffer();
buffer.writeln('FileOperationException: $message');
if (filePath != null) {
buffer.writeln('文件路径: $filePath');
}
if (operation != null) {
buffer.writeln('操作: $operation');
}
if (errorCode != null) {
buffer.writeln('错误代码: $errorCode');
}
if (details != null) {
buffer.writeln('详细信息: $details');
}
return buffer.toString().trim();
}
}
///
class CommandException extends SwaggerException {
final String? commandName;
final List<String>? arguments;
final int? exitCode;
CommandException(
super.message, {
super.details,
this.commandName,
this.arguments,
this.exitCode,
});
@override
String toString() {
final buffer = StringBuffer();
buffer.writeln('CommandException: $message');
if (commandName != null) {
buffer.writeln('命令: $commandName');
}
if (arguments != null && arguments!.isNotEmpty) {
buffer.writeln('参数: ${arguments!.join(' ')}');
}
if (exitCode != null) {
buffer.writeln('退出代码: $exitCode');
}
if (details != null) {
buffer.writeln('详细信息: $details');
}
return buffer.toString().trim();
}
}
///
class ValidationException extends SwaggerException {
final String? field;
final dynamic value;
final String? rule;
ValidationException(
super.message, {
super.details,
this.field,
this.value,
this.rule,
});
@override
String toString() {
final buffer = StringBuffer();
buffer.writeln('ValidationException: $message');
if (field != null) {
buffer.writeln('字段: $field');
}
if (value != null) {
buffer.writeln('值: $value');
}
if (rule != null) {
buffer.writeln('验证规则: $rule');
}
if (details != null) {
buffer.writeln('详细信息: $details');
}
return buffer.toString().trim();
}
}
///
class ConfigurationException extends SwaggerException {
final String? configKey;
final dynamic configValue;
final String? source;
ConfigurationException(
super.message, {
super.details,
this.configKey,
this.configValue,
this.source,
});
@override
String toString() {
final buffer = StringBuffer();
buffer.writeln('ConfigurationException: $message');
if (configKey != null) {
buffer.writeln('配置键: $configKey');
}
if (configValue != null) {
buffer.writeln('配置值: $configValue');
}
if (source != null) {
buffer.writeln('来源: $source');
}
if (details != null) {
buffer.writeln('详细信息: $details');
}
return buffer.toString().trim();
}
}
///
class NetworkException extends SwaggerException {
final String? url;
final int? statusCode;
final String? method;
final Duration? timeout;
NetworkException(
super.message, {
super.details,
this.url,
this.statusCode,
this.method,
this.timeout,
});
@override
String toString() {
final buffer = StringBuffer();
buffer.writeln('NetworkException: $message');
if (url != null) {
buffer.writeln('URL: $url');
}
if (method != null) {
buffer.writeln('方法: $method');
}
if (statusCode != null) {
buffer.writeln('状态码: $statusCode');
}
if (timeout != null) {
buffer.writeln('超时: ${timeout!.inSeconds}');
}
if (details != null) {
buffer.writeln('详细信息: $details');
}
return buffer.toString().trim();
}
}
///
class CacheException extends SwaggerException {
final String? cacheKey;
final String? operation;
final String? cacheType;
CacheException(
super.message, {
super.details,
this.cacheKey,
this.operation,
this.cacheType,
});
@override
String toString() {
final buffer = StringBuffer();
buffer.writeln('CacheException: $message');
if (cacheKey != null) {
buffer.writeln('缓存键: $cacheKey');
}
if (operation != null) {
buffer.writeln('操作: $operation');
}
if (cacheType != null) {
buffer.writeln('缓存类型: $cacheType');
}
if (details != null) {
buffer.writeln('详细信息: $details');
}
return buffer.toString().trim();
}
}
///
class PerformanceException extends SwaggerException {
final String? operation;
final Duration? duration;
final Duration? threshold;
PerformanceException(
super.message, {
super.details,
this.operation,
this.duration,
this.threshold,
});
@override
String toString() {
final buffer = StringBuffer();
buffer.writeln('PerformanceException: $message');
if (operation != null) {
buffer.writeln('操作: $operation');
}
if (duration != null) {
buffer.writeln('耗时: ${duration!.inMilliseconds}ms');
}
if (threshold != null) {
buffer.writeln('阈值: ${threshold!.inMilliseconds}ms');
}
if (details != null) {
buffer.writeln('详细信息: $details');
}
return buffer.toString().trim();
}
}
///
class TypeException extends SwaggerException {
final String? propertyName;
final String? expectedType;
final String? actualType;
final dynamic value;
TypeException(
super.message, {
super.details,
this.propertyName,
this.expectedType,
this.actualType,
this.value,
});
@override
String toString() {
final buffer = StringBuffer();
buffer.writeln('TypeException: $message');
if (propertyName != null) {
buffer.writeln('属性名: $propertyName');
}
if (expectedType != null) {
buffer.writeln('期望类型: $expectedType');
}
if (actualType != null) {
buffer.writeln('实际类型: $actualType');
}
if (value != null) {
buffer.writeln('值: $value');
}
if (details != null) {
buffer.writeln('详细信息: $details');
}
return buffer.toString().trim();
}
}
///
class ExceptionHandler {
static final Map<Type, void Function(SwaggerException)> _handlers = {};
///
static void register<T extends SwaggerException>(
void Function(T exception) handler,
) {
_handlers[T] = (exception) => handler(exception as T);
}
///
static void handle(SwaggerException exception) {
final handler = _handlers[exception.runtimeType];
if (handler != null) {
handler(exception);
} else {
//
_defaultHandler(exception);
}
}
///
static void _defaultHandler(SwaggerException exception) {
print('🚨 异常: ${exception.toString()}');
print('时间: ${exception.timestamp.toIso8601String()}');
print('');
}
///
static Future<void> logException(
SwaggerException exception, {
String? logFilePath,
}) async {
try {
final logFile = logFilePath != null
? File(logFilePath)
: File('swagger_cli_errors.log');
final logEntry = [
'[${'=' * 50}]',
'时间: ${exception.timestamp.toIso8601String()}',
'类型: ${exception.runtimeType}',
'消息: ${exception.message}',
if (exception.details != null) '详细信息: ${exception.details}',
'堆栈跟踪: ${StackTrace.current}',
'',
].join('\n');
await logFile.writeAsString(logEntry, mode: FileMode.append);
} catch (e) {
print('记录异常到文件失败: $e');
}
}
///
static void clear() {
_handlers.clear();
}
}
///
class ExceptionFactory {
///
static SwaggerParseException createParseException(
String message, {
String? url,
int? statusCode,
String? operation,
dynamic cause,
}) {
return SwaggerParseException(
message,
details: cause?.toString(),
url: url,
statusCode: statusCode,
operation: operation,
);
}
///
static CodeGenerationException createCodeGenerationException(
String message, {
String? generatorType,
String? modelName,
String? phase,
dynamic cause,
}) {
return CodeGenerationException(
message,
details: cause?.toString(),
generatorType: generatorType,
modelName: modelName,
phase: phase,
);
}
///
static FileOperationException createFileOperationException(
String message, {
String? filePath,
String? operation,
int? errorCode,
dynamic cause,
}) {
return FileOperationException(
message,
details: cause?.toString(),
filePath: filePath,
operation: operation,
errorCode: errorCode,
);
}
///
static ValidationException createValidationException(
String message, {
String? field,
dynamic value,
String? rule,
dynamic cause,
}) {
return ValidationException(
message,
details: cause?.toString(),
field: field,
value: value,
rule: rule,
);
}
///
static NetworkException createNetworkException(
String message, {
String? url,
int? statusCode,
String? method,
Duration? timeout,
dynamic cause,
}) {
return NetworkException(
message,
details: cause?.toString(),
url: url,
statusCode: statusCode,
method: method,
timeout: timeout,
);
}
///
static SwaggerException fromStandardException(
Exception exception, {
String? context,
}) {
if (exception is FileSystemException) {
return FileOperationException(
'文件系统错误',
details: exception.message,
filePath: exception.path,
operation: context,
);
} else if (exception is SocketException) {
return NetworkException(
'网络连接错误',
details: exception.message,
url: context,
);
} else if (exception is FormatException) {
return SwaggerParseException(
'格式错误',
details: exception.message,
operation: context,
);
} else {
return GeneralSwaggerException(
'未知错误',
details: exception.toString(),
);
}
}
}
/// Swagger异常使
class GeneralSwaggerException extends SwaggerException {
GeneralSwaggerException(super.message, {super.details});
}

502
lib/core/models.dart Normal file
View File

@ -0,0 +1,502 @@
/// Swagger数据模型定义
///
library;
/// HTTP方法枚举
/// RESTful API
enum HttpMethod {
/// GET
get('GET'),
/// POST
post('POST'),
/// PUT
put('PUT'),
/// DELETE
delete('DELETE'),
/// PATCH
patch('PATCH'),
/// HEAD
head('HEAD'),
/// OPTIONS
options('OPTIONS');
///
const HttpMethod(this.value);
final String value;
/// HttpMethod
static HttpMethod fromString(String value) {
return HttpMethod.values.firstWhere(
(method) => method.value == value.toUpperCase(),
orElse: () => HttpMethod.get,
);
}
}
///
/// API
enum PropertyType {
///
string('string'),
///
integer('integer'),
///
number('number'),
///
boolean('boolean'),
///
array('array'),
///
object('object'),
///
file('file'),
///
date('date'),
///
dateTime('date-time'),
///
reference('reference'),
///
enumType('enum');
///
const PropertyType(this.value);
final String value;
/// PropertyType
static PropertyType fromString(String value) {
return PropertyType.values.firstWhere(
(type) => type.value == value.toLowerCase(),
orElse: () => PropertyType.string,
);
}
}
///
/// API
enum ParameterLocation {
///
query('query'),
///
path('path'),
///
header('header'),
///
body('body'),
///
form('formData'),
/// Cookie
cookie('cookie');
///
const ParameterLocation(this.value);
final String value;
/// ParameterLocation
static ParameterLocation fromString(String value) {
return ParameterLocation.values.firstWhere(
(location) => location.value == value.toLowerCase(),
orElse: () => ParameterLocation.query,
);
}
}
/// Swagger文档信息
/// API
class SwaggerDocument {
///
final String title;
///
final String version;
///
final String description;
///
final String host;
///
final String basePath;
///
final List<String> schemes;
///
final List<String> consumes;
///
final List<String> produces;
///
final Map<String, ApiPath> paths;
///
final Map<String, ApiModel> models;
///
final Map<String, ApiController> controllers;
///
const SwaggerDocument({
required this.title,
required this.version,
required this.description,
required this.host,
required this.basePath,
required this.schemes,
required this.consumes,
required this.produces,
required this.paths,
required this.models,
required this.controllers,
});
/// JSON创建SwaggerDocument
factory SwaggerDocument.fromJson(Map<String, dynamic> json) {
final info = json['info'] as Map<String, dynamic>? ?? {};
return SwaggerDocument(
title: info['title'] as String? ?? 'API',
version: info['version'] as String? ?? '1.0.0',
description: info['description'] as String? ?? '',
host: json['host'] as String? ?? '',
basePath: json['basePath'] as String? ?? '',
schemes: List<String>.from(json['schemes'] ?? ['https']),
consumes: List<String>.from(json['consumes'] ?? ['application/json']),
produces: List<String>.from(json['produces'] ?? ['application/json']),
paths: {},
models: {},
controllers: {},
);
}
}
/// API路径信息
class ApiPath {
final String path;
final HttpMethod method;
final String summary;
final String description;
final String operationId;
final List<String> tags;
final List<ApiParameter> parameters;
final Map<String, ApiResponse> responses;
final ApiRequestBody? requestBody;
final bool deprecated;
const ApiPath({
required this.path,
required this.method,
required this.summary,
required this.description,
required this.operationId,
required this.tags,
required this.parameters,
required this.responses,
this.requestBody,
this.deprecated = false,
});
/// JSON创建ApiPath
factory ApiPath.fromJson(
String path,
String method,
Map<String, dynamic> json,
) {
return ApiPath(
path: path,
method: HttpMethod.fromString(method),
summary: json['summary'] as String? ?? '',
description: json['description'] as String? ?? '',
operationId: json['operationId'] as String? ?? '',
tags: List<String>.from(json['tags'] ?? []),
parameters: (json['parameters'] as List?)
?.map((p) => ApiParameter.fromJson(p as Map<String, dynamic>))
.toList() ??
[],
responses: (json['responses'] as Map<String, dynamic>?)?.map(
(code, response) => MapEntry(
code,
ApiResponse.fromJson(code, response as Map<String, dynamic>),
),
) ??
{},
requestBody: json['requestBody'] != null
? ApiRequestBody.fromJson(json['requestBody'] as Map<String, dynamic>)
: null,
deprecated: json['deprecated'] as bool? ?? false,
);
}
}
/// API参数信息
class ApiParameter {
final String name;
final ParameterLocation location;
final bool required;
final PropertyType type;
final String description;
final String? format;
final dynamic example;
final dynamic defaultValue;
const ApiParameter({
required this.name,
required this.location,
required this.required,
required this.type,
required this.description,
this.format,
this.example,
this.defaultValue,
});
/// JSON创建ApiParameter
factory ApiParameter.fromJson(Map<String, dynamic> json) {
final schema = json['schema'] as Map<String, dynamic>?;
final type =
schema?['type'] as String? ?? json['type'] as String? ?? 'string';
return ApiParameter(
name: json['name'] as String? ?? '',
location: ParameterLocation.fromString(json['in'] as String? ?? 'query'),
required: json['required'] as bool? ?? false,
type: PropertyType.fromString(type),
description: json['description'] as String? ?? '',
format: schema?['format'] as String? ?? json['format'] as String?,
example: json['example'],
defaultValue: json['default'],
);
}
}
/// API响应信息
class ApiResponse {
final String code;
final String description;
final Map<String, dynamic>? schema;
final Map<String, dynamic>? content;
const ApiResponse({
required this.code,
required this.description,
this.schema,
this.content,
});
/// JSON创建ApiResponse
factory ApiResponse.fromJson(String code, Map<String, dynamic> json) {
return ApiResponse(
code: code,
description: json['description'] as String? ?? '',
schema: json['schema'] as Map<String, dynamic>?,
content: json['content'] as Map<String, dynamic>?,
);
}
}
/// API请求体信息
class ApiRequestBody {
final String description;
final bool required;
final Map<String, dynamic>? content;
const ApiRequestBody({
required this.description,
required this.required,
this.content,
});
/// JSON创建ApiRequestBody
factory ApiRequestBody.fromJson(Map<String, dynamic> json) {
return ApiRequestBody(
description: json['description'] as String? ?? '',
required: json['required'] as bool? ?? false,
content: json['content'] as Map<String, dynamic>?,
);
}
}
/// API模型信息
class ApiModel {
final String name;
final String description;
final Map<String, ApiProperty> properties;
final List<String> required;
final bool isEnum;
final List<dynamic> enumValues;
final PropertyType? enumType;
const ApiModel({
required this.name,
required this.description,
required this.properties,
required this.required,
this.isEnum = false,
this.enumValues = const [],
this.enumType,
});
/// JSON创建ApiModel
factory ApiModel.fromJson(String name, Map<String, dynamic> json) {
final isEnum = json['enum'] != null;
final enumValues = isEnum ? List<dynamic>.from(json['enum']) : <dynamic>[];
final properties = json['properties'] as Map<String, dynamic>? ?? {};
List<String> required;
if (json.containsKey('required')) {
required = List<String>.from(json['required']);
} else {
// required nullable != true required
required = properties.entries
.where((e) => !(e.value['nullable'] as bool? ?? false))
.map((e) => e.key)
.toList();
}
return ApiModel(
name: name,
description: json['description'] as String? ?? '',
required: required,
isEnum: isEnum,
enumValues: enumValues,
enumType: isEnum
? PropertyType.fromString(json['type'] as String? ?? 'string')
: null,
properties: properties.map(
(propName, propData) => MapEntry(
propName,
ApiProperty.fromJson(
propName,
propData as Map<String, dynamic>,
required,
),
),
),
);
}
}
/// API属性信息
class ApiProperty {
final String name;
final PropertyType type;
final String? format;
final String description;
final bool required;
final bool nullable;
final dynamic example;
final dynamic defaultValue;
final String? reference;
final ApiModel? items; //
const ApiProperty({
required this.name,
required this.type,
this.format,
required this.description,
required this.required,
this.nullable = false,
this.example,
this.defaultValue,
this.reference,
this.items,
});
/// JSON创建ApiProperty
factory ApiProperty.fromJson(
String name,
Map<String, dynamic> json,
List<String> requiredFields,
) {
final type = PropertyType.fromString(json['type'] as String? ?? 'string');
String? reference;
ApiModel? items;
//
if (json['\$ref'] != null) {
final ref = json['\$ref'] as String;
reference = ref.split('/').last;
}
// items
if (type == PropertyType.array && json['items'] != null) {
final itemsJson = json['items'] as Map<String, dynamic>;
// items
if (itemsJson['\$ref'] != null) {
final itemRef = itemsJson['\$ref'] as String;
final itemRefName = itemRef.split('/').last;
items = ApiModel(
name: itemRefName,
description: '',
properties: {},
required: [],
isEnum: false,
);
} else {
// items
final itemType =
PropertyType.fromString(itemsJson['type'] as String? ?? 'string');
items = ApiModel(
name: itemType.value,
description: '',
properties: {},
required: [],
isEnum: false,
);
}
}
return ApiProperty(
name: name,
type: reference != null ? PropertyType.reference : type,
format: json['format'] as String?,
description: json['description'] as String? ?? '',
required: requiredFields.contains(name),
nullable: json['nullable'] as bool? ?? false,
example: json['example'],
defaultValue: json['default'],
reference: reference,
items: items,
);
}
}
/// API控制器信息
class ApiController {
final String name;
final String description;
final List<ApiPath> paths;
const ApiController({
required this.name,
required this.description,
required this.paths,
});
/// ApiController
factory ApiController.fromPaths(String name, List<ApiPath> paths) {
return ApiController(name: name, description: name, paths: paths);
}
}

View File

@ -0,0 +1,361 @@
import '../core/config.dart';
import '../core/exceptions.dart';
import '../core/models.dart';
import '../utils/string_utils.dart';
///
///
abstract class BaseGenerator {
///
String get generatorType;
///
String generate();
///
String generateFileHeader(String description) {
return StringUtils.generateFileHeader(
description,
SwaggerConfig.swaggerJsonUrl,
);
}
///
String generateTypeCheckedCode(String code) {
//
try {
if (code.trim().isEmpty) {
throw CodeGenerationException(
'生成的代码不能为空',
generatorType: generatorType,
);
}
return code;
} catch (e) {
throw CodeGenerationException(
'代码生成失败',
details: e.toString(),
generatorType: generatorType,
);
}
}
/// Dart语法规范
bool validateDartCode(String code) {
//
final validationRules = [
//
(String code) => _countMatches(code, '{') == _countMatches(code, '}'),
//
(String code) => _countMatches(code, '(') == _countMatches(code, ')'),
//
(String code) => _countMatches(code, '[') == _countMatches(code, ']'),
//
(String code) =>
code.contains('class ') ||
code.contains('enum ') ||
code.contains('const '),
];
for (final rule in validationRules) {
if (!rule(code)) {
return false;
}
}
return true;
}
///
int _countMatches(String text, String pattern) {
return pattern.allMatches(text).length;
}
}
///
abstract class ModelGenerator extends BaseGenerator {
final SwaggerDocument document;
final bool useSimpleModels;
ModelGenerator(this.document, {this.useSimpleModels = false});
@override
String get generatorType => 'ModelGenerator';
///
String generateModelCode(ApiModel model);
///
String generateEnumCode(ApiModel model) {
if (!model.isEnum) {
throw CodeGenerationException('模型不是枚举类型', generatorType: generatorType);
}
final className = StringUtils.generateClassName(model.name);
final enumType = model.enumType?.value ?? 'string';
final buffer = StringBuffer();
//
buffer.writeln(generateFileHeader('${model.name} 枚举定义'));
buffer.writeln('');
//
if (model.description.isNotEmpty) {
buffer.writeln(StringUtils.generateComment(model.description));
}
buffer.writeln('enum $className {');
//
for (int i = 0; i < model.enumValues.length; i++) {
final value = model.enumValues[i];
final enumName = StringUtils.generateEnumValueName(value, i);
if (enumType == 'integer' || enumType == 'number') {
buffer.writeln(' $enumName($value),');
} else {
buffer.writeln(' $enumName(\'$value\'),');
}
}
//
final content = buffer.toString().trimRight();
buffer.clear();
buffer.writeln(content.substring(0, content.lastIndexOf(',')));
buffer.writeln(';');
buffer.writeln('');
//
buffer.writeln(' const $className(this.value);');
buffer.writeln(
' final ${enumType == 'integer' || enumType == 'number' ? 'int' : 'String'} value;',
);
buffer.writeln('');
// fromValue
buffer.writeln(' static $className fromValue(dynamic value) {');
buffer.writeln(' for (final enumValue in $className.values) {');
buffer.writeln(' if (enumValue.value == value) {');
buffer.writeln(' return enumValue;');
buffer.writeln(' }');
buffer.writeln(' }');
buffer.writeln(' throw ArgumentError(\'Unknown enum value: \$value\');');
buffer.writeln(' }');
buffer.writeln('');
// fromJson
buffer.writeln(' factory $className.fromJson(dynamic json) {');
buffer.writeln(' return fromValue(json);');
buffer.writeln(' }');
buffer.writeln('');
// toJson
buffer.writeln(' dynamic toJson() => value;');
buffer.writeln('');
buffer.writeln('}');
return generateTypeCheckedCode(buffer.toString());
}
// StringUtils.generateEnumValueName
///
Set<String> getImportedTypes(ApiModel model) {
final importedTypes = <String>{};
model.properties.forEach((_, property) {
if (property.type == PropertyType.reference &&
property.reference != null) {
importedTypes.add(property.reference!);
} else if (property.type == PropertyType.array) {
//
if (property.items != null) {
final itemType = _getItemType(property.items!);
//
if (itemType != 'String' &&
itemType != 'int' &&
itemType != 'double' &&
itemType != 'bool' &&
itemType != 'dynamic') {
importedTypes.add(property.items!.name);
}
}
}
});
return importedTypes;
}
/// Dart类型
String getDartPropertyType(ApiProperty property) {
switch (property.type) {
case PropertyType.string:
switch (property.format) {
case 'date':
case 'date-time':
return 'DateTime';
default:
return 'String';
}
case PropertyType.integer:
return 'int';
case PropertyType.number:
return 'double';
case PropertyType.boolean:
return 'bool';
case PropertyType.array:
//
if (property.items != null) {
final itemType = _getItemType(property.items!);
return 'List<$itemType>';
}
return 'List<dynamic>';
case PropertyType.object:
return 'Map<String, dynamic>';
case PropertyType.reference:
return property.reference != null
? StringUtils.generateClassName(property.reference!)
: 'dynamic';
default:
return 'dynamic';
}
}
///
String _getItemType(ApiModel items) {
//
if (items.name != 'string' &&
items.name != 'integer' &&
items.name != 'number' &&
items.name != 'boolean') {
return StringUtils.generateClassName(items.name);
}
// Dart类型
switch (items.name) {
case 'string':
return 'String';
case 'integer':
return 'int';
case 'number':
return 'double';
case 'boolean':
return 'bool';
default:
return 'dynamic';
}
}
}
///
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({
this.generateEndpoints = true,
this.generateModels = true,
this.generateDocs = true,
this.useSimpleModels = false,
this.separateModelFiles = true,
this.modelsDirectory = 'models',
this.outputDirectory = 'generator',
this.endpointsFileName = 'api_paths.dart',
this.docsFileName = 'api_documentation.md',
});
///
factory GeneratorOptions.fromArgs(List<String> args) {
bool generateEndpoints = false;
bool generateModels = false;
bool generateDocs = false;
bool useSimpleModels = false;
bool separateModelFiles = true;
String modelsDirectory = 'models';
String outputDirectory = 'generator';
String endpointsFileName = 'api_paths.dart';
String docsFileName = 'api_documentation.md';
bool hasSpecificOption = false;
for (int i = 0; i < args.length; i++) {
final arg = args[i];
switch (arg) {
case '--endpoints':
generateEndpoints = true;
hasSpecificOption = true;
break;
case '--models':
generateModels = true;
hasSpecificOption = true;
break;
case '--docs':
generateDocs = true;
hasSpecificOption = true;
break;
case '--all':
generateEndpoints = true;
generateModels = true;
generateDocs = true;
hasSpecificOption = true;
break;
case '--simple':
useSimpleModels = true;
break;
case '--models-dir':
if (i + 1 < args.length) {
modelsDirectory = args[i + 1];
i++; //
}
break;
case '--output-dir':
if (i + 1 < args.length) {
outputDirectory = args[i + 1];
i++; //
}
break;
case '--endpoints-file':
if (i + 1 < args.length) {
endpointsFileName = args[i + 1];
i++; //
}
break;
case '--docs-file':
if (i + 1 < args.length) {
docsFileName = args[i + 1];
i++; //
}
break;
}
}
//
if (!hasSpecificOption) {
generateEndpoints = true;
generateModels = true;
generateDocs = true;
}
return GeneratorOptions(
generateEndpoints: generateEndpoints,
generateModels: generateModels,
generateDocs: generateDocs,
useSimpleModels: useSimpleModels,
separateModelFiles: separateModelFiles,
modelsDirectory: modelsDirectory,
outputDirectory: outputDirectory,
endpointsFileName: endpointsFileName,
docsFileName: docsFileName,
);
}
}

View File

@ -0,0 +1,702 @@
import '../core/models.dart';
import '../utils/string_utils.dart';
import 'base_generator.dart';
///
/// API文档
class DocumentationGenerator extends BaseGenerator {
final SwaggerDocument document;
final bool includeExamples;
final bool includeSchemas;
final bool includeResponses;
final String? customTitle;
DocumentationGenerator(
this.document, {
this.includeExamples = true,
this.includeSchemas = true,
this.includeResponses = true,
this.customTitle,
});
@override
String get generatorType => 'DocumentationGenerator';
@override
String generate() {
final buffer = StringBuffer();
//
_generateHeader(buffer);
//
_generateTableOfContents(buffer);
// API概述
_generateApiOverview(buffer);
//
_generateAuthenticationInfo(buffer);
// API端点文档
_generateEndpointsDocumentation(buffer);
//
if (includeSchemas) {
_generateSchemasDocumentation(buffer);
}
//
_generateErrorCodesDocumentation(buffer);
//
if (includeExamples) {
_generateExamplesDocumentation(buffer);
}
//
_generateChangeLog(buffer);
return generateTypeCheckedCode(buffer.toString());
}
///
void _generateHeader(StringBuffer buffer) {
final title = customTitle ?? document.title;
buffer.writeln('# $title');
buffer.writeln('');
if (document.description.isNotEmpty) {
buffer.writeln('${document.description}');
buffer.writeln('');
}
buffer.writeln('**版本**: ${document.version}');
buffer.writeln('**基础URL**: ${_getBaseUrl()}');
buffer.writeln('**生成时间**: ${DateTime.now().toIso8601String()}');
buffer.writeln('');
//
buffer.writeln(
'![API版本](https://img.shields.io/badge/API-${document.version}-blue.svg)');
buffer.writeln('![状态](https://img.shields.io/badge/状态-活跃-green.svg)');
buffer.writeln('');
}
///
void _generateTableOfContents(StringBuffer buffer) {
buffer.writeln('## 📋 目录');
buffer.writeln('');
buffer.writeln('- [API概述](#api概述)');
buffer.writeln('- [认证](#认证)');
buffer.writeln('- [API端点](#api端点)');
//
final controllerGroups = _groupPathsByController();
for (final controllerName in controllerGroups.keys) {
final anchor = controllerName.toLowerCase().replaceAll(' ', '-');
buffer.writeln(' - [$controllerName](#$anchor)');
}
if (includeSchemas) {
buffer.writeln('- [数据模型](#数据模型)');
}
buffer.writeln('- [错误代码](#错误代码)');
if (includeExamples) {
buffer.writeln('- [示例代码](#示例代码)');
}
buffer.writeln('- [更新日志](#更新日志)');
buffer.writeln('');
}
/// API概述
void _generateApiOverview(StringBuffer buffer) {
buffer.writeln('## 🚀 API概述');
buffer.writeln('');
//
final stats = _generateStats();
buffer.writeln('### 📊 统计信息');
buffer.writeln('');
buffer.writeln('- **总端点数**: ${stats['totalEndpoints']}');
buffer.writeln('- **控制器数**: ${stats['controllersCount']}');
buffer.writeln('- **数据模型数**: ${stats['modelsCount']}');
buffer.writeln('');
// HTTP方法统计
final methodStats = stats['methodStats'] as Map<String, int>;
buffer.writeln('### 🔗 HTTP方法分布');
buffer.writeln('');
for (final entry in methodStats.entries) {
final method = entry.key;
final count = entry.value;
final percentage =
((count / stats['totalEndpoints']) * 100).toStringAsFixed(1);
buffer.writeln('- **$method**: $count个 ($percentage%)');
}
buffer.writeln('');
//
buffer.writeln('### 📝 支持的格式');
buffer.writeln('');
buffer.writeln('**请求格式**:');
for (final format in document.consumes) {
buffer.writeln('- `$format`');
}
buffer.writeln('');
buffer.writeln('**响应格式**:');
for (final format in document.produces) {
buffer.writeln('- `$format`');
}
buffer.writeln('');
}
///
void _generateAuthenticationInfo(StringBuffer buffer) {
buffer.writeln('## 🔐 认证');
buffer.writeln('');
buffer.writeln('本API使用以下认证方式');
buffer.writeln('');
buffer.writeln('### Bearer Token');
buffer.writeln('');
buffer.writeln('在请求头中包含Authorization字段');
buffer.writeln('');
buffer.writeln('```');
buffer.writeln('Authorization: Bearer YOUR_TOKEN_HERE');
buffer.writeln('```');
buffer.writeln('');
buffer.writeln('### 获取Token');
buffer.writeln('');
buffer.writeln('请使用登录接口获取访问令牌。');
buffer.writeln('');
}
///
void _generateEndpointsDocumentation(StringBuffer buffer) {
buffer.writeln('## 📡 API端点');
buffer.writeln('');
final controllerGroups = _groupPathsByController();
for (final entry in controllerGroups.entries) {
final controllerName = entry.key;
final paths = entry.value;
buffer.writeln('### $controllerName');
buffer.writeln('');
// HTTP方法和路径排序
paths.sort((a, b) {
final methodOrder = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
final aIndex = methodOrder.indexOf(a.method.value);
final bIndex = methodOrder.indexOf(b.method.value);
if (aIndex != bIndex) {
return aIndex.compareTo(bIndex);
}
return a.path.compareTo(b.path);
});
for (final path in paths) {
_generateEndpointDocumentation(buffer, path);
}
buffer.writeln('');
}
}
///
void _generateEndpointDocumentation(StringBuffer buffer, ApiPath path) {
//
final title = path.summary.isNotEmpty ? path.summary : path.operationId;
buffer.writeln('#### ${path.method.value} ${path.path}');
buffer.writeln('');
if (title.isNotEmpty) {
buffer.writeln('**$title**');
buffer.writeln('');
}
//
if (path.description.isNotEmpty) {
buffer.writeln(path.description);
buffer.writeln('');
}
//
if (path.tags.isNotEmpty) {
buffer.writeln('**标签**: ${path.tags.join(', ')}');
buffer.writeln('');
}
//
if (path.parameters.isNotEmpty) {
buffer.writeln('**参数**:');
buffer.writeln('');
//
final paramGroups = <ParameterLocation, List<ApiParameter>>{};
for (final param in path.parameters) {
paramGroups.putIfAbsent(param.location, () => []).add(param);
}
for (final entry in paramGroups.entries) {
final location = entry.key;
final params = entry.value;
buffer.writeln('*${_getLocationName(location)}参数*:');
buffer.writeln('');
buffer.writeln('| 参数名 | 类型 | 必填 | 描述 | 示例 |');
buffer.writeln('|--------|------|------|------|------|');
for (final param in params) {
final required = param.required ? '' : '';
final example = param.example?.toString() ?? '-';
final description =
param.description.isNotEmpty ? param.description : '-';
buffer.writeln(
'| ${param.name} | ${param.type.value} | $required | $description | $example |');
}
buffer.writeln('');
}
}
//
if (includeResponses && path.responses.isNotEmpty) {
buffer.writeln('**响应**:');
buffer.writeln('');
for (final entry in path.responses.entries) {
final code = entry.key;
final response = entry.value;
buffer.writeln('*HTTP $code*:');
if (response.description.isNotEmpty) {
buffer.writeln('- ${response.description}');
}
buffer.writeln('');
}
}
//
if (includeExamples) {
_generateEndpointExample(buffer, path);
}
buffer.writeln('---');
buffer.writeln('');
}
///
void _generateEndpointExample(StringBuffer buffer, ApiPath path) {
buffer.writeln('**示例**:');
buffer.writeln('');
// cURL示例
buffer.writeln('```bash');
buffer.write('curl -X ${path.method.value} ');
buffer.write('${_getBaseUrl()}${path.path}');
if (path.parameters.any((p) => p.location == ParameterLocation.header)) {
buffer.write(' \\');
buffer.writeln('');
buffer.write(' -H "Authorization: Bearer YOUR_TOKEN"');
}
if (path.method == HttpMethod.post || path.method == HttpMethod.put) {
buffer.write(' \\');
buffer.writeln('');
buffer.write(' -H "Content-Type: application/json"');
buffer.write(' \\');
buffer.writeln('');
buffer.write(' -d \'{"key": "value"}\'');
}
buffer.writeln('');
buffer.writeln('```');
buffer.writeln('');
// Dart示例
buffer.writeln('```dart');
buffer.writeln('import \'dart:convert\';');
buffer.writeln('import \'package:http/http.dart\' as http;');
buffer.writeln('');
buffer.writeln('class ApiClient {');
buffer.writeln(' static const String baseUrl = \'${_getBaseUrl()}\';');
buffer.writeln(' String? _token;');
buffer.writeln('');
buffer.writeln(' void setToken(String token) {');
buffer.writeln(' _token = token;');
buffer.writeln(' }');
buffer.writeln('');
buffer.writeln(' Map<String, String> get _headers => {');
buffer.writeln(' \'Content-Type\': \'application/json\',');
buffer.writeln(
' if (_token != null) \'Authorization\': \'Bearer \$_token\',');
buffer.writeln(' };');
buffer.writeln('');
buffer
.writeln(' Future<Map<String, dynamic>> get(String endpoint) async {');
buffer.writeln(' final response = await http.get(');
buffer.writeln(' Uri.parse(\'\$baseUrl\$endpoint\'),');
buffer.writeln(' headers: _headers,');
buffer.writeln(' );');
buffer.writeln('');
buffer.writeln(' if (response.statusCode == 200) {');
buffer.writeln(' return jsonDecode(response.body);');
buffer.writeln(' } else {');
buffer.writeln(
' throw Exception(\'Failed to load data: \${response.statusCode}\');');
buffer.writeln(' }');
buffer.writeln(' }');
buffer.writeln('');
buffer.writeln(
' Future<Map<String, dynamic>> post(String endpoint, Map<String, dynamic> data) async {');
buffer.writeln(' final response = await http.post(');
buffer.writeln(' Uri.parse(\'\$baseUrl\$endpoint\'),');
buffer.writeln(' headers: _headers,');
buffer.writeln(' body: jsonEncode(data),');
buffer.writeln(' );');
buffer.writeln('');
buffer.writeln(
' if (response.statusCode == 200 || response.statusCode == 201) {');
buffer.writeln(' return jsonDecode(response.body);');
buffer.writeln(' } else {');
buffer.writeln(
' throw Exception(\'Failed to post data: \${response.statusCode}\');');
buffer.writeln(' }');
buffer.writeln(' }');
buffer.writeln('}');
buffer.writeln('```');
buffer.writeln('');
}
///
void _generateSchemasDocumentation(StringBuffer buffer) {
if (document.models.isEmpty) return;
buffer.writeln('## 📋 数据模型');
buffer.writeln('');
final sortedModels = document.models.values.toList()
..sort((a, b) => a.name.compareTo(b.name));
for (final model in sortedModels) {
_generateModelDocumentation(buffer, model);
}
}
///
void _generateModelDocumentation(StringBuffer buffer, ApiModel model) {
buffer.writeln('### ${model.name}');
buffer.writeln('');
if (model.description.isNotEmpty) {
buffer.writeln(model.description);
buffer.writeln('');
}
if (model.isEnum) {
buffer.writeln('**枚举值**:');
buffer.writeln('');
for (final value in model.enumValues) {
buffer.writeln('- `$value`');
}
buffer.writeln('');
} else {
buffer.writeln('**属性**:');
buffer.writeln('');
if (model.properties.isNotEmpty) {
buffer.writeln('| 属性名 | 类型 | 必填 | 描述 |');
buffer.writeln('|--------|------|------|------|');
for (final entry in model.properties.entries) {
final propName = entry.key;
final prop = entry.value;
final required = model.required.contains(propName) ? '' : '';
final type = _getPropertyTypeDescription(prop);
final description =
prop.description.isNotEmpty ? prop.description : '-';
buffer.writeln('| $propName | $type | $required | $description |');
}
}
buffer.writeln('');
}
// JSON示例
if (includeExamples) {
buffer.writeln('**JSON示例**:');
buffer.writeln('');
buffer.writeln('```json');
buffer.writeln(_generateModelExample(model));
buffer.writeln('```');
buffer.writeln('');
}
buffer.writeln('---');
buffer.writeln('');
}
///
void _generateErrorCodesDocumentation(StringBuffer buffer) {
buffer.writeln('## ❌ 错误代码');
buffer.writeln('');
// HTTP状态码
final errorCodes = {
'400': '请求参数错误',
'401': '未授权访问',
'403': '禁止访问',
'404': '资源不存在',
'405': '方法不允许',
'422': '参数验证失败',
'500': '服务器内部错误',
'502': '网关错误',
'503': '服务不可用',
};
buffer.writeln('| 状态码 | 描述 |');
buffer.writeln('|--------|------|');
for (final entry in errorCodes.entries) {
buffer.writeln('| ${entry.key} | ${entry.value} |');
}
buffer.writeln('');
//
buffer.writeln('### 错误响应格式');
buffer.writeln('');
buffer.writeln('```json');
buffer.writeln('{');
buffer.writeln(' "error": {');
buffer.writeln(' "code": "ERROR_CODE",');
buffer.writeln(' "message": "错误描述",');
buffer.writeln(' "details": "详细信息"');
buffer.writeln(' }');
buffer.writeln('}');
buffer.writeln('```');
buffer.writeln('');
}
///
void _generateExamplesDocumentation(StringBuffer buffer) {
buffer.writeln('## 💡 示例代码');
buffer.writeln('');
// Dart HTTP客户端示例
buffer.writeln('### Dart HTTP客户端');
buffer.writeln('');
buffer.writeln('```dart');
buffer.writeln('import \'dart:convert\';');
buffer.writeln('import \'package:http/http.dart\' as http;');
buffer.writeln('');
buffer.writeln('class ApiClient {');
buffer.writeln(' static const String baseUrl = \'${_getBaseUrl()}\';');
buffer.writeln(' String? _token;');
buffer.writeln('');
buffer.writeln(' void setToken(String token) {');
buffer.writeln(' _token = token;');
buffer.writeln(' }');
buffer.writeln('');
buffer.writeln(' Map<String, String> get _headers => {');
buffer.writeln(' \'Content-Type\': \'application/json\',');
buffer.writeln(
' if (_token != null) \'Authorization\': \'Bearer \$_token\',');
buffer.writeln(' };');
buffer.writeln('');
buffer
.writeln(' Future<Map<String, dynamic>> get(String endpoint) async {');
buffer.writeln(' final response = await http.get(');
buffer.writeln(' Uri.parse(\'\$baseUrl\$endpoint\'),');
buffer.writeln(' headers: _headers,');
buffer.writeln(' );');
buffer.writeln('');
buffer.writeln(' if (response.statusCode == 200) {');
buffer.writeln(' return jsonDecode(response.body);');
buffer.writeln(' } else {');
buffer.writeln(
' throw Exception(\'Failed to load data: \${response.statusCode}\');');
buffer.writeln(' }');
buffer.writeln(' }');
buffer.writeln('');
buffer.writeln(
' Future<Map<String, dynamic>> post(String endpoint, Map<String, dynamic> data) async {');
buffer.writeln(' final response = await http.post(');
buffer.writeln(' Uri.parse(\'\$baseUrl\$endpoint\'),');
buffer.writeln(' headers: _headers,');
buffer.writeln(' body: jsonEncode(data),');
buffer.writeln(' );');
buffer.writeln('');
buffer.writeln(
' if (response.statusCode == 200 || response.statusCode == 201) {');
buffer.writeln(' return jsonDecode(response.body);');
buffer.writeln(' } else {');
buffer.writeln(
' throw Exception(\'Failed to post data: \${response.statusCode}\');');
buffer.writeln(' }');
buffer.writeln(' }');
buffer.writeln('}');
buffer.writeln('```');
buffer.writeln('');
}
///
void _generateChangeLog(StringBuffer buffer) {
buffer.writeln('## 📝 更新日志');
buffer.writeln('');
buffer.writeln(
'### ${document.version} - ${DateTime.now().toIso8601String().split('T')[0]}');
buffer.writeln('');
buffer.writeln('- 🎉 初始版本发布');
buffer.writeln('- 📡 ${document.paths.length} 个API端点');
buffer.writeln('- 📋 ${document.models.length} 个数据模型');
buffer.writeln('- 🔧 完整的API文档');
buffer.writeln('');
buffer.writeln('---');
buffer.writeln('');
buffer.writeln('*文档由 Swagger CLI By Max 自动生成*');
buffer.writeln('');
}
///
Map<String, List<ApiPath>> _groupPathsByController() {
final groups = <String, List<ApiPath>>{};
for (final path in document.paths.values) {
final controllerName = StringUtils.extractControllerName(path);
groups.putIfAbsent(controllerName, () => []).add(path);
}
return groups;
}
// StringUtils.extractControllerName
/// URL
String _getBaseUrl() {
return document.schemes.isNotEmpty
? '${document.schemes.first}://${document.host}${document.basePath}'
: 'https://${document.host}${document.basePath}';
}
///
String _getLocationName(ParameterLocation location) {
switch (location) {
case ParameterLocation.query:
return '查询';
case ParameterLocation.path:
return '路径';
case ParameterLocation.header:
return '请求头';
case ParameterLocation.body:
return '请求体';
case ParameterLocation.form:
return '表单';
case ParameterLocation.cookie:
return 'Cookie';
}
}
///
String _getPropertyTypeDescription(ApiProperty prop) {
String baseType = prop.type.value;
if (prop.format != null) {
baseType += ' (${prop.format})';
}
if (prop.nullable) {
baseType += '?';
}
return baseType;
}
///
String _generateModelExample(ApiModel model) {
if (model.isEnum) {
return '"${model.enumValues.first}"';
}
final buffer = StringBuffer();
buffer.writeln('{');
final properties = model.properties.entries.toList();
for (int i = 0; i < properties.length; i++) {
final entry = properties[i];
final propName = entry.key;
final prop = entry.value;
final exampleValue = _generatePropertyExample(prop);
buffer.write(' "$propName": $exampleValue');
if (i < properties.length - 1) {
buffer.write(',');
}
buffer.writeln();
}
buffer.write('}');
return buffer.toString();
}
///
String _generatePropertyExample(ApiProperty prop) {
switch (prop.type) {
case PropertyType.string:
return '"string"';
case PropertyType.integer:
return '0';
case PropertyType.number:
return '0.0';
case PropertyType.boolean:
return 'true';
case PropertyType.array:
return '[]';
case PropertyType.object:
return '{}';
case PropertyType.reference:
return '{}';
default:
return 'null';
}
}
///
Map<String, dynamic> _generateStats() {
final stats = <String, dynamic>{};
stats['totalEndpoints'] = document.paths.length;
stats['controllersCount'] = _groupPathsByController().length;
stats['modelsCount'] = document.models.length;
// HTTP方法统计
final methodStats = <String, int>{};
for (final path in document.paths.values) {
final method = path.method.value;
methodStats[method] = (methodStats[method] ?? 0) + 1;
}
stats['methodStats'] = methodStats;
return stats;
}
}

View File

@ -0,0 +1,257 @@
import '../core/models.dart';
import '../utils/string_utils.dart';
import 'base_generator.dart';
///
/// API端点常量代码
class EndpointCodeGenerator extends BaseGenerator {
final SwaggerDocument document;
final bool includeBaseUrl;
final String? customBaseUrl;
EndpointCodeGenerator(
this.document, {
this.includeBaseUrl = true,
this.customBaseUrl,
});
@override
String get generatorType => 'EndpointCodeGenerator';
@override
String generate() {
final buffer = StringBuffer();
//
buffer.writeln(generateFileHeader('API 端点常量定义'));
buffer.writeln('');
//
buffer.writeln('/// API路径常量定义');
buffer.writeln('/// 统一管理所有API端点路径便于维护和修改');
buffer.writeln('class ApiPaths {');
buffer.writeln(' ApiPaths._(); // 私有构造函数,防止实例化');
buffer.writeln('');
// URL常量
if (includeBaseUrl) {
final baseUrl = customBaseUrl ??
(document.schemes.isNotEmpty
? '${document.schemes.first}://${document.host}${document.basePath}'
: 'https://${document.host}${document.basePath}');
buffer.writeln(' /// 基础URL');
buffer.writeln(' static const String baseUrl = \'$baseUrl\';');
buffer.writeln('');
}
//
final controllerGroups = _groupPathsByController();
for (final entry in controllerGroups.entries) {
final controllerName = entry.key;
final paths = entry.value;
buffer.writeln(' // ${controllerName}相关端点');
for (final path in paths) {
final constantName = _generateConstantName(path);
final cleanPath = StringUtils.cleanPath(path.path);
//
if (path.summary.isNotEmpty) {
buffer.writeln(' /// ${path.summary}');
}
if (path.description.isNotEmpty && path.description != path.summary) {
buffer.writeln(' /// ${path.description}');
}
buffer.writeln(' static const String $constantName = \'$cleanPath\';');
buffer.writeln('');
}
buffer.writeln('');
}
//
buffer.writeln(' /// 所有端点列表');
buffer.writeln(' static const List<String> allEndpoints = [');
for (final entry in controllerGroups.entries) {
final paths = entry.value;
for (final path in paths) {
final constantName = _generateConstantName(path);
buffer.writeln(' $constantName,');
}
}
buffer.writeln(' ];');
buffer.writeln('');
// HTTP方法常量
buffer.writeln(' /// HTTP方法常量');
buffer.writeln(' static const Map<String, String> httpMethods = {');
for (final entry in controllerGroups.entries) {
final paths = entry.value;
for (final path in paths) {
final constantName = _generateConstantName(path);
buffer.writeln(' \'$constantName\': \'${path.method.value}\',');
}
}
buffer.writeln(' };');
buffer.writeln('');
// URL构建方法
if (includeBaseUrl) {
buffer.writeln(' /// 构建完整URL');
buffer.writeln(
' static String buildUrl(String endpoint, {Map<String, dynamic>? params}) {');
buffer.writeln(' String url = baseUrl + endpoint;');
buffer.writeln(' ');
buffer.writeln(' if (params != null && params.isNotEmpty) {');
buffer.writeln(' final queryParams = <String>[];');
buffer.writeln(' params.forEach((key, value) {');
buffer.writeln(' if (value != null) {');
buffer.writeln(
' queryParams.add(\'\\\${Uri.encodeComponent(key)}=\\\${Uri.encodeComponent(value.toString())}\');');
buffer.writeln(' }');
buffer.writeln(' });');
buffer.writeln(' ');
buffer.writeln(' if (queryParams.isNotEmpty) {');
buffer.writeln(' url += \'?\' + queryParams.join(\'&\');');
buffer.writeln(' }');
buffer.writeln(' }');
buffer.writeln(' ');
buffer.writeln(' return url;');
buffer.writeln(' }');
buffer.writeln('');
//
buffer.writeln(' /// 替换路径参数');
buffer.writeln(
' static String replacePathParams(String endpoint, Map<String, dynamic> params) {');
buffer.writeln(' String result = endpoint;');
buffer.writeln(' params.forEach((key, value) {');
buffer.writeln(
' result = result.replaceAll(\'{\\\$key}\', value.toString());');
buffer.writeln(' });');
buffer.writeln(' return result;');
buffer.writeln(' }');
buffer.writeln('');
}
//
buffer.writeln(' /// 验证端点是否存在');
buffer.writeln(' static bool isValidEndpoint(String endpoint) {');
buffer.writeln(' return allEndpoints.contains(endpoint);');
buffer.writeln(' }');
buffer.writeln('');
// HTTP方法的方法
buffer.writeln(' /// 获取端点的HTTP方法');
buffer.writeln(' static String? getHttpMethod(String endpoint) {');
buffer.writeln(' return httpMethods[endpoint];');
buffer.writeln(' }');
buffer.writeln('');
buffer.writeln('}');
//
buffer.writeln('');
buffer.writeln('/// API端点枚举');
buffer.writeln('/// 提供类型安全的端点访问');
buffer.writeln('enum ApiEndpoint {');
for (final entry in controllerGroups.entries) {
final paths = entry.value;
for (final path in paths) {
final enumName = _generateEnumName(path);
final constantName = _generateConstantName(path);
if (path.summary.isNotEmpty) {
buffer.writeln(' /// ${path.summary}');
}
buffer.writeln(
' $enumName(ApiPaths.$constantName, \'${path.method.value}\'),');
}
}
buffer.writeln(';');
buffer.writeln('');
//
buffer.writeln(' const ApiEndpoint(this.path, this.method);');
buffer.writeln('');
buffer.writeln(' /// 端点路径');
buffer.writeln(' final String path;');
buffer.writeln('');
buffer.writeln(' /// HTTP方法');
buffer.writeln(' final String method;');
buffer.writeln('');
if (includeBaseUrl) {
buffer.writeln(' /// 获取完整URL');
buffer.writeln(' String get fullUrl => ApiPaths.baseUrl + path;');
buffer.writeln('');
}
buffer.writeln(' /// 根据路径查找端点');
buffer.writeln(' static ApiEndpoint? findByPath(String path) {');
buffer.writeln(' for (final endpoint in values) {');
buffer.writeln(' if (endpoint.path == path) {');
buffer.writeln(' return endpoint;');
buffer.writeln(' }');
buffer.writeln(' }');
buffer.writeln(' return null;');
buffer.writeln(' }');
buffer.writeln('');
buffer.writeln(' /// 根据HTTP方法过滤端点');
buffer
.writeln(' static List<ApiEndpoint> filterByMethod(String method) {');
buffer.writeln(
' return values.where((endpoint) => endpoint.method == method).toList();');
buffer.writeln(' }');
buffer.writeln('');
buffer.writeln('}');
return generateTypeCheckedCode(buffer.toString());
}
///
Map<String, List<ApiPath>> _groupPathsByController() {
final groups = <String, List<ApiPath>>{};
for (final path in document.paths.values) {
final controllerName = StringUtils.extractControllerName(path);
groups.putIfAbsent(controllerName, () => []).add(path);
}
return groups;
}
// StringUtils.extractControllerName
///
String _generateConstantName(ApiPath path) {
final baseName =
StringUtils.generateEndpointName(path.path, path.operationId);
final methodPrefix = path.method.value.toLowerCase();
return StringUtils.toCamelCase('${methodPrefix}_$baseName');
}
///
String _generateEnumName(ApiPath path) {
final baseName =
StringUtils.generateEndpointName(path.path, path.operationId);
final methodPrefix = path.method.value.toLowerCase();
return StringUtils.toCamelCase('${methodPrefix}_$baseName');
}
// StringUtils.cleanPath
}

View File

@ -0,0 +1,682 @@
import '../core/models.dart';
import '../utils/string_utils.dart';
import 'base_generator.dart';
///
/// Dart模型类代码
class ModelCodeGenerator extends ModelGenerator {
ModelCodeGenerator(super.document, {super.useSimpleModels});
@override
String get generatorType => 'ModelCodeGenerator';
@override
String generate() {
final buffer = StringBuffer();
//
buffer.writeln(generateFileHeader('API 数据模型定义'));
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);
}
return useSimpleModels
? generateSimpleModelCode(model)
: generateAnnotatedModelCode(model);
}
///
String generateSimpleModelCode(ApiModel model) {
final className = StringUtils.generateClassName(model.name);
final buffer = StringBuffer();
//
final importedTypes = getImportedTypes(model);
for (final importType in importedTypes) {
final importFileName = StringUtils.generateFileName(importType);
buffer.writeln('import \'$importFileName\';');
}
if (importedTypes.isNotEmpty) {
buffer.writeln('');
}
//
if (model.description.isNotEmpty) {
buffer.writeln(StringUtils.generateComment(model.description));
}
buffer.writeln('class $className {');
//
model.properties.forEach((propName, property) {
final dartType = getDartPropertyType(property);
final nullable = property.nullable ? '?' : '';
final dartPropName = StringUtils.toDartPropertyName(propName);
if (property.description.isNotEmpty) {
buffer.writeln(
' ${StringUtils.generateComment(property.description)}',
);
}
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);
final required = property.required ? 'required ' : '';
buffer.writeln(' ${required}this.$dartPropName,');
});
buffer.writeln(' });');
}
buffer.writeln('');
// fromJson
buffer.writeln(
' factory $className.fromJson(Map<String, dynamic> json) {',
);
if (model.properties.isEmpty) {
buffer.writeln(' return const $className();');
} else {
buffer.writeln(' return $className(');
model.properties.forEach((propName, property) {
final dartPropName = StringUtils.toDartPropertyName(propName);
final dartType = getDartPropertyType(property);
buffer.write(' $dartPropName: ');
//
if (property.type == PropertyType.reference &&
property.reference != null) {
final refType = StringUtils.generateClassName(property.reference!);
if (property.nullable) {
buffer.write(
'json[\'$propName\'] != null ? $refType.fromJson(json[\'$propName\']) : null',
);
} else {
buffer.write('$refType.fromJson(json[\'$propName\'])');
}
} else if (property.type == PropertyType.array) {
//
buffer.write(
'json[\'$propName\'] != null ? List<dynamic>.from(json[\'$propName\']) : null',
);
} else {
//
if (property.nullable) {
buffer.write('json[\'$propName\'] as $dartType?');
} else {
buffer.write('json[\'$propName\'] as $dartType');
}
}
buffer.writeln(',');
});
buffer.writeln(' );');
}
buffer.writeln(' }');
buffer.writeln('');
// toJson
buffer.writeln(' Map<String, dynamic> toJson() {');
buffer.writeln(' return {');
model.properties.forEach((propName, property) {
final dartPropName = StringUtils.toDartPropertyName(propName);
if (property.type == PropertyType.reference &&
property.reference != null) {
buffer.write(' \'$propName\': $dartPropName?.toJson()');
} else if (property.type == PropertyType.array) {
buffer.write(' \'$propName\': $dartPropName');
} else {
buffer.write(' \'$propName\': $dartPropName');
}
buffer.writeln(',');
});
buffer.writeln(' };');
buffer.writeln(' }');
buffer.writeln('');
// copyWith
if (model.properties.isNotEmpty) {
buffer.writeln(' $className copyWith({');
model.properties.forEach((propName, property) {
final dartType = getDartPropertyType(property);
final dartPropName = StringUtils.toDartPropertyName(propName);
buffer.writeln(' $dartType? $dartPropName,');
});
buffer.writeln(' }) {');
buffer.writeln(' return $className(');
model.properties.forEach((propName, property) {
final dartPropName = StringUtils.toDartPropertyName(propName);
buffer.writeln(
' $dartPropName: $dartPropName ?? this.$dartPropName,',
);
});
buffer.writeln(' );');
buffer.writeln(' }');
buffer.writeln('');
}
buffer.writeln('}');
return buffer.toString();
}
///
String generateAnnotatedModelCode(ApiModel model) {
final className = StringUtils.generateClassName(model.name);
final buffer = StringBuffer();
//
final importedTypes = getImportedTypes(model);
for (final importType in importedTypes) {
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()');
buffer.writeln('class $className {');
//
model.properties.forEach((propName, property) {
final dartType = getDartPropertyType(property);
final nullable = property.nullable ? '?' : '';
final dartPropName = StringUtils.toDartPropertyName(propName);
if (property.description.isNotEmpty) {
buffer.writeln(
' ${StringUtils.generateComment(property.description)}',
);
}
// JsonKey注解
final needsJsonKey =
_needsJsonKeyAnnotation(dartPropName, propName, property);
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);
final required = property.required ? 'required ' : '';
buffer.writeln(' ${required}this.$dartPropName,');
});
buffer.writeln(' });');
}
buffer.writeln('');
// fromJson
buffer.writeln(
' factory $className.fromJson(Map<String, dynamic> json) => _\$${className}FromJson(json);',
);
buffer.writeln('');
// toJson
buffer.writeln(
' Map<String, dynamic> toJson() => _\$${className}ToJson(this);',
);
buffer.writeln('');
buffer.writeln('}');
return buffer.toString();
}
///
Map<String, String> generateSeparateModelFiles() {
final files = <String, String>{};
//
for (final model in document.models.values) {
final fileName = StringUtils.generateFileName(model.name);
final content = generateSingleModelFile(model);
files[fileName] = content;
}
// index.dart
final modelFileNames = document.models.keys
.map((name) => StringUtils.generateFileName(name))
.toList();
final indexContent = generateIndexFile(modelFileNames);
files['index.dart'] = indexContent;
return files;
}
///
String generateSingleModelFile(ApiModel model) {
final buffer = StringBuffer();
//
buffer.writeln(generateFileHeader('${model.name} 模型定义'));
buffer.writeln('');
// json_annotation 使 @JsonEnum
if (!useSimpleModels && model.isEnum) {
buffer.writeln(
'import \'package:json_annotation/json_annotation.dart\';',
);
buffer.writeln('');
}
// json_annotation
else if (!useSimpleModels && !model.isEnum) {
buffer.writeln(
'import \'package:json_annotation/json_annotation.dart\';',
);
buffer.writeln('');
}
// - 使 index.dart
final importedTypes = getImportedTypes(model);
if (importedTypes.isNotEmpty) {
buffer.writeln('import \'index.dart\';');
buffer.writeln('');
}
//
buffer.writeln(_generateModelCodeWithoutImports(model));
return generateTypeCheckedCode(buffer.toString());
}
///
String _generateModelCodeWithoutImports(ApiModel model) {
if (model.isEnum) {
return _generateEnumCodeWithoutImports(model);
}
return useSimpleModels
? _generateSimpleModelCodeWithoutImports(model)
: _generateAnnotatedModelCodeWithoutImports(model);
}
///
String _generateEnumCodeWithoutImports(ApiModel model) {
final className = StringUtils.generateClassName(model.name);
final enumType = model.enumType?.value ?? 'string';
final buffer = StringBuffer();
//
if (model.description.isNotEmpty) {
buffer.writeln(StringUtils.generateComment(model.description));
}
// @JsonEnum
buffer.writeln('@JsonEnum()');
buffer.writeln('enum $className {');
//
for (int i = 0; i < model.enumValues.length; i++) {
final value = model.enumValues[i];
final enumName = StringUtils.generateEnumValueName(value, i);
if (enumType == 'integer' || enumType == 'number') {
buffer.writeln(' $enumName($value),');
} else {
buffer.writeln(' $enumName(\'$value\'),');
}
}
//
final content = buffer.toString().trimRight();
buffer.clear();
buffer.writeln(content.substring(0, content.lastIndexOf(',')));
buffer.writeln(';');
buffer.writeln('');
//
buffer.writeln(' const $className(this.value);');
buffer.writeln(
' final ${enumType == 'integer' || enumType == 'number' ? 'int' : 'String'} value;',
);
buffer.writeln('');
// fromValue
buffer.writeln(' static $className fromValue(dynamic value) {');
buffer.writeln(' for (final enumValue in $className.values) {');
buffer.writeln(' if (enumValue.value == value) {');
buffer.writeln(' return enumValue;');
buffer.writeln(' }');
buffer.writeln(' }');
buffer.writeln(' throw ArgumentError(\'Unknown enum value: \$value\');');
buffer.writeln(' }');
buffer.writeln('');
// fromJson
buffer.writeln(' factory $className.fromJson(dynamic json) {');
buffer.writeln(' return fromValue(json);');
buffer.writeln(' }');
buffer.writeln('');
// toJson
buffer.writeln(' dynamic toJson() => value;');
buffer.writeln('');
buffer.writeln('}');
return buffer.toString();
}
// StringUtils.generateEnumValueName
///
String _generateSimpleModelCodeWithoutImports(ApiModel model) {
final className = StringUtils.generateClassName(model.name);
final buffer = StringBuffer();
//
if (model.description.isNotEmpty) {
buffer.writeln(StringUtils.generateComment(model.description));
}
buffer.writeln('class $className {');
//
model.properties.forEach((propName, property) {
final dartType = getDartPropertyType(property);
final nullable = property.nullable ? '?' : '';
final dartPropName = StringUtils.toDartPropertyName(propName);
if (property.description.isNotEmpty) {
buffer.writeln(
' ${StringUtils.generateComment(property.description)}',
);
}
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);
final required = property.required ? 'required ' : '';
buffer.writeln(' ${required}this.$dartPropName,');
});
buffer.writeln(' });');
}
buffer.writeln('');
// fromJson
buffer.writeln(
' factory $className.fromJson(Map<String, dynamic> json) {',
);
if (model.properties.isEmpty) {
buffer.writeln(' return const $className();');
} else {
buffer.writeln(' return $className(');
model.properties.forEach((propName, property) {
final dartPropName = StringUtils.toDartPropertyName(propName);
final dartType = getDartPropertyType(property);
buffer.write(' $dartPropName: ');
//
if (property.type == PropertyType.reference &&
property.reference != null) {
final refType = StringUtils.generateClassName(property.reference!);
if (property.nullable) {
buffer.write(
'json[\'$propName\'] != null ? $refType.fromJson(json[\'$propName\']) : null',
);
} else {
buffer.write('$refType.fromJson(json[\'$propName\'])');
}
} else if (property.type == PropertyType.array) {
//
buffer.write(
'json[\'$propName\'] != null ? List<dynamic>.from(json[\'$propName\']) : null',
);
} else {
//
if (property.nullable) {
buffer.write('json[\'$propName\'] as $dartType?');
} else {
buffer.write('json[\'$propName\'] as $dartType');
}
}
buffer.writeln(',');
});
buffer.writeln(' );');
}
buffer.writeln(' }');
buffer.writeln('');
// toJson
buffer.writeln(' Map<String, dynamic> toJson() {');
buffer.writeln(' return {');
model.properties.forEach((propName, property) {
final dartPropName = StringUtils.toDartPropertyName(propName);
if (property.type == PropertyType.reference &&
property.reference != null) {
buffer.write(' \'$propName\': $dartPropName?.toJson()');
} else if (property.type == PropertyType.array) {
buffer.write(' \'$propName\': $dartPropName');
} else {
buffer.write(' \'$propName\': $dartPropName');
}
buffer.writeln(',');
});
buffer.writeln(' };');
buffer.writeln(' }');
buffer.writeln('');
// copyWith
if (model.properties.isNotEmpty) {
buffer.writeln(' $className copyWith({');
model.properties.forEach((propName, property) {
final dartType = getDartPropertyType(property);
final dartPropName = StringUtils.toDartPropertyName(propName);
buffer.writeln(' $dartType? $dartPropName,');
});
buffer.writeln(' }) {');
buffer.writeln(' return $className(');
model.properties.forEach((propName, property) {
final dartPropName = StringUtils.toDartPropertyName(propName);
buffer.writeln(
' $dartPropName: $dartPropName ?? this.$dartPropName,',
);
});
buffer.writeln(' );');
buffer.writeln(' }');
buffer.writeln('');
}
buffer.writeln('}');
return buffer.toString();
}
///
String _generateAnnotatedModelCodeWithoutImports(ApiModel model) {
final className = StringUtils.generateClassName(model.name);
final buffer = StringBuffer();
// 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()');
buffer.writeln('class $className {');
//
model.properties.forEach((propName, property) {
final dartType = getDartPropertyType(property);
final nullable = property.nullable ? '?' : '';
final dartPropName = StringUtils.toDartPropertyName(propName);
if (property.description.isNotEmpty) {
buffer.writeln(
' ${StringUtils.generateComment(property.description)}',
);
}
// JsonKey注解
final needsJsonKey =
_needsJsonKeyAnnotation(dartPropName, propName, property);
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);
final required = property.required ? 'required ' : '';
buffer.writeln(' ${required}this.$dartPropName,');
});
buffer.writeln(' });');
}
buffer.writeln('');
// fromJson
buffer.writeln(
' factory $className.fromJson(Map<String, dynamic> json) => _\$${className}FromJson(json);',
);
buffer.writeln('');
// toJson
buffer.writeln(
' Map<String, dynamic> toJson() => _\$${className}ToJson(this);',
);
buffer.writeln('');
buffer.writeln('}');
return buffer.toString();
}
///
String generateIndexFile(List<String> modelFileNames) {
final buffer = StringBuffer();
buffer.writeln(generateFileHeader('API 模型导出文件'));
buffer.writeln('');
// library
buffer.writeln('library;');
buffer.writeln('');
//
final sortedFiles = List<String>.from(modelFileNames)..sort();
for (final fileName in sortedFiles) {
buffer.writeln('export \'$fileName\';');
}
return generateTypeCheckedCode(buffer.toString());
}
/// JsonKey注解以及注解的内容
String _needsJsonKeyAnnotation(
String dartPropName, String propName, ApiProperty property) {
final annotations = <String>[];
// JSON字段名不同时需要name参数
if (dartPropName != propName) {
annotations.add('name: \'$propName\'');
}
// DateTime类型需要特殊处理
if (property.type == PropertyType.string &&
(property.format == 'date-time' || property.format == 'date')) {
// DateTime类型json_annotation会自动处理
// annotations.add('fromJson: DateTime.parse, toJson: _dateTimeToString');
}
//
if (property.type == PropertyType.reference) {
//
//
}
//
// if (shouldIgnore) {
// annotations.add('ignore: true');
// }
return annotations.join(', ');
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,412 @@
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
import '../core/config.dart';
import '../core/exceptions.dart';
import '../core/models.dart';
import '../utils/cache_manager.dart';
import '../utils/performance_monitor.dart';
import '../utils/string_utils.dart';
import '../utils/type_validator.dart';
/// Swagger数据解析器
/// Swagger JSON文档并提取相关信息
class SwaggerDataParser {
final CacheManager _cacheManager;
final PerformanceMonitor _performanceMonitor;
final TypeValidator _typeValidator;
//
SwaggerDocument? _cachedDocument;
SwaggerDataParser()
: _cacheManager = CacheManager(),
_performanceMonitor = PerformanceMonitor(),
_typeValidator = TypeValidator();
/// Swagger JSON文档
Future<SwaggerDocument> fetchAndParseSwaggerDocument() async {
//
if (_cachedDocument != null) {
return _cachedDocument!;
}
return _performanceMonitor.measure(
'fetchAndParseSwaggerDocument',
() async {
try {
print('🔄 正在获取Swagger JSON文档...');
Map<String, dynamic> jsonData;
if (SwaggerConfig.swaggerJsonUrl.startsWith('file://')) {
//
final filePath =
SwaggerConfig.swaggerJsonUrl.replaceFirst('file://', '');
final file = File(filePath);
if (await file.exists()) {
final content = await file.readAsString();
jsonData = json.decode(content) as Map<String, dynamic>;
} else {
throw SwaggerParseException(
'本地文件不存在',
url: SwaggerConfig.swaggerJsonUrl,
details: '文件路径: $filePath',
);
}
} else {
// URL
final response = await http.get(
Uri.parse(SwaggerConfig.swaggerJsonUrl),
headers: SwaggerConfig.httpHeaders,
);
if (response.statusCode == 200) {
jsonData = json.decode(response.body) as Map<String, dynamic>;
} else {
throw SwaggerParseException(
'HTTP请求失败',
url: SwaggerConfig.swaggerJsonUrl,
statusCode: response.statusCode,
details: 'HTTP响应状态码: ${response.statusCode}',
);
}
}
_cachedDocument = await parseSwaggerDocument(jsonData);
print('✅ Swagger文档解析完成');
return _cachedDocument!;
} catch (e) {
if (e is SwaggerParseException) {
rethrow;
}
throw SwaggerParseException(
'获取Swagger文档失败',
url: SwaggerConfig.swaggerJsonUrl,
details: e.toString(),
);
}
},
);
}
/// Swagger JSON文档
Future<SwaggerDocument> parseSwaggerDocument(
Map<String, dynamic> jsonData,
) async {
return _performanceMonitor.measure('parseSwaggerDocument', () async {
//
final cacheKey = 'swagger_doc_${jsonData.hashCode}';
final cachedResult = _cacheManager.get<SwaggerDocument>(cacheKey);
if (cachedResult != null) {
_cachedDocument = cachedResult;
return cachedResult;
}
//
final info = jsonData['info'] as Map<String, dynamic>? ?? {};
final title = info['title'] as String? ?? 'API Documentation';
final version = info['version'] as String? ?? '1.0.0';
final description = info['description'] as String? ?? '';
//
final host = jsonData['host'] as String? ?? '';
final basePath = jsonData['basePath'] as String? ?? '/';
final schemes = List<String>.from(jsonData['schemes'] ?? ['https']);
final consumes = List<String>.from(jsonData['consumes'] ?? []);
final produces = List<String>.from(jsonData['produces'] ?? []);
// tags信息 ()
final tagsInfo = _parseTagsInfo(jsonData);
// API路径
final paths = _parseApiPaths(jsonData);
// API模型
final models = _parseApiModels(jsonData);
// API控制器 (tags信息)
final controllers = _parseApiControllers(paths, tagsInfo);
final document = SwaggerDocument(
title: title,
version: version,
description: description,
host: host,
basePath: basePath,
schemes: schemes,
consumes: consumes,
produces: produces,
paths: paths,
models: models,
controllers: controllers,
);
//
_cacheManager.put(cacheKey, document);
_cachedDocument = document;
return document;
});
}
/// tags信息
Map<String, String> _parseTagsInfo(Map<String, dynamic> jsonData) {
final tagsInfo = <String, String>{};
try {
final tags = jsonData['tags'] as List<dynamic>?;
if (tags != null) {
for (final tag in tags) {
if (tag is Map<String, dynamic>) {
final name = tag['name'] as String?;
final description = tag['description'] as String?;
if (name != null && description != null) {
tagsInfo[name] = description;
}
}
}
}
} catch (e) {
print('⚠️ 解析tags信息时发生错误: $e');
}
return tagsInfo;
}
/// API路径
Map<String, ApiPath> _parseApiPaths(Map<String, dynamic> jsonData) {
final paths = <String, ApiPath>{};
final pathsData = jsonData['paths'] as Map<String, dynamic>?;
if (pathsData == null) {
throw SwaggerParseException('未发现API路径定义');
}
try {
pathsData.forEach((pathKey, pathValue) {
if (pathValue is Map<String, dynamic>) {
pathValue.forEach((methodKey, methodValue) {
if (methodValue is Map<String, dynamic>) {
final method = HttpMethod.fromString(methodKey);
final apiPath = ApiPath.fromJson(
pathKey,
methodKey, // HttpMethod对象
methodValue,
);
final key =
'${method.value.toUpperCase()}_${pathKey.replaceAll('/', '_')}';
paths[key] = apiPath;
}
});
}
});
} catch (e) {
throw SwaggerParseException('解析API路径失败', details: e.toString());
}
return paths;
}
/// API模型
Map<String, ApiModel> _parseApiModels(Map<String, dynamic> jsonData) {
final models = <String, ApiModel>{};
// components/schemas (Swagger 3.0)
final schemas = jsonData['components']?['schemas'] as Map<String, dynamic>?;
// components/schemas definitions (Swagger 2.0)
final definitions = jsonData['definitions'] as Map<String, dynamic>?;
final modelDefinitions = schemas ?? definitions;
if (modelDefinitions == null) {
print(' 未发现模型定义 (components/schemas 或 definitions)');
return models;
}
print(
'🔍 发现模型定义位置: ${schemas != null ? 'components/schemas' : 'definitions'}',
);
try {
modelDefinitions.forEach((name, definition) {
final model = ApiModel.fromJson(
name,
definition as Map<String, dynamic>,
);
models[name] = model;
});
} catch (e) {
throw SwaggerParseException('解析API模型失败', details: e.toString());
}
return models;
}
/// API控制器
Map<String, ApiController> _parseApiControllers(
Map<String, ApiPath> paths,
Map<String, String> tagsInfo,
) {
final controllers = <String, List<ApiPath>>{};
try {
// tags分组API路径
for (final apiPath in paths.values) {
for (final tag in apiPath.tags) {
controllers.putIfAbsent(tag, () => []).add(apiPath);
}
}
//
final result = <String, ApiController>{};
controllers.forEach((name, pathList) {
// tags信息中获取描述
final swaggerDescription = tagsInfo[name];
// 使
final description = SwaggerConfig.getControllerDescription(
name,
swaggerDescription: swaggerDescription,
);
result[name] = ApiController(
name: name,
description: description,
paths: pathList,
);
});
return result;
} catch (e) {
throw SwaggerParseException('解析API控制器失败', details: e.toString());
}
}
///
String parsePropertyType(Map<String, dynamic> propData) {
try {
//
if (propData['type'] != null) {
return propData['type'] as String;
}
// ($ref)
if (propData['\$ref'] != null) {
final ref = propData['\$ref'] as String;
// #/components/schemas/ModelName #/definitions/ModelName
final parts = ref.split('/');
if (parts.isNotEmpty) {
return parts.last;
}
}
//
if (propData['items'] != null) {
final items = propData['items'] as Map<String, dynamic>;
final itemType = parsePropertyType(items);
return 'array<$itemType>';
}
//
return 'string';
} catch (e) {
throw SwaggerParseException('解析属性类型失败', details: e.toString());
}
}
/// Dart类型映射
String getDartType(String swaggerType, String? format) {
switch (swaggerType.toLowerCase()) {
case 'string':
switch (format?.toLowerCase()) {
case 'date':
case 'date-time':
return 'DateTime';
case 'byte':
case 'binary':
return 'String';
default:
return 'String';
}
case 'integer':
switch (format?.toLowerCase()) {
case 'int64':
return 'int';
case 'int32':
default:
return 'int';
}
case 'number':
switch (format?.toLowerCase()) {
case 'float':
case 'double':
return 'double';
default:
return 'double';
}
case 'boolean':
return 'bool';
case 'array':
return 'List<dynamic>';
case 'object':
return 'Map<String, dynamic>';
case 'file':
return 'String';
default:
//
if (swaggerType.startsWith('array<') && swaggerType.endsWith('>')) {
final itemType = swaggerType.substring(6, swaggerType.length - 1);
final dartItemType = getDartType(itemType, null);
return 'List<$dartItemType>';
}
//
return StringUtils.generateClassName(swaggerType);
}
}
///
void clearCache() {
_cachedDocument = null;
_cacheManager.clear();
}
///
Map<String, dynamic> getDocumentStats() {
if (_cachedDocument == null) {
return {};
}
final doc = _cachedDocument!;
// HTTP方法
final methodStats = <String, int>{};
for (final path in doc.paths.values) {
final method = path.method.value;
methodStats[method] = (methodStats[method] ?? 0) + 1;
}
//
final modelStats = <String, int>{};
for (final model in doc.models.values) {
if (model.isEnum) {
modelStats['enum'] = (modelStats['enum'] ?? 0) + 1;
} else {
modelStats['class'] = (modelStats['class'] ?? 0) + 1;
}
}
return {
'title': doc.title,
'version': doc.version,
'paths': doc.paths.length,
'models': doc.models.length,
'controllers': doc.controllers.length,
'methods': methodStats,
'modelTypes': modelStats,
};
}
}

193
lib/swagger_cli_new.dart Normal file
View File

@ -0,0 +1,193 @@
import 'dart:io';
import 'commands/base_command.dart';
import 'commands/generate_command.dart';
import 'core/config.dart';
import 'utils/performance_monitor.dart';
import 'utils/string_utils.dart';
/// Swagger CLI
/// 使CLI工具
class SwaggerCLI {
final Map<String, BaseCommand> _commands = {};
final PerformanceMonitor _monitor = PerformanceMonitor();
SwaggerCLI() {
_registerCommands();
}
///
void _registerCommands() {
_registerCommand(GenerateCommand());
//
// _registerCommand(ParseCommand());
// _registerCommand(ValidateCommand());
// _registerCommand(InfoCommand());
// _registerCommand(TestCommand());
// _registerCommand(CleanCommand());
}
///
void _registerCommand(BaseCommand command) {
_commands[command.name] = command;
}
/// CLI应用程序
Future<int> run(List<String> arguments) async {
try {
_showBanner();
if (arguments.isEmpty ||
arguments.first == 'help' ||
arguments.first == '--help') {
_showHelp();
return 0;
}
final commandName = arguments.first;
final commandArgs =
arguments.length > 1 ? arguments.sublist(1) : <String>[];
//
if (commandName == 'version' || commandName == '--version') {
_showVersion();
return 0;
}
//
final command = _commands[commandName];
if (command == null) {
print('❌ 未知命令: $commandName');
print('');
_showAvailableCommands();
return 1;
}
//
if (commandArgs.contains('--help') || commandArgs.contains('-h')) {
command.showHelp();
return 0;
}
//
final stopwatch = Stopwatch()..start();
final exitCode = await command.execute(commandArgs);
stopwatch.stop();
//
if (exitCode == 0) {
print('');
print('⏱️ 执行时间: ${StringUtils.formatDuration(stopwatch.elapsed)}');
}
return exitCode;
} catch (error, stackTrace) {
print('❌ 应用程序错误: $error');
print('堆栈跟踪: $stackTrace');
return 1;
}
}
///
void _showBanner() {
print('');
print('🚀 Swagger API 代码生成器 v2.0');
print('=====================================');
print('强大的 Swagger API 代码生成工具');
print('');
}
///
void _showHelp() {
print('用法: dart swagger_cli_new.dart <命令> [选项]');
print('');
print('全新的命令式架构,提供更好的可扩展性和用户体验。');
print('');
_showAvailableCommands();
_showGlobalOptions();
_showExamples();
_showContact();
}
///
void _showAvailableCommands() {
print('📋 可用命令:');
print('');
for (final command in _commands.values) {
print(' ${command.name.padRight(12)} ${command.description}');
}
print(' help 显示帮助信息');
print(' version 显示版本信息');
print('');
}
///
void _showGlobalOptions() {
print('🔧 全局选项:');
print(' -h, --help 显示帮助信息');
print(' --version 显示版本信息');
print('');
}
/// 使
void _showExamples() {
print('💡 使用示例:');
print('');
print(' # 生成所有文件');
print(' dart swagger_cli_new.dart generate --all');
print('');
print(' # 只生成模型文件(简洁版本)');
print(' dart swagger_cli_new.dart generate --models --simple');
print('');
print(' # 生成到指定目录并启用性能监控');
print(
' dart swagger_cli_new.dart generate --all --output-dir lib/generated --performance',
);
print('');
print(' # 查看具体命令的帮助');
print(' dart swagger_cli_new.dart generate --help');
print('');
}
///
void _showContact() {
print('🌐 更多信息:');
print(' API文档: ${SwaggerConfig.swaggerJsonUrl}');
print(' 基础URL: ${SwaggerConfig.baseUrl}');
print('');
}
///
void _showVersion() {
print('Swagger CLI v2.0.0');
print('构建于: ${DateTime.now().toIso8601String()}');
print('Dart SDK: ${Platform.version}');
print('');
print('功能特性:');
print('- 🏗️ 模块化命令架构');
print('- 🚀 性能监控和优化');
print('- 🔍 智能类型验证');
print('- 📋 详细的错误报告');
print('- 💾 智能缓存机制');
print('- 📚 丰富的文档生成');
print('');
}
///
// StringUtils.formatDuration
///
List<String> get availableCommands => _commands.keys.toList();
///
BaseCommand? getCommand(String name) => _commands[name];
}
/// CLI应用程序入口点
Future<void> main(List<String> arguments) async {
final cli = SwaggerCLI();
final exitCode = await cli.run(arguments);
exit(exitCode);
}

View File

@ -0,0 +1,125 @@
import 'dart:collection';
import 'string_utils.dart';
///
///
class CacheManager {
static const int _maxMemoryItems = 100;
//
final Map<String, _CacheEntry> _memoryCache = {};
final Queue<String> _accessOrder = Queue<String>();
CacheManager();
///
T? get<T>(String key) {
final entry = _memoryCache[key];
if (entry != null && !entry.isExpired) {
_updateAccessOrder(key);
return entry.value as T?;
}
return null;
}
///
void put<T>(String key, T value, [Duration? ttl]) {
ttl ??= const Duration(hours: 1);
//
if (_memoryCache.length >= _maxMemoryItems) {
_evictOldestItem();
}
final entry = _CacheEntry(
value: value,
expiresAt: DateTime.now().add(ttl),
);
_memoryCache[key] = entry;
_updateAccessOrder(key);
}
///
bool has(String key) {
final entry = _memoryCache[key];
return entry != null && !entry.isExpired;
}
///
void remove(String key) {
_memoryCache.remove(key);
_accessOrder.removeWhere((k) => k == key);
}
///
void clear() {
_memoryCache.clear();
_accessOrder.clear();
}
///
CacheStats getStats() {
return CacheStats(
memoryItems: _memoryCache.length,
diskItems: 0,
hitRate: 0.0,
totalSize: 0,
);
}
/// 访
void _updateAccessOrder(String key) {
_accessOrder.removeWhere((k) => k == key);
_accessOrder.addLast(key);
}
///
void _evictOldestItem() {
if (_accessOrder.isNotEmpty) {
final oldestKey = _accessOrder.removeFirst();
_memoryCache.remove(oldestKey);
}
}
}
///
class _CacheEntry {
final dynamic value;
final DateTime expiresAt;
_CacheEntry({
required this.value,
required this.expiresAt,
});
bool get isExpired => DateTime.now().isAfter(expiresAt);
}
///
class CacheStats {
final int memoryItems;
final int diskItems;
final double hitRate;
final int totalSize;
const CacheStats({
required this.memoryItems,
required this.diskItems,
required this.hitRate,
required this.totalSize,
});
@override
String toString() {
return 'CacheStats{'
'memoryItems: $memoryItems, '
'diskItems: $diskItems, '
'hitRate: ${(hitRate * 100).toStringAsFixed(1)}%, '
'totalSize: ${StringUtils.formatBytes(totalSize)}'
'}';
}
// StringUtils.formatBytes
}

457
lib/utils/file_utils.dart Normal file
View File

@ -0,0 +1,457 @@
import 'dart:io';
import 'package:path/path.dart' as path;
///
///
class FileUtils {
///
static Future<Directory> ensureDirectoryExists(String dirPath) async {
final directory = Directory(dirPath);
if (!await directory.exists()) {
await directory.create(recursive: true);
}
return directory;
}
///
static Future<void> safeWriteFile(String filePath, String content) async {
try {
final file = File(filePath);
final directory = file.parent;
//
if (!await directory.exists()) {
await directory.create(recursive: true);
}
//
await file.writeAsString(content);
} catch (e) {
throw FileSystemException('写入文件失败: $filePath', filePath);
}
}
///
static Future<String> safeReadFile(String filePath) async {
try {
final file = File(filePath);
if (!await file.exists()) {
throw FileSystemException('文件不存在: $filePath', filePath);
}
return await file.readAsString();
} catch (e) {
throw FileSystemException('读取文件失败: $filePath', filePath);
}
}
///
static Future<bool> fileExists(String filePath) async {
return await File(filePath).exists();
}
///
static Future<bool> directoryExists(String dirPath) async {
return await Directory(dirPath).exists();
}
///
static Future<void> deleteFileIfExists(String filePath) async {
final file = File(filePath);
if (await file.exists()) {
await file.delete();
}
}
///
static Future<void> deleteDirectoryIfExists(String dirPath) async {
final directory = Directory(dirPath);
if (await directory.exists()) {
await directory.delete(recursive: true);
}
}
///
static Future<void> copyFile(
String sourcePath, String destinationPath) async {
try {
final sourceFile = File(sourcePath);
final destinationFile = File(destinationPath);
if (!await sourceFile.exists()) {
throw FileSystemException('源文件不存在: $sourcePath', sourcePath);
}
//
final destinationDir = destinationFile.parent;
if (!await destinationDir.exists()) {
await destinationDir.create(recursive: true);
}
await sourceFile.copy(destinationPath);
} catch (e) {
throw FileSystemException('复制文件失败: $sourcePath -> $destinationPath',
sourcePath, e is OSError ? e : null);
}
}
///
static Future<void> moveFile(
String sourcePath, String destinationPath) async {
try {
final sourceFile = File(sourcePath);
final destinationFile = File(destinationPath);
if (!await sourceFile.exists()) {
throw FileSystemException('源文件不存在: $sourcePath', sourcePath);
}
//
final destinationDir = destinationFile.parent;
if (!await destinationDir.exists()) {
await destinationDir.create(recursive: true);
}
await sourceFile.rename(destinationPath);
} catch (e) {
throw FileSystemException('移动文件失败: $sourcePath -> $destinationPath',
sourcePath, e is OSError ? e : null);
}
}
///
static Future<int> getFileSize(String filePath) async {
try {
final file = File(filePath);
if (!await file.exists()) {
return 0;
}
return await file.length();
} catch (e) {
return 0;
}
}
///
static Future<int> getDirectorySize(String dirPath) async {
try {
final directory = Directory(dirPath);
if (!await directory.exists()) {
return 0;
}
int totalSize = 0;
await for (final entity in directory.list(recursive: true)) {
if (entity is File) {
totalSize += await entity.length();
}
}
return totalSize;
} catch (e) {
return 0;
}
}
///
static Future<List<String>> listFiles(String dirPath,
{String? extension}) async {
try {
final directory = Directory(dirPath);
if (!await directory.exists()) {
return [];
}
final files = <String>[];
await for (final entity in directory.list()) {
if (entity is File) {
if (extension == null || entity.path.endsWith(extension)) {
files.add(entity.path);
}
}
}
return files;
} catch (e) {
return [];
}
}
///
static Future<List<String>> listDirectories(String dirPath) async {
try {
final directory = Directory(dirPath);
if (!await directory.exists()) {
return [];
}
final directories = <String>[];
await for (final entity in directory.list()) {
if (entity is Directory) {
directories.add(entity.path);
}
}
return directories;
} catch (e) {
return [];
}
}
///
static Future<String> createBackup(String filePath) async {
try {
final file = File(filePath);
if (!await file.exists()) {
throw FileSystemException('文件不存在: $filePath', filePath);
}
final timestamp = DateTime.now().toIso8601String().replaceAll(':', '-');
final backupPath = '${filePath}.backup.$timestamp';
await file.copy(backupPath);
return backupPath;
} catch (e) {
throw FileSystemException(
'创建备份失败: $filePath', filePath, e is OSError ? e : null);
}
}
///
static Future<void> restoreBackup(
String backupPath, String originalPath) async {
try {
final backupFile = File(backupPath);
if (!await backupFile.exists()) {
throw FileSystemException('备份文件不存在: $backupPath', backupPath);
}
await backupFile.copy(originalPath);
} catch (e) {
throw FileSystemException('恢复备份失败: $backupPath -> $originalPath',
backupPath, e is OSError ? e : null);
}
}
///
static String formatPath(String filePath) {
return path.normalize(filePath);
}
///
static String getFileName(String filePath) {
return path.basename(filePath);
}
///
static String getFileNameWithoutExtension(String filePath) {
return path.basenameWithoutExtension(filePath);
}
///
static String getFileExtension(String filePath) {
return path.extension(filePath);
}
///
static String getDirectoryPath(String filePath) {
return path.dirname(filePath);
}
///
static String joinPath(List<String> parts) {
return path.joinAll(parts);
}
///
static String getRelativePath(String filePath, String basePath) {
return path.relative(filePath, from: basePath);
}
///
static String getAbsolutePath(String filePath) {
return path.absolute(filePath);
}
///
static bool isAbsolute(String filePath) {
return path.isAbsolute(filePath);
}
///
static String sanitizeFileName(String fileName) {
//
return fileName
.replaceAll(RegExp(r'[<>:"/\\|?*]'), '_')
.replaceAll(RegExp(r'\s+'), '_')
.replaceAll(RegExp(r'_{2,}'), '_')
.replaceAll(RegExp(r'^_|_$'), '');
}
///
static Future<String> generateUniqueFileName(
String basePath, String fileName) async {
final directory = Directory(basePath);
final extension = getFileExtension(fileName);
final nameWithoutExt = getFileNameWithoutExtension(fileName);
String uniqueName = fileName;
int counter = 1;
while (await File(path.join(basePath, uniqueName)).exists()) {
uniqueName = '${nameWithoutExt}_$counter$extension';
counter++;
}
return uniqueName;
}
///
static Future<void> batchOperation(
List<String> filePaths,
Future<void> Function(String filePath) operation,
) async {
for (final filePath in filePaths) {
try {
await operation(filePath);
} catch (e) {
print('批量操作失败: $filePath - $e');
}
}
}
///
static Future<List<String>> findFiles(
String searchPath,
String pattern, {
bool recursive = false,
}) async {
try {
final directory = Directory(searchPath);
if (!await directory.exists()) {
return [];
}
final regex = RegExp(pattern);
final foundFiles = <String>[];
await for (final entity in directory.list(recursive: recursive)) {
if (entity is File) {
final fileName = getFileName(entity.path);
if (regex.hasMatch(fileName)) {
foundFiles.add(entity.path);
}
}
}
return foundFiles;
} catch (e) {
return [];
}
}
///
static Future<DateTime?> getFileModifiedTime(String filePath) async {
try {
final file = File(filePath);
if (!await file.exists()) {
return null;
}
final stat = await file.stat();
return stat.modified;
} catch (e) {
return null;
}
}
///
static Future<bool> isFileNewer(String filePath1, String filePath2) async {
final time1 = await getFileModifiedTime(filePath1);
final time2 = await getFileModifiedTime(filePath2);
if (time1 == null || time2 == null) {
return false;
}
return time1.isAfter(time2);
}
///
static Future<String?> calculateFileHash(String filePath) async {
try {
final file = File(filePath);
if (!await file.exists()) {
return null;
}
final bytes = await file.readAsBytes();
return bytes.hashCode.toString();
} catch (e) {
return null;
}
}
///
static String formatFileSize(int bytes) {
if (bytes < 1024) {
return '${bytes}B';
} else if (bytes < 1024 * 1024) {
return '${(bytes / 1024).toStringAsFixed(1)}KB';
} else if (bytes < 1024 * 1024 * 1024) {
return '${(bytes / (1024 * 1024)).toStringAsFixed(1)}MB';
} else {
return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)}GB';
}
}
///
static Future<File> createTempFile(String prefix, {String? suffix}) async {
final tempDir = Directory.systemTemp;
final timestamp = DateTime.now().millisecondsSinceEpoch;
final fileName = '$prefix$timestamp${suffix ?? ''}';
return File(path.join(tempDir.path, fileName));
}
///
static Future<void> cleanupTempFiles(String pattern) async {
try {
final tempDir = Directory.systemTemp;
final regex = RegExp(pattern);
await for (final entity in tempDir.list()) {
if (entity is File) {
final fileName = getFileName(entity.path);
if (regex.hasMatch(fileName)) {
await entity.delete();
}
}
}
} catch (e) {
print('清理临时文件失败: $e');
}
}
/// generator目录路径
static String getProjectRootGeneratorDir() {
final currentDir = Directory.current.path;
return joinPath([currentDir, 'generator']);
}
///
static Future<void> writeFile(String path, String content) async {
await safeWriteFile(path, content);
}
///
static Future<List<FileSystemEntity>> listDirectory(String path) async {
try {
final directory = Directory(path);
if (!await directory.exists()) {
return [];
}
return await directory.list().toList();
} catch (e) {
return [];
}
}
}

View File

@ -0,0 +1,335 @@
import 'dart:convert';
import 'dart:io';
import 'package:logging/logging.dart';
///
///
class PerformanceMonitor {
static final Logger _logger = Logger('PerformanceMonitor');
final Map<String, PerformanceMetric> _metrics = {};
final Map<String, List<Duration>> _measurements = {};
final bool _enabled;
PerformanceMonitor({bool enabled = true}) : _enabled = enabled {
if (_enabled) {
_logger.info('性能监控器已启用');
}
}
///
Future<T> measure<T>(
String operationName, Future<T> Function() operation) async {
if (!_enabled) {
return await operation();
}
final stopwatch = Stopwatch()..start();
try {
final result = await operation();
stopwatch.stop();
_recordMeasurement(operationName, stopwatch.elapsed);
return result;
} catch (e) {
stopwatch.stop();
_recordMeasurement(operationName, stopwatch.elapsed, error: e);
rethrow;
}
}
///
T measureSync<T>(String operationName, T Function() operation) {
if (!_enabled) {
return operation();
}
final stopwatch = Stopwatch()..start();
try {
final result = operation();
stopwatch.stop();
_recordMeasurement(operationName, stopwatch.elapsed);
return result;
} catch (e) {
stopwatch.stop();
_recordMeasurement(operationName, stopwatch.elapsed, error: e);
rethrow;
}
}
///
PerformanceTimer startTimer(String operationName) {
return PerformanceTimer(operationName, this);
}
///
void _recordMeasurement(String operationName, Duration duration,
{dynamic error}) {
if (!_enabled) return;
//
_measurements.putIfAbsent(operationName, () => []);
_measurements[operationName]!.add(duration);
//
if (_metrics.containsKey(operationName)) {
_metrics[operationName]!.addMeasurement(duration, error: error);
} else {
_metrics[operationName] = PerformanceMetric(operationName);
_metrics[operationName]!.addMeasurement(duration, error: error);
}
//
final status = error != null ? 'ERROR' : 'SUCCESS';
_logger.info('$operationName: ${duration.inMilliseconds}ms [$status]');
}
///
Map<String, PerformanceMetric> getMetrics() {
return Map.unmodifiable(_metrics);
}
///
PerformanceMetric? getMetric(String operationName) {
return _metrics[operationName];
}
///
PerformanceReport generateReport() {
final totalOperations =
_metrics.values.fold(0, (sum, metric) => sum + metric.count);
final totalTime = _metrics.values.fold(
Duration.zero,
(sum, metric) => sum + metric.totalTime,
);
return PerformanceReport(
totalOperations: totalOperations,
totalTime: totalTime,
metrics: Map.from(_metrics),
generatedAt: DateTime.now(),
);
}
///
void clear() {
_metrics.clear();
_measurements.clear();
}
///
List<SlowOperation> getSlowOperations(
[Duration threshold = const Duration(milliseconds: 500)]) {
final slowOps = <SlowOperation>[];
for (final metric in _metrics.values) {
if (metric.maxTime >= threshold) {
slowOps.add(SlowOperation(
name: metric.operationName,
maxTime: metric.maxTime,
avgTime: metric.averageTime,
count: metric.count,
));
}
}
//
slowOps.sort((a, b) => b.maxTime.compareTo(a.maxTime));
return slowOps;
}
///
Future<void> exportToFile(String filePath) async {
if (!_enabled) return;
try {
final report = generateReport();
final data = {
'generatedAt': report.generatedAt.toIso8601String(),
'summary': {
'totalOperations': report.totalOperations,
'totalTime': report.totalTime.inMilliseconds,
},
'metrics': report.metrics.map((key, value) => MapEntry(key, {
'operationName': value.operationName,
'count': value.count,
'totalTime': value.totalTime.inMilliseconds,
'averageTime': value.averageTime.inMilliseconds,
'minTime': value.minTime.inMilliseconds,
'maxTime': value.maxTime.inMilliseconds,
'errorCount': value.errorCount,
'lastExecuted': value.lastExecuted?.toIso8601String(),
})),
};
final file = File(filePath);
await file.writeAsString(json.encode(data));
_logger.info('性能数据已导出到: $filePath');
} catch (e) {
_logger.severe('导出性能数据失败: $e');
}
}
///
void printSummary() {
if (!_enabled) {
print('性能监控器已禁用');
return;
}
if (_metrics.isEmpty) {
print('没有性能数据');
return;
}
print('\n🔍 性能监控摘要:');
print('=' * 50);
final sortedMetrics = _metrics.values.toList()
..sort((a, b) => b.totalTime.compareTo(a.totalTime));
for (final metric in sortedMetrics) {
print('${metric.operationName}:');
print(' 执行次数: ${metric.count}');
print(' 总时间: ${metric.totalTime.inMilliseconds}ms');
print(' 平均时间: ${metric.averageTime.inMilliseconds}ms');
print(' 最小时间: ${metric.minTime.inMilliseconds}ms');
print(' 最大时间: ${metric.maxTime.inMilliseconds}ms');
if (metric.errorCount > 0) {
print(' 错误次数: ${metric.errorCount}');
}
print('');
}
final slowOps = getSlowOperations();
if (slowOps.isNotEmpty) {
print('🐌 慢操作 (>500ms):');
for (final op in slowOps.take(5)) {
print(' ${op.name}: ${op.maxTime.inMilliseconds}ms (最大)');
}
}
}
}
///
class PerformanceTimer {
final String operationName;
final PerformanceMonitor monitor;
final Stopwatch _stopwatch;
PerformanceTimer(this.operationName, this.monitor)
: _stopwatch = Stopwatch()..start();
///
void stop({dynamic error}) {
if (!_stopwatch.isRunning) return;
_stopwatch.stop();
monitor._recordMeasurement(operationName, _stopwatch.elapsed, error: error);
}
///
Duration get elapsed => _stopwatch.elapsed;
}
///
class PerformanceMetric {
final String operationName;
int count = 0;
Duration totalTime = Duration.zero;
Duration minTime = Duration.zero;
Duration maxTime = Duration.zero;
int errorCount = 0;
DateTime? lastExecuted;
PerformanceMetric(this.operationName);
///
void addMeasurement(Duration duration, {dynamic error}) {
count++;
totalTime += duration;
lastExecuted = DateTime.now();
if (error != null) {
errorCount++;
}
if (count == 1) {
minTime = duration;
maxTime = duration;
} else {
if (duration < minTime) minTime = duration;
if (duration > maxTime) maxTime = duration;
}
}
///
Duration get averageTime {
if (count == 0) return Duration.zero;
return Duration(microseconds: totalTime.inMicroseconds ~/ count);
}
///
double get successRate {
if (count == 0) return 0.0;
return (count - errorCount) / count;
}
}
///
class PerformanceReport {
final int totalOperations;
final Duration totalTime;
final Map<String, PerformanceMetric> metrics;
final DateTime generatedAt;
const PerformanceReport({
required this.totalOperations,
required this.totalTime,
required this.metrics,
required this.generatedAt,
});
///
PerformanceMetric? get slowestOperation {
if (metrics.isEmpty) return null;
return metrics.values.reduce((a, b) => a.maxTime > b.maxTime ? a : b);
}
///
PerformanceMetric? get mostFrequentOperation {
if (metrics.isEmpty) return null;
return metrics.values.reduce((a, b) => a.count > b.count ? a : b);
}
///
Duration get averageExecutionTime {
if (totalOperations == 0) return Duration.zero;
return Duration(microseconds: totalTime.inMicroseconds ~/ totalOperations);
}
}
///
class SlowOperation {
final String name;
final Duration maxTime;
final Duration avgTime;
final int count;
const SlowOperation({
required this.name,
required this.maxTime,
required this.avgTime,
required this.count,
});
}

343
lib/utils/string_utils.dart Normal file
View File

@ -0,0 +1,343 @@
///
///
///
///
/// #
/// ```dart
/// // snake_case camelCase
/// StringUtils.toDartPropertyName('user_id'); // userId
/// //
/// StringUtils.toDartPropertyName('user-id'); // userId
/// //
/// StringUtils.toDartPropertyName('1st_field'); // n1stField
/// //
/// StringUtils.toDartPropertyName(''); // property
/// ```
///
/// This utility provides string conversion helpers for code generation, such as
/// converting snake_case to camelCase, generating Dart class names, and cleaning descriptions.
///
library;
import '../core/models.dart';
class StringUtils {
/// camelCase
static String toCamelCase(String input) {
if (input.isEmpty) return input;
final parts = input.split('_').where((p) => p.isNotEmpty).toList();
if (parts.isEmpty) return input;
String result = parts.first.toLowerCase();
for (int i = 1; i < parts.length; i++) {
final part = parts[i];
if (part.isNotEmpty) {
result += part[0].toUpperCase() + part.substring(1).toLowerCase();
}
}
return result.isEmpty ? input : result;
}
/// PascalCase
static String toPascalCase(String input) {
if (input.isEmpty) return input;
// 线线
if (input.contains('_')) {
final parts = input.split('_');
String result = '';
for (final part in parts) {
if (part.isNotEmpty) {
//
if (part[0].toUpperCase() == part[0]) {
//
result += part;
} else {
//
result += part[0].toUpperCase() + part.substring(1);
}
}
}
return result.isEmpty ? input : result;
}
// PascalCase格式线
if (input[0].toUpperCase() == input[0]) {
return input;
}
// camelCase格式线
if (RegExp(r'^[a-zA-Z][a-zA-Z0-9]*$').hasMatch(input)) {
return input[0].toUpperCase() + input.substring(1);
}
return input;
}
/// snake_case
static String toSnakeCase(String input) {
if (input.isEmpty) return input;
// PascalCase和camelCase
final result =
input.replaceAllMapped(RegExp(r'([A-Z]+)([A-Z][a-z])'), (match) {
return '${match[1]!.substring(0, match[1]!.length - 1)}_${match[2]}';
}).replaceAllMapped(RegExp(r'([a-z\d])([A-Z])'), (match) {
return '${match[1]}_${match[2]}';
}).toLowerCase();
return result;
}
/// Dart命名规范的属性名
///
/// - snake_casekebab-case camelCase
/// -
/// - n
/// - 'property'
///
/// #
/// ```dart
/// StringUtils.toDartPropertyName('user_id'); // userId
/// StringUtils.toDartPropertyName('user-id'); // userId
/// StringUtils.toDartPropertyName('1st_field'); // n1stField
/// StringUtils.toDartPropertyName('classCadreId'); // classCadreId
/// StringUtils.toDartPropertyName(''); // property
/// ```
static String toDartPropertyName(String propName) {
// 线
if (RegExp(r'^[a-z][a-zA-Z0-9]*$').hasMatch(propName)) {
return propName;
}
//
String result = propName;
//
if (RegExp(r'^[0-9]').hasMatch(result)) {
result = 'n$result';
}
// 线
result = result.replaceAll(RegExp(r'[^a-zA-Z0-9_]'), '_');
// camelCase
result = toCamelCase(result);
//
if (result.isEmpty) {
result = 'property';
}
return result;
}
///
static String cleanDescription(String description) {
if (description.isEmpty) return description;
//
String cleaned = description
.replaceAll(RegExp(r'\s+'), ' ')
.replaceAll(RegExp(r'[\r\n]+'), ' ')
.trim();
//
cleaned = cleaned
.replaceAll(RegExp(r'[^\w\s\u4e00-\u9fa5(),.。:;!?_/\\-]'), '')
.replaceAll(RegExp(r'\s+'), ' ')
.trim();
// 200
if (cleaned.length > 200) {
cleaned = '${cleaned.substring(0, 200)}...';
}
return cleaned;
}
///
static String generateEndpointName(String path, String? operationId) {
// operationId使
if (operationId != null && operationId.isNotEmpty) {
return toCamelCase(operationId);
}
//
String cleanPath = path.replaceFirst('/api/v1', '');
//
if (cleanPath.startsWith('/')) {
cleanPath = cleanPath.substring(1);
}
// camelCase
final parts = cleanPath.split('/');
if (parts.length >= 2) {
final controller = parts[0];
final action = parts[1];
//
if (controller.toLowerCase() == 'login' &&
action.toLowerCase() == 'userlogin') {
return 'login';
}
// camelCase
return toCamelCase('${controller}_$action');
}
return toCamelCase(cleanPath.replaceAll('/', '_'));
}
/// Dart类名
static String generateClassName(String name) {
//
final cleanName = name.replaceAll(RegExp(r'[^a-zA-Z0-9_]'), '_');
return toPascalCase(cleanName);
}
///
static String generateFileName(String name) {
// snake_case并添加.dart扩展名
return '${toSnakeCase(name)}.dart';
}
/// Dart标识符
static bool isValidDartIdentifier(String identifier) {
if (identifier.isEmpty) return false;
// Dart标识符规则线线
final regex = RegExp(r'^[a-zA-Z_][a-zA-Z0-9_]*$');
return regex.hasMatch(identifier);
}
///
static String generateEnumValueName(dynamic value, int index) {
if (value is String) {
//
final cleanValue = value.replaceAll(RegExp(r'[^a-zA-Z0-9_]'), '');
if (cleanValue.isNotEmpty && isValidDartIdentifier(cleanValue)) {
return toCamelCase(cleanValue);
}
}
// 使 value +
return 'value${index + 1}';
}
///
static String extractControllerName(ApiPath path) {
// tags中提取控制器名称
if (path.tags.isNotEmpty) {
return path.tags.first;
}
//
final pathParts = path.path.split('/');
if (pathParts.length > 1) {
return toPascalCase(pathParts[1]);
}
return 'General';
}
///
static String cleanPath(String path) {
//
return path.replaceFirst(RegExp(r'^/api/v\d+'), '');
}
///
static String formatBytes(int bytes) {
if (bytes < 1024) return '${bytes}B';
if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)}KB';
if (bytes < 1024 * 1024 * 1024)
return '${(bytes / (1024 * 1024)).toStringAsFixed(1)}MB';
return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)}GB';
}
///
static String formatDuration(Duration duration) {
if (duration.inMilliseconds >= 1000) {
return '${(duration.inMilliseconds / 1000).toStringAsFixed(2)}';
} else {
return '${duration.inMilliseconds}毫秒';
}
}
///
String escapeString(String input) {
return input
.replaceAll('\\', '\\\\')
.replaceAll('"', '\\"')
.replaceAll('\n', '\\n')
.replaceAll('\r', '\\r')
.replaceAll('\t', '\\t');
}
///
String indent(String text, int spaces) {
final indentation = ' ' * spaces;
return text.split('\n').map((line) => '$indentation$line').join('\n');
}
///
static String generateComment(String text, {bool isBlock = false}) {
if (text.isEmpty) return '';
final cleanText = cleanDescription(text);
if (isBlock) {
return '/**\n * $cleanText\n */';
} else {
return '/// $cleanText';
}
}
/// pluralize
String pluralize(String word) {
if (word.isEmpty) return word;
final lowerWord = word.toLowerCase();
//
const irregularPlurals = {
'child': 'children',
'person': 'people',
'man': 'men',
'woman': 'women',
'mouse': 'mice',
'goose': 'geese',
};
if (irregularPlurals.containsKey(lowerWord)) {
return irregularPlurals[lowerWord]!;
}
//
if (lowerWord.endsWith('y')) {
return '${word.substring(0, word.length - 1)}ies';
} else if (lowerWord.endsWith('s') ||
lowerWord.endsWith('sh') ||
lowerWord.endsWith('ch') ||
lowerWord.endsWith('x') ||
lowerWord.endsWith('z')) {
return '${word}es';
} else {
return '${word}s';
}
}
///
static String generateFileHeader(String description, String source) {
final timestamp = DateTime.now().toIso8601String();
return '''// $description
// Swagger API :
// : $timestamp by Max
// Copyright (C) 2025 YuanXuan. All rights reserved.
''';
}
}

View File

@ -0,0 +1,632 @@
import '../core/models.dart';
///
///
class TypeValidator {
/// API模型
static ValidationResult validateApiModel(ApiModel model) {
final errors = <ValidationError>[];
final warnings = <String>[];
//
if (!_isValidDartIdentifier(model.name)) {
errors.add(
ValidationError(
field: 'name',
message: '模型名称不符合Dart命名规范: ${model.name}',
severity: ErrorSeverity.error,
),
);
}
//
if (model.isEnum) {
final enumValidation = _validateEnum(model);
errors.addAll(enumValidation.errors);
warnings.addAll(enumValidation.warnings);
} else {
//
final propertiesValidation = _validateProperties(model.properties);
errors.addAll(propertiesValidation.errors);
warnings.addAll(propertiesValidation.warnings);
}
return ValidationResult(
isValid: errors.isEmpty,
errors: errors,
warnings: warnings,
);
}
/// API属性
static ValidationResult validateApiProperty(
String propertyName,
ApiProperty property,
) {
final errors = <ValidationError>[];
final warnings = <String>[];
//
if (!_isValidDartIdentifier(propertyName)) {
errors.add(
ValidationError(
field: 'name',
message: '属性名称不符合Dart命名规范: $propertyName',
severity: ErrorSeverity.error,
),
);
}
//
if (!_isValidPropertyType(property.type)) {
errors.add(
ValidationError(
field: 'type',
message: '不支持的属性类型: ${property.type}',
severity: ErrorSeverity.error,
),
);
}
//
if (property.type == PropertyType.reference) {
if (property.reference == null || property.reference!.isEmpty) {
errors.add(
ValidationError(
field: 'reference',
message: '引用类型缺少引用目标',
severity: ErrorSeverity.error,
),
);
} else if (!_isValidDartIdentifier(property.reference!)) {
errors.add(
ValidationError(
field: 'reference',
message: '引用目标不符合Dart命名规范: ${property.reference}',
severity: ErrorSeverity.error,
),
);
}
}
//
if (property.required && property.nullable) {
warnings.add('属性 $propertyName 同时标记为必填和可空,这可能导致逻辑冲突');
}
//
if (property.type == PropertyType.string &&
(property.format == 'date' || property.format == 'date-time')) {
//
if (property.example != null &&
!_isValidDateFormat(property.example.toString())) {
warnings.add('属性 $propertyName 的示例值不符合日期格式');
}
}
return ValidationResult(
isValid: errors.isEmpty,
errors: errors,
warnings: warnings,
);
}
/// API路径
static ValidationResult validateApiPath(ApiPath path) {
final errors = <ValidationError>[];
final warnings = <String>[];
//
if (!path.path.startsWith('/')) {
errors.add(
ValidationError(
field: 'path',
message: 'API路径必须以/开头: ${path.path}',
severity: ErrorSeverity.error,
),
);
}
// ID
if (path.operationId.isNotEmpty &&
!_isValidDartIdentifier(path.operationId)) {
warnings.add('操作ID不符合Dart命名规范: ${path.operationId}');
}
//
for (final param in path.parameters) {
final paramValidation = _validateParameter(param);
errors.addAll(paramValidation.errors);
warnings.addAll(paramValidation.warnings);
}
//
final pathParams = _extractPathParameters(path.path);
final definedPathParams = path.parameters
.where((p) => p.location == ParameterLocation.path)
.map((p) => p.name)
.toSet();
for (final pathParam in pathParams) {
if (!definedPathParams.contains(pathParam)) {
errors.add(
ValidationError(
field: 'parameters',
message: '路径参数 $pathParam 在路径中定义但未在参数列表中声明',
severity: ErrorSeverity.error,
),
);
}
}
return ValidationResult(
isValid: errors.isEmpty,
errors: errors,
warnings: warnings,
);
}
/// Swagger文档
static ValidationResult validateSwaggerDocument(SwaggerDocument document) {
final errors = <ValidationError>[];
final warnings = <String>[];
//
if (document.title.isEmpty) {
warnings.add('文档标题为空');
}
if (document.version.isEmpty) {
warnings.add('文档版本为空');
}
//
for (final model in document.models.values) {
final modelValidation = validateApiModel(model);
errors.addAll(modelValidation.errors);
warnings.addAll(modelValidation.warnings);
}
//
for (final path in document.paths.values) {
final pathValidation = validateApiPath(path);
errors.addAll(pathValidation.errors);
warnings.addAll(pathValidation.warnings);
}
//
final referenceValidation = _validateReferences(document);
errors.addAll(referenceValidation.errors);
warnings.addAll(referenceValidation.warnings);
return ValidationResult(
isValid: errors.isEmpty,
errors: errors,
warnings: warnings,
);
}
///
static ValidationResult validateGeneratedCode(
String code,
CodeType codeType,
) {
final errors = <ValidationError>[];
final warnings = <String>[];
//
if (code.trim().isEmpty) {
errors.add(
ValidationError(
field: 'code',
message: '生成的代码为空',
severity: ErrorSeverity.error,
),
);
}
//
if (!_isBalancedBrackets(code)) {
errors.add(
ValidationError(
field: 'syntax',
message: '代码中存在不匹配的括号',
severity: ErrorSeverity.error,
),
);
}
// Dart语法要素
switch (codeType) {
case CodeType.model:
if (!code.contains('class ') && !code.contains('enum ')) {
errors.add(
ValidationError(
field: 'content',
message: '模型代码必须包含class或enum声明',
severity: ErrorSeverity.error,
),
);
}
break;
case CodeType.endpoints:
if (!code.contains('class ')) {
errors.add(
ValidationError(
field: 'content',
message: '端点代码必须包含class声明',
severity: ErrorSeverity.error,
),
);
}
break;
case CodeType.documentation:
if (!code.contains('#')) {
warnings.add('文档代码似乎不包含Markdown标题');
}
break;
}
//
final identifiers = _extractIdentifiers(code);
final duplicates = _findDuplicates(identifiers);
if (duplicates.isNotEmpty) {
warnings.add('检测到可能的命名冲突: ${duplicates.join(', ')}');
}
return ValidationResult(
isValid: errors.isEmpty,
errors: errors,
warnings: warnings,
);
}
///
static ValidationResult _validateEnum(ApiModel model) {
final errors = <ValidationError>[];
final warnings = <String>[];
if (model.enumValues.isEmpty) {
errors.add(
ValidationError(
field: 'enumValues',
message: '枚举类型必须包含至少一个值',
severity: ErrorSeverity.error,
),
);
}
//
if (model.enumValues.length > 1) {
final firstType = model.enumValues.first.runtimeType;
for (final value in model.enumValues) {
if (value.runtimeType != firstType) {
warnings.add('枚举值类型不一致');
break;
}
}
}
//
final uniqueValues = model.enumValues.toSet();
if (uniqueValues.length != model.enumValues.length) {
errors.add(
ValidationError(
field: 'enumValues',
message: '枚举值存在重复',
severity: ErrorSeverity.error,
),
);
}
return ValidationResult(
isValid: errors.isEmpty,
errors: errors,
warnings: warnings,
);
}
///
static ValidationResult _validateProperties(
Map<String, ApiProperty> properties,
) {
final errors = <ValidationError>[];
final warnings = <String>[];
for (final entry in properties.entries) {
final propertyValidation = validateApiProperty(entry.key, entry.value);
errors.addAll(propertyValidation.errors);
warnings.addAll(propertyValidation.warnings);
}
//
final reservedWords = _getDartReservedWords();
for (final propertyName in properties.keys) {
if (reservedWords.contains(propertyName.toLowerCase())) {
warnings.add('属性名 $propertyName 是Dart保留字可能导致编译错误');
}
}
return ValidationResult(
isValid: errors.isEmpty,
errors: errors,
warnings: warnings,
);
}
///
static ValidationResult _validateParameter(ApiParameter parameter) {
final errors = <ValidationError>[];
final warnings = <String>[];
//
if (!_isValidDartIdentifier(parameter.name)) {
warnings.add('参数名称不符合Dart命名规范: ${parameter.name}');
}
// required
if (parameter.location == ParameterLocation.path && !parameter.required) {
errors.add(
ValidationError(
field: 'required',
message: '路径参数 ${parameter.name} 必须标记为required',
severity: ErrorSeverity.error,
),
);
}
return ValidationResult(
isValid: errors.isEmpty,
errors: errors,
warnings: warnings,
);
}
///
static ValidationResult _validateReferences(SwaggerDocument document) {
final errors = <ValidationError>[];
final warnings = <String>[];
final modelNames = document.models.keys.toSet();
//
for (final model in document.models.values) {
for (final property in model.properties.values) {
if (property.type == PropertyType.reference &&
property.reference != null) {
if (!modelNames.contains(property.reference)) {
errors.add(
ValidationError(
field: 'reference',
message:
'模型 ${model.name} 中的属性 ${property.name} 引用了不存在的类型: ${property.reference}',
severity: ErrorSeverity.error,
),
);
}
}
}
}
return ValidationResult(
isValid: errors.isEmpty,
errors: errors,
warnings: warnings,
);
}
/// Dart标识符
static bool _isValidDartIdentifier(String identifier) {
if (identifier.isEmpty) return false;
// Dart标识符规则线线
final regex = RegExp(r'^[a-zA-Z_][a-zA-Z0-9_]*$');
return regex.hasMatch(identifier);
}
///
static bool _isValidPropertyType(PropertyType type) {
//
return true;
}
///
static bool _isValidDateFormat(String dateString) {
try {
DateTime.parse(dateString);
return true;
} catch (e) {
return false;
}
}
///
static Set<String> _extractPathParameters(String path) {
final regex = RegExp(r'\{(\w+)\}');
final matches = regex.allMatches(path);
return matches.map((match) => match.group(1)!).toSet();
}
///
static bool _isBalancedBrackets(String code) {
final brackets = {'{': '}', '(': ')', '[': ']'};
final stack = <String>[];
for (final char in code.split('')) {
if (brackets.containsKey(char)) {
stack.add(char);
} else if (brackets.containsValue(char)) {
if (stack.isEmpty) return false;
final last = stack.removeLast();
if (brackets[last] != char) return false;
}
}
return stack.isEmpty;
}
///
static List<String> _extractIdentifiers(String code) {
final regex = RegExp(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b');
final matches = regex.allMatches(code);
return matches.map((match) => match.group(0)!).toList();
}
///
static List<String> _findDuplicates(List<String> items) {
final seen = <String>{};
final duplicates = <String>{};
for (final item in items) {
if (seen.contains(item)) {
duplicates.add(item);
} else {
seen.add(item);
}
}
return duplicates.toList();
}
/// Dart保留字列表
static Set<String> _getDartReservedWords() {
return {
'abstract',
'as',
'assert',
'async',
'await',
'break',
'case',
'catch',
'class',
'const',
'continue',
'default',
'deferred',
'do',
'dynamic',
'else',
'enum',
'export',
'extends',
'external',
'factory',
'false',
'final',
'finally',
'for',
'function',
'get',
'hide',
'if',
'implements',
'import',
'in',
'interface',
'is',
'library',
'mixin',
'new',
'null',
'on',
'operator',
'part',
'required',
'rethrow',
'return',
'set',
'show',
'static',
'super',
'switch',
'sync',
'this',
'throw',
'true',
'try',
'typedef',
'var',
'void',
'while',
'with',
'yield',
};
}
}
///
class ValidationResult {
final bool isValid;
final List<ValidationError> errors;
final List<String> warnings;
const ValidationResult({
required this.isValid,
required this.errors,
required this.warnings,
});
///
bool get hasErrors => errors.isNotEmpty;
///
bool get hasWarnings => warnings.isNotEmpty;
///
List<ValidationError> get criticalErrors =>
errors.where((e) => e.severity == ErrorSeverity.error).toList();
///
String generateReport() {
final buffer = StringBuffer();
if (isValid) {
buffer.writeln('✅ 验证通过');
} else {
buffer.writeln('❌ 验证失败');
}
if (errors.isNotEmpty) {
buffer.writeln('\n🚨 错误:');
for (final error in errors) {
buffer.writeln(
'- [${error.severity.name.toUpperCase()}] ${error.field}: ${error.message}',
);
}
}
if (warnings.isNotEmpty) {
buffer.writeln('\n⚠️ 警告:');
for (final warning in warnings) {
buffer.writeln('- $warning');
}
}
return buffer.toString();
}
}
///
class ValidationError {
final String field;
final String message;
final ErrorSeverity severity;
const ValidationError({
required this.field,
required this.message,
required this.severity,
});
@override
String toString() {
return 'ValidationError(field: $field, message: $message, severity: $severity)';
}
}
///
enum ErrorSeverity { warning, error, critical }
///
enum CodeType { model, endpoints, documentation }

637
pubspec.lock Normal file
View File

@ -0,0 +1,637 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_fe_analyzer_shared:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f
url: "https://pub.flutter-io.cn"
source: hosted
version: "85.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
sha256: de617bfdc64f3d8b00835ec2957441ceca0a29cdf7881f7ab231bc14f71159c0
url: "https://pub.flutter-io.cn"
source: hosted
version: "7.5.6"
args:
dependency: transitive
description:
name: args
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.7.0"
async:
dependency: transitive
description:
name: async
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.13.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.2"
build:
dependency: transitive
description:
name: build
sha256: "51dc711996cbf609b90cbe5b335bbce83143875a9d58e4b5c6d3c4f684d3dda7"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.5.4"
build_config:
dependency: transitive
description:
name: build_config
sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.2"
build_daemon:
dependency: transitive
description:
name: build_daemon
sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa"
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.0.4"
build_resolvers:
dependency: transitive
description:
name: build_resolvers
sha256: ee4257b3f20c0c90e72ed2b57ad637f694ccba48839a821e87db762548c22a62
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.5.4"
build_runner:
dependency: "direct dev"
description:
name: build_runner
sha256: "382a4d649addbfb7ba71a3631df0ec6a45d5ab9b098638144faf27f02778eb53"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.5.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:
dependency: transitive
description:
name: built_collection
sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.1.1"
built_value:
dependency: transitive
description:
name: built_value
sha256: "082001b5c3dc495d4a42f1d5789990505df20d8547d42507c29050af6933ee27"
url: "https://pub.flutter-io.cn"
source: hosted
version: "8.10.1"
characters:
dependency: transitive
description:
name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.4.0"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.4"
cli_config:
dependency: transitive
description:
name: cli_config
sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.0"
clock:
dependency: transitive
description:
name: clock
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.2"
code_builder:
dependency: transitive
description:
name: code_builder
sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e"
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.10.1"
collection:
dependency: transitive
description:
name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.19.1"
convert:
dependency: transitive
description:
name: convert
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.1.2"
coverage:
dependency: transitive
description:
name: coverage
sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.15.0"
crypto:
dependency: transitive
description:
name: crypto
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.6"
dart_style:
dependency: transitive
description:
name: dart_style
sha256: "5b236382b47ee411741447c1f1e111459c941ea1b3f2b540dde54c210a3662af"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.1.0"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.3.3"
file:
dependency: transitive
description:
name: file
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.flutter-io.cn"
source: hosted
version: "7.0.1"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.1"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
frontend_server_client:
dependency: transitive
description:
name: frontend_server_client
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.0.0"
glob:
dependency: transitive
description:
name: glob
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.3"
graphs:
dependency: transitive
description:
name: graphs
sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.3.2"
http:
dependency: "direct main"
description:
name: http
sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.4.0"
http_multi_server:
dependency: transitive
description:
name: http_multi_server
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.2.2"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.1.2"
io:
dependency: transitive
description:
name: io
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
url: "https://pub.flutter-io.cn"
source: hosted
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:
dependency: "direct main"
description:
name: json_annotation
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.9.0"
json_serializable:
dependency: "direct dev"
description:
name: json_serializable
sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c
url: "https://pub.flutter-io.cn"
source: hosted
version: "6.9.5"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
url: "https://pub.flutter-io.cn"
source: hosted
version: "10.0.9"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.9"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.1"
logging:
dependency: transitive
description:
name: logging
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.3.0"
matcher:
dependency: transitive
description:
name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.12.17"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.16.0"
mime:
dependency: transitive
description:
name: mime
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.0"
node_preamble:
dependency: transitive
description:
name: node_preamble
sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.2"
package_config:
dependency: transitive
description:
name: package_config
sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.2.0"
path:
dependency: transitive
description:
name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.9.1"
pool:
dependency: transitive
description:
name: pool
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.5.1"
pub_semver:
dependency: transitive
description:
name: pub_semver
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.2.0"
pubspec_parse:
dependency: transitive
description:
name: pubspec_parse
sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.5.0"
shelf:
dependency: transitive
description:
name: shelf
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.4.2"
shelf_packages_handler:
dependency: transitive
description:
name: shelf_packages_handler
sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.2"
shelf_static:
dependency: transitive
description:
name: shelf_static
sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.3"
shelf_web_socket:
dependency: transitive
description:
name: shelf_web_socket
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.0"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
source_gen:
dependency: transitive
description:
name: source_gen
sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.0"
source_helper:
dependency: transitive
description:
name: source_helper
sha256: "4f81479fe5194a622cdd1713fe1ecb683a6e6c85cd8cec8e2e35ee5ab3fdf2a1"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.3.6"
source_map_stack_trace:
dependency: transitive
description:
name: source_map_stack_trace
sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.2"
source_maps:
dependency: transitive
description:
name: source_maps
sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.10.13"
source_span:
dependency: transitive
description:
name: source_span
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.10.1"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.12.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.4"
stream_transform:
dependency: transitive
description:
name: stream_transform
sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.1"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.4.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.2.2"
test:
dependency: "direct dev"
description:
name: test
sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.25.15"
test_api:
dependency: transitive
description:
name: test_api
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.7.4"
test_core:
dependency: transitive
description:
name: test_core
sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.6.8"
timing:
dependency: transitive
description:
name: timing
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.2"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.4.0"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.4"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
url: "https://pub.flutter-io.cn"
source: hosted
version: "15.0.0"
watcher:
dependency: transitive
description:
name: watcher
sha256: "0b7fd4a0bbc4b92641dbf20adfd7e3fd1398fe17102d94b674234563e110088a"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.2"
web:
dependency: transitive
description:
name: web
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.1"
web_socket:
dependency: transitive
description:
name: web_socket
sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.1"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.3"
webkit_inspection_protocol:
dependency: transitive
description:
name: webkit_inspection_protocol
sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.2.1"
yaml:
dependency: transitive
description:
name: yaml
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.8.0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"

27
pubspec.yaml Normal file
View File

@ -0,0 +1,27 @@
name: swagger_generator_flutter
description: A Flutter project using generated API models
version: 1.0.0+1
environment:
sdk: '>=3.0.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
# 推荐版本必需的依赖
json_annotation: ^4.8.1
http: ^1.1.0
dev_dependencies:
flutter_test:
sdk: flutter
# 代码生成必需的依赖
build_runner: ^2.4.7
json_serializable: ^6.7.1
test: ^1.24.0
flutter:
uses-material-design: true

65
run_swagger.sh Executable file
View File

@ -0,0 +1,65 @@
#!/bin/bash
# 简化版 Swagger CLI 运行脚本
# 提供便捷的命令行界面
# 颜色定义
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# 脚本路径
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CLI_DART_FILE="$SCRIPT_DIR/bin/main.dart"
# 显示帮助
show_help() {
echo -e "${CYAN}🚀 Swagger CLI 工具${NC}"
echo ""
echo -e "${YELLOW}用法: $0 [命令] [选项]${NC}"
echo ""
echo -e "${GREEN}快速命令:${NC}"
echo -e " $0 all # 生成所有文件"
echo -e " $0 models # 生成数据模型"
echo -e " $0 endpoints # 生成API端点"
echo -e " $0 docs # 生成API文档"
echo -e " $0 api # 生成Retrofit API"
echo ""
echo -e "${GREEN}直接使用:${NC}"
echo -e " dart run bin/main.dart generate --help"
echo ""
}
# 主函数
main() {
if [ $# -eq 0 ] || [ "$1" = "help" ] || [ "$1" = "--help" ]; then
show_help
exit 0
fi
case "$1" in
all)
dart run "$CLI_DART_FILE" generate --models --api --split-by-tags
;;
models)
dart run "$CLI_DART_FILE" generate --models
;;
endpoints)
dart run "$CLI_DART_FILE" generate --endpoints
;;
docs)
dart run "$CLI_DART_FILE" generate --docs
;;
api)
dart run "$CLI_DART_FILE" generate --api
;;
*)
echo -e "${YELLOW}未知命令: $1${NC}"
show_help
exit 1
;;
esac
}
main "$@"

20
test_property_name.dart Normal file
View File

@ -0,0 +1,20 @@
import 'lib/utils/string_utils.dart';
void main() {
//
print('Testing property name conversion:');
print('classCadreId -> ${StringUtils.toDartPropertyName('classCadreId')}');
print('meetingTitle -> ${StringUtils.toDartPropertyName('meetingTitle')}');
print('taskInfo -> ${StringUtils.toDartPropertyName('taskInfo')}');
print(
'sunTaskUserResults -> ${StringUtils.toDartPropertyName('sunTaskUserResults')}');
print(
'sunTaskFileResults -> ${StringUtils.toDartPropertyName('sunTaskFileResults')}');
// 线
print('\nTesting snake_case conversion:');
print(
'class_cadre_id -> ${StringUtils.toDartPropertyName('class_cadre_id')}');
print('meeting_title -> ${StringUtils.toDartPropertyName('meeting_title')}');
print('task_info -> ${StringUtils.toDartPropertyName('task_info')}');
}

923
tests/models_test.dart Normal file
View File

@ -0,0 +1,923 @@
import 'package:test/test.dart';
import '../lib/core/models.dart';
void main() {
group('ApiPath', () {
test('creates ApiPath with required fields', () {
final path = ApiPath(
path: '/api/users',
method: HttpMethod.get,
summary: 'Get users',
description: 'Retrieve all users',
operationId: 'getUsers',
tags: ['User'],
parameters: [],
responses: {},
requestBody: null,
);
expect(path.path, '/api/users');
expect(path.method, HttpMethod.get);
expect(path.summary, 'Get users');
expect(path.description, 'Retrieve all users');
expect(path.tags, ['User']);
expect(path.parameters, isEmpty);
expect(path.responses, isEmpty);
expect(path.requestBody, isNull);
});
test('creates ApiPath with all fields', () {
final parameters = [
ApiParameter(
name: 'id',
location: ParameterLocation.path,
required: true,
type: PropertyType.integer,
description: 'User ID',
),
];
final responses = {
'200': ApiResponse(
code: '200',
description: 'Success',
schema: {'type': 'object'},
content: null,
),
};
final requestBody = ApiRequestBody(
description: 'User data',
required: true,
content: {
'application/json': {
'schema': {'type': 'object'}
}
},
);
final path = ApiPath(
path: '/api/users/{id}',
method: HttpMethod.put,
summary: 'Update user',
description: 'Update user by ID',
operationId: 'updateUser',
tags: ['User'],
parameters: parameters,
responses: responses,
requestBody: requestBody,
);
expect(path.parameters, hasLength(1));
expect(path.responses, hasLength(1));
expect(path.requestBody, isNotNull);
});
});
group('ApiParameter', () {
test('creates ApiParameter with required fields', () {
final param = ApiParameter(
name: 'id',
location: ParameterLocation.path,
required: true,
type: PropertyType.integer,
description: 'User ID',
);
expect(param.name, 'id');
expect(param.location, ParameterLocation.path);
expect(param.required, true);
expect(param.type, PropertyType.integer);
expect(param.description, 'User ID');
});
test('creates ApiParameter with optional fields', () {
final param = ApiParameter(
name: 'page',
location: ParameterLocation.query,
required: false,
type: PropertyType.integer,
description: 'Page number',
format: 'int32',
example: 1,
defaultValue: 1,
);
expect(param.format, 'int32');
expect(param.example, 1);
expect(param.defaultValue, 1);
});
test('creates ApiParameter from JSON', () {
final json = {
'name': 'id',
'in': 'path',
'required': true,
'type': 'integer',
'description': 'User ID',
'format': 'int64',
'example': 123,
'default': 1,
};
final param = ApiParameter.fromJson(json);
expect(param.name, 'id');
expect(param.location, ParameterLocation.path);
expect(param.required, true);
expect(param.type, PropertyType.integer);
expect(param.description, 'User ID');
expect(param.format, 'int64');
expect(param.example, 123);
expect(param.defaultValue, 1);
});
});
group('ApiResponse', () {
test('creates ApiResponse with required fields', () {
final response = ApiResponse(
code: '200',
description: 'Success',
schema: null,
content: null,
);
expect(response.code, '200');
expect(response.description, 'Success');
expect(response.schema, isNull);
expect(response.content, isNull);
});
test('creates ApiResponse with schema', () {
final schema = {
'type': 'object',
'properties': {
'id': {'type': 'integer'},
'name': {'type': 'string'},
},
};
final response = ApiResponse(
code: '200',
description: 'Success',
schema: schema,
content: null,
);
expect(response.schema, equals(schema));
});
test('creates ApiResponse from JSON', () {
final json = {
'description': 'Success',
'schema': {'type': 'object'},
'content': {
'application/json': {
'schema': {'type': 'object'},
},
},
};
final response = ApiResponse.fromJson('200', json);
expect(response.code, '200');
expect(response.description, 'Success');
expect(response.schema, isNotNull);
expect(response.content, isNotNull);
});
});
group('ApiRequestBody', () {
test('creates ApiRequestBody with required fields', () {
final requestBody = ApiRequestBody(
description: 'User data',
required: true,
content: null,
);
expect(requestBody.description, 'User data');
expect(requestBody.required, true);
expect(requestBody.content, isNull);
});
test('creates ApiRequestBody with content', () {
final content = {
'application/json': {
'schema': {
'type': 'object',
'properties': {
'name': {'type': 'string'},
'email': {'type': 'string'},
},
},
},
};
final requestBody = ApiRequestBody(
description: 'User data',
required: true,
content: content,
);
expect(requestBody.content, equals(content));
});
test('creates ApiRequestBody from JSON', () {
final json = {
'description': 'User data',
'required': true,
'content': {
'application/json': {
'schema': {'type': 'object'},
},
},
};
final requestBody = ApiRequestBody.fromJson(json);
expect(requestBody.description, 'User data');
expect(requestBody.required, true);
expect(requestBody.content, isNotNull);
});
});
group('ApiModel', () {
test('creates ApiModel with required fields', () {
final model = ApiModel(
name: 'User',
description: 'User model',
properties: {},
required: [],
);
expect(model.name, 'User');
expect(model.description, 'User model');
expect(model.properties, isEmpty);
expect(model.required, isEmpty);
expect(model.isEnum, false);
expect(model.enumValues, isEmpty);
expect(model.enumType, isNull);
});
test('creates ApiModel with properties', () {
final properties = {
'id': ApiProperty(
name: 'id',
type: PropertyType.integer,
description: 'User ID',
required: true,
),
'name': ApiProperty(
name: 'name',
type: PropertyType.string,
description: 'User name',
required: true,
),
};
final model = ApiModel(
name: 'User',
description: 'User model',
properties: properties,
required: ['id', 'name'],
);
expect(model.properties, hasLength(2));
expect(model.required, ['id', 'name']);
});
test('creates enum ApiModel', () {
final model = ApiModel(
name: 'UserStatus',
description: 'User status enum',
properties: {},
required: [],
isEnum: true,
enumValues: ['active', 'inactive', 'pending'],
enumType: PropertyType.string,
);
expect(model.isEnum, true);
expect(model.enumValues, ['active', 'inactive', 'pending']);
expect(model.enumType, PropertyType.string);
});
test('creates ApiModel from JSON', () {
final json = {
'description': 'User model',
'properties': {
'id': {
'type': 'integer',
'description': 'User ID',
},
'name': {
'type': 'string',
'description': 'User name',
},
},
'required': ['id', 'name'],
};
final model = ApiModel.fromJson('User', json);
expect(model.name, 'User');
expect(model.description, 'User model');
expect(model.properties, hasLength(2));
expect(model.required, ['id', 'name']);
});
test('creates enum ApiModel from JSON', () {
final json = {
'description': 'User status enum',
'enum': ['active', 'inactive', 'pending'],
'type': 'string',
};
final model = ApiModel.fromJson('UserStatus', json);
expect(model.isEnum, true);
expect(model.enumValues, ['active', 'inactive', 'pending']);
expect(model.enumType, PropertyType.string);
});
});
group('ApiProperty', () {
test('creates ApiProperty with required fields', () {
final property = ApiProperty(
name: 'id',
type: PropertyType.integer,
description: 'User ID',
required: true,
);
expect(property.name, 'id');
expect(property.type, PropertyType.integer);
expect(property.description, 'User ID');
expect(property.required, true);
expect(property.nullable, false);
});
test('creates ApiProperty with optional fields', () {
final property = ApiProperty(
name: 'name',
type: PropertyType.string,
description: 'User name',
required: false,
nullable: true,
format: 'string',
example: 'John Doe',
defaultValue: 'Unknown',
reference: 'User',
);
expect(property.nullable, true);
expect(property.format, 'string');
expect(property.example, 'John Doe');
expect(property.defaultValue, 'Unknown');
expect(property.reference, 'User');
});
test('creates ApiProperty from JSON', () {
final json = {
'type': 'string',
'description': 'User name',
'nullable': true,
'format': 'string',
'example': 'John Doe',
'default': 'Unknown',
};
final property = ApiProperty.fromJson('name', json, []);
expect(property.name, 'name');
expect(property.type, PropertyType.string);
expect(property.description, 'User name');
expect(property.nullable, true);
expect(property.format, 'string');
expect(property.example, 'John Doe');
expect(property.defaultValue, 'Unknown');
});
test('creates ApiProperty with reference', () {
final json = {
'type': 'object',
'description': 'User object',
'\$ref': '#/components/schemas/User',
};
final property = ApiProperty.fromJson('user', json, []);
expect(property.type, PropertyType.reference);
expect(property.reference, 'User');
});
test('creates ApiProperty with array items', () {
final json = {
'type': 'array',
'description': 'User list',
'items': {
'type': 'object',
'\$ref': '#/components/schemas/User',
},
};
final property = ApiProperty.fromJson('users', json, []);
expect(property.type, PropertyType.array);
expect(property.items, isNotNull);
expect(property.items!.name, 'User');
});
});
group('PropertyType', () {
test('converts string to PropertyType', () {
expect(PropertyType.fromString('string'), PropertyType.string);
expect(PropertyType.fromString('integer'), PropertyType.integer);
expect(PropertyType.fromString('number'), PropertyType.number);
expect(PropertyType.fromString('boolean'), PropertyType.boolean);
expect(PropertyType.fromString('array'), PropertyType.array);
expect(PropertyType.fromString('object'), PropertyType.object);
expect(PropertyType.fromString('unknown'), PropertyType.string);
});
test('gets PropertyType value', () {
expect(PropertyType.string.value, 'string');
expect(PropertyType.integer.value, 'integer');
expect(PropertyType.number.value, 'number');
expect(PropertyType.boolean.value, 'boolean');
expect(PropertyType.array.value, 'array');
expect(PropertyType.object.value, 'object');
expect(PropertyType.reference.value, 'reference');
});
});
group('ParameterLocation', () {
test('converts string to ParameterLocation', () {
expect(ParameterLocation.fromString('query'), ParameterLocation.query);
expect(ParameterLocation.fromString('path'), ParameterLocation.path);
expect(ParameterLocation.fromString('header'), ParameterLocation.header);
expect(ParameterLocation.fromString('cookie'), ParameterLocation.cookie);
expect(ParameterLocation.fromString('unknown'), ParameterLocation.query);
});
});
group('HttpMethod', () {
test('converts string to HttpMethod', () {
expect(HttpMethod.fromString('GET'), HttpMethod.get);
expect(HttpMethod.fromString('POST'), HttpMethod.post);
expect(HttpMethod.fromString('PUT'), HttpMethod.put);
expect(HttpMethod.fromString('DELETE'), HttpMethod.delete);
expect(HttpMethod.fromString('PATCH'), HttpMethod.patch);
expect(HttpMethod.fromString('HEAD'), HttpMethod.head);
expect(HttpMethod.fromString('OPTIONS'), HttpMethod.options);
});
test('handles case insensitive input', () {
expect(HttpMethod.fromString('get'), HttpMethod.get);
expect(HttpMethod.fromString('Post'), HttpMethod.post);
expect(HttpMethod.fromString('put'), HttpMethod.put);
});
test('returns default for unknown method', () {
expect(HttpMethod.fromString('UNKNOWN'), HttpMethod.get);
expect(HttpMethod.fromString(''), HttpMethod.get);
});
test('gets HttpMethod value', () {
expect(HttpMethod.get.value, 'GET');
expect(HttpMethod.post.value, 'POST');
expect(HttpMethod.put.value, 'PUT');
expect(HttpMethod.delete.value, 'DELETE');
expect(HttpMethod.patch.value, 'PATCH');
expect(HttpMethod.head.value, 'HEAD');
expect(HttpMethod.options.value, 'OPTIONS');
});
});
group('SwaggerDocument', () {
test('creates SwaggerDocument with required fields', () {
final document = SwaggerDocument(
title: 'Test API',
version: '1.0.0',
description: 'Test API description',
host: 'api.example.com',
basePath: '/api',
schemes: ['https'],
consumes: ['application/json'],
produces: ['application/json'],
paths: {},
models: {},
controllers: {},
);
expect(document.title, 'Test API');
expect(document.version, '1.0.0');
expect(document.description, 'Test API description');
expect(document.host, 'api.example.com');
expect(document.basePath, '/api');
expect(document.schemes, ['https']);
expect(document.consumes, ['application/json']);
expect(document.produces, ['application/json']);
expect(document.paths, isEmpty);
expect(document.models, isEmpty);
expect(document.controllers, isEmpty);
});
test('creates SwaggerDocument from JSON with all fields', () {
final json = {
'info': {
'title': 'Test API',
'version': '1.0.0',
'description': 'Test API description',
},
'host': 'api.example.com',
'basePath': '/api',
'schemes': ['https', 'http'],
'consumes': ['application/json', 'application/xml'],
'produces': ['application/json', 'text/plain'],
};
final document = SwaggerDocument.fromJson(json);
expect(document.title, 'Test API');
expect(document.version, '1.0.0');
expect(document.description, 'Test API description');
expect(document.host, 'api.example.com');
expect(document.basePath, '/api');
expect(document.schemes, ['https', 'http']);
expect(document.consumes, ['application/json', 'application/xml']);
expect(document.produces, ['application/json', 'text/plain']);
});
test('creates SwaggerDocument from JSON with minimal fields', () {
final json = <String, dynamic>{};
final document = SwaggerDocument.fromJson(json);
expect(document.title, 'API');
expect(document.version, '1.0.0');
expect(document.description, '');
expect(document.host, '');
expect(document.basePath, '');
expect(document.schemes, ['https']);
expect(document.consumes, ['application/json']);
expect(document.produces, ['application/json']);
});
test('creates SwaggerDocument from JSON with null info', () {
final json = {'info': null};
final document = SwaggerDocument.fromJson(json);
expect(document.title, 'API');
expect(document.version, '1.0.0');
expect(document.description, '');
});
});
group('ApiController', () {
test('creates ApiController with required fields', () {
final controller = ApiController(
name: 'UserController',
description: 'User management controller',
paths: [],
);
expect(controller.name, 'UserController');
expect(controller.description, 'User management controller');
expect(controller.paths, isEmpty);
});
test('creates ApiController with paths', () {
final paths = [
ApiPath(
path: '/api/users',
method: HttpMethod.get,
summary: 'Get users',
description: 'Retrieve all users',
operationId: 'getUsers',
tags: ['User'],
parameters: [],
responses: {},
requestBody: null,
),
ApiPath(
path: '/api/users/{id}',
method: HttpMethod.post,
summary: 'Create user',
description: 'Create a new user',
operationId: 'createUser',
tags: ['User'],
parameters: [],
responses: {},
requestBody: null,
),
];
final controller = ApiController(
name: 'UserController',
description: 'User management controller',
paths: paths,
);
expect(controller.paths, hasLength(2));
expect(controller.paths[0].path, '/api/users');
expect(controller.paths[1].path, '/api/users/{id}');
});
test('creates ApiController from paths', () {
final paths = [
ApiPath(
path: '/api/users',
method: HttpMethod.get,
summary: 'Get users',
description: 'Retrieve all users',
operationId: 'getUsers',
tags: ['User'],
parameters: [],
responses: {},
requestBody: null,
),
];
final controller = ApiController.fromPaths('UserController', paths);
expect(controller.name, 'UserController');
expect(controller.description, 'UserController');
expect(controller.paths, hasLength(1));
});
});
group('ApiPath fromJson', () {
test('creates ApiPath from JSON with all fields', () {
final json = {
'summary': 'Get users',
'description': 'Retrieve all users',
'operationId': 'getUsers',
'tags': ['User'],
'parameters': [
<String, dynamic>{
'name': 'id',
'in': 'path',
'required': true,
'type': 'integer',
'description': 'User ID',
}
],
'responses': <String, dynamic>{
'200': <String, dynamic>{
'description': 'Success',
'schema': <String, dynamic>{'type': 'object'},
}
},
'requestBody': <String, dynamic>{
'description': 'User data',
'required': true,
'content': <String, dynamic>{
'application/json': <String, dynamic>{
'schema': <String, dynamic>{'type': 'object'},
},
},
},
'deprecated': true,
};
final path = ApiPath.fromJson('/api/users/{id}', 'PUT', json);
expect(path.path, '/api/users/{id}');
expect(path.method, HttpMethod.put);
expect(path.summary, 'Get users');
expect(path.description, 'Retrieve all users');
expect(path.operationId, 'getUsers');
expect(path.tags, ['User']);
expect(path.parameters, hasLength(1));
expect(path.responses, hasLength(1));
expect(path.requestBody, isNotNull);
expect(path.deprecated, true);
});
test('creates ApiPath from JSON with minimal fields', () {
final json = <String, dynamic>{};
final path = ApiPath.fromJson('/api/users', 'GET', json);
expect(path.path, '/api/users');
expect(path.method, HttpMethod.get);
expect(path.summary, '');
expect(path.description, '');
expect(path.operationId, '');
expect(path.tags, isEmpty);
expect(path.parameters, isEmpty);
expect(path.responses, isEmpty);
expect(path.requestBody, isNull);
expect(path.deprecated, false);
});
});
group('ApiModel fromJson edge cases', () {
test('creates ApiModel with nullable properties', () {
final json = {
'description': 'User model',
'properties': {
'id': {
'type': 'integer',
'description': 'User ID',
'nullable': true,
},
'name': {
'type': 'string',
'description': 'User name',
'nullable': false,
},
},
};
final model = ApiModel.fromJson('User', json);
expect(model.properties['id']!.nullable, true);
expect(model.properties['id']!.required, false);
expect(model.properties['name']!.nullable, false);
expect(model.properties['name']!.required, true);
expect(model.required, ['name']);
});
test('creates ApiModel with explicit required fields', () {
final json = {
'description': 'User model',
'properties': {
'id': {
'type': 'integer',
'description': 'User ID',
'nullable': true,
},
'name': {
'type': 'string',
'description': 'User name',
'nullable': false,
},
},
'required': ['id', 'name'],
};
final model = ApiModel.fromJson('User', json);
expect(model.properties['id']!.required, true);
expect(model.properties['name']!.required, true);
expect(model.required, ['id', 'name']);
});
});
group('ApiProperty complex types', () {
test('creates ApiProperty with nested object', () {
final json = {
'type': 'object',
'description': 'User address',
'properties': {
'street': {'type': 'string'},
'city': {'type': 'string'},
},
};
final property = ApiProperty.fromJson('address', json, []);
expect(property.type, PropertyType.object);
expect(property.description, 'User address');
expect(property.required, false);
});
test('creates ApiProperty with array of primitives', () {
final json = {
'type': 'array',
'description': 'User tags',
'items': {
'type': 'string',
},
};
final property = ApiProperty.fromJson('tags', json, []);
expect(property.type, PropertyType.array);
expect(property.description, 'User tags');
expect(property.items, isNotNull);
expect(property.items!.name, 'string');
});
test('creates ApiProperty with array of objects', () {
final json = {
'type': 'array',
'description': 'User list',
'items': {
'type': 'object',
'\$ref': '#/components/schemas/User',
},
};
final property = ApiProperty.fromJson('users', json, []);
expect(property.type, PropertyType.array);
expect(property.description, 'User list');
expect(property.items, isNotNull);
expect(property.items!.name, 'User');
});
test('creates ApiProperty with file type', () {
final json = {
'type': 'file',
'description': 'User avatar',
'format': 'binary',
};
final property = ApiProperty.fromJson('avatar', json, []);
expect(property.type, PropertyType.file);
expect(property.description, 'User avatar');
expect(property.format, 'binary');
});
test('creates ApiProperty with date type', () {
final json = {
'type': 'string',
'format': 'date',
'description': 'User birth date',
};
final property = ApiProperty.fromJson('birthDate', json, []);
expect(property.type, PropertyType.string);
expect(property.format, 'date');
expect(property.description, 'User birth date');
});
test('creates ApiProperty with date-time type', () {
final json = {
'type': 'string',
'format': 'date-time',
'description': 'User created at',
};
final property = ApiProperty.fromJson('createdAt', json, []);
expect(property.type, PropertyType.string);
expect(property.format, 'date-time');
expect(property.description, 'User created at');
});
});
group('Error handling and edge cases', () {
test('ApiParameter fromJson handles missing fields gracefully', () {
final json = <String, dynamic>{};
final param = ApiParameter.fromJson(json);
expect(param.name, '');
expect(param.location, ParameterLocation.query);
expect(param.required, false);
expect(param.type, PropertyType.string);
expect(param.description, '');
expect(param.format, isNull);
expect(param.example, isNull);
expect(param.defaultValue, isNull);
});
test('ApiResponse fromJson handles missing fields gracefully', () {
final json = <String, dynamic>{};
final response = ApiResponse.fromJson('200', json);
expect(response.code, '200');
expect(response.description, '');
expect(response.schema, isNull);
expect(response.content, isNull);
});
test('ApiRequestBody fromJson handles missing fields gracefully', () {
final json = <String, dynamic>{};
final requestBody = ApiRequestBody.fromJson(json);
expect(requestBody.description, '');
expect(requestBody.required, false);
expect(requestBody.content, isNull);
});
test('PropertyType fromString handles unknown types', () {
expect(PropertyType.fromString('unknown'), PropertyType.string);
expect(PropertyType.fromString(''), PropertyType.string);
expect(PropertyType.fromString('CUSTOM_TYPE'), PropertyType.string);
});
test('ParameterLocation fromString handles unknown locations', () {
expect(ParameterLocation.fromString('unknown'), ParameterLocation.query);
expect(ParameterLocation.fromString(''), ParameterLocation.query);
expect(ParameterLocation.fromString('CUSTOM_LOCATION'),
ParameterLocation.query);
});
test('HttpMethod fromString handles unknown methods', () {
expect(HttpMethod.fromString('unknown'), HttpMethod.get);
expect(HttpMethod.fromString(''), HttpMethod.get);
expect(HttpMethod.fromString('CUSTOM_METHOD'), HttpMethod.get);
});
});
}

View File

@ -0,0 +1,204 @@
import 'package:test/test.dart';
import '../lib/utils/string_utils.dart';
void main() {
group('StringUtils', () {
group('toDartPropertyName', () {
test('converts snake_case to camelCase', () {
expect(StringUtils.toDartPropertyName('user_id'), 'userId');
expect(StringUtils.toDartPropertyName('user-id'), 'userId');
expect(StringUtils.toDartPropertyName('1st_field'), 'n1stField');
expect(StringUtils.toDartPropertyName(''), 'property');
});
test('handles special characters', () {
expect(StringUtils.toDartPropertyName('field@name'), 'fieldName');
expect(StringUtils.toDartPropertyName('with space'), 'withSpace');
expect(StringUtils.toDartPropertyName('with.dot'), 'withDot');
expect(StringUtils.toDartPropertyName('with/slash'), 'withSlash');
});
test('handles numbers at start', () {
expect(StringUtils.toDartPropertyName('1field'), 'n1field');
expect(StringUtils.toDartPropertyName('123test'), 'n123test');
});
});
group('toCamelCase', () {
test('converts snake_case to camelCase', () {
expect(StringUtils.toCamelCase('user_id'), 'userId');
expect(StringUtils.toCamelCase('first_name'), 'firstName');
expect(StringUtils.toCamelCase('api_version'), 'apiVersion');
});
test('handles single word', () {
expect(StringUtils.toCamelCase('user'), 'user');
expect(StringUtils.toCamelCase(''), '');
});
test('handles multiple underscores', () {
expect(StringUtils.toCamelCase('user__id'), 'userId');
expect(StringUtils.toCamelCase('_user_id_'), 'userId');
});
});
group('toPascalCase', () {
test('converts snake_case to PascalCase', () {
expect(StringUtils.toPascalCase('user_id'), 'UserId');
expect(StringUtils.toPascalCase('first_name'), 'FirstName');
expect(StringUtils.toPascalCase('api_version'), 'ApiVersion');
});
test('handles already PascalCase', () {
expect(StringUtils.toPascalCase('User'), 'User');
expect(StringUtils.toPascalCase('UserID'), 'UserID');
});
test('handles camelCase input', () {
expect(StringUtils.toPascalCase('userId'), 'UserId');
expect(StringUtils.toPascalCase('firstName'), 'FirstName');
});
test('handles empty and single character', () {
expect(StringUtils.toPascalCase(''), '');
expect(StringUtils.toPascalCase('a'), 'A');
});
});
group('toSnakeCase', () {
test('converts camelCase to snake_case', () {
expect(StringUtils.toSnakeCase('userId'), 'user_id');
expect(StringUtils.toSnakeCase('firstName'), 'first_name');
expect(StringUtils.toSnakeCase('apiVersion'), 'api_version');
});
test('converts PascalCase to snake_case', () {
expect(StringUtils.toSnakeCase('UserID'), 'user_id');
expect(StringUtils.toSnakeCase('FirstName'), 'first_name');
});
test('handles acronyms', () {
expect(StringUtils.toSnakeCase('API'), 'api');
expect(StringUtils.toSnakeCase('URL'), 'url');
});
test('handles empty and single character', () {
expect(StringUtils.toSnakeCase(''), '');
expect(StringUtils.toSnakeCase('a'), 'a');
});
});
group('generateClassName', () {
test('generates valid class names', () {
expect(StringUtils.generateClassName('user'), 'User');
expect(StringUtils.generateClassName('user_id'), 'UserId');
expect(StringUtils.generateClassName('api-version'), 'ApiVersion');
});
test('handles special characters', () {
expect(StringUtils.generateClassName('user@name'), 'UserName');
expect(StringUtils.generateClassName('user.name'), 'UserName');
});
});
group('generateFileName', () {
test('generates valid file names', () {
expect(StringUtils.generateFileName('User'), 'user.dart');
expect(StringUtils.generateFileName('UserID'), 'user_id.dart');
expect(StringUtils.generateFileName('ApiVersion'), 'api_version.dart');
});
});
group('isValidDartIdentifier', () {
test('valid identifiers', () {
expect(StringUtils.isValidDartIdentifier('user'), true);
expect(StringUtils.isValidDartIdentifier('user_id'), true);
expect(StringUtils.isValidDartIdentifier('_private'), true);
expect(StringUtils.isValidDartIdentifier('user123'), true);
});
test('invalid identifiers', () {
expect(StringUtils.isValidDartIdentifier(''), false);
expect(StringUtils.isValidDartIdentifier('123user'), false);
expect(StringUtils.isValidDartIdentifier('user-name'), false);
expect(StringUtils.isValidDartIdentifier('user@name'), false);
});
});
group('generateEnumValueName', () {
test('generates valid enum names from strings', () {
expect(StringUtils.generateEnumValueName('active', 0), 'active');
expect(
StringUtils.generateEnumValueName('user_status', 1), 'userStatus');
});
test('handles invalid strings', () {
expect(StringUtils.generateEnumValueName('', 0), 'value1');
expect(StringUtils.generateEnumValueName('123', 1), 'value2');
});
test('handles non-string values', () {
expect(StringUtils.generateEnumValueName(123, 0), 'value1');
expect(StringUtils.generateEnumValueName('', 1), 'value2');
});
});
group('cleanDescription', () {
test('cleans basic descriptions', () {
expect(StringUtils.cleanDescription(' test description '),
'test description');
expect(StringUtils.cleanDescription('line1\nline2'), 'line1 line2');
});
test('removes special characters', () {
expect(StringUtils.cleanDescription('test@#\$%'), 'test');
expect(StringUtils.cleanDescription('test[description]'),
'testdescription');
});
test('truncates long descriptions', () {
final longDesc = 'a' * 300;
final result = StringUtils.cleanDescription(longDesc);
expect(result.length, lessThanOrEqualTo(203)); // 200 + '...'
expect(result.endsWith('...'), true);
});
test('handles empty and null', () {
expect(StringUtils.cleanDescription(''), '');
});
});
group('generateComment', () {
test('generates single line comments', () {
expect(StringUtils.generateComment('test'), '/// test');
expect(StringUtils.generateComment(''), '');
});
test('generates block comments', () {
final result = StringUtils.generateComment('test', isBlock: true);
expect(result, contains('/**'));
expect(result, contains('test'));
expect(result, contains('*/'));
});
});
group('formatBytes', () {
test('formats bytes correctly', () {
expect(StringUtils.formatBytes(1023), '1023B');
expect(StringUtils.formatBytes(1024), '1.0KB');
expect(StringUtils.formatBytes(1024 * 1024), '1.0MB');
expect(StringUtils.formatBytes(1024 * 1024 * 1024), '1.0GB');
});
});
group('formatDuration', () {
test('formats duration correctly', () {
expect(
StringUtils.formatDuration(Duration(milliseconds: 500)), '500毫秒');
expect(StringUtils.formatDuration(Duration(seconds: 1)), '1.00秒');
expect(StringUtils.formatDuration(Duration(seconds: 2)), '2.00秒');
});
});
});
}