Release 0.2.0
Complete OpenAPI 3.0/3.1 generation, diagnostics, lifecycle safety, enum and naming mappings, bilingual documentation, examples, and pub.dev release validation.
This commit is contained in:
@@ -0,0 +1,267 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:koi_swagger_parser/koi_swagger_parser.dart';
|
||||
import 'package:koi_swagger_parser/src/openapi/openapi.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('HTTP wire semantics', () {
|
||||
test(
|
||||
'生成 style/explode/cookie/allowReserved transport adapter 与表单',
|
||||
() async {
|
||||
final tempDir = await Directory.systemTemp.createTemp(
|
||||
'koi_swagger_parser_http_semantics_',
|
||||
);
|
||||
addTearDown(() => tempDir.delete(recursive: true));
|
||||
|
||||
final specFile = File(p.join(tempDir.path, 'openapi.json'));
|
||||
await specFile.writeAsString(r'''
|
||||
{
|
||||
"openapi": "3.1.0",
|
||||
"info": {"title": "HTTP Semantics", "version": "1.0.0"},
|
||||
"paths": {
|
||||
"/api/v1/items/{coordinates}": {
|
||||
"post": {
|
||||
"tags": ["items"],
|
||||
"operationId": "search_items",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "coordinates",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"style": "matrix",
|
||||
"explode": false,
|
||||
"schema": {"type": "array", "items": {"type": "integer"}}
|
||||
},
|
||||
{
|
||||
"name": "filter",
|
||||
"in": "query",
|
||||
"style": "deepObject",
|
||||
"explode": true,
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"role": {"type": "string"},
|
||||
"active": {"type": "boolean"}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "sort",
|
||||
"in": "query",
|
||||
"style": "form",
|
||||
"explode": false,
|
||||
"schema": {"type": "array", "items": {"type": "string"}}
|
||||
},
|
||||
{
|
||||
"name": "fields",
|
||||
"in": "query",
|
||||
"style": "pipeDelimited",
|
||||
"schema": {"type": "array", "items": {"type": "string"}}
|
||||
},
|
||||
{
|
||||
"name": "redirect",
|
||||
"in": "query",
|
||||
"allowReserved": true,
|
||||
"schema": {"type": "string"}
|
||||
},
|
||||
{
|
||||
"name": "X-Flags",
|
||||
"in": "header",
|
||||
"schema": {"type": "array", "items": {"type": "string"}}
|
||||
},
|
||||
{
|
||||
"name": "session",
|
||||
"in": "cookie",
|
||||
"required": true,
|
||||
"schema": {"type": "string"}
|
||||
},
|
||||
{
|
||||
"name": "features",
|
||||
"in": "cookie",
|
||||
"explode": true,
|
||||
"schema": {"type": "array", "items": {"type": "string"}}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/x-www-form-urlencoded": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["name", "state"],
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"state": {
|
||||
"type": "string",
|
||||
"enum": ["draft", "published"]
|
||||
},
|
||||
"labels": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {"204": {"description": "updated"}}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
''');
|
||||
|
||||
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}
|
||||
output:
|
||||
base_dir: $outputDir
|
||||
api_dir: ${p.join(outputDir, 'api')}
|
||||
models_dir: ${p.join(outputDir, 'api_models')}
|
||||
generation:
|
||||
api:
|
||||
response_wrapper:
|
||||
mode: passthrough
|
||||
models:
|
||||
class_prefix: Koi
|
||||
formatting:
|
||||
run_dart_format: false
|
||||
''');
|
||||
|
||||
await const KoiSwaggerParser().generate(configPath: configFile.path);
|
||||
|
||||
final apiFile = File(p.join(outputDir, 'api', 'v1', 'items_api.dart'));
|
||||
final api = await apiFile.readAsString();
|
||||
|
||||
expect(api, contains('class ItemsApi {'));
|
||||
expect(api, contains('final _ItemsApiRetrofit _delegate;'));
|
||||
expect(api, contains('required List<int> coordinates'));
|
||||
expect(api, contains('@RestApi(parser: Parser.JsonSerializable)'));
|
||||
expect(api, contains('abstract class _ItemsApiRetrofit'));
|
||||
expect(
|
||||
api,
|
||||
contains("@POST('/api/v1/items/{coordinates}{reservedQuery}')"),
|
||||
);
|
||||
expect(
|
||||
api,
|
||||
contains("@Path('coordinates') required String coordinates"),
|
||||
);
|
||||
expect(api, contains("@Query('filter') required"));
|
||||
expect(api, contains("@Query('sort') required String? sort"));
|
||||
expect(api, contains("@Header('X-Flags') required String? xFlags"));
|
||||
expect(
|
||||
api,
|
||||
contains("@Header('Cookie') required String? cookieHeader"),
|
||||
);
|
||||
expect(
|
||||
api,
|
||||
contains("@Path('reservedQuery') required String reservedQuery"),
|
||||
);
|
||||
expect(api, contains('@FormUrlEncoded()'));
|
||||
expect(api, contains("@Field('state') required Object? state"));
|
||||
expect(api, contains('_openApiCookieHeader('));
|
||||
expect(api, contains('_openApiReservedQuery('));
|
||||
expect(api, contains("case 'matrix':"));
|
||||
expect(api, contains("'pipeDelimited' => '|'"));
|
||||
|
||||
final format = await Process.run(Platform.resolvedExecutable, <String>[
|
||||
'format',
|
||||
'--output=none',
|
||||
apiFile.path,
|
||||
]);
|
||||
expect(
|
||||
format.exitCode,
|
||||
0,
|
||||
reason: '${format.stdout}\n${format.stderr}',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test('可选 JSON body 生成 nullable 类型与 nullToAbsent', () async {
|
||||
final config = KoiSwaggerConfig(
|
||||
configPath: null,
|
||||
workingDirectory: Directory.current.path,
|
||||
input: const InputConfig(swaggerUrls: <String>[]),
|
||||
output: OutputConfig(
|
||||
baseDir: Directory.current.path,
|
||||
apiDir: p.join(Directory.current.path, 'api'),
|
||||
modelsDir: p.join(Directory.current.path, 'models'),
|
||||
splitByTags: true,
|
||||
extractVersionFromUrl: false,
|
||||
),
|
||||
generation: GenerationConfig(
|
||||
preset: GeneratorPresetRegistry.koiFlutter,
|
||||
api: const ApiGenerationConfig(
|
||||
enabled: true,
|
||||
useRetrofit: true,
|
||||
cancelTokenSupport: false,
|
||||
client: ClientConfig(
|
||||
className: 'ApiClient',
|
||||
fileName: 'api_client',
|
||||
),
|
||||
responseWrapper: ResponseWrapperConfig(
|
||||
mode: ResponseWrapperMode.passthrough,
|
||||
successDataField: 'data',
|
||||
detectStandardEnvelope: false,
|
||||
),
|
||||
),
|
||||
models: const ModelGenerationConfig(
|
||||
enabled: true,
|
||||
classPrefix: 'Koi',
|
||||
defaultNonNullableListToEmpty: true,
|
||||
jsonSerializable: JsonSerializableConfig(
|
||||
checked: true,
|
||||
explicitToJson: true,
|
||||
includeIfNull: false,
|
||||
),
|
||||
),
|
||||
formatting: const FormattingConfig(
|
||||
runDartFormat: false,
|
||||
runDartFix: false,
|
||||
),
|
||||
),
|
||||
);
|
||||
final parsed = OpenApiDocumentParser(config).parse(
|
||||
LoadedOpenApiDocument(
|
||||
document: <String, dynamic>{
|
||||
'openapi': '3.1.0',
|
||||
'info': <String, dynamic>{'title': 'optional body', 'version': '1'},
|
||||
'paths': <String, dynamic>{
|
||||
'/api/v1/items': <String, dynamic>{
|
||||
'post': <String, dynamic>{
|
||||
'tags': <String>['items'],
|
||||
'requestBody': <String, dynamic>{
|
||||
'required': false,
|
||||
'content': <String, dynamic>{
|
||||
'application/json': <String, dynamic>{
|
||||
'schema': <String, dynamic>{
|
||||
'type': 'object',
|
||||
'properties': <String, dynamic>{
|
||||
'name': <String, dynamic>{'type': 'string'},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'responses': <String, dynamic>{
|
||||
'204': <String, dynamic>{'description': 'ok'},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
sources: const <String>['memory://optional-body'],
|
||||
),
|
||||
);
|
||||
|
||||
expect(parsed.operations.single.requestBody!.type!.nullable, isTrue);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user