swagger_generator_flutter/lib/generators/base_generator.dart

364 lines
10 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:swagger_generator_flutter/core/config.dart';
import 'package:swagger_generator_flutter/core/exceptions.dart';
import 'package:swagger_generator_flutter/core/models.dart';
import 'package:swagger_generator_flutter/utils/string_utils.dart';
/// 代码生成器基类
/// 定义通用的接口和功能
abstract class BaseGenerator {
/// 生成器类型
String get generatorType;
/// 生成代码
String generate();
/// 生成文件头注释
/// [description] 文件描述
/// [fileName] 文件名(可选)
String generateFileHeader(String description, {String? fileName}) {
final header = StringUtils.generateFileHeader(
description,
SwaggerConfig.swaggerJsonUrls.isNotEmpty
? SwaggerConfig.swaggerJsonUrls.first
: '',
fileName: fileName,
fileType: description,
);
// 添加 lint 忽略注释
return '$header\n// ignore_for_file: type=lint, invalid_annotation_target\n';
}
/// 生成类型安全的代码
String generateTypeCheckedCode(String code) {
// 基础类型检查和验证
try {
if (code.trim().isEmpty) {
throw CodeGenerationException(
'生成的代码不能为空',
generatorType: generatorType,
);
}
// 确保文件以换行符结尾
if (!code.endsWith('\n')) {
return '$code\n';
}
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 {
ModelGenerator(this.document, {this.useSimpleModels = false});
final SwaggerDocument document;
final bool useSimpleModels;
@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 valueType =
enumType == 'integer' || enumType == 'number' ? 'int' : 'String';
final buffer = StringBuffer()
// 生成文件头
..writeln(generateFileHeader('${model.name} 枚举定义'))
..writeln();
// 生成枚举类
if (model.description.isNotEmpty) {
buffer.writeln(StringUtils.generateComment(model.description));
}
buffer.writeln('enum $className {');
// 生成枚举值
for (var i = 0; i < model.enumValues.length; i++) {
final value = model.enumValues[i];
final enumName = StringUtils.generateEnumValueName(value, i);
final enumLine = enumType == 'integer' || enumType == 'number'
? ' $enumName($value),'
: " $enumName('$value'),";
buffer.writeln(enumLine);
}
// 移除最后一个逗号
final content = buffer.toString().trimRight();
buffer
..clear()
..writeAll(
[
content.substring(0, content.lastIndexOf(',')),
';',
'',
' const $className(this.value);',
' final $valueType value;',
'',
' static $className fromValue(dynamic value) {',
' for (final enumValue in $className.values) {',
' if (enumValue.value == value) {',
' return enumValue;',
' }',
' }',
r" throw ArgumentError('Unknown enum value: $value');",
' }',
'',
' factory $className.fromJson(dynamic json) {',
' return fromValue(json);',
' }',
'',
' dynamic toJson() => value;',
'',
'}',
],
'\n',
);
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.enumType:
return 'String';
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';
case PropertyType.file:
return 'dynamic';
case PropertyType.date:
return 'DateTime';
case PropertyType.dateTime:
return 'DateTime';
case PropertyType.unknown:
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 {
const GeneratorOptions({
this.generateEndpoints = true,
this.generateModels = true,
this.generateDocs = true,
this.useSimpleModels = false,
this.modelsDirectory = 'models',
this.outputDirectory = 'generator',
this.endpointsFileName = 'api_paths.dart',
this.docsFileName = 'api_documentation.md',
});
/// 从命令行参数创建选项
factory GeneratorOptions.fromArgs(List<String> args) {
var generateEndpoints = false;
var generateModels = false;
var generateDocs = false;
var useSimpleModels = false;
var modelsDirectory = 'models';
var outputDirectory = 'generator';
var endpointsFileName = 'api_paths.dart';
var docsFileName = 'api_documentation.md';
var hasSpecificOption = false;
for (var i = 0; i < args.length; i++) {
final arg = args[i];
switch (arg) {
case '--endpoints':
generateEndpoints = true;
hasSpecificOption = true;
case '--models':
generateModels = true;
hasSpecificOption = true;
case '--docs':
generateDocs = true;
hasSpecificOption = true;
case '--all':
generateEndpoints = true;
generateModels = true;
generateDocs = true;
hasSpecificOption = true;
case '--simple':
useSimpleModels = true;
case '--models-dir':
if (i + 1 < args.length) {
modelsDirectory = args[i + 1];
i++; // 跳过下一个参数
}
case '--output-dir':
if (i + 1 < args.length) {
outputDirectory = args[i + 1];
i++; // 跳过下一个参数
}
case '--endpoints-file':
if (i + 1 < args.length) {
endpointsFileName = args[i + 1];
i++; // 跳过下一个参数
}
case '--docs-file':
if (i + 1 < args.length) {
docsFileName = args[i + 1];
i++; // 跳过下一个参数
}
}
}
// 如果没有指定特定选项,默认生成所有文件
if (!hasSpecificOption) {
generateEndpoints = true;
generateModels = true;
generateDocs = true;
}
return GeneratorOptions(
generateEndpoints: generateEndpoints,
generateModels: generateModels,
generateDocs: generateDocs,
useSimpleModels: useSimpleModels,
modelsDirectory: modelsDirectory,
outputDirectory: outputDirectory,
endpointsFileName: endpointsFileName,
docsFileName: docsFileName,
);
}
final bool generateEndpoints;
final bool generateModels;
final bool generateDocs;
final bool useSimpleModels;
final String modelsDirectory;
final String outputDirectory;
final String endpointsFileName;
final String docsFileName;
}