2454 lines
65 KiB
Dart
2454 lines
65 KiB
Dart
/// 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 ModelUsageType {
|
||
/// 请求模型 - 用于 requestBody 或 parameters
|
||
/// 此类模型不应添加 defaultValue,以确保数据的明确性
|
||
request,
|
||
|
||
/// 响应模型 - 用于 response
|
||
/// 此类模型应添加 defaultValue,以提高安全性和容错性
|
||
response,
|
||
|
||
/// 通用模型 - 既用于请求又用于响应
|
||
/// 此类模型的处理策略可配置,默认添加 defaultValue
|
||
common,
|
||
|
||
/// 未知 - 未被任何 API 使用,或无法确定用途
|
||
/// 此类模型的处理策略可配置,默认添加 defaultValue
|
||
unknown,
|
||
}
|
||
|
||
/// API服务器信息 (OpenAPI 3.0)
|
||
class ApiServer {
|
||
const ApiServer({
|
||
required this.url,
|
||
this.description = '',
|
||
this.variables = const {},
|
||
});
|
||
|
||
/// 从JSON创建ApiServer
|
||
factory ApiServer.fromJson(Map<String, dynamic> json) {
|
||
final variablesJson = json['variables'] as Map<String, dynamic>? ?? {};
|
||
final variables = <String, ApiServerVariable>{};
|
||
|
||
variablesJson.forEach((key, value) {
|
||
if (value is Map<String, dynamic>) {
|
||
variables[key] = ApiServerVariable.fromJson(value);
|
||
}
|
||
});
|
||
|
||
return ApiServer(
|
||
url: json['url'] as String? ?? '',
|
||
description: json['description'] as String? ?? '',
|
||
variables: variables,
|
||
);
|
||
}
|
||
|
||
/// 服务器URL
|
||
final String url;
|
||
|
||
/// 服务器描述
|
||
final String description;
|
||
|
||
/// 服务器变量
|
||
final Map<String, ApiServerVariable> variables;
|
||
}
|
||
|
||
/// API服务器变量 (OpenAPI 3.0)
|
||
class ApiServerVariable {
|
||
const ApiServerVariable({
|
||
required this.defaultValue,
|
||
this.enumValues = const [],
|
||
this.description = '',
|
||
});
|
||
|
||
/// 从JSON创建ApiServerVariable
|
||
factory ApiServerVariable.fromJson(Map<String, dynamic> json) {
|
||
return ApiServerVariable(
|
||
enumValues:
|
||
(json['enum'] as List<dynamic>?)?.map((e) => e.toString()).toList() ??
|
||
[],
|
||
defaultValue: json['default'] as String? ?? '',
|
||
description: json['description'] as String? ?? '',
|
||
);
|
||
}
|
||
|
||
/// 变量的可选值
|
||
final List<String> enumValues;
|
||
|
||
/// 默认值
|
||
final String defaultValue;
|
||
|
||
/// 变量描述
|
||
final String description;
|
||
}
|
||
|
||
/// 属性类型枚举
|
||
/// 用于描述 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'),
|
||
|
||
/// 未知类型
|
||
unknown('unknown');
|
||
|
||
/// 枚举值对应的字符串
|
||
const PropertyType(this.value);
|
||
final String value;
|
||
|
||
/// 通过字符串获取 PropertyType 枚举
|
||
static PropertyType fromString(String value) {
|
||
return PropertyType.values.firstWhere(
|
||
(type) => type.value == value.toLowerCase(),
|
||
orElse: () => PropertyType.unknown,
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 参数位置枚举
|
||
/// 用于描述 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,
|
||
);
|
||
}
|
||
}
|
||
|
||
/// OpenAPI 3.0 文档信息
|
||
/// 描述整个 API 的元数据和结构。
|
||
class SwaggerDocument {
|
||
/// 构造函数
|
||
const SwaggerDocument({
|
||
required this.title,
|
||
required this.version,
|
||
required this.description,
|
||
required this.paths,
|
||
required this.models,
|
||
required this.controllers,
|
||
this.servers = const [],
|
||
this.components = const ApiComponents(),
|
||
this.security = const [],
|
||
});
|
||
|
||
/// 从JSON创建SwaggerDocument
|
||
factory SwaggerDocument.fromJson(Map<String, dynamic> json) {
|
||
final info = json['info'] as Map<String, dynamic>? ?? {};
|
||
|
||
// 解析 servers (OpenAPI 3.0)
|
||
final serversJson = json['servers'] as List<dynamic>? ?? [];
|
||
final servers = serversJson
|
||
.map((server) => ApiServer.fromJson(server as Map<String, dynamic>))
|
||
.toList();
|
||
|
||
// 如果没有 servers 配置,提供默认值
|
||
if (servers.isEmpty) {
|
||
servers.add(const ApiServer(url: '/'));
|
||
}
|
||
|
||
// 解析 components (OpenAPI 3.0)
|
||
final componentsJson = json['components'] as Map<String, dynamic>?;
|
||
final components = componentsJson != null
|
||
? ApiComponents.fromJson(componentsJson)
|
||
: const ApiComponents();
|
||
|
||
// 解析全局安全要求
|
||
final securityJson = json['security'] as List<dynamic>? ?? [];
|
||
final security = securityJson
|
||
.map((s) => ApiSecurityRequirement.fromJson(s as Map<String, dynamic>))
|
||
.toList();
|
||
|
||
return SwaggerDocument(
|
||
title: info['title'] as String? ?? 'API',
|
||
version: info['version'] as String? ?? '1.0.0',
|
||
description: info['description'] as String? ?? '',
|
||
servers: servers,
|
||
components: components,
|
||
paths: _parsePaths(json['paths'] as Map<String, dynamic>? ?? {}),
|
||
models: components.schemas, // 从 components 中提取 schemas
|
||
controllers: {},
|
||
security: security,
|
||
);
|
||
}
|
||
|
||
/// 文档标题
|
||
final String title;
|
||
|
||
/// 版本号
|
||
final String version;
|
||
|
||
/// 文档描述
|
||
final String description;
|
||
|
||
/// 服务器配置 (OpenAPI 3.0)
|
||
final List<ApiServer> servers;
|
||
|
||
/// 可重用组件 (OpenAPI 3.0)
|
||
final ApiComponents components;
|
||
|
||
/// 路径定义
|
||
final Map<String, ApiPath> paths;
|
||
|
||
/// 数据模型定义 (从 components.schemas 提取)
|
||
final Map<String, ApiModel> models;
|
||
|
||
/// 控制器定义
|
||
final Map<String, ApiController> controllers;
|
||
|
||
/// 全局安全要求
|
||
final List<ApiSecurityRequirement> security;
|
||
|
||
/// 从JSON解析API路径 (静态辅助方法)
|
||
static Map<String, ApiPath> _parsePaths(Map<String, dynamic> pathsJson) {
|
||
final paths = <String, ApiPath>{};
|
||
pathsJson.forEach((path, pathJson) {
|
||
final pathData = pathJson as Map<String, dynamic>;
|
||
pathData.forEach((method, methodJson) {
|
||
if (HttpMethod.values.any((m) => m.name == method)) {
|
||
final httpMethod =
|
||
HttpMethod.values.firstWhere((m) => m.name == method);
|
||
// This is a simplified parser for tests. It might overwrite paths if a path has multiple methods.
|
||
// The main parser in SwaggerDataParser handles this by creating unique keys.
|
||
paths[path] = ApiPath.fromJson(
|
||
path,
|
||
httpMethod,
|
||
methodJson as Map<String, dynamic>,
|
||
);
|
||
}
|
||
});
|
||
});
|
||
return paths;
|
||
}
|
||
}
|
||
|
||
/// API路径信息
|
||
class ApiPath {
|
||
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,
|
||
this.security = const [],
|
||
});
|
||
|
||
/// 从JSON创建ApiPath
|
||
factory ApiPath.fromJson(
|
||
String path,
|
||
HttpMethod method,
|
||
Map<String, dynamic> json,
|
||
) {
|
||
return ApiPath(
|
||
path: path,
|
||
method: method,
|
||
summary: json['summary'] as String? ?? '',
|
||
description: json['description'] as String? ?? '',
|
||
operationId: json['operationId'] as String? ?? '',
|
||
tags:
|
||
(json['tags'] as List<dynamic>?)?.map((e) => e.toString()).toList() ??
|
||
[],
|
||
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,
|
||
security: (json['security'] as List?)
|
||
?.map(
|
||
(s) =>
|
||
ApiSecurityRequirement.fromJson(s as Map<String, dynamic>),
|
||
)
|
||
.toList() ??
|
||
[],
|
||
);
|
||
}
|
||
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;
|
||
|
||
/// 安全要求
|
||
final List<ApiSecurityRequirement> security;
|
||
}
|
||
|
||
/// API参数信息
|
||
class ApiParameter {
|
||
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: schema?['default'] ?? json['default'],
|
||
);
|
||
}
|
||
final String name;
|
||
final ParameterLocation location;
|
||
final bool required;
|
||
final PropertyType type;
|
||
final String description;
|
||
final String? format;
|
||
final dynamic example;
|
||
final dynamic defaultValue;
|
||
}
|
||
|
||
/// API响应信息 (OpenAPI 3.0)
|
||
class ApiResponse {
|
||
const ApiResponse({
|
||
required this.code,
|
||
required this.description,
|
||
this.headers = const {},
|
||
this.content = const {},
|
||
this.links = const {},
|
||
@Deprecated('Use content instead') this.schema,
|
||
});
|
||
|
||
/// 从JSON创建ApiResponse
|
||
factory ApiResponse.fromJson(String code, Map<String, dynamic> json) {
|
||
// 解析 headers
|
||
final headersJson = json['headers'] as Map<String, dynamic>? ?? {};
|
||
final headers = <String, ApiHeader>{};
|
||
headersJson.forEach((key, value) {
|
||
if (value is Map<String, dynamic>) {
|
||
headers[key] = ApiHeader.fromJson(value);
|
||
}
|
||
});
|
||
|
||
// 解析 content
|
||
final contentJson = json['content'] as Map<String, dynamic>? ?? {};
|
||
final content = <String, ApiMediaType>{};
|
||
contentJson.forEach((mediaType, mediaTypeData) {
|
||
if (mediaTypeData is Map<String, dynamic>) {
|
||
content[mediaType] = ApiMediaType.fromJson(mediaTypeData, mediaType);
|
||
}
|
||
});
|
||
|
||
// 解析 links
|
||
final linksJson = json['links'] as Map<String, dynamic>? ?? {};
|
||
final links = <String, ApiLink>{};
|
||
linksJson.forEach((key, value) {
|
||
if (value is Map<String, dynamic>) {
|
||
links[key] = ApiLink.fromJson(value);
|
||
}
|
||
});
|
||
|
||
return ApiResponse(
|
||
code: code,
|
||
description: json['description'] as String? ?? '',
|
||
headers: headers,
|
||
content: content,
|
||
links: links,
|
||
// 保留 schema 字段以兼容 Swagger 2.0
|
||
schema: json['schema'] as Map<String, dynamic>?,
|
||
);
|
||
}
|
||
|
||
/// 响应状态码
|
||
final String code;
|
||
|
||
/// 响应描述
|
||
final String description;
|
||
|
||
/// 响应头定义
|
||
final Map<String, ApiHeader> headers;
|
||
|
||
/// 内容类型映射 (media type -> MediaTypeObject)
|
||
final Map<String, ApiMediaType> content;
|
||
|
||
/// 响应链接
|
||
final Map<String, ApiLink> links;
|
||
|
||
/// Schema 定义 (Swagger 2.0 兼容,已弃用)
|
||
@Deprecated(
|
||
'Use content instead. This field is for Swagger 2.0 compatibility only.',
|
||
)
|
||
final Map<String, dynamic>? schema;
|
||
|
||
/// 获取支持的媒体类型列表
|
||
List<String> get supportedMediaTypes => content.keys.toList();
|
||
|
||
/// 检查是否支持指定的媒体类型
|
||
bool supportsMediaType(String mediaType) => content.containsKey(mediaType);
|
||
|
||
/// 获取指定媒体类型的 schema
|
||
Map<String, dynamic>? getSchemaForMediaType(String mediaType) {
|
||
return content[mediaType]?.schema;
|
||
}
|
||
|
||
/// 检查是否有响应头定义
|
||
bool get hasHeaders => headers.isNotEmpty;
|
||
|
||
/// 检查是否有响应链接
|
||
bool get hasLinks => links.isNotEmpty;
|
||
}
|
||
|
||
/// API请求体信息 (OpenAPI 3.0)
|
||
class ApiRequestBody {
|
||
const ApiRequestBody({
|
||
this.description = '',
|
||
this.required = false,
|
||
this.content = const {},
|
||
});
|
||
|
||
/// 从JSON创建ApiRequestBody
|
||
factory ApiRequestBody.fromJson(Map<String, dynamic> json) {
|
||
final contentJson = json['content'] as Map<String, dynamic>? ?? {};
|
||
final content = <String, ApiMediaType>{};
|
||
|
||
contentJson.forEach((mediaType, mediaTypeData) {
|
||
if (mediaTypeData is Map<String, dynamic>) {
|
||
content[mediaType] = ApiMediaType.fromJson(mediaTypeData, mediaType);
|
||
}
|
||
});
|
||
|
||
return ApiRequestBody(
|
||
description: json['description'] as String? ?? '',
|
||
required: json['required'] as bool? ?? false,
|
||
content: content,
|
||
);
|
||
}
|
||
|
||
/// 请求体描述
|
||
final String description;
|
||
|
||
/// 是否必需
|
||
final bool required;
|
||
|
||
/// 内容类型映射 (media type -> MediaTypeObject)
|
||
final Map<String, ApiMediaType> content;
|
||
|
||
/// 获取支持的媒体类型列表
|
||
List<String> get supportedMediaTypes => content.keys.toList();
|
||
|
||
/// 检查是否支持指定的媒体类型
|
||
bool supportsMediaType(String mediaType) => content.containsKey(mediaType);
|
||
|
||
/// 获取指定媒体类型的 schema
|
||
Map<String, dynamic>? getSchemaForMediaType(String mediaType) {
|
||
return content[mediaType]?.schema;
|
||
}
|
||
}
|
||
|
||
/// 媒体类型枚举
|
||
enum MediaType {
|
||
json,
|
||
xml,
|
||
formData,
|
||
formUrlEncoded,
|
||
multipartFormData,
|
||
textPlain,
|
||
textHtml,
|
||
textCsv,
|
||
applicationOctetStream,
|
||
applicationPdf,
|
||
imagePng,
|
||
imageJpeg,
|
||
imageGif,
|
||
imageSvg,
|
||
audioMp3,
|
||
videoMp4,
|
||
custom,
|
||
}
|
||
|
||
extension MediaTypeExtension on MediaType {
|
||
String get value {
|
||
switch (this) {
|
||
case MediaType.json:
|
||
return 'application/json';
|
||
case MediaType.xml:
|
||
return 'application/xml';
|
||
case MediaType.formData:
|
||
return 'multipart/form-data';
|
||
case MediaType.formUrlEncoded:
|
||
return 'application/x-www-form-urlencoded';
|
||
case MediaType.multipartFormData:
|
||
return 'multipart/form-data';
|
||
case MediaType.textPlain:
|
||
return 'text/plain';
|
||
case MediaType.textHtml:
|
||
return 'text/html';
|
||
case MediaType.textCsv:
|
||
return 'text/csv';
|
||
case MediaType.applicationOctetStream:
|
||
return 'application/octet-stream';
|
||
case MediaType.applicationPdf:
|
||
return 'application/pdf';
|
||
case MediaType.imagePng:
|
||
return 'image/png';
|
||
case MediaType.imageJpeg:
|
||
return 'image/jpeg';
|
||
case MediaType.imageGif:
|
||
return 'image/gif';
|
||
case MediaType.imageSvg:
|
||
return 'image/svg+xml';
|
||
case MediaType.audioMp3:
|
||
return 'audio/mpeg';
|
||
case MediaType.videoMp4:
|
||
return 'video/mp4';
|
||
case MediaType.custom:
|
||
return 'custom';
|
||
}
|
||
}
|
||
|
||
static MediaType fromString(String value) {
|
||
final lowerValue = value.toLowerCase();
|
||
switch (lowerValue) {
|
||
case 'application/json':
|
||
return MediaType.json;
|
||
case 'application/xml':
|
||
case 'text/xml':
|
||
return MediaType.xml;
|
||
case 'multipart/form-data':
|
||
return MediaType.multipartFormData;
|
||
case 'application/x-www-form-urlencoded':
|
||
return MediaType.formUrlEncoded;
|
||
case 'text/plain':
|
||
return MediaType.textPlain;
|
||
case 'text/html':
|
||
return MediaType.textHtml;
|
||
case 'text/csv':
|
||
return MediaType.textCsv;
|
||
case 'application/octet-stream':
|
||
return MediaType.applicationOctetStream;
|
||
case 'application/pdf':
|
||
return MediaType.applicationPdf;
|
||
case 'image/png':
|
||
return MediaType.imagePng;
|
||
case 'image/jpeg':
|
||
case 'image/jpg':
|
||
return MediaType.imageJpeg;
|
||
case 'image/gif':
|
||
return MediaType.imageGif;
|
||
case 'image/svg+xml':
|
||
return MediaType.imageSvg;
|
||
case 'audio/mpeg':
|
||
case 'audio/mp3':
|
||
return MediaType.audioMp3;
|
||
case 'video/mp4':
|
||
return MediaType.videoMp4;
|
||
default:
|
||
return MediaType.custom;
|
||
}
|
||
}
|
||
|
||
/// 检查是否是文本类型
|
||
bool get isText {
|
||
switch (this) {
|
||
case MediaType.textPlain:
|
||
case MediaType.textHtml:
|
||
case MediaType.textCsv:
|
||
case MediaType.json:
|
||
case MediaType.xml:
|
||
return true;
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// 检查是否是二进制类型
|
||
bool get isBinary {
|
||
switch (this) {
|
||
case MediaType.applicationOctetStream:
|
||
case MediaType.applicationPdf:
|
||
case MediaType.imagePng:
|
||
case MediaType.imageJpeg:
|
||
case MediaType.imageGif:
|
||
case MediaType.imageSvg:
|
||
case MediaType.audioMp3:
|
||
case MediaType.videoMp4:
|
||
return true;
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// 检查是否是表单类型
|
||
bool get isForm {
|
||
switch (this) {
|
||
case MediaType.formData:
|
||
case MediaType.formUrlEncoded:
|
||
case MediaType.multipartFormData:
|
||
return true;
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// 检查是否是图片类型
|
||
bool get isImage {
|
||
switch (this) {
|
||
case MediaType.imagePng:
|
||
case MediaType.imageJpeg:
|
||
case MediaType.imageGif:
|
||
case MediaType.imageSvg:
|
||
return true;
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// 检查是否是音频类型
|
||
bool get isAudio {
|
||
switch (this) {
|
||
case MediaType.audioMp3:
|
||
return true;
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// 检查是否是视频类型
|
||
bool get isVideo {
|
||
switch (this) {
|
||
case MediaType.videoMp4:
|
||
return true;
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// 获取适合的 Dart 类型
|
||
String get dartType {
|
||
switch (this) {
|
||
case MediaType.json:
|
||
return 'Map<String, dynamic>';
|
||
case MediaType.xml:
|
||
return 'String';
|
||
case MediaType.formData:
|
||
case MediaType.formUrlEncoded:
|
||
case MediaType.multipartFormData:
|
||
return 'FormData';
|
||
case MediaType.textPlain:
|
||
case MediaType.textHtml:
|
||
case MediaType.textCsv:
|
||
return 'String';
|
||
case MediaType.applicationOctetStream:
|
||
case MediaType.applicationPdf:
|
||
case MediaType.imagePng:
|
||
case MediaType.imageJpeg:
|
||
case MediaType.imageGif:
|
||
case MediaType.audioMp3:
|
||
case MediaType.videoMp4:
|
||
return 'List<int>';
|
||
case MediaType.imageSvg:
|
||
return 'String';
|
||
case MediaType.custom:
|
||
return 'dynamic';
|
||
}
|
||
}
|
||
}
|
||
|
||
/// API媒体类型信息 (OpenAPI 3.0)
|
||
class ApiMediaType {
|
||
const ApiMediaType({
|
||
this.schema,
|
||
this.example,
|
||
this.examples = const {},
|
||
this.encoding = const {},
|
||
this.mediaType = MediaType.json,
|
||
this.rawMediaType = 'application/json',
|
||
});
|
||
|
||
/// 从JSON创建ApiMediaType
|
||
factory ApiMediaType.fromJson(
|
||
Map<String, dynamic> json, [
|
||
String? contentType,
|
||
]) {
|
||
final examplesJson = json['examples'] as Map<String, dynamic>? ?? {};
|
||
final examples = <String, ApiExample>{};
|
||
|
||
examplesJson.forEach((key, value) {
|
||
if (value is Map<String, dynamic>) {
|
||
examples[key] = ApiExample.fromJson(value);
|
||
}
|
||
});
|
||
|
||
final encodingJson = json['encoding'] as Map<String, dynamic>? ?? {};
|
||
final encoding = <String, ApiEncoding>{};
|
||
|
||
encodingJson.forEach((key, value) {
|
||
if (value is Map<String, dynamic>) {
|
||
encoding[key] = ApiEncoding.fromJson(value);
|
||
}
|
||
});
|
||
|
||
// 确定媒体类型
|
||
final rawType = contentType ?? 'application/json';
|
||
final mediaType = MediaTypeExtension.fromString(rawType);
|
||
|
||
return ApiMediaType(
|
||
schema: json['schema'] as Map<String, dynamic>?,
|
||
example: json['example'],
|
||
examples: examples,
|
||
encoding: encoding,
|
||
mediaType: mediaType,
|
||
rawMediaType: rawType,
|
||
);
|
||
}
|
||
|
||
/// Schema 定义
|
||
final Map<String, dynamic>? schema;
|
||
|
||
/// 示例数据
|
||
final dynamic example;
|
||
|
||
/// 多个示例数据
|
||
final Map<String, ApiExample> examples;
|
||
|
||
/// 编码信息 (用于 multipart 和 form data)
|
||
final Map<String, ApiEncoding> encoding;
|
||
|
||
/// 媒体类型
|
||
final MediaType mediaType;
|
||
|
||
/// 原始媒体类型字符串
|
||
final String rawMediaType;
|
||
|
||
/// 检查是否是 JSON 类型
|
||
bool get isJson => mediaType == MediaType.json;
|
||
|
||
/// 检查是否是 XML 类型
|
||
bool get isXml => mediaType == MediaType.xml;
|
||
|
||
/// 检查是否是表单类型
|
||
bool get isForm => mediaType.isForm;
|
||
|
||
/// 检查是否是文件上传类型
|
||
bool get isFileUpload => mediaType == MediaType.multipartFormData;
|
||
|
||
/// 检查是否是二进制类型
|
||
bool get isBinary => mediaType.isBinary;
|
||
|
||
/// 检查是否是文本类型
|
||
bool get isText => mediaType.isText;
|
||
|
||
/// 检查是否是图片类型
|
||
bool get isImage => mediaType.isImage;
|
||
|
||
/// 获取适合的 Dart 类型
|
||
String get dartType => mediaType.dartType;
|
||
}
|
||
|
||
/// API示例信息 (OpenAPI 3.0)
|
||
class ApiExample {
|
||
const ApiExample({
|
||
this.summary = '',
|
||
this.description = '',
|
||
this.value,
|
||
this.externalValue,
|
||
});
|
||
|
||
/// 从JSON创建ApiExample
|
||
factory ApiExample.fromJson(Map<String, dynamic> json) {
|
||
return ApiExample(
|
||
summary: json['summary'] as String? ?? '',
|
||
description: json['description'] as String? ?? '',
|
||
value: json['value'],
|
||
externalValue: json['externalValue'] as String?,
|
||
);
|
||
}
|
||
|
||
/// 示例摘要
|
||
final String summary;
|
||
|
||
/// 示例描述
|
||
final String description;
|
||
|
||
/// 示例值
|
||
final dynamic value;
|
||
|
||
/// 外部示例URL
|
||
final String? externalValue;
|
||
}
|
||
|
||
/// API编码信息 (OpenAPI 3.0)
|
||
class ApiEncoding {
|
||
const ApiEncoding({
|
||
this.contentType,
|
||
this.headers = const {},
|
||
this.style,
|
||
this.explode = false,
|
||
this.allowReserved = false,
|
||
});
|
||
|
||
/// 从JSON创建ApiEncoding
|
||
factory ApiEncoding.fromJson(Map<String, dynamic> json) {
|
||
final headersJson = json['headers'] as Map<String, dynamic>? ?? {};
|
||
final headers = <String, ApiHeader>{};
|
||
|
||
headersJson.forEach((key, value) {
|
||
if (value is Map<String, dynamic>) {
|
||
headers[key] = ApiHeader.fromJson(value);
|
||
}
|
||
});
|
||
|
||
return ApiEncoding(
|
||
contentType: json['contentType'] as String?,
|
||
headers: headers,
|
||
style: json['style'] as String?,
|
||
explode: json['explode'] as bool? ?? false,
|
||
allowReserved: json['allowReserved'] as bool? ?? false,
|
||
);
|
||
}
|
||
|
||
/// 内容类型
|
||
final String? contentType;
|
||
|
||
/// 头部信息
|
||
final Map<String, ApiHeader> headers;
|
||
|
||
/// 样式
|
||
final String? style;
|
||
|
||
/// 是否展开
|
||
final bool explode;
|
||
|
||
/// 是否允许保留字符
|
||
final bool allowReserved;
|
||
|
||
/// 检查是否是文件类型
|
||
bool get isFile {
|
||
if (contentType == null) return false;
|
||
final type = contentType!.toLowerCase();
|
||
return type.startsWith('image/') ||
|
||
type.startsWith('audio/') ||
|
||
type.startsWith('video/') ||
|
||
type == 'application/octet-stream' ||
|
||
type == 'application/pdf' ||
|
||
type.contains('binary');
|
||
}
|
||
|
||
/// 检查是否是图片文件
|
||
bool get isImage {
|
||
if (contentType == null) return false;
|
||
return contentType!.toLowerCase().startsWith('image/');
|
||
}
|
||
|
||
/// 检查是否是音频文件
|
||
bool get isAudio {
|
||
if (contentType == null) return false;
|
||
return contentType!.toLowerCase().startsWith('audio/');
|
||
}
|
||
|
||
/// 检查是否是视频文件
|
||
bool get isVideo {
|
||
if (contentType == null) return false;
|
||
return contentType!.toLowerCase().startsWith('video/');
|
||
}
|
||
|
||
/// 检查是否有自定义头部
|
||
bool get hasHeaders => headers.isNotEmpty;
|
||
}
|
||
|
||
/// API头部信息 (OpenAPI 3.0)
|
||
class ApiHeader {
|
||
const ApiHeader({
|
||
this.description = '',
|
||
this.required = false,
|
||
this.deprecated = false,
|
||
this.schema,
|
||
this.example,
|
||
});
|
||
|
||
/// 从JSON创建ApiHeader
|
||
factory ApiHeader.fromJson(Map<String, dynamic> json) {
|
||
return ApiHeader(
|
||
description: json['description'] as String? ?? '',
|
||
required: json['required'] as bool? ?? false,
|
||
deprecated: json['deprecated'] as bool? ?? false,
|
||
schema: json['schema'] as Map<String, dynamic>?,
|
||
example: json['example'],
|
||
);
|
||
}
|
||
|
||
/// 头部描述
|
||
final String description;
|
||
|
||
/// 是否必需
|
||
final bool required;
|
||
|
||
/// 是否已弃用
|
||
final bool deprecated;
|
||
|
||
/// Schema 定义
|
||
final Map<String, dynamic>? schema;
|
||
|
||
/// 示例值
|
||
final dynamic example;
|
||
}
|
||
|
||
/// API链接信息 (OpenAPI 3.0)
|
||
class ApiLink {
|
||
const ApiLink({
|
||
this.description = '',
|
||
this.operationRef,
|
||
this.operationId,
|
||
this.parameters = const {},
|
||
this.requestBody,
|
||
this.server,
|
||
});
|
||
|
||
/// 从JSON创建ApiLink
|
||
factory ApiLink.fromJson(Map<String, dynamic> json) {
|
||
final serverJson = json['server'] as Map<String, dynamic>?;
|
||
final server = serverJson != null ? ApiServer.fromJson(serverJson) : null;
|
||
|
||
return ApiLink(
|
||
description: json['description'] as String? ?? '',
|
||
operationRef: json['operationRef'] as String?,
|
||
operationId: json['operationId'] as String?,
|
||
parameters: json['parameters'] as Map<String, dynamic>? ?? {},
|
||
requestBody: json['requestBody'],
|
||
server: server,
|
||
);
|
||
}
|
||
|
||
/// 链接描述
|
||
final String description;
|
||
|
||
/// 操作引用
|
||
final String? operationRef;
|
||
|
||
/// 操作ID
|
||
final String? operationId;
|
||
|
||
/// 参数映射
|
||
final Map<String, dynamic> parameters;
|
||
|
||
/// 请求体映射
|
||
final dynamic requestBody;
|
||
|
||
/// 服务器信息
|
||
final ApiServer? server;
|
||
}
|
||
|
||
/// API组件信息 (OpenAPI 3.0)
|
||
/// 包含可重用的组件定义
|
||
class ApiComponents {
|
||
const ApiComponents({
|
||
this.schemas = const {},
|
||
this.responses = const {},
|
||
this.parameters = const {},
|
||
this.examples = const {},
|
||
this.requestBodies = const {},
|
||
this.headers = const {},
|
||
this.securitySchemes = const {},
|
||
this.links = const {},
|
||
this.callbacks = const {},
|
||
});
|
||
|
||
/// 从JSON创建ApiComponents
|
||
factory ApiComponents.fromJson(Map<String, dynamic> json) {
|
||
// 解析 schemas
|
||
final schemasJson = json['schemas'] as Map<String, dynamic>? ?? {};
|
||
final schemas = <String, ApiModel>{};
|
||
schemasJson.forEach((key, value) {
|
||
if (value is Map<String, dynamic>) {
|
||
schemas[key] = ApiModel.fromJson(key, value);
|
||
}
|
||
});
|
||
|
||
// 解析 responses
|
||
final responsesJson = json['responses'] as Map<String, dynamic>? ?? {};
|
||
final responses = <String, ApiResponse>{};
|
||
responsesJson.forEach((key, value) {
|
||
if (value is Map<String, dynamic>) {
|
||
responses[key] = ApiResponse.fromJson(key, value);
|
||
}
|
||
});
|
||
|
||
// 解析 parameters
|
||
final parametersJson = json['parameters'] as Map<String, dynamic>? ?? {};
|
||
final parameters = <String, ApiParameter>{};
|
||
parametersJson.forEach((key, value) {
|
||
if (value is Map<String, dynamic>) {
|
||
parameters[key] = ApiParameter.fromJson(value);
|
||
}
|
||
});
|
||
|
||
// 解析 examples
|
||
final examplesJson = json['examples'] as Map<String, dynamic>? ?? {};
|
||
final examples = <String, ApiExample>{};
|
||
examplesJson.forEach((key, value) {
|
||
if (value is Map<String, dynamic>) {
|
||
examples[key] = ApiExample.fromJson(value);
|
||
}
|
||
});
|
||
|
||
// 解析 requestBodies
|
||
final requestBodiesJson =
|
||
json['requestBodies'] as Map<String, dynamic>? ?? {};
|
||
final requestBodies = <String, ApiRequestBody>{};
|
||
requestBodiesJson.forEach((key, value) {
|
||
if (value is Map<String, dynamic>) {
|
||
requestBodies[key] = ApiRequestBody.fromJson(value);
|
||
}
|
||
});
|
||
|
||
// 解析 headers
|
||
final headersJson = json['headers'] as Map<String, dynamic>? ?? {};
|
||
final headers = <String, ApiHeader>{};
|
||
headersJson.forEach((key, value) {
|
||
if (value is Map<String, dynamic>) {
|
||
headers[key] = ApiHeader.fromJson(value);
|
||
}
|
||
});
|
||
|
||
// 解析 securitySchemes
|
||
final securitySchemesJson =
|
||
json['securitySchemes'] as Map<String, dynamic>? ?? {};
|
||
final securitySchemes = <String, ApiSecurityScheme>{};
|
||
securitySchemesJson.forEach((key, value) {
|
||
if (value is Map<String, dynamic>) {
|
||
securitySchemes[key] = ApiSecurityScheme.fromJson(value);
|
||
}
|
||
});
|
||
|
||
// 解析 links
|
||
final linksJson = json['links'] as Map<String, dynamic>? ?? {};
|
||
final links = <String, ApiLink>{};
|
||
linksJson.forEach((key, value) {
|
||
if (value is Map<String, dynamic>) {
|
||
links[key] = ApiLink.fromJson(value);
|
||
}
|
||
});
|
||
|
||
// 解析 callbacks
|
||
final callbacksJson = json['callbacks'] as Map<String, dynamic>? ?? {};
|
||
final callbacks = <String, ApiCallback>{};
|
||
callbacksJson.forEach((key, value) {
|
||
if (value is Map<String, dynamic>) {
|
||
callbacks[key] = ApiCallback.fromJson(value);
|
||
}
|
||
});
|
||
|
||
return ApiComponents(
|
||
schemas: schemas,
|
||
responses: responses,
|
||
parameters: parameters,
|
||
examples: examples,
|
||
requestBodies: requestBodies,
|
||
headers: headers,
|
||
securitySchemes: securitySchemes,
|
||
links: links,
|
||
callbacks: callbacks,
|
||
);
|
||
}
|
||
|
||
/// Schema 定义
|
||
final Map<String, ApiModel> schemas;
|
||
|
||
/// 响应定义
|
||
final Map<String, ApiResponse> responses;
|
||
|
||
/// 参数定义
|
||
final Map<String, ApiParameter> parameters;
|
||
|
||
/// 示例定义
|
||
final Map<String, ApiExample> examples;
|
||
|
||
/// 请求体定义
|
||
final Map<String, ApiRequestBody> requestBodies;
|
||
|
||
/// 头部定义
|
||
final Map<String, ApiHeader> headers;
|
||
|
||
/// 安全方案定义
|
||
final Map<String, ApiSecurityScheme> securitySchemes;
|
||
|
||
/// 链接定义
|
||
final Map<String, ApiLink> links;
|
||
|
||
/// 回调定义
|
||
final Map<String, ApiCallback> callbacks;
|
||
}
|
||
|
||
/// API安全方案信息 (OpenAPI 3.0)
|
||
class ApiSecurityScheme {
|
||
const ApiSecurityScheme({
|
||
required this.type,
|
||
this.description = '',
|
||
this.name,
|
||
this.location,
|
||
this.scheme,
|
||
this.bearerFormat,
|
||
this.flows,
|
||
this.openIdConnectUrl,
|
||
});
|
||
|
||
/// 从JSON创建ApiSecurityScheme
|
||
factory ApiSecurityScheme.fromJson(Map<String, dynamic> json) {
|
||
final type = SecuritySchemeTypeExtension.fromString(
|
||
json['type'] as String? ?? 'apiKey',
|
||
);
|
||
|
||
return ApiSecurityScheme(
|
||
type: type,
|
||
description: json['description'] as String? ?? '',
|
||
name: json['name'] as String?,
|
||
location: json['in'] != null
|
||
? ApiKeyLocationExtension.fromString(json['in'] as String)
|
||
: null,
|
||
scheme: json['scheme'] as String?,
|
||
bearerFormat: json['bearerFormat'] as String?,
|
||
flows: json['flows'] != null
|
||
? OAuth2Flows.fromJson(json['flows'] as Map<String, dynamic>)
|
||
: null,
|
||
openIdConnectUrl: json['openIdConnectUrl'] as String?,
|
||
);
|
||
}
|
||
|
||
/// 安全方案类型
|
||
final SecuritySchemeType type;
|
||
|
||
/// 描述
|
||
final String description;
|
||
|
||
/// 名称 (用于 apiKey)
|
||
final String? name;
|
||
|
||
/// 位置 (用于 apiKey)
|
||
final ApiKeyLocation? location;
|
||
|
||
/// 方案 (用于 http)
|
||
final String? scheme;
|
||
|
||
/// Bearer 格式 (用于 http bearer)
|
||
final String? bearerFormat;
|
||
|
||
/// OAuth2 流程信息 (用于 oauth2)
|
||
final OAuth2Flows? flows;
|
||
|
||
/// OpenID Connect URL (用于 openIdConnect)
|
||
final String? openIdConnectUrl;
|
||
|
||
/// 检查是否是 API Key 认证
|
||
bool get isApiKey => type == SecuritySchemeType.apiKey;
|
||
|
||
/// 检查是否是 HTTP 认证
|
||
bool get isHttp => type == SecuritySchemeType.http;
|
||
|
||
/// 检查是否是 OAuth2 认证
|
||
bool get isOAuth2 => type == SecuritySchemeType.oauth2;
|
||
|
||
/// 检查是否是 OpenID Connect 认证
|
||
bool get isOpenIdConnect => type == SecuritySchemeType.openIdConnect;
|
||
|
||
/// 检查是否是 Bearer 认证
|
||
bool get isBearer => isHttp && scheme?.toLowerCase() == 'bearer';
|
||
|
||
/// 检查是否是 Basic 认证
|
||
bool get isBasic => isHttp && scheme?.toLowerCase() == 'basic';
|
||
|
||
/// 检查是否有 OAuth2 流程
|
||
bool get hasOAuth2Flows => flows?.hasAnyFlow ?? false;
|
||
|
||
/// 获取 API Key 的完整配置信息
|
||
String get apiKeyInfo {
|
||
if (!isApiKey || name == null || location == null) return '';
|
||
return '${location!.value}:$name';
|
||
}
|
||
|
||
/// 获取 HTTP 认证的完整配置信息
|
||
String get httpAuthInfo {
|
||
if (!isHttp || scheme == null) return '';
|
||
if (bearerFormat != null) {
|
||
return '$scheme ($bearerFormat)';
|
||
}
|
||
return scheme!;
|
||
}
|
||
}
|
||
|
||
/// API回调信息 (OpenAPI 3.0)
|
||
class ApiCallback {
|
||
const ApiCallback({
|
||
this.expressions = const {},
|
||
});
|
||
|
||
/// 从JSON创建ApiCallback
|
||
factory ApiCallback.fromJson(Map<String, dynamic> json) {
|
||
final expressions = <String, ApiPathItem>{};
|
||
|
||
json.forEach((expression, pathItemData) {
|
||
if (pathItemData is Map<String, dynamic>) {
|
||
expressions[expression] = ApiPathItem.fromJson(pathItemData);
|
||
}
|
||
});
|
||
|
||
return ApiCallback(
|
||
expressions: expressions,
|
||
);
|
||
}
|
||
|
||
/// 回调表达式映射
|
||
final Map<String, ApiPathItem> expressions;
|
||
}
|
||
|
||
/// API路径项信息 (OpenAPI 3.0)
|
||
class ApiPathItem {
|
||
const ApiPathItem({
|
||
this.summary = '',
|
||
this.description = '',
|
||
this.operations = const {},
|
||
this.servers = const [],
|
||
this.parameters = const [],
|
||
});
|
||
|
||
/// 从JSON创建ApiPathItem
|
||
factory ApiPathItem.fromJson(Map<String, dynamic> json) {
|
||
final operations = <String, ApiOperation>{};
|
||
final httpMethods = [
|
||
'get',
|
||
'put',
|
||
'post',
|
||
'delete',
|
||
'options',
|
||
'head',
|
||
'patch',
|
||
'trace',
|
||
];
|
||
|
||
for (final method in httpMethods) {
|
||
if (json[method] != null) {
|
||
operations[method] =
|
||
ApiOperation.fromJson(json[method] as Map<String, dynamic>);
|
||
}
|
||
}
|
||
|
||
final serversJson = json['servers'] as List<dynamic>? ?? [];
|
||
final servers = serversJson
|
||
.map((server) => ApiServer.fromJson(server as Map<String, dynamic>))
|
||
.toList();
|
||
|
||
final parametersJson = json['parameters'] as List<dynamic>? ?? [];
|
||
final parameters = parametersJson
|
||
.map((param) => ApiParameter.fromJson(param as Map<String, dynamic>))
|
||
.toList();
|
||
|
||
return ApiPathItem(
|
||
summary: json['summary'] as String? ?? '',
|
||
description: json['description'] as String? ?? '',
|
||
operations: operations,
|
||
servers: servers,
|
||
parameters: parameters,
|
||
);
|
||
}
|
||
|
||
/// 路径摘要
|
||
final String summary;
|
||
|
||
/// 路径描述
|
||
final String description;
|
||
|
||
/// 操作映射 (HTTP方法 -> 操作)
|
||
final Map<String, ApiOperation> operations;
|
||
|
||
/// 服务器信息
|
||
final List<ApiServer> servers;
|
||
|
||
/// 参数信息
|
||
final List<ApiParameter> parameters;
|
||
}
|
||
|
||
/// API操作信息 (OpenAPI 3.0)
|
||
class ApiOperation {
|
||
const ApiOperation({
|
||
this.summary = '',
|
||
this.description = '',
|
||
this.operationId,
|
||
this.tags = const [],
|
||
this.parameters = const [],
|
||
this.requestBody,
|
||
this.responses = const {},
|
||
this.security = const [],
|
||
});
|
||
|
||
/// 从JSON创建ApiOperation
|
||
factory ApiOperation.fromJson(Map<String, dynamic> json) {
|
||
final parametersJson = json['parameters'] as List<dynamic>? ?? [];
|
||
final parameters = parametersJson
|
||
.map((param) => ApiParameter.fromJson(param as Map<String, dynamic>))
|
||
.toList();
|
||
|
||
final requestBodyJson = json['requestBody'] as Map<String, dynamic>?;
|
||
final requestBody = requestBodyJson != null
|
||
? ApiRequestBody.fromJson(requestBodyJson)
|
||
: null;
|
||
|
||
final responsesJson = json['responses'] as Map<String, dynamic>? ?? {};
|
||
final responses = <String, ApiResponse>{};
|
||
responsesJson.forEach((code, responseData) {
|
||
if (responseData is Map<String, dynamic>) {
|
||
responses[code] = ApiResponse.fromJson(code, responseData);
|
||
}
|
||
});
|
||
|
||
final securityJson = json['security'] as List<dynamic>? ?? [];
|
||
final security = securityJson
|
||
.map((s) => ApiSecurityRequirement.fromJson(s as Map<String, dynamic>))
|
||
.toList();
|
||
|
||
return ApiOperation(
|
||
summary: json['summary'] as String? ?? '',
|
||
description: json['description'] as String? ?? '',
|
||
operationId: json['operationId'] as String?,
|
||
tags:
|
||
(json['tags'] as List<dynamic>?)?.map((e) => e.toString()).toList() ??
|
||
[],
|
||
parameters: parameters,
|
||
requestBody: requestBody,
|
||
responses: responses,
|
||
security: security,
|
||
);
|
||
}
|
||
|
||
/// 操作摘要
|
||
final String summary;
|
||
|
||
/// 操作描述
|
||
final String description;
|
||
|
||
/// 操作ID
|
||
final String? operationId;
|
||
|
||
/// 标签
|
||
final List<String> tags;
|
||
|
||
/// 参数
|
||
final List<ApiParameter> parameters;
|
||
|
||
/// 请求体
|
||
final ApiRequestBody? requestBody;
|
||
|
||
/// 响应
|
||
final Map<String, ApiResponse> responses;
|
||
|
||
/// 安全要求
|
||
final List<ApiSecurityRequirement> security;
|
||
}
|
||
|
||
/// API 判别器信息 (OpenAPI 3.0)
|
||
/// 用于多态类型的判别
|
||
class ApiDiscriminator {
|
||
const ApiDiscriminator({
|
||
required this.propertyName,
|
||
this.mapping = const {},
|
||
});
|
||
|
||
/// 从JSON创建ApiDiscriminator
|
||
factory ApiDiscriminator.fromJson(Map<String, dynamic> json) {
|
||
final mappingJson = json['mapping'] as Map<String, dynamic>? ?? {};
|
||
final mapping = <String, String>{};
|
||
|
||
mappingJson.forEach((key, value) {
|
||
if (value is String) {
|
||
mapping[key] = value;
|
||
}
|
||
});
|
||
|
||
return ApiDiscriminator(
|
||
propertyName: json['propertyName'] as String? ?? '',
|
||
mapping: mapping,
|
||
);
|
||
}
|
||
|
||
/// 判别器属性名
|
||
final String propertyName;
|
||
|
||
/// 映射表 (值 -> schema 引用)
|
||
final Map<String, String> mapping;
|
||
|
||
/// 检查是否有映射表
|
||
bool get hasMapping => mapping.isNotEmpty;
|
||
|
||
/// 根据值获取对应的 schema 引用
|
||
String? getSchemaForValue(String value) => mapping[value];
|
||
}
|
||
|
||
/// API Schema 信息 (OpenAPI 3.0)
|
||
/// 表示一个 JSON Schema 对象,支持组合模式
|
||
class ApiSchema {
|
||
const ApiSchema({
|
||
this.type,
|
||
this.format,
|
||
this.description = '',
|
||
this.properties = const {},
|
||
this.required = const [],
|
||
this.items,
|
||
this.reference,
|
||
this.enumValues = const [],
|
||
this.allOf = const [],
|
||
this.oneOf = const [],
|
||
this.anyOf = const [],
|
||
this.not,
|
||
this.discriminator,
|
||
this.additionalProperties,
|
||
this.patternProperties = const {},
|
||
this.propertyNames,
|
||
this.dependencies = const {},
|
||
this.constValue,
|
||
this.ifSchema,
|
||
this.thenSchema,
|
||
this.elseSchema,
|
||
this.minimum,
|
||
this.maximum,
|
||
this.exclusiveMinimum,
|
||
this.exclusiveMaximum,
|
||
this.minLength,
|
||
this.maxLength,
|
||
this.pattern,
|
||
this.minItems,
|
||
this.maxItems,
|
||
this.uniqueItems,
|
||
this.nullable = false,
|
||
this.example,
|
||
this.defaultValue,
|
||
});
|
||
|
||
/// 从JSON创建ApiSchema
|
||
factory ApiSchema.fromJson(Map<String, dynamic> json) {
|
||
// 解析 properties
|
||
final propertiesJson = json['properties'] as Map<String, dynamic>? ?? {};
|
||
final properties = <String, ApiProperty>{};
|
||
final requiredFields = (json['required'] as List<dynamic>?)
|
||
?.map((e) => e.toString())
|
||
.toList() ??
|
||
[];
|
||
|
||
propertiesJson.forEach((propName, propData) {
|
||
if (propData is Map<String, dynamic>) {
|
||
properties[propName] = ApiProperty.fromJson(
|
||
propName,
|
||
propData,
|
||
requiredFields,
|
||
);
|
||
}
|
||
});
|
||
|
||
// 解析 items (用于数组类型)
|
||
final itemsJson = json['items'] as Map<String, dynamic>?;
|
||
final items = itemsJson != null ? ApiSchema.fromJson(itemsJson) : null;
|
||
|
||
// 解析组合模式
|
||
final allOfJson = json['allOf'] as List<dynamic>? ?? [];
|
||
final allOf = allOfJson
|
||
.map((schema) => ApiSchema.fromJson(schema as Map<String, dynamic>))
|
||
.toList();
|
||
|
||
final oneOfJson = json['oneOf'] as List<dynamic>? ?? [];
|
||
final oneOf = oneOfJson
|
||
.map((schema) => ApiSchema.fromJson(schema as Map<String, dynamic>))
|
||
.toList();
|
||
|
||
final anyOfJson = json['anyOf'] as List<dynamic>? ?? [];
|
||
final anyOf = anyOfJson
|
||
.map((schema) => ApiSchema.fromJson(schema as Map<String, dynamic>))
|
||
.toList();
|
||
|
||
final notJson = json['not'] as Map<String, dynamic>?;
|
||
final not = notJson != null ? ApiSchema.fromJson(notJson) : null;
|
||
|
||
// 解析 discriminator
|
||
final discriminatorJson = json['discriminator'] as Map<String, dynamic>?;
|
||
final discriminator = discriminatorJson != null
|
||
? ApiDiscriminator.fromJson(discriminatorJson)
|
||
: null;
|
||
|
||
// 解析 patternProperties
|
||
final patternPropertiesJson =
|
||
json['patternProperties'] as Map<String, dynamic>? ?? {};
|
||
final patternProperties = <String, ApiSchema>{};
|
||
patternPropertiesJson.forEach((pattern, schemaData) {
|
||
if (schemaData is Map<String, dynamic>) {
|
||
patternProperties[pattern] = ApiSchema.fromJson(schemaData);
|
||
}
|
||
});
|
||
|
||
// 解析 propertyNames
|
||
final propertyNamesJson = json['propertyNames'] as Map<String, dynamic>?;
|
||
final propertyNames = propertyNamesJson != null
|
||
? ApiSchema.fromJson(propertyNamesJson)
|
||
: null;
|
||
|
||
// 解析 dependencies
|
||
final dependencies = json['dependencies'] as Map<String, dynamic>? ?? {};
|
||
|
||
// 解析条件 Schema (if/then/else)
|
||
final ifJson = json['if'] as Map<String, dynamic>?;
|
||
final ifSchema = ifJson != null ? ApiSchema.fromJson(ifJson) : null;
|
||
|
||
final thenJson = json['then'] as Map<String, dynamic>?;
|
||
final thenSchema = thenJson != null ? ApiSchema.fromJson(thenJson) : null;
|
||
|
||
final elseJson = json['else'] as Map<String, dynamic>?;
|
||
final elseSchema = elseJson != null ? ApiSchema.fromJson(elseJson) : null;
|
||
|
||
// 处理引用
|
||
String? reference;
|
||
if (json[r'$ref'] != null) {
|
||
final ref = json[r'$ref'] as String;
|
||
reference = ref.split('/').last;
|
||
}
|
||
|
||
return ApiSchema(
|
||
type: json['type'] as String?,
|
||
format: json['format'] as String?,
|
||
description: json['description'] as String? ?? '',
|
||
properties: properties,
|
||
required: requiredFields,
|
||
items: items,
|
||
reference: reference,
|
||
enumValues: (json['enum'] as List<dynamic>?) ?? [],
|
||
allOf: allOf,
|
||
oneOf: oneOf,
|
||
anyOf: anyOf,
|
||
not: not,
|
||
discriminator: discriminator,
|
||
additionalProperties: json['additionalProperties'],
|
||
patternProperties: patternProperties,
|
||
propertyNames: propertyNames,
|
||
dependencies: dependencies,
|
||
constValue: json['const'],
|
||
ifSchema: ifSchema,
|
||
thenSchema: thenSchema,
|
||
elseSchema: elseSchema,
|
||
minimum: json['minimum'] as num?,
|
||
maximum: json['maximum'] as num?,
|
||
exclusiveMinimum: json['exclusiveMinimum'] as bool?,
|
||
exclusiveMaximum: json['exclusiveMaximum'] as bool?,
|
||
minLength: json['minLength'] as int?,
|
||
maxLength: json['maxLength'] as int?,
|
||
pattern: json['pattern'] as String?,
|
||
minItems: json['minItems'] as int?,
|
||
maxItems: json['maxItems'] as int?,
|
||
uniqueItems: json['uniqueItems'] as bool?,
|
||
nullable: json['nullable'] as bool? ?? false,
|
||
example: json['example'],
|
||
defaultValue: json['default'],
|
||
);
|
||
}
|
||
|
||
/// Schema 类型
|
||
final String? type;
|
||
|
||
/// Schema 格式
|
||
final String? format;
|
||
|
||
/// Schema 描述
|
||
final String description;
|
||
|
||
/// 属性定义 (用于 object 类型)
|
||
final Map<String, ApiProperty> properties;
|
||
|
||
/// 必需字段
|
||
final List<String> required;
|
||
|
||
/// 数组项定义 (用于 array 类型)
|
||
final ApiSchema? items;
|
||
|
||
/// 引用 ($ref)
|
||
final String? reference;
|
||
|
||
/// 枚举值
|
||
final List<dynamic> enumValues;
|
||
|
||
/// 组合模式
|
||
final List<ApiSchema> allOf;
|
||
final List<ApiSchema> oneOf;
|
||
final List<ApiSchema> anyOf;
|
||
final ApiSchema? not;
|
||
|
||
/// 多态类型判别器 (OpenAPI 3.0)
|
||
final ApiDiscriminator? discriminator;
|
||
|
||
/// 额外属性 (可以是 boolean 或 Schema)
|
||
final dynamic additionalProperties;
|
||
|
||
/// 模式属性 (patternProperties)
|
||
final Map<String, ApiSchema> patternProperties;
|
||
|
||
/// 属性名称约束
|
||
final ApiSchema? propertyNames;
|
||
|
||
/// 属性依赖关系
|
||
final Map<String, dynamic> dependencies;
|
||
|
||
/// 常量值
|
||
final dynamic constValue;
|
||
|
||
/// 条件 Schema (if/then/else)
|
||
final ApiSchema? ifSchema;
|
||
final ApiSchema? thenSchema;
|
||
final ApiSchema? elseSchema;
|
||
|
||
/// 最小值/最大值 (用于数值类型)
|
||
final num? minimum;
|
||
final num? maximum;
|
||
final bool? exclusiveMinimum;
|
||
final bool? exclusiveMaximum;
|
||
|
||
/// 字符串长度限制
|
||
final int? minLength;
|
||
final int? maxLength;
|
||
final String? pattern;
|
||
|
||
/// 数组长度限制
|
||
final int? minItems;
|
||
final int? maxItems;
|
||
final bool? uniqueItems;
|
||
|
||
/// 可空性
|
||
final bool nullable;
|
||
|
||
/// 示例值
|
||
final dynamic example;
|
||
|
||
/// 默认值
|
||
final dynamic defaultValue;
|
||
|
||
/// 检查是否是组合模式
|
||
bool get isComposition =>
|
||
allOf.isNotEmpty || oneOf.isNotEmpty || anyOf.isNotEmpty;
|
||
|
||
/// 检查是否是 allOf 组合
|
||
bool get isAllOf => allOf.isNotEmpty;
|
||
|
||
/// 检查是否是 oneOf 组合
|
||
bool get isOneOf => oneOf.isNotEmpty;
|
||
|
||
/// 检查是否是 anyOf 组合
|
||
bool get isAnyOf => anyOf.isNotEmpty;
|
||
|
||
/// 检查是否有 not 约束
|
||
bool get hasNot => not != null;
|
||
|
||
/// 检查是否有判别器
|
||
bool get hasDiscriminator => discriminator != null;
|
||
|
||
/// 检查是否是引用类型
|
||
bool get isReference => reference != null;
|
||
|
||
/// 检查是否是枚举类型
|
||
bool get isEnum => enumValues.isNotEmpty;
|
||
|
||
/// 检查是否是数组类型
|
||
bool get isArray => type == 'array';
|
||
|
||
/// 检查是否是对象类型
|
||
bool get isObject => type == 'object' || properties.isNotEmpty;
|
||
|
||
/// 检查是否有模式属性
|
||
bool get hasPatternProperties => patternProperties.isNotEmpty;
|
||
|
||
/// 检查是否有属性名称约束
|
||
bool get hasPropertyNames => propertyNames != null;
|
||
|
||
/// 检查是否有属性依赖
|
||
bool get hasDependencies => dependencies.isNotEmpty;
|
||
|
||
/// 检查是否有常量值
|
||
bool get hasConstValue => constValue != null;
|
||
|
||
/// 检查是否有条件 Schema
|
||
bool get hasConditionalSchema =>
|
||
ifSchema != null || thenSchema != null || elseSchema != null;
|
||
|
||
/// 检查是否允许额外属性
|
||
bool get allowsAdditionalProperties {
|
||
if (additionalProperties == null) return true; // 默认允许
|
||
if (additionalProperties is bool) return additionalProperties as bool;
|
||
return true; // 如果是 Schema 对象,表示允许但有约束
|
||
}
|
||
|
||
/// 获取额外属性的 Schema(如果 additionalProperties 是 Schema 对象)
|
||
ApiSchema? get additionalPropertiesSchema {
|
||
if (additionalProperties is Map<String, dynamic>) {
|
||
return ApiSchema.fromJson(additionalProperties as Map<String, dynamic>);
|
||
}
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// API模型信息
|
||
class ApiModel {
|
||
const ApiModel({
|
||
required this.name,
|
||
required this.description,
|
||
required this.properties,
|
||
required this.required,
|
||
this.isEnum = false,
|
||
this.enumValues = const [],
|
||
this.enumType,
|
||
this.allOf = const [],
|
||
this.oneOf = const [],
|
||
this.anyOf = const [],
|
||
this.not,
|
||
this.discriminator,
|
||
this.usageType = ModelUsageType.unknown,
|
||
});
|
||
|
||
/// 从JSON创建ApiModel
|
||
factory ApiModel.fromJson(
|
||
String name,
|
||
Map<String, dynamic> json, {
|
||
ModelUsageType usageType = ModelUsageType.unknown,
|
||
}) {
|
||
final isEnum = json['enum'] != null;
|
||
final enumValues =
|
||
isEnum ? (json['enum'] as List<dynamic>?) ?? <dynamic>[] : <dynamic>[];
|
||
final properties = json['properties'] as Map<String, dynamic>? ?? {};
|
||
List<String> required;
|
||
if (json.containsKey('required')) {
|
||
required = (json['required'] as List<dynamic>?)
|
||
?.map((e) => e.toString())
|
||
.toList() ??
|
||
[];
|
||
} else {
|
||
// 没有 required 字段时,凡 nullable != true 的都视为 required
|
||
required = properties.entries
|
||
.where((e) => !(e.value['nullable'] as bool? ?? false))
|
||
.map((e) => e.key)
|
||
.toList();
|
||
}
|
||
|
||
// 解析组合模式
|
||
final allOfJson = json['allOf'] as List<dynamic>? ?? [];
|
||
final allOf = allOfJson
|
||
.map((schema) => ApiSchema.fromJson(schema as Map<String, dynamic>))
|
||
.toList();
|
||
|
||
final oneOfJson = json['oneOf'] as List<dynamic>? ?? [];
|
||
final oneOf = oneOfJson
|
||
.map((schema) => ApiSchema.fromJson(schema as Map<String, dynamic>))
|
||
.toList();
|
||
|
||
final anyOfJson = json['anyOf'] as List<dynamic>? ?? [];
|
||
final anyOf = anyOfJson
|
||
.map((schema) => ApiSchema.fromJson(schema as Map<String, dynamic>))
|
||
.toList();
|
||
|
||
final notJson = json['not'] as Map<String, dynamic>?;
|
||
final not = notJson != null ? ApiSchema.fromJson(notJson) : null;
|
||
|
||
// 解析 discriminator
|
||
final discriminatorJson = json['discriminator'] as Map<String, dynamic>?;
|
||
final discriminator = discriminatorJson != null
|
||
? ApiDiscriminator.fromJson(discriminatorJson)
|
||
: null;
|
||
|
||
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,
|
||
allOf: allOf,
|
||
oneOf: oneOf,
|
||
anyOf: anyOf,
|
||
not: not,
|
||
discriminator: discriminator,
|
||
usageType: usageType,
|
||
properties: properties.map(
|
||
(propName, propData) => MapEntry(
|
||
propName,
|
||
ApiProperty.fromJson(
|
||
propName,
|
||
propData as Map<String, dynamic>,
|
||
required,
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
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;
|
||
|
||
/// 组合模式支持 (OpenAPI 3.0)
|
||
final List<ApiSchema> allOf;
|
||
final List<ApiSchema> oneOf;
|
||
final List<ApiSchema> anyOf;
|
||
final ApiSchema? not;
|
||
|
||
/// 多态类型判别器 (OpenAPI 3.0)
|
||
final ApiDiscriminator? discriminator;
|
||
|
||
/// 模型用途类型
|
||
/// 标识该模型在 API 中的实际用途(请求/响应/通用/未知)
|
||
final ModelUsageType usageType;
|
||
|
||
/// 检查是否使用了组合模式
|
||
bool get isComposition =>
|
||
allOf.isNotEmpty || oneOf.isNotEmpty || anyOf.isNotEmpty;
|
||
|
||
/// 检查是否是 allOf 组合
|
||
bool get isAllOf => allOf.isNotEmpty;
|
||
|
||
/// 检查是否是 oneOf 组合
|
||
bool get isOneOf => oneOf.isNotEmpty;
|
||
|
||
/// 检查是否是 anyOf 组合
|
||
bool get isAnyOf => anyOf.isNotEmpty;
|
||
|
||
/// 检查是否有 not 约束
|
||
bool get hasNot => not != null;
|
||
|
||
/// 检查是否有判别器
|
||
bool get hasDiscriminator => discriminator != null;
|
||
|
||
/// 创建副本并更新用途类型
|
||
/// 用于在分析 schema 使用情况后更新模型的用途类型
|
||
ApiModel copyWithUsageType(ModelUsageType newUsageType) {
|
||
return ApiModel(
|
||
name: name,
|
||
description: description,
|
||
properties: properties,
|
||
required: required,
|
||
isEnum: isEnum,
|
||
enumValues: enumValues,
|
||
enumType: enumType,
|
||
allOf: allOf,
|
||
oneOf: oneOf,
|
||
anyOf: anyOf,
|
||
not: not,
|
||
discriminator: discriminator,
|
||
usageType: newUsageType,
|
||
);
|
||
}
|
||
}
|
||
|
||
/// API属性信息
|
||
class ApiProperty {
|
||
const ApiProperty({
|
||
required this.name,
|
||
required this.type,
|
||
required this.description,
|
||
required this.required,
|
||
this.format,
|
||
this.nullable = false,
|
||
this.example,
|
||
this.defaultValue,
|
||
this.reference,
|
||
this.items,
|
||
this.nestedProperties = const {},
|
||
this.nestedRequired = const [],
|
||
this.schema,
|
||
});
|
||
|
||
/// 从JSON创建ApiProperty
|
||
factory ApiProperty.fromJson(
|
||
String name,
|
||
Map<String, dynamic> json,
|
||
List<String> requiredFields, {
|
||
int maxDepth = 10,
|
||
int currentDepth = 0,
|
||
}) {
|
||
// 防止过深的嵌套
|
||
if (currentDepth >= maxDepth) {
|
||
return ApiProperty(
|
||
name: name,
|
||
type: PropertyType.object,
|
||
description: '达到最大嵌套深度的对象',
|
||
required: requiredFields.contains(name),
|
||
);
|
||
}
|
||
|
||
final type = PropertyType.fromString(json['type'] as String? ?? 'string');
|
||
String? reference;
|
||
ApiModel? items;
|
||
final nestedProperties = <String, ApiProperty>{};
|
||
var nestedRequired = <String>[];
|
||
ApiSchema? schema;
|
||
|
||
// 处理引用类型
|
||
if (json[r'$ref'] != null) {
|
||
final ref = json[r'$ref'] as String;
|
||
reference = ref.split('/').last;
|
||
}
|
||
|
||
// 处理复杂 schema(组合模式等)
|
||
if (json['allOf'] != null ||
|
||
json['oneOf'] != null ||
|
||
json['anyOf'] != null) {
|
||
schema = ApiSchema.fromJson(json);
|
||
}
|
||
|
||
// 处理嵌套对象类型
|
||
if (type == PropertyType.object && json['properties'] != null) {
|
||
final propertiesJson = json['properties'] as Map<String, dynamic>;
|
||
nestedRequired = (json['required'] as List<dynamic>?)
|
||
?.map((e) => e.toString())
|
||
.toList() ??
|
||
[];
|
||
|
||
propertiesJson.forEach((propName, propData) {
|
||
if (propData is Map<String, dynamic>) {
|
||
try {
|
||
final nestedProperty = ApiProperty.fromJson(
|
||
propName,
|
||
propData,
|
||
nestedRequired,
|
||
maxDepth: maxDepth,
|
||
currentDepth: currentDepth + 1,
|
||
);
|
||
nestedProperties[propName] = nestedProperty;
|
||
} catch (e) {
|
||
// 如果解析嵌套属性失败,创建一个基本属性
|
||
nestedProperties[propName] = ApiProperty(
|
||
name: propName,
|
||
type: PropertyType.string,
|
||
description: '解析失败的嵌套属性',
|
||
required: nestedRequired.contains(propName),
|
||
);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// 处理数组类型的 items
|
||
if (type == PropertyType.array && json['items'] != null) {
|
||
final itemsJson = json['items'] as Map<String, dynamic>;
|
||
|
||
// 如果 items 是引用类型
|
||
if (itemsJson[r'$ref'] != null) {
|
||
final itemRef = itemsJson[r'$ref'] as String;
|
||
final itemRefName = itemRef.split('/').last;
|
||
items = ApiModel(
|
||
name: itemRefName,
|
||
description: '',
|
||
properties: {},
|
||
required: [],
|
||
);
|
||
} else if (itemsJson['type'] == 'object' &&
|
||
itemsJson['properties'] != null) {
|
||
// 如果 items 是嵌套对象
|
||
final itemProperties = <String, ApiProperty>{};
|
||
final itemRequired = (itemsJson['required'] as List<dynamic>?)
|
||
?.map((e) => e.toString())
|
||
.toList() ??
|
||
[];
|
||
final itemPropertiesJson =
|
||
itemsJson['properties'] as Map<String, dynamic>;
|
||
|
||
itemPropertiesJson.forEach((propName, propData) {
|
||
if (propData is Map<String, dynamic>) {
|
||
try {
|
||
final itemProperty = ApiProperty.fromJson(
|
||
propName,
|
||
propData,
|
||
itemRequired,
|
||
maxDepth: maxDepth,
|
||
currentDepth: currentDepth + 1,
|
||
);
|
||
itemProperties[propName] = itemProperty;
|
||
} catch (e) {
|
||
// 创建基本属性作为后备
|
||
itemProperties[propName] = ApiProperty(
|
||
name: propName,
|
||
type: PropertyType.string,
|
||
description: '解析失败的数组项属性',
|
||
required: itemRequired.contains(propName),
|
||
);
|
||
}
|
||
}
|
||
});
|
||
|
||
items = ApiModel(
|
||
name: '${name}Item',
|
||
description: itemsJson['description'] as String? ?? '',
|
||
properties: itemProperties,
|
||
required: itemRequired,
|
||
);
|
||
} else {
|
||
// 如果 items 是基本类型
|
||
final itemType =
|
||
PropertyType.fromString(itemsJson['type'] as String? ?? 'string');
|
||
items = ApiModel(
|
||
name: itemType.value,
|
||
description: '',
|
||
properties: {},
|
||
required: [],
|
||
);
|
||
}
|
||
}
|
||
|
||
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,
|
||
nestedProperties: nestedProperties,
|
||
nestedRequired: nestedRequired,
|
||
schema: schema,
|
||
);
|
||
}
|
||
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; // 用于数组类型
|
||
|
||
/// 嵌套对象属性 (用于 object 类型)
|
||
final Map<String, ApiProperty> nestedProperties;
|
||
|
||
/// 嵌套对象的必需字段
|
||
final List<String> nestedRequired;
|
||
|
||
/// Schema 定义 (用于复杂类型)
|
||
final ApiSchema? schema;
|
||
|
||
/// 检查是否有嵌套属性
|
||
bool get hasNestedProperties => nestedProperties.isNotEmpty;
|
||
|
||
/// 检查是否有复杂 schema
|
||
bool get hasComplexSchema => schema != null;
|
||
}
|
||
|
||
/// API控制器信息
|
||
class ApiController {
|
||
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);
|
||
}
|
||
final String name;
|
||
final String description;
|
||
final List<ApiPath> paths;
|
||
}
|
||
|
||
/// 安全方案类型
|
||
enum SecuritySchemeType {
|
||
apiKey,
|
||
http,
|
||
oauth2,
|
||
openIdConnect,
|
||
}
|
||
|
||
extension SecuritySchemeTypeExtension on SecuritySchemeType {
|
||
String get value {
|
||
switch (this) {
|
||
case SecuritySchemeType.apiKey:
|
||
return 'apiKey';
|
||
case SecuritySchemeType.http:
|
||
return 'http';
|
||
case SecuritySchemeType.oauth2:
|
||
return 'oauth2';
|
||
case SecuritySchemeType.openIdConnect:
|
||
return 'openIdConnect';
|
||
}
|
||
}
|
||
|
||
static SecuritySchemeType fromString(String value) {
|
||
switch (value.toLowerCase()) {
|
||
case 'apikey':
|
||
return SecuritySchemeType.apiKey;
|
||
case 'http':
|
||
return SecuritySchemeType.http;
|
||
case 'oauth2':
|
||
return SecuritySchemeType.oauth2;
|
||
case 'openidconnect':
|
||
return SecuritySchemeType.openIdConnect;
|
||
default:
|
||
return SecuritySchemeType.apiKey;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// API Key 位置
|
||
enum ApiKeyLocation {
|
||
query,
|
||
header,
|
||
cookie,
|
||
}
|
||
|
||
extension ApiKeyLocationExtension on ApiKeyLocation {
|
||
String get value {
|
||
switch (this) {
|
||
case ApiKeyLocation.query:
|
||
return 'query';
|
||
case ApiKeyLocation.header:
|
||
return 'header';
|
||
case ApiKeyLocation.cookie:
|
||
return 'cookie';
|
||
}
|
||
}
|
||
|
||
static ApiKeyLocation fromString(String value) {
|
||
switch (value.toLowerCase()) {
|
||
case 'query':
|
||
return ApiKeyLocation.query;
|
||
case 'header':
|
||
return ApiKeyLocation.header;
|
||
case 'cookie':
|
||
return ApiKeyLocation.cookie;
|
||
default:
|
||
return ApiKeyLocation.header;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// OAuth2 流程类型
|
||
enum OAuth2FlowType {
|
||
authorizationCode,
|
||
implicit,
|
||
password,
|
||
clientCredentials,
|
||
}
|
||
|
||
extension OAuth2FlowTypeExtension on OAuth2FlowType {
|
||
String get value {
|
||
switch (this) {
|
||
case OAuth2FlowType.authorizationCode:
|
||
return 'authorizationCode';
|
||
case OAuth2FlowType.implicit:
|
||
return 'implicit';
|
||
case OAuth2FlowType.password:
|
||
return 'password';
|
||
case OAuth2FlowType.clientCredentials:
|
||
return 'clientCredentials';
|
||
}
|
||
}
|
||
|
||
static OAuth2FlowType fromString(String value) {
|
||
switch (value.toLowerCase()) {
|
||
case 'authorizationcode':
|
||
return OAuth2FlowType.authorizationCode;
|
||
case 'implicit':
|
||
return OAuth2FlowType.implicit;
|
||
case 'password':
|
||
return OAuth2FlowType.password;
|
||
case 'clientcredentials':
|
||
return OAuth2FlowType.clientCredentials;
|
||
default:
|
||
return OAuth2FlowType.authorizationCode;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// OAuth2 流程配置
|
||
class OAuth2Flow {
|
||
const OAuth2Flow({
|
||
this.authorizationUrl,
|
||
this.tokenUrl,
|
||
this.refreshUrl,
|
||
this.scopes = const {},
|
||
});
|
||
|
||
/// 从 JSON 创建 OAuth2Flow
|
||
factory OAuth2Flow.fromJson(Map<String, dynamic> json) {
|
||
final scopesData = json['scopes'];
|
||
final Map<String, String> scopes;
|
||
|
||
if (scopesData is Map) {
|
||
scopes = scopesData
|
||
.map((key, value) => MapEntry(key.toString(), value.toString()));
|
||
} else {
|
||
scopes = {};
|
||
}
|
||
|
||
return OAuth2Flow(
|
||
authorizationUrl: json['authorizationUrl'] as String?,
|
||
tokenUrl: json['tokenUrl'] as String?,
|
||
refreshUrl: json['refreshUrl'] as String?,
|
||
scopes: scopes,
|
||
);
|
||
}
|
||
|
||
/// 授权 URL (用于 authorizationCode 和 implicit 流程)
|
||
final String? authorizationUrl;
|
||
|
||
/// 令牌 URL (用于 authorizationCode, password 和 clientCredentials 流程)
|
||
final String? tokenUrl;
|
||
|
||
/// 刷新 URL (可选)
|
||
final String? refreshUrl;
|
||
|
||
/// 可用的作用域
|
||
final Map<String, String> scopes;
|
||
|
||
/// 检查是否有授权 URL
|
||
bool get hasAuthorizationUrl =>
|
||
authorizationUrl != null && authorizationUrl!.isNotEmpty;
|
||
|
||
/// 检查是否有令牌 URL
|
||
bool get hasTokenUrl => tokenUrl != null && tokenUrl!.isNotEmpty;
|
||
|
||
/// 检查是否有刷新 URL
|
||
bool get hasRefreshUrl => refreshUrl != null && refreshUrl!.isNotEmpty;
|
||
|
||
/// 检查是否有作用域
|
||
bool get hasScopes => scopes.isNotEmpty;
|
||
}
|
||
|
||
/// OAuth2 流程集合
|
||
class OAuth2Flows {
|
||
const OAuth2Flows({
|
||
this.authorizationCode,
|
||
this.implicit,
|
||
this.password,
|
||
this.clientCredentials,
|
||
});
|
||
|
||
/// 从 JSON 创建 OAuth2Flows
|
||
factory OAuth2Flows.fromJson(Map<String, dynamic> json) {
|
||
return OAuth2Flows(
|
||
authorizationCode: json['authorizationCode'] != null
|
||
? OAuth2Flow.fromJson(
|
||
json['authorizationCode'] as Map<String, dynamic>,
|
||
)
|
||
: null,
|
||
implicit: json['implicit'] != null
|
||
? OAuth2Flow.fromJson(json['implicit'] as Map<String, dynamic>)
|
||
: null,
|
||
password: json['password'] != null
|
||
? OAuth2Flow.fromJson(json['password'] as Map<String, dynamic>)
|
||
: null,
|
||
clientCredentials: json['clientCredentials'] != null
|
||
? OAuth2Flow.fromJson(
|
||
json['clientCredentials'] as Map<String, dynamic>,
|
||
)
|
||
: null,
|
||
);
|
||
}
|
||
|
||
/// 授权码流程
|
||
final OAuth2Flow? authorizationCode;
|
||
|
||
/// 隐式流程
|
||
final OAuth2Flow? implicit;
|
||
|
||
/// 密码流程
|
||
final OAuth2Flow? password;
|
||
|
||
/// 客户端凭证流程
|
||
final OAuth2Flow? clientCredentials;
|
||
|
||
/// 获取所有可用的流程
|
||
List<OAuth2FlowType> get availableFlows {
|
||
final flows = <OAuth2FlowType>[];
|
||
if (authorizationCode != null) flows.add(OAuth2FlowType.authorizationCode);
|
||
if (implicit != null) flows.add(OAuth2FlowType.implicit);
|
||
if (password != null) flows.add(OAuth2FlowType.password);
|
||
if (clientCredentials != null) flows.add(OAuth2FlowType.clientCredentials);
|
||
return flows;
|
||
}
|
||
|
||
/// 检查是否有任何流程
|
||
bool get hasAnyFlow => availableFlows.isNotEmpty;
|
||
|
||
/// 获取指定类型的流程
|
||
OAuth2Flow? getFlow(OAuth2FlowType type) {
|
||
switch (type) {
|
||
case OAuth2FlowType.authorizationCode:
|
||
return authorizationCode;
|
||
case OAuth2FlowType.implicit:
|
||
return implicit;
|
||
case OAuth2FlowType.password:
|
||
return password;
|
||
case OAuth2FlowType.clientCredentials:
|
||
return clientCredentials;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 安全要求 (单个安全方案的要求)
|
||
class ApiSecurityRequirement {
|
||
const ApiSecurityRequirement({
|
||
this.requirements = const {},
|
||
});
|
||
|
||
/// 从 JSON 创建 ApiSecurityRequirement
|
||
factory ApiSecurityRequirement.fromJson(Map<String, dynamic> json) {
|
||
final requirements = <String, List<String>>{};
|
||
|
||
json.forEach((schemeName, scopes) {
|
||
if (scopes is List) {
|
||
requirements[schemeName] = List<String>.from(scopes);
|
||
} else {
|
||
requirements[schemeName] = [];
|
||
}
|
||
});
|
||
|
||
return ApiSecurityRequirement(requirements: requirements);
|
||
}
|
||
|
||
/// 安全方案名称到作用域列表的映射
|
||
final Map<String, List<String>> requirements;
|
||
|
||
/// 检查是否为空
|
||
bool get isEmpty => requirements.isEmpty;
|
||
|
||
/// 检查是否非空
|
||
bool get isNotEmpty => requirements.isNotEmpty;
|
||
|
||
/// 获取所有安全方案名称
|
||
List<String> get schemeNames => requirements.keys.toList();
|
||
|
||
/// 获取指定方案的作用域
|
||
List<String> getScopesForScheme(String schemeName) {
|
||
return requirements[schemeName] ?? [];
|
||
}
|
||
|
||
/// 检查是否包含指定的安全方案
|
||
bool hasScheme(String schemeName) {
|
||
return requirements.containsKey(schemeName);
|
||
}
|
||
}
|