Complete OpenAPI 3.0/3.1 generation, diagnostics, lifecycle safety, enum and naming mappings, bilingual documentation, examples, and pub.dev release validation.
299 lines
7.8 KiB
Dart
299 lines
7.8 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:koi_swagger_parser/koi_swagger_parser.dart';
|
|
import 'package:path/path.dart' as p;
|
|
import 'package:test/test.dart';
|
|
|
|
void main() {
|
|
group('multipart generation', () {
|
|
test('按 preset 生成 Retrofit multipart 参数', () async {
|
|
final tempDir = await Directory.systemTemp.createTemp(
|
|
'koi_swagger_parser_multipart_',
|
|
);
|
|
addTearDown(() => tempDir.delete(recursive: true));
|
|
|
|
final specFile = File(p.join(tempDir.path, 'openapi.json'));
|
|
await specFile.writeAsString(r'''
|
|
{
|
|
"openapi": "3.1.0",
|
|
"info": {
|
|
"title": "Multipart Sample",
|
|
"version": "1.0.0"
|
|
},
|
|
"paths": {
|
|
"/api/v1/uploads": {
|
|
"post": {
|
|
"tags": ["uploads"],
|
|
"operationId": "create_upload_api_v1_uploads_post",
|
|
"requestBody": {
|
|
"required": true,
|
|
"content": {
|
|
"multipart/form-data": {
|
|
"schema": {
|
|
"type": "object",
|
|
"required": ["file", "meta"],
|
|
"properties": {
|
|
"file": {"type": "string", "format": "binary"},
|
|
"files": {
|
|
"type": "array",
|
|
"items": {"type": "string", "format": "binary"}
|
|
},
|
|
"meta": {"$ref": "#/components/schemas/UploadMeta"},
|
|
"note": {"type": "string"}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"responses": {
|
|
"200": {
|
|
"description": "ok",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {"$ref": "#/components/schemas/UploadResponse"}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"components": {
|
|
"schemas": {
|
|
"UploadMeta": {
|
|
"type": "object",
|
|
"required": ["album_id"],
|
|
"properties": {
|
|
"album_id": {"type": "integer"},
|
|
"tags": {
|
|
"type": "array",
|
|
"items": {"type": "string"}
|
|
}
|
|
}
|
|
},
|
|
"UploadResponse": {
|
|
"type": "object",
|
|
"required": ["id"],
|
|
"properties": {
|
|
"id": {"type": "integer"}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
''');
|
|
|
|
final outputDir = p.join(tempDir.path, 'lib', 'src');
|
|
final configFile = File(p.join(tempDir.path, 'koi_swagger_parser.yaml'));
|
|
await configFile.writeAsString('''
|
|
input:
|
|
swagger_urls:
|
|
- url: ${specFile.path}
|
|
enabled: true
|
|
|
|
output:
|
|
base_dir: $outputDir
|
|
api_dir: ${p.join(outputDir, 'api')}
|
|
models_dir: ${p.join(outputDir, 'api_models')}
|
|
split_by_tags: true
|
|
|
|
generation:
|
|
preset:
|
|
name: koi_flutter
|
|
api:
|
|
enabled: true
|
|
use_retrofit: true
|
|
cancel_token_support: false
|
|
client:
|
|
class_name: SampleApiClient
|
|
file_name: sample_api_client
|
|
response_wrapper:
|
|
mode: passthrough
|
|
detect_standard_envelope: false
|
|
success_data_field: data
|
|
models:
|
|
enabled: true
|
|
class_prefix: Koi
|
|
default_non_nullable_list_to_empty: true
|
|
json_serializable:
|
|
checked: true
|
|
explicit_to_json: true
|
|
include_if_null: false
|
|
formatting:
|
|
run_dart_format: false
|
|
''');
|
|
|
|
await const KoiSwaggerParser().generate(configPath: configFile.path);
|
|
|
|
final apiFile = File(
|
|
p.join(outputDir, 'api', 'v1', 'uploads_api.dart'),
|
|
).readAsStringSync();
|
|
expect(apiFile, contains('@MultiPart()'));
|
|
expect(
|
|
apiFile,
|
|
contains("@Part(name: 'file') required MultipartFile file"),
|
|
);
|
|
expect(
|
|
apiFile,
|
|
contains("@Part(name: 'files') List<MultipartFile>? files"),
|
|
);
|
|
expect(
|
|
apiFile,
|
|
contains(
|
|
"@Part(name: 'meta', contentType: 'application/json') required KoiUploadMeta meta",
|
|
),
|
|
);
|
|
expect(apiFile, contains("@Part(name: 'note') String? note"));
|
|
|
|
final metaFile = File(
|
|
p.join(outputDir, 'api_models', 'request', 'upload_meta.dart'),
|
|
).readAsStringSync();
|
|
expect(metaFile, contains('class KoiUploadMeta'));
|
|
expect(metaFile, contains("@JsonKey(name: 'album_id',"));
|
|
});
|
|
});
|
|
|
|
group('discriminator union generation', () {
|
|
test('生成 sealed wrapper 并在结果模型中引用', () async {
|
|
final tempDir = await Directory.systemTemp.createTemp(
|
|
'koi_swagger_parser_union_',
|
|
);
|
|
addTearDown(() => tempDir.delete(recursive: true));
|
|
|
|
final specFile = File(p.join(tempDir.path, 'openapi.json'));
|
|
await specFile.writeAsString(r'''
|
|
{
|
|
"openapi": "3.1.0",
|
|
"info": {
|
|
"title": "Union Sample",
|
|
"version": "1.0.0"
|
|
},
|
|
"paths": {
|
|
"/api/v1/pets/{pet_id}": {
|
|
"get": {
|
|
"tags": ["pets"],
|
|
"operationId": "get_pet_api_v1_pets__pet_id__get",
|
|
"parameters": [
|
|
{
|
|
"name": "pet_id",
|
|
"in": "path",
|
|
"required": true,
|
|
"schema": {"type": "integer"}
|
|
}
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "ok",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {"$ref": "#/components/schemas/PetResponse"}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"components": {
|
|
"schemas": {
|
|
"PetResponse": {
|
|
"type": "object",
|
|
"required": ["pet"],
|
|
"properties": {
|
|
"pet": {"$ref": "#/components/schemas/Pet"}
|
|
}
|
|
},
|
|
"Pet": {
|
|
"oneOf": [
|
|
{"$ref": "#/components/schemas/Cat"},
|
|
{"$ref": "#/components/schemas/Dog"}
|
|
],
|
|
"discriminator": {
|
|
"propertyName": "pet_type",
|
|
"mapping": {
|
|
"cat": "#/components/schemas/Cat",
|
|
"dog": "#/components/schemas/Dog"
|
|
}
|
|
}
|
|
},
|
|
"Cat": {
|
|
"type": "object",
|
|
"required": ["pet_type", "lives"],
|
|
"properties": {
|
|
"pet_type": {"type": "string", "enum": ["cat"]},
|
|
"lives": {"type": "integer"}
|
|
}
|
|
},
|
|
"Dog": {
|
|
"type": "object",
|
|
"required": ["pet_type", "breed"],
|
|
"properties": {
|
|
"pet_type": {"type": "string", "enum": ["dog"]},
|
|
"breed": {"type": "string"}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
''');
|
|
|
|
final outputDir = p.join(tempDir.path, 'lib', 'src');
|
|
final configFile = File(p.join(tempDir.path, 'koi_swagger_parser.yaml'));
|
|
await configFile.writeAsString('''
|
|
input:
|
|
swagger_urls:
|
|
- url: ${specFile.path}
|
|
enabled: true
|
|
|
|
output:
|
|
base_dir: $outputDir
|
|
api_dir: ${p.join(outputDir, 'api')}
|
|
models_dir: ${p.join(outputDir, 'api_models')}
|
|
split_by_tags: true
|
|
|
|
generation:
|
|
api:
|
|
enabled: true
|
|
use_retrofit: true
|
|
cancel_token_support: false
|
|
client:
|
|
class_name: SampleApiClient
|
|
file_name: sample_api_client
|
|
response_wrapper:
|
|
mode: passthrough
|
|
detect_standard_envelope: false
|
|
success_data_field: data
|
|
models:
|
|
enabled: true
|
|
class_prefix: Koi
|
|
default_non_nullable_list_to_empty: true
|
|
json_serializable:
|
|
checked: true
|
|
explicit_to_json: true
|
|
include_if_null: false
|
|
formatting:
|
|
run_dart_format: false
|
|
''');
|
|
|
|
await const KoiSwaggerParser().generate(configPath: configFile.path);
|
|
|
|
final unionFile = File(
|
|
p.join(outputDir, 'api_models', 'result', 'pet.dart'),
|
|
).readAsStringSync();
|
|
expect(unionFile, contains('sealed class KoiPet'));
|
|
expect(
|
|
unionFile,
|
|
contains('factory KoiPet.fromJson(Map<String, dynamic> json)'),
|
|
);
|
|
expect(unionFile, contains("case 'cat':"));
|
|
expect(unionFile, contains('return KoiPetCat(KoiCat.fromJson(json));'));
|
|
expect(unionFile, contains('final class KoiPetDog extends KoiPet'));
|
|
|
|
final responseFile = File(
|
|
p.join(outputDir, 'api_models', 'result', 'pet_response.dart'),
|
|
).readAsStringSync();
|
|
expect(responseFile, contains('required KoiPet pet'));
|
|
});
|
|
});
|
|
}
|