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:
mr.koi
2026-07-31 16:47:39 +08:00
parent ea93f09154
commit 454e6c51d7
56 changed files with 14506 additions and 491 deletions
+132 -74
View File
@@ -1,114 +1,172 @@
# Architecture
`koi_swagger_parser` 采用“加载 -> 解析 -> IR -> 渲染 -> 落盘”的分层设计,避免把 OpenAPI 细节、Koi 业务约定、具体 Dart 输出模板揉在一起。
[English](ARCHITECTURE.md) | [Chinese (Simplified)](zh-CN/ARCHITECTURE.md)
## 1. 分层
The generation pipeline is deliberately split into stages:
### config
```text
configuration
-> source loader
-> external/local $ref resolver
-> multi-source merger
-> validation + parser
-> transport/schema IR
-> preset + Dart renderer
-> manifest lifecycle
-> fix / format / build_runner
```
负责读取与归一化配置:
The main invariant is that loading produces a self-contained document with
traceable dependencies; the parser represents OpenAPI semantics; the renderer
does not reinterpret the specification; and the lifecycle manages only files
declared in its manifest.
- 新配置:`koi_swagger_parser.yaml`
- 兼容配置:`generator_config.yaml`
- 路径归一化、默认值、旧字段兼容映射
- preset 解析(当前内置 `koi_flutter`)
## Configuration
### openapi
`lib/src/config/config.dart`:
负责:
- discovers `koi_swagger_parser.yaml` and compatible
`generator_config.yaml` names;
- resolves source and output paths against the configuration directory;
- chooses the host-project command directory from `--cwd` or the explicit
configuration directory;
- validates YAML scalar types before casting;
- parses enum, naming, type/import mappings, and filters;
- validates unsupported combinations, mapping collisions, regular
expressions, and enum mappings;
- supports migration from the legacy `base_result_import` setting.
- 本地/远程文档加载
- 多 source 深合并
- schema/ref 解析
- OpenAPI 3.1 nullable / anyOf / oneOf / additionalProperties / allOf 归一化
- multipart request body 与 discriminator union 识别
Unknown keys produce warnings. Invalid types and incomplete combinations
produce errors. See [Configuration](CONFIGURATION.md).
### ir
## Loader, resolver, and merger
产出与 OpenAPI 原文解耦的中间表示:
For each root source, `OpenApiReferenceResolver`:
- API operation
- request body / parameter
- model / enum
- Dart type graph
1. normalizes a local file, file URL, or HTTP(S) source;
2. caches each loaded document;
3. resolves local, relative, and remote `$ref` values;
4. bundles external components into the root document;
5. assigns deterministic names to component conflicts;
6. records dependencies and diagnoses unsupported cross-document cycles.
IR 是后续接入 `koi_blueprint_flutter` preset 的关键。
`OpenApiDocumentMerger` then merges roots in configuration order:
### generator
- a duplicate operation method/path is an error;
- components with the same name but different content are renamed
deterministically, and references from that source are rewritten;
- later inputs never silently overwrite conflicts;
- version, info, and path-parameter conflicts are reported.
只关心如何把 IR 渲染成 Dart 文件:
The parser should receive only root-document JSON Pointers. A remaining
external reference is treated as a resolver error.
- Retrofit API
- Freezed model
- enums
- index/client 聚合文件
## Validation and parsing
这一层不直接处理 OpenAPI 细节。
`OpenApiDocumentParser` handles:
### preset
- OpenAPI 3.0/3.1 version and core structure checks;
- operation, path, tag, and model filters;
- parameter location, style, explode, `allowReserved`, and `content`;
- request media types and response status, content, and headers;
- servers, security schemes, and security requirements;
- required, nullable, read-only/write-only, and `additionalProperties`
semantics;
- enum configuration/vendor-extension precedence;
- `allOf`, discriminated unions, and supported generic unions;
- custom mappings and Dart name collisions.
用于把 parser core 的 IR 和具体客户端技术栈解耦。当前先提供一个很薄的 preset 层:
A structure that cannot be rendered with reliable wire semantics must not
silently become `dynamic`. A safe but lossy continuation produces a warning;
for example, different successful JSON body types may converge on `Object?`.
A media type without a supported decoder, such as XML or Protobuf, produces an
error.
- `koi_flutter`:默认面向 Retrofit + Dio + Freezed
- 当前主要控制 multipart 二进制字段该生成为 `MultipartFile`、`File`、`Uint8List` 还是 `String`
- 后续可以继续扩展 blueprint preset、网络层 preset、命名/包装策略 preset
## Intermediate representation
## 2. Response Wrapper 设计
`lib/src/ir/ir.dart` is the parser/renderer contract. Its main nodes include:
parser core 不直接写死 `BaseResult`。
- `ApiOperation`: method, path, tag, version, server, and security data;
- `ApiParameter`: Dart/wire name, location, style, explode, and
`allowReserved`;
- `ApiRequestBody`: JSON, multipart, form-urlencoded, and binary requests;
- `ApiResponse`: status/default, body kind/type, content type, and headers;
- `ModelSpec`: object, enum, and union models with usage category and extras;
- `ModelField`: required/nullability/read-write flags and constraints;
- `DartType`: named, list, map, object, dynamic, and void type graphs.
当前支持两种模式:
Models are grouped as request, result, shared, or enum models. A component with
read-only/write-only properties can be projected into request and response
variants so that read-only values are not required in requests and
write-only secrets are not exposed in responses.
- `passthrough`
- 返回真实 OpenAPI response schema
- 适合你希望保留服务端 envelope 模型的场景
- `external_generic`
- 识别标准成功 envelope(默认 `code/message/data`)
- 将 `data` 提取为 inner type
- API 返回 `ExternalWrapper<Inner>`,例如 `BaseResult<KoiStoreResponse>`
A generic `oneOf` requires exactly one decoder match. A generic `anyOf` retains
the raw value and all matching cases. Object branches are prefiltered by
required keys and enum constraints before decoding.
这样做的意义是:
## Preset and renderer
- parser core 保持通用
- Koi 网络层包裹策略可配置
- 未来可以挂更多 wrapper preset,而不是继续堆服务端补丁
The current `koi_flutter` preset targets Dio, Retrofit, Freezed, and
json_serializable.
## 3. 模型分组
The renderer:
模型按使用语义归档:
- uses Retrofit annotations for parameter forms Retrofit can represent;
- emits a typed public adapter and private Retrofit delegate when a supported
style, cookie, or `allowReserved` combination needs custom serialization;
- renders Freezed object, enum, and union models;
- renders custom root-map read/write helpers for
`properties` plus `additionalProperties`;
- collects model dependencies and external type imports;
- groups API files by version and tag and emits client/index files.
- `request/`:只在请求体根节点使用
- `result/`:只在成功响应根节点使用
- `shared/`:被请求与响应共同依赖,或用于错误响应
- `enums/`:枚举单独分组
The renderer does not load references or decide whether an OpenAPI structure
is valid.
这比“按 swagger component 平铺”更符合 Koi 项目结构,也更利于未来蓝图模板整理。
## Generation lifecycle
## 4. 命名原则
Before writing, `GenerationLifecycle` calculates:
- 类名统一 PascalCase。
- 字段统一 camelCase,并保留 `@JsonKey(name: ...)` 对应 wire name。
- 文件名统一 snake_case。
- API 默认按 `version + tag` 分文件。
- the canonical merged-spec SHA-256;
- the normalized configuration SHA-256;
- each rendered path and content SHA-256.
命名转换统一收口到 util 层,避免每个阶段各写一套字符串处理逻辑。
The manifest records its schema/generator version, source roots, dependencies,
and the actual hash of each primary generated file. Writes use an operating
system exclusive lock and same-directory temporary-file rename.
## 5. 为什么新建项目而不是继续修补旧生成器
Safety invariants:
旧生成器的问题不是单点 bug,而是结构性耦合:
1. Renderer output must stay inside the configured API/model roots.
2. A stale candidate must come from the previous manifest.
3. Its path must still be inside the current managed roots.
4. Its current hash must equal the previous manifest hash before deletion.
5. A corrupt or unknown-version manifest is never trusted for deletion.
6. A failed fix, format, or build step does not write a success manifest.
7. Watch mode starts after the generation lock is released.
- OpenAPI 解析和业务输出绑定太深。
- nullable / anyOf / wrapper 规则分散在多个模板与解析阶段。
- 很难为未来 `koi_blueprint_flutter` 抽出复用 IR。
build_runner owns `.g.dart` and `.freezed.dart` files; they are not included in
this generator's stale-file manifest.
所以这里直接重新收口:
## Response wrappers
- parser core 面向 OpenAPI 语义
- Koi 输出作为 renderer/preset
- 兼容旧配置,但不延续旧架构负担
The parser does not hard-code `BaseResult`:
## 6. 近期路线
- `passthrough` returns the OpenAPI response schema;
- `external_generic` detects configured success envelopes and returns
`ExternalWrapper<Inner>`.
- 第一阶段:打通 Koi/FastAPI 场景(已完成)
- 第二阶段:补齐 multipart、discriminator union、preset 基础层(当前版本)
- 第三阶段:扩更强 union fallback、更多 preset,并服务 `koi_blueprint_flutter`
A `default` response remains an error response. `response_mode:
http_response` retains Dio status and headers. Error response models remain in
the IR and generated model set.
## Extension boundaries
The IR/preset/renderer split leaves room for a versioned renderer interface,
template overrides, semantic-diff tooling, and optional runtime adapters.
Those are design directions, not features of the current public release. The
current implementation boundaries are documented in
[Compatibility](COMPATIBILITY.md).
Additional ecosystem comparisons are kept as internal project research and
are not part of the published package.
+119
View File
@@ -0,0 +1,119 @@
# Compatibility
[English](COMPATIBILITY.md) | [Chinese (Simplified)](zh-CN/COMPATIBILITY.md)
This matrix describes behavior covered by the current implementation and
automated tests.
- **Supported**: implemented and tested.
- **Partial**: only the boundary stated in the notes is supported.
- **Unsupported**: diagnosed before generation, or explicitly ignored with a
warning.
## Documents, references, and merging
| Capability | Status | Boundary |
|---|---|---|
| OpenAPI 3.0.x / 3.1.x | Supported | JSON and YAML; common Schema Object subset needed by client generation |
| Swagger/OpenAPI 2.0 | Unsupported | `openapi.unsupported_version` error |
| Local `$ref` | Supported | JSON Pointer across component groups |
| Relative file / file URL `$ref` | Supported | Resolved relative to the referring document |
| HTTP(S) `$ref` | Supported | Per-run cache, dependency tracking, and cycle diagnostics |
| External component name conflicts | Supported | Deterministic rename with warning |
| Multiple root sources | Supported | Deterministic merge; component and operation conflict diagnostics |
| Unresolved `$ref` | Diagnosed | Error; never silently converted to `dynamic` |
| Callbacks / webhooks | Unsupported | Warning; no reverse-call client is emitted |
| Link Object | Unsupported | `openapi.links_not_supported` warning |
This is not a complete JSON Schema 2020-12 implementation. `$defs` as
generation roots, `if`/`then`/`else`, `not`, `contains`,
`dependentSchemas`, and unevaluated-property/item semantics are outside the
current contract.
## Schemas and models
| Capability | Status | Boundary |
|---|---|---|
| Object / array / map | Supported | Recursive containers and inline schemas |
| `properties` + `additionalProperties` | Supported | Extra entries round-trip at the JSON root |
| String/number/integer/bool/date/date-time | Supported | Formats can be overridden by type mappings |
| Enum | Supported | String, number, and boolean wire values; inline or component |
| Enum member-name mappings | Supported | Configuration and common `x-enum` extensions |
| Enum unknown fallback | Supported | Configurable `JsonKey.unknownEnumValue` output |
| Required / nullable / absent | Supported | Deserialization distinguishes missing from disallowed null; ordinary Freezed fields do not retain presence history |
| Read-only / write-only | Supported | Directional request/response variants when needed |
| `allOf` | Supported | Object properties, required fields, and additional properties are merged |
| Discriminator `oneOf`/`anyOf` | Supported | Local or normalized object branches |
| Non-discriminator `oneOf`/`anyOf` | Partial | Primitive, container, and object branches; overlapping `oneOf` matches are rejected |
| Custom schema/type/format | Supported | External imports; host code owns the JSON conversion contract |
| Constraint metadata | Partial | Descriptions/doc comments retained; no generated runtime validator |
| Presence/dirty tracking | Unsupported | No Kiota-style backing store |
## HTTP wire semantics
| Capability | Status | Boundary |
|---|---|---|
| Path/query/header/cookie parameters | Supported | Type, required/default, and wire names |
| Matrix / label / simple | Supported | Validated location/style combinations |
| Form / spaceDelimited / pipeDelimited / deepObject | Supported | Validated query/cookie combinations |
| Explode / `allowReserved` | Supported | A transport adapter is emitted when Retrofit cannot express the behavior |
| Parameter Object `content` | Partial | Exactly one media type is required |
| JSON / `+json` request body | Supported | Required bodies and null-to-absent behavior |
| Text request body | Supported | Typed `@Body` |
| `multipart/form-data` | Supported | Binary, primitive, array, and structured JSON parts |
| `application/x-www-form-urlencoded` | Supported | Typed fields; complex encoding uses an adapter |
| Binary upload | Supported | `Uint8List` |
| Binary download | Supported | `Uint8List` or Dio `ResponseBody` stream |
| XML / Protobuf / arbitrary custom codec | Unsupported | Content-type error; no unsafe decoder is emitted |
## Responses, servers, and security
| Capability | Status | Boundary |
|---|---|---|
| Multiple 2xx / 204 / `2XX` | Supported | Deterministic primary response; differing JSON types may become `Object?` with a warning |
| `default` / 4xx / 5xx | Supported | Error model/IR metadata retained; never selected as success |
| Response headers | Supported | Status, type, required flag, and description |
| Raw response metadata | Supported | `response_mode: http_response` |
| Mixed binary and JSON success media | Diagnosed | Error when one Retrofit method cannot decode the union safely |
| Root/path/operation servers | Supported | Server variable default/enum validation; preferred server becomes client default |
| Runtime switching among servers | Partial | Metadata retained; no environment-selector UI |
| API key / Bearer / Basic | Metadata supported | Scheme and operation requirements generated; credentials supplied by host interceptor/provider |
| OAuth2 / OpenID / mutual TLS | Partial | Scheme/scope metadata only; no complete authorization flow |
| Security OR/AND requirements | Supported | List entries are OR; schemes inside one map entry are AND |
## Generation and lifecycle
| Capability | Status | Boundary |
|---|---|---|
| Retrofit + Freezed | Supported | The only complete renderer in this release |
| Passthrough / external generic wrapper | Supported | Configurable standard-envelope detection |
| Model/property/parameter/operation mappings | Supported | Collision checks and safe identifiers |
| Schema/type/import mappings | Supported | Recursive Dart `List`/`Map` type expressions |
| Tag/path/operation/model filters | Supported | Exact, glob, and `regex:` patterns |
| Dry run | Supported | Reports would-write and would-delete sets |
| Dart fix / format | Supported | Changed primary generated files only |
| build_runner build/watch | Supported | Runs in the configured host-project directory |
| Unchanged skip | Supported | Spec/config/content and actual-file SHA-256 |
| Stale cleanup | Supported | Manifest ownership, root containment, and user-modification protection |
| Concurrent generation lock | Supported | Operating-system exclusive lock beside the manifest |
| External templates / plugin renderer | Unsupported | IR/preset are separated, but there is no public renderer SPI |
| Complete SDK package scaffold | Unsupported | Generates APIs/models inside an existing project |
## Verification coverage
The repository test suite covers:
- parser, IR, renderer, and lifecycle stages;
- OpenAPI 3.0 YAML and OpenAPI 3.1 JSON/FastAPI fixtures;
- enum and naming mappings, parameter wire styles, responses, schema
semantics, security, and external/cyclic references;
- reviewed SHA-256 golden output;
- generation into a temporary Dart package followed by build_runner, analysis,
and runtime tests.
Coverage of a feature in this matrix is not a claim of full OpenAPI or JSON
Schema conformance outside the stated boundary. Use a minimal fixture and
dry-run when adopting a new schema corpus.
Diagnostic meanings and remediation are documented in
[Diagnostics](DIAGNOSTICS.md).
+269
View File
@@ -0,0 +1,269 @@
# Configuration reference
[English](CONFIGURATION.md) | [Chinese (Simplified)](zh-CN/CONFIGURATION.md)
`koi_swagger_parser` reads YAML configuration. The complete commented example
is [koi_swagger_parser.example.yaml](../koi_swagger_parser.example.yaml).
Unknown keys produce `config.unknown_key`; invalid scalar types and invalid
combinations produce errors before OpenAPI parsing starts.
## Discovery and path semantics
Without `--config`, the loader searches the working directory in this order:
1. `koi_swagger_parser.yaml`
2. `koi_swagger_parser.yml`
3. `generator_config.yaml`
4. `generator_config.yml`
All relative OpenAPI file sources, output directories, and explicit manifest
paths are resolved against the directory containing the selected
configuration file. If no configuration file is found, they are resolved
against the invocation directory.
When an explicit configuration file is supplied and `--cwd` is omitted, its
directory is also the host-project working directory used for build_runner.
`--cwd` overrides that command working directory but does not rebase relative
configuration paths. HTTP(S) source URLs remain URLs.
## Top-level sections
| Field | Type | Required | Default |
|---|---|---:|---|
| `input` | map | yes | none |
| `output` | map | no | defaults below |
| `generation` | map | no | defaults below |
| `formatting` | map | no | defaults below |
At least one enabled `input.swagger_urls` entry is required.
## `input`
### `input.swagger_urls`
Type: list of strings or source objects. Default: empty, which is an error.
```yaml
input:
swagger_urls:
- ./openapi.yaml
- url: https://example.com/openapi.json
enabled: true
- url: ./experimental.yaml
enabled: false
```
Each source object accepts:
| Field | Type | Required | Default | Meaning |
|---|---|---:|---|---|
| `url` | string | yes | none | Local path, file URL, or HTTP(S) URL |
| `enabled` | boolean | no | `true` | Whether the source participates |
Sources are processed in configuration order. Multiple roots are merged
deterministically; conflicting operations are errors and conflicting
components are renamed with diagnostics.
## `output`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `base_dir` | string path | `./lib/src` | Common generated-code base |
| `api_dir` | string path | `<base_dir>/api` | Generated API root |
| `models_dir` | string path | `<base_dir>/api_models` | Generated model root |
| `included_tags` | string list | unset | Include matching operation tags |
| `excluded_tags` | string list | unset | Exclude matching operation tags |
| `included_paths` | string list | unset | Include matching OpenAPI paths |
| `excluded_paths` | string list | unset | Exclude matching OpenAPI paths |
| `included_operations` | string list | unset | Include matching operations |
| `excluded_operations` | string list | unset | Exclude matching operations |
| `included_models` | string list | unset | Add matching schemas as roots |
| `excluded_models` | string list | unset | Exclude matching models |
| `split_by_tags` | boolean | `true` | Split API files by operation tag |
| `extract_version_from_url` | boolean | `false` | Try to derive grouping version from a source URL |
| `clean_stale_files` | boolean | `true` | Safely remove obsolete owned files |
| `skip_if_unchanged` | boolean | `true` | Skip writes when spec, config, and content are unchanged |
| `manifest_path` | string path | `<project>/.dart_tool/koi_swagger_parser/manifest.json` | Ownership/hash manifest |
Filter entries accept:
- an exact string;
- `*`, `?`, and `**` glob syntax;
- `regex:<expression>`, using Dart regular-expression syntax.
An exclusion wins when the same value matches include and exclude rules.
Putting the same literal in both lists produces a warning. If an included
operation requires an excluded model, generation fails instead of emitting an
uncompilable client.
Stale cleanup is limited to files recorded in the previous manifest, inside
the current API/model roots, with content that still matches the previous
hash. Keep the manifest outside the generated API/model roots.
## `generation.preset`
| Field | Type | Default | Allowed values |
|---|---|---|---|
| `name` | string | `koi_flutter` | `koi_flutter` |
The current release has one renderer preset: Dio + Retrofit APIs and
Freezed/json_serializable models.
## `generation.api`
| Field | Type | Default | Allowed values / meaning |
|---|---|---|---|
| `enabled` | boolean | `true` | Generate API files |
| `use_retrofit` | boolean | `true` | Must remain `true` when API generation is enabled |
| `cancel_token_support` | boolean | `false` | Add Dio `CancelToken` parameters |
| `response_mode` | string | `body` | `body`, `http_response` |
| `binary_response_mode` | string | `bytes` | `bytes`, `stream` |
`generation.api.enabled: true` requires
`generation.models.enabled: true`, because the current Retrofit renderer
imports the generated model index.
### `generation.api.client`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `class_name` | string | `ApiClient` | Generated client class name |
| `file_name` | string | `api_client` | Generated client file stem |
Values are converted to safe Dart identifiers/file names.
### `generation.api.response_wrapper`
| Field | Type | Default | Allowed values / meaning |
|---|---|---|---|
| `mode` | string | `passthrough` | `passthrough`, `external_generic` |
| `success_data_field` | string | `data` | Envelope field containing the success payload |
| `detect_standard_envelope` | boolean | `true` | Detect configured code/message/data-style envelopes |
| `external_generic.import` | string | none | Required in `external_generic` mode |
| `external_generic.class_name` | string | `BaseResult` | External generic wrapper type |
Example:
```yaml
generation:
api:
response_wrapper:
mode: external_generic
success_data_field: data
detect_standard_envelope: true
external_generic:
import: package:your_network/your_network.dart
class_name: BaseResult
```
The legacy `generation.api.base_result_import` key selects
`external_generic` with class name `BaseResult` when no explicit mode is set.
Prefer the structured form for new configurations.
## `generation.models`
| Field | Type | Default | Meaning |
|---|---|---|---|
| `enabled` | boolean | `true` | Generate model files |
| `class_prefix` | string | `Koi` | Prefix for generated model names |
| `default_non_nullable_list_to_empty` | boolean | `true` | Default eligible non-nullable list values to an empty list |
| `enum_unknown_fallback` | boolean | `false` | Generate and use an `unknown` enum member |
| `enum_unknown_default_case` | boolean | `false` | Legacy alias for `enum_unknown_fallback` |
### `generation.models.json_serializable`
| Field | Type | Default |
|---|---|---|
| `checked` | boolean | `true` |
| `explicit_to_json` | boolean | `true` |
| `include_if_null` | boolean | `false` |
These values are forwarded to generated json_serializable annotations where
applicable.
### Enum mappings
Both list and map forms are accepted:
```yaml
generation:
models:
enum_mappings:
OrderStatus:
- value: pending
name: waitingForPayment
description: Waiting for payment
- value: done
name: completed
Priority:
1: low
2:
name: high
description: Requires attention
```
The outer key is the OpenAPI enum/schema name. A wire `value` may be a string,
number, or boolean; `name` must be a non-empty Dart member name.
`description` is optional.
`enum_key_mappings` accepts the same shape for compatibility. When both fields
map the same enum value, `enum_mappings` wins and a warning is emitted. See
[Enum mappings in the README](../README.md#enum-mappings) for vendor-extension
precedence.
## `generation.mappings`
All mapping sections default to an empty map.
| Field | Shape | Meaning |
|---|---|---|
| `models` | `schema: DartName` | Rename generated component models |
| `properties` | `schema: {wireProperty: dartField}` | Rename properties within one schema |
| `parameters` | `operation: {wireParameter: dartParameter}` | Rename parameters within one operation |
| `operations` | `operation: dartMethod` | Rename operations |
| `schemas` | `schema: type-or-object` | Reuse an external Dart type instead of generating the schema |
| `types` | `openapiType[:format]: type-or-object` | Map an OpenAPI scalar/type pattern |
| `imports` | `DartType: packageImport` | Supply imports for mapped Dart types |
Operation keys may be an `operationId` or `METHOD /path`. Type mappings try
`type:format` before `type`.
A type-or-object value is either a Dart type string or:
```yaml
type: UuidValue
import: package:uuid/uuid_value.dart
```
Within one mapping scope, two source names cannot target the same Dart name.
The same schema cannot appear in both `models` and `schemas`. A Dart type
cannot resolve to conflicting imports. These combinations are configuration
errors.
## `formatting`
| Field | Type | Default | Allowed values / meaning |
|---|---|---|---|
| `run_dart_fix` | boolean | `false` | Run `dart fix --apply` on changed primary generated files |
| `run_dart_format` | boolean | `true` | Format changed primary generated files |
| `run_build_runner` | boolean | `false` | Run build_runner after the primary write |
| `build_runner_mode` | string | `build` | `build`, `watch` |
| `delete_conflicting_outputs` | boolean | `true` | Pass `--delete-conflicting-outputs` to build_runner |
The CLI `--watch` flag starts build_runner watch after a non-dry generation,
regardless of the configured build_runner mode. Watch mode is long-running and
is intended for interactive development rather than one-shot CI jobs.
## Combination rules
The loader enforces these relationships:
- API and model generation cannot both be disabled.
- API generation currently requires model generation.
- API generation currently requires `use_retrofit: true`.
- `external_generic` response wrapping requires a non-empty import.
- Unknown preset and enum/response/build-runner mode values are errors.
- Invalid filter regular expressions are errors.
- Mapping target/import conflicts are errors.
See [Diagnostics](DIAGNOSTICS.md) for stable codes and remediation guidance.
+158
View File
@@ -0,0 +1,158 @@
# Diagnostics
[English](DIAGNOSTICS.md) | [Chinese (Simplified)](zh-CN/DIAGNOSTICS.md)
Configuration, loading, parsing, rendering, and lifecycle issues are reported
as `KoiDiagnostic` values:
```dart
class KoiDiagnostic {
final String code;
final DiagnosticSeverity severity; // info | warning | error
final String message;
final String? source;
final String? location;
}
```
`code` is the stable machine-readable identifier. Message wording may improve
between releases. `source` identifies a configuration path, document
URL/file, or generated file. `location` usually contains a JSONPath,
JSON Pointer, or other logical location inside the source.
## Severity behavior
- `info`: a normal decision, such as skipping an unchanged generation.
- `warning`: output remains reliable, but a rename, safe degradation, or
explicitly omitted non-blocking capability requires attention.
- `error`: continuing could produce an invalid client; generation throws
`KoiDiagnosticException`.
The CLI prints warnings and errors by default. `--verbose` also prints
informational diagnostics and failure stack traces.
Library callers can inspect diagnostics:
```dart
try {
final result = await const KoiSwaggerParser().generate();
for (final item in result.diagnostics) {
print(item.format());
}
} on KoiDiagnosticException catch (error) {
for (final item in error.diagnostics) {
print(item.format());
}
}
```
Automation should match `code`, not message text. Assert `source` or
`location` as well when the distinction matters.
## Common codes
### Configuration
| Code | Meaning and remediation |
|---|---|
| `config.missing_sources` | Add at least one enabled `input.swagger_urls` entry. |
| `config.unknown_key` | Correct the spelling or remove a field not recognized by this version; warning. |
| `config.invalid_section` | Replace a scalar/list with the expected YAML map. |
| `config.invalid_string` / `config.invalid_boolean` | Correct the YAML scalar type. |
| `config.invalid_string_list` | Use a list of strings for include/exclude filters. |
| `config.invalid_filter_regex` | Correct the Dart expression after `regex:`. |
| `config.no_outputs_enabled` | API and model generation cannot both be disabled. |
| `config.api_requires_models` | The current API renderer requires generated models. |
| `config.unsupported_api_renderer` | Keep `use_retrofit: true`; no other API renderer is public yet. |
| `config.external_wrapper_import_required` | Add `external_generic.import`. |
| `config.mapping_target_collision` | Give mapping sources distinct target Dart names. |
| `config.import_mapping_conflict` | Use one import for a mapped Dart type. |
| `config.conflicting_schema_mapping` | Do not both rename and externally replace the same schema. |
| `config.ambiguous_enum_mapping` | Use unambiguous typed enum wire values. |
### Documents and references
| Code | Meaning and remediation |
|---|---|
| `openapi.missing_version` / `openapi.unsupported_version` | Supply an OpenAPI 3.0.x or 3.1.x document. |
| `openapi.invalid_info` / `openapi.invalid_paths` | Correct the corresponding core document structure. |
| `openapi.source_load_failed` / `openapi.source_http_error` | Check the file path, URL, access, and response status. |
| `openapi.unresolved_ref` | The local JSON Pointer does not exist. |
| `openapi.unresolved_external_ref` | The external resource or fragment does not exist or cannot be loaded. |
| `openapi.external_ref_not_bundled` | Reference normalization failed; inspect preceding source/resolver diagnostics. |
| `openapi.external_component_renamed` | An external component conflict was renamed deterministically; warning. |
| `openapi.multisource_component_renamed` | A component conflict across roots was renamed deterministically; warning. |
| `openapi.multisource_operation_conflict` | Two roots define the same method/path; remove or separate one definition. |
| `openapi.callbacks_not_supported` / `openapi.webhooks_not_supported` | Reverse-call clients are not generated; warning. |
| `openapi.links_not_supported` | Link Object call relationships are not generated; warning. |
### Parameters, requests, and responses
| Code | Meaning and remediation |
|---|---|
| `openapi.invalid_parameter_style` | Use a style valid for the parameter location. |
| `openapi.deep_object_requires_object` | A deepObject parameter needs an object schema. |
| `openapi.delimited_parameter_requires_array` | A space/pipe-delimited parameter needs an array schema. |
| `openapi.parameter_content_count` | Parameter Object `content` must contain exactly one media type. |
| `openapi.request_body_schema_missing` | Add a schema to the selected request media type. |
| `openapi.unsupported_request_content_type` | Use a supported request codec or map the endpoint manually. |
| `openapi.unsupported_response_content_type` | Use a supported response codec or map the endpoint manually. |
| `openapi.success_response_missing` | Add an explicit 2xx response; `default` is not success. |
| `openapi.multiple_success_response_types` | Multiple successful JSON body types converge on `Object?`; warning. |
| `openapi.mixed_success_media_types` | Binary/stream and JSON successes cannot be decoded safely by one generated method. |
| `openapi.path_parameter_forced_required` | A path parameter declared optional is forced to required; warning. |
### Schemas and names
| Code | Meaning and remediation |
|---|---|
| `openapi.property_read_write_only` | A property cannot be both read-only and write-only. |
| `openapi.invalid_additional_properties` | Use a boolean or Schema Object. |
| `openapi.property_name_collision` | An automatic suffix prevented a Dart field collision; warning. |
| `openapi.operation_name_collision` | An automatic suffix prevented a method collision; warning. |
| `openapi.operation_argument_name_collision` | A parameter conflicts with a body, cancel token, or another argument. |
| `openapi.duplicate_enum_member_name` | Multiple wire values map to one name; a suffix was added. |
| `openapi.enum_member_name_sanitized` | An enum member name was changed into a valid Dart identifier. |
| `filter.required_model_excluded` | A selected API needs an excluded model; include it or exclude its consumers. |
| `filter.model_pattern_unmatched` | An `included_models` pattern matched no schema. |
### Servers and security
| Code | Meaning and remediation |
|---|---|
| `openapi.server_url_missing` | Add a non-empty server URL. |
| `openapi.server_variable_default_missing` | Give every server variable a default value. |
| `openapi.server_variable_default_invalid` | Make the default one of the declared enum values. |
| `openapi.server_variable_unresolved` | Define every variable used by the server URL. |
| `openapi.unknown_security_scheme` | Define the referenced scheme in components. |
| `openapi.unsupported_security_scheme` | The scheme is retained only where documented or rejected. |
| `openapi.http_auth_requires_custom_interceptor` | Implement credential injection in the host Dio layer; warning. |
### Generation lifecycle
| Code | Meaning and remediation |
|---|---|
| `lifecycle.unchanged_skipped` | Hashes and files match; generation was safely skipped. |
| `lifecycle.manifest_invalid` | The old manifest is corrupt; output is rebuilt without trusting its deletion list. |
| `lifecycle.manifest_version_ignored` | The manifest schema is incompatible; its deletion list is ignored. |
| `lifecycle.output_outside_managed_roots` | Renderer output escaped the API/model roots and was rejected. |
| `lifecycle.duplicate_output_path` | Two rendered files target the same path with different content. |
| `lifecycle.stale_path_outside_roots` | A former path is outside current roots and is preserved. |
| `lifecycle.stale_file_modified` | A stale generated file was edited and is preserved for manual review. |
| `lifecycle.generated_file_missing` | A primary generated file disappeared during fix/format/build processing. |
| `lifecycle.control_file_not_stale` | A lifecycle control file is never treated as stale output. |
## Design principles
1. Unresolved references, invalid wire styles, and unsupported codecs do not
silently become `dynamic`.
2. Safe, compilable automatic renaming can continue with a warning.
3. Unsupported constructs that do not invalidate other output are explicitly
omitted with a warning.
4. Stale cleanup preserves files whenever containment or hash ownership is
uncertain.
5. Diagnostics include component, operation, parameter, or file context when
available.
New codes can be added in minor releases. Existing automation should treat
unknown warning codes as reviewable and unknown error codes as blocking.
+237
View File
@@ -0,0 +1,237 @@
# Migrating from swagger_generator_flutter
[English](MIGRATION.md) | [Chinese (Simplified)](zh-CN/MIGRATION.md)
This guide covers migration from the `generator_config.yaml` conventions used
by `swagger_generator_flutter`. Perform the migration in the consuming
Dart/Flutter repository; no local machine path is assumed.
## Recommended sequence
1. Preserve the old generated output on a separate branch.
2. Copy
[koi_swagger_parser.example.yaml](../koi_swagger_parser.example.yaml) and
migrate only sources, output roots, and response wrapping first.
3. Set `clean_stale_files: false` for the first run and use `--dry-run`.
4. Generate into temporary directories and compare API signatures, JSON keys,
and imports.
5. Migrate enum, naming, and custom type mappings.
6. Run build_runner, `dart analyze`, and the host project's tests.
7. After reviewing the output, switch to the final directories and enable
`clean_stale_files: true`.
## Settings that carry over
| Previous field | New field or behavior |
|---|---|
| `input.swagger_urls` | Same name; strings/objects, `enabled`, local paths, and HTTP(S) sources are supported |
| `output.base_dir/api_dir/models_dir` | Same names |
| `output.included_tags/excluded_tags` | Same names, with glob and regular-expression support |
| `output.split_by_tags` | Same name |
| `generation.api.client.class_name/file_name` | Same names |
| `generation.api.cancel_token_support` | Same name |
| `generation.api.base_result_import` | Compatibility input; selects `external_generic` with `BaseResult` |
| `generation.models.class_prefix` | Same name |
| `generation.models.json_serializable.*` | Same names |
| `generation.models.enum_key_mappings` | Still accepted; migrate toward `enum_mappings` |
| `formatting.run_dart_format` | Same name |
An existing enum mapping does not need to be rewritten immediately:
```yaml
generation:
models:
enum_key_mappings:
TaskType:
- value: 1
name: spotCheck
description: Spot check
```
When both fields map the same wire value, `enum_mappings` wins. The generator
also recognizes `x-enumNames`, `x-enum-varnames`, and `x-enum-values`.
## Settings that need rewriting
### Response wrapper
Previous form:
```yaml
generation:
api:
base_result_type: BaseResult
base_result_import: package:app/network/base_result.dart
```
Recommended form:
```yaml
generation:
api:
response_wrapper:
mode: external_generic
detect_standard_envelope: true
success_data_field: data
external_generic:
import: package:app/network/base_result.dart
class_name: BaseResult
```
There is no automatic one-to-one migration for
`base_page_result_type/base_page_result_import`. Prefer an accurate pagination
schema in OpenAPI. To reuse an existing type, use
`generation.mappings.schemas`, or keep the server envelope in `passthrough`
mode.
### Type and import mappings
Previous top-level `type_mapping` and `imports` values were global. The new
configuration groups validated mappings under `generation.mappings`:
```yaml
generation:
mappings:
schemas:
Money:
type: Money
import: package:money2/money2.dart
types:
string:uuid:
type: UuidValue
import: package:uuid/uuid_value.dart
string:date-time: DateTime
imports:
UuidValue: package:uuid/uuid_value.dart
```
Type lookup tries `type:format` before `type`. A `schemas` entry means that the
schema is completely supplied by an external type and no Dart model is
generated for it.
### Ignored directories and files
The previous `ignored_directories` and `ignored_files` settings skipped files
after rendering and could leave APIs referencing missing models. Replace them
with semantic source filters:
```yaml
output:
excluded_paths:
- /api/v1/legacy/**
excluded_operations:
- regex:^internal
excluded_models:
- Legacy*
```
If a retained operation depends on an excluded model, generation fails with
`filter.required_model_excluded`. There is intentionally no setting that
arbitrarily drops a rendered file.
### Version extraction
Paths such as `/api/v1/` and `/v2/` are grouped automatically. To also try
extracting a version from the source URL:
```yaml
output:
extract_version_from_url: true
```
The previous custom
`generation.api.version_extraction.pattern/default_version` setting has no
one-to-one equivalent. A URL extraction failure produces a diagnostic and
falls back to path-based grouping.
### build_runner
The previous workflow commonly ran build_runner separately. It can now be
part of generation:
```yaml
formatting:
run_dart_format: true
run_build_runner: true
build_runner_mode: build
delete_conflicting_outputs: true
```
For interactive development:
```bash
dart run koi_swagger_parser generate --watch
```
Watch mode is long-running and should not be used for a one-shot CI job.
## Previous settings that are not read
These fields produce a `config.unknown_key` warning:
- `generator.name/version/author/copyright`;
- `input.validate_schema/strict_mode`;
- `output.api_file_suffix/model_file_suffix`;
- `generation.api.use_dio/parser/method_naming`;
- `generation.models.class_naming/field_naming/use_const_constructor`;
- top-level `validation.on_error/on_warning`;
- top-level generic import ordering/grouping settings.
The new generator uses structured errors for cases that would break wire
semantics or compilation. It does not provide a warn/ignore override for those
errors. Unsupported constructs that can be omitted safely, such as callbacks
and links, produce warnings.
## Generated-code differences to review
- A schema with read-only/write-only fields can become `FooRequest` and
`FooResponse`.
- An optional field whose schema disallows null still uses a nullable Dart
argument to represent absence, together with `disallowNullValue`; explicit
JSON null is rejected.
- A root object with `properties` plus `additionalProperties` flattens extras
into the JSON root.
- A `default` response is never treated as successful.
- Complex parameter styles can introduce a public transport adapter and a
private Retrofit delegate.
- API methods can return `HttpResponse<T>`; binary output can use bytes or a
stream.
- Generated enums include wire conversion, and unknown fallback uses an
internal sentinel wire value.
- Stale files are removed only through manifest ownership; a user-modified
stale file is preserved.
## CLI comparison
Previous:
```bash
dart run swagger_generator_flutter generate --all
```
New:
```bash
dart run koi_swagger_parser generate --all
dart run koi_swagger_parser generate --dry-run --verbose
```
Previous `--api`, `--models`, `--docs`, and CLI tag overrides are not retained.
Use `generation.*.enabled` and output filters. The current generator does not
emit a separate API documentation site.
## Acceptance checklist
- The dry run has no error diagnostics.
- Enum wire values and Dart member names match the previous contract.
- Request/response directional models contain the expected fields.
- Cookie, query, and path serialization produces equivalent requests.
- Error and default responses are not decoded as successful responses.
- External custom types have correct imports and JSON conversion.
- `dart run build_runner build --delete-conflicting-outputs` succeeds.
- `dart analyze` and host-project tests pass.
- The second generation reports `lifecycle.unchanged_skipped`.
- Removing an operation deletes only unmodified files owned by the manifest.
For every available backend fixture, compare runtime serialization in addition
to generated source text.
+109
View File
@@ -0,0 +1,109 @@
# Comparison with mature open-source generators
Internal research reviewed on 2026-07-31 against official documentation:
- [OpenAPI Generator dart-dio](https://openapi-generator.tech/docs/generators/dart-dio/)
- [OpenAPI Generator customization](https://openapi-generator.tech/docs/customization/)
- [Swagger Codegen](https://swagger.io/docs/open-source-tools/swagger-codegen/codegen-v3/about/)
- [Swagger Codegen generator customization](https://swagger.io/docs/open-source-tools/swagger-codegen/codegen-v3/generators/)
- [Microsoft Kiota](https://learn.microsoft.com/en-us/openapi/kiota/)
- [Kiota design](https://learn.microsoft.com/en-us/openapi/kiota/design)
- [Kiota authentication](https://learn.microsoft.com/en-us/openapi/kiota/authentication)
- [Kiota backing store](https://learn.microsoft.com/en-us/openapi/kiota/backing-store)
This document is a planning comparison, not a public compatibility promise.
See [Compatibility](../COMPATIBILITY.md) for verified behavior.
## Positioning
| Dimension | Mature community tools | `koi_swagger_parser` |
|---|---|---|
| Output scope | Many client languages, server stubs, and documentation | Dart/Flutter code inside an existing Retrofit + Freezed project |
| Specification parsing | Mature OpenAPI models/validation and large corpora | Purpose-built loader/parser/IR for a documented OpenAPI 3.0/3.1 subset |
| Extension model | Templates, custom generators, language backends, plugins | Separated preset/IR/renderer, but no stable external renderer interface |
| Naming/type reuse | Model/property/parameter/operation/type/import mappings | Comparable mapping categories with early collision checks |
| HTTP runtime | Kiota request adapter, authentication, and middleware abstractions | Dio/Retrofit, with generated adapters for supported wire styles |
| Authentication | Generated schemes or provider abstractions | Server/security metadata; credentials supplied by host Dio code |
| Model state | Optional backing stores and explicit presence tracking | Immutable Freezed models; absent/null validation without dirty tracking |
| Deliverable | Can scaffold a standalone SDK package | Generates APIs/models into an existing package |
| Lifecycle | Established CLI/plugin/build-system integrations | Local manifest, locking, hashes, safe stale cleanup, and build_runner |
## Current differentiators
The official OpenAPI Generator dart-dio feature table marks multi-server,
parameterized-server, and parameter-styling support as unavailable. Within its
documented subset, `koi_swagger_parser` implements root/path/operation server
variables and path/query/header/cookie style/explode handling, using a typed
adapter when Retrofit annotations cannot express the wire format.
Other project-specific strengths include:
- OpenAPI 3.1 nullable versus required/absent behavior for
json_serializable;
- root `properties` plus `additionalProperties` round trips;
- directional read-only/write-only models;
- discriminator and structural union matching;
- external generic Koi response envelopes;
- multiple enum vendor extensions plus configuration precedence;
- hash-protected cleanup limited to generator-owned files;
- generated-package build_runner/analyze/test verification.
These are useful differentiators for Koi-style Dart clients; they do not imply
broader specification coverage than mature general-purpose generators.
## Gaps compared with mature ecosystems
### Specification and JSON Schema breadth
The implementation is not a complete JSON Schema 2020-12 validator. Broader
third-party document coverage would require selected `$defs`/JSON Pointer
cases, conditional and unevaluated schemas, richer constraint interaction,
larger official/community corpora, fuzz/property tests, and resource/performance
benchmarks.
### Renderer and template extensibility
OpenAPI Generator and Swagger Codegen allow template replacement and custom
language generators. A comparable extension boundary would require a
versioned IR contract, renderer interface, template overrides, alternative
Dart stacks, layout/header hooks, and generator compatibility checks.
### Runtime abstraction
Kiota separates requests, serialization, authentication, and transport. A
future Dio-oriented optional runtime layer could provide typed authentication
providers, retry/telemetry hooks, error mapping, pagination/long-running
operation helpers, download progress, and presence tracking for PATCH.
### Standalone SDK delivery
Generating a publishable SDK rather than files inside an application would
also require package scaffolding, dependency-version policy, generated
documentation/examples, semantic breaking-change reports, runtime/generator
version pinning, and reproducible release archives.
## Explicitly deferred scope
- Swagger/OpenAPI 2.0.
- Callbacks, webhooks, and Link Object execution.
- XML, Protobuf, and arbitrary codecs.
- Complete OAuth login and mutual-TLS runtime flows.
- Server stubs and mock servers.
- Non-Dart code generation.
These do not block the current Koi/FastAPI client goal, but encountering them
must produce documented diagnostics rather than plausible-looking output.
## Suggested priority
1. Expand fixture/fuzz coverage and add large-document performance benchmarks.
2. Version the IR and define a renderer/template extension boundary.
3. Add semantic diff and CI-check modes.
4. Add optional authentication/error/middleware hooks.
5. Add PATCH presence and dirty tracking if demanded by consumer APIs.
6. Expand the JSON Schema subset only for concrete production fixtures.
7. Add Swagger 2, XML, or callbacks only when a confirmed consumer requires
them.
This order preserves the focused Dart/Koi value while adopting engineering
features with demonstrated reuse value.
+75
View File
@@ -0,0 +1,75 @@
# Product goal
This is an internal planning record for the 0.2.0 development milestone.
User-facing support commitments are defined by
[Compatibility](../COMPATIBILITY.md).
## Objective
Evolve `koi_swagger_parser` from a Koi/FastAPI happy-path script into a
reliable, diagnosable, and extensible OpenAPI 3.0/3.1 Dart/Flutter client
generator. Preserve the loader -> resolver/merger -> parser/IR ->
preset/renderer -> lifecycle architecture and the Koi-oriented
Retrofit/Freezed output style.
## Definition of done
1. **Diagnostics**: versions, invalid structures, unsupported constructs,
conflicts, and degradations have stable codes and source/location context.
2. **References**: local, relative-file, and remote references cover component
groups with caching, cycle detection, multi-source conflict handling, and
dependency tracking.
3. **Mappings**: enum configuration and vendor extensions have deterministic
precedence and unknown fallback; model, property, parameter, operation,
schema, type, and import mappings are validated.
4. **HTTP**: path/query/header/cookie serialization and supported
style/explode/allowReserved combinations are correct; JSON, multipart,
form-urlencoded, text, and binary transfers work.
5. **Responses**: multiple 2xx, 204, `2XX`, `default`, error models, headers,
raw `HttpResponse`, and byte/stream output are represented.
6. **Schemas**: required/nullable/absent, read-only/write-only,
additional-properties, composition/union, custom-scalar, and constraint
metadata enter the IR and output within documented boundaries.
7. **Client metadata**: servers/base URLs and security requirements are
available while remaining compatible with host Dio interceptors.
8. **Lifecycle**: configuration is effective; manifest locking/hashing,
unchanged skips, safe stale cleanup, filters, and build_runner integration
are complete.
9. **Quality gate**: layered tests, conflict/error cases, OpenAPI 3.0/3.1
fixtures, golden output, and a temporary generated-package
build/analyze/test verification pass.
10. **Documentation**: README, configuration, compatibility, diagnostics, and
migration documentation match the implementation in English and
Simplified Chinese.
## Non-goals for this milestone
- Swagger/OpenAPI 2.0.
- Callback, webhook, and Link Object call generation.
- XML, Protobuf, and arbitrary codecs.
- Complete JSON Schema 2020-12.
- Server stubs, mock servers, and non-Dart output.
- Independent generated-SDK publishing.
Non-goals must still produce an explicit warning or error when encountered.
## Evidence
```bash
dart format .
dart analyze
dart test
dart run tool/verify_generated_package.dart
```
The end-to-end verifier must let the generator invoke build_runner and then
run analysis and runtime tests in the generated package. String-fragment checks
alone are insufficient.
## Next horizon
Potential follow-up work includes a larger fixture/fuzz/performance corpus,
versioned IR and renderer interfaces, semantic-diff/CI mode, optional
authentication/error/middleware adapters, and PATCH presence tracking.
JSON Schema or Swagger 2 scope should expand only in response to concrete
consumer fixtures.
+154
View File
@@ -0,0 +1,154 @@
# 架构
[English](../ARCHITECTURE.md) | [简体中文](ARCHITECTURE.md)
生成流水线有意拆分为多个阶段:
```text
配置
-> source loader
-> external/local $ref resolver
-> multi-source merger
-> validation + parser
-> transport/schema IR
-> preset + Dart renderer
-> manifest lifecycle
-> fix / format / build_runner
```
核心不变量是:加载阶段产生自包含且依赖可追踪的文档;parser 表达 OpenAPI
语义;renderer 不重新解释规范;lifecycle 只管理 manifest 中声明的文件。
## 配置
`lib/src/config/config.dart`:
- 查找 `koi_swagger_parser.yaml` 和兼容的 `generator_config.yaml` 名称;
- 相对 source/output 路径以配置文件目录解析;
- 从 `--cwd` 或显式配置目录确定宿主项目命令目录;
- 在转换 YAML scalar 前完成类型校验;
- 解析 enum、命名、type/import mapping 和 filter;
- 校验不支持的组合、mapping collision、正则和 enum mapping;
- 支持从旧 `base_result_import` 迁移。
未知 key 产生 warning;非法类型和不完整组合产生 error。详见
[配置参考](CONFIGURATION.md)。
## Loader、resolver 与 merger
对每个 root source,`OpenApiReferenceResolver`:
1. 规范化本地文件、file URL 或 HTTP(S) source;
2. 缓存每个加载的文档;
3. 解析 local、relative 和 remote `$ref`;
4. 把外部 component bundle 到根文档;
5. 为 component 冲突分配确定性名称;
6. 记录依赖并诊断无法支持的跨文档循环。
`OpenApiDocumentMerger` 再按配置顺序合并 root:
- 重复的 operation method/path 是 error;
- 同名但内容不同的 component 会确定性改名,并重写该 source 的 ref;
- 后输入不会静默覆盖冲突;
- 版本、info 和 path parameter 冲突会被报告。
parser 原则上只应收到根文档 JSON Pointer。仍然存在的 external ref 会被视为
resolver error。
## Validation 与 parser
`OpenApiDocumentParser` 负责:
- OpenAPI 3.0/3.1 版本和关键结构校验;
- operation、path、tag 和 model filter;
- parameter location、style、explode、`allowReserved` 和 `content`;
- request media type,以及 response status/content/header;
- server、security scheme 和 security requirement;
- required、nullable、readOnly/writeOnly 和 `additionalProperties` 语义;
- enum 配置/vendor extension 优先级;
- `allOf`、discriminator union 和已支持的 generic union;
- custom mapping 和 Dart 名称冲突。
无法用可靠 wire 语义渲染的结构不能静默退化为 `dynamic`。安全但有信息损失的
继续处理会产生 warning,例如不同成功 JSON body type 可能统一为 `Object?`。
XML、Protobuf 等没有已支持 decoder 的 media type 会产生 error。
## 中间表示
`lib/src/ir/ir.dart` 是 parser/renderer 契约,主要节点包括:
- `ApiOperation`:method、path、tag、version、server 和 security;
- `ApiParameter`:Dart/wire name、location、style、explode 和
`allowReserved`;
- `ApiRequestBody`:JSON、multipart、form-urlencoded 和 binary request;
- `ApiResponse`:status/default、body kind/type、content type 和 header;
- `ModelSpec`:object、enum、union model,以及 usage category 和 extras;
- `ModelField`:required/nullability/read-write flag 和 constraint;
- `DartType`:named、list、map、object、dynamic 和 void type graph。
model 按 request、result、shared、enum 分类。包含 readOnly/writeOnly 属性的
component 可投影为 request/response variant,避免 request 强制只读值,也避免
response 暴露 write-only secret。
generic `oneOf` 要求恰好一个 decoder 匹配。generic `anyOf` 保留 raw value 和
全部匹配 case。object 分支在 decode 前会用 required key 和 enum constraint
预筛。
## Preset 与 renderer
当前 `koi_flutter` preset 面向 Dio、Retrofit、Freezed 和 json_serializable。
renderer:
- 对 Retrofit 可表达的 parameter 形式使用 Retrofit annotation;
- 当已支持的 style、cookie 或 `allowReserved` 组合需要自定义序列化时,生成
typed public adapter 和 private Retrofit delegate;
- 渲染 Freezed object、enum 和 union model;
- 为根级 `properties` + `additionalProperties` 渲染自定义 map 读写 helper;
- 收集 model dependency 和 external type import;
- 按 version/tag 分组 API,并输出 client/index 文件。
renderer 不加载 ref,也不判断 OpenAPI 结构是否合法。
## 生成生命周期
落盘前,`GenerationLifecycle` 计算:
- canonical merged spec SHA-256;
- normalized config SHA-256;
- 每个渲染 path/content SHA-256。
manifest 记录 schema/generator version、source root、dependency 和每个主生成
文件的实际 hash。写入使用操作系统 exclusive lock 和同目录临时文件 rename。
安全不变量:
1. renderer 输出必须位于配置的 API/model 根;
2. stale candidate 必须来自旧 manifest;
3. stale path 仍必须位于当前 managed root;
4. 删除前当前 hash 必须等于旧 manifest hash;
5. 损坏或未知版本 manifest 不参与删除;
6. fix/format/build 失败时不写 success manifest;
7. watch 在释放生成锁后启动。
`.g.dart` 和 `.freezed.dart` 由 build_runner 管理,不进入本生成器的 stale
manifest。
## Response wrapper
parser 不硬编码 `BaseResult`:
- `passthrough` 返回 OpenAPI response schema;
- `external_generic` 检测配置的成功 envelope,并返回
`ExternalWrapper<Inner>`。
`default` response 仍是错误响应。`response_mode: http_response` 保留 Dio
status/header。错误 response model 仍保留在 IR 和生成 model 集合中。
## 扩展边界
IR/preset/renderer 分层为未来的版本化 renderer interface、template override、
semantic diff 和可选 runtime adapter 留出空间。这些是设计方向,不是当前
公开版本的功能。当前实现边界见[兼容性](COMPATIBILITY.md)。
更多生态对比属于项目内部调研,不随发布 package 交付。
+67
View File
@@ -0,0 +1,67 @@
# 更新日志
本文件记录此 package 的重要变更。
[English](../../CHANGELOG.md) | [简体中文](CHANGELOG.md)
## 0.2.0 - 2026-07-31
### 破坏性变更
- 主 library 不再导出 `DartType`、`ModelSpec`、`ApiOperation`、
`GeneratedFile`、`GeneratedBundle` 等 parser IR 实现类型;请改用
`KoiSwaggerParser` 及其文档化的 `GenerationResult`;
- 不再导出 `KoiSwaggerConfigLoader`;程序化加载配置请改用
`KoiSwaggerConfig.load()`。
### 新增
- 结构化配置、OpenAPI、过滤和生命周期诊断;error 不再静默生成已知错误代码;
- local、relative、file 和 HTTP(S) `$ref` 解析,包括缓存、循环检测、依赖
追踪、外部 component bundling 和确定性多 source 冲突处理;
- `enum_mappings`、兼容字段 `enum_key_mappings`、常见 `x-enum` 扩展、描述和
可选 unknown enum fallback;
- model、property、parameter、operation、schema、type 和 import 映射及冲突校验;
- OpenAPI path、query、header、cookie 参数序列化,包括已支持的 matrix、label、
form、simple、spaceDelimited、pipeDelimited、deepObject、explode 和
`allowReserved` 组合;
- JSON、text、multipart、form-urlencoded 和 binary upload,以及 bytes/stream
download;
- 多成功响应、204、`2XX`、`default`、错误模型、响应头和 `HttpResponse<T>` 模式;
- required、nullable、absent 语义、readOnly/writeOnly 投影,以及根级
`properties` + `additionalProperties` round trip;
- `allOf`、discriminator union,以及已支持的 primitive、container、object
`oneOf`/`anyOf`;
- server variables、security schemes 和 operation OR/AND security metadata;
- tag、path、operation、model 的精确、glob 和正则过滤;
- 带锁 manifest、spec/config/content SHA-256、未变化跳过、安全 stale cleanup、
原子写入和 dry-run 变更计划;
- 内置 build_runner build/watch 集成、reviewed golden、OpenAPI 3.0/3.1 fixture
和临时生成包验证;
- 英文 canonical 文档与完整简体中文镜像,以及配置、兼容性、诊断和迁移参考。
### 变更
- `GenerationResult.writtenFiles` 现在只包含实际写入文件;`generatedFiles` 包含
完整目标集合;dry-run 使用 `wouldWriteFiles` 和 `wouldDeleteFiles`;
- 显式传入配置文件且未设置 `workingDirectory`/`--cwd` 时,配置文件目录成为
宿主项目工作目录;
- `default` response 不再视为成功;XML、Protobuf 和没有安全 decoder 的其他
response media type 会产生诊断;
- `dart fix` 和 `dart format` 仅处理本轮改变的主生成文件。
- 所有非测试 Dart 声明均在不使用文件级排除的情况下满足
`public_member_api_docs`,包括内部 IR 和 lifecycle 类型。
### 修复
- 使 `formatting.run_dart_fix` 和 `output.extract_version_from_url` 真正生效;
- 避免 Dio/Retrofit `Headers` import 冲突、Freezed custom `toJson` 缺失,以及
parameter、field、method mapping 碰撞。
- lifecycle manifest 记录的生成器版本已与 package `0.2.0` 同步。
## 0.1.0 - 2026-06-02
- 首次发布 `koi_swagger_parser`;
- 支持常见 OpenAPI 3.1/FastAPI 解析、Koi 风格 Retrofit API 生成和 Freezed
模型生成;
- 支持兼容读取旧版 `generator_config.yaml`。
+113
View File
@@ -0,0 +1,113 @@
# 兼容性
[English](../COMPATIBILITY.md) | [简体中文](COMPATIBILITY.md)
本矩阵只描述当前实现和自动化测试已覆盖的行为。
- **支持**:已经实现并测试;
- **部分支持**:仅支持“边界”列出的子集;
- **不支持**:生成前产生诊断,或 warning 后明确忽略。
## 文档、引用与合并
| 能力 | 状态 | 边界 |
|---|---|---|
| OpenAPI 3.0.x / 3.1.x | 支持 | JSON/YAML;客户端生成所需常见 Schema Object 子集 |
| Swagger/OpenAPI 2.0 | 不支持 | `openapi.unsupported_version` error |
| 本地 `$ref` | 支持 | 各 component 分组的 JSON Pointer |
| 相对文件 / file URL `$ref` | 支持 | 相对当前引用文档解析 |
| HTTP(S) `$ref` | 支持 | 单次运行缓存、依赖追踪和循环诊断 |
| 外部 component 重名 | 支持 | 确定性改名并 warning |
| 多 root source | 支持 | 确定性 merge;component/operation 冲突诊断 |
| unresolved `$ref` | 支持诊断 | error,不静默转换成 `dynamic` |
| callbacks / webhooks | 不支持 | warning,不生成反向调用 client |
| Link Object | 不支持 | `openapi.links_not_supported` warning |
当前不是完整 JSON Schema 2020-12 实现。`$defs` 作为生成根、
`if`/`then`/`else`、`not`、`contains`、`dependentSchemas` 和 unevaluated
property/item 语义不在当前契约中。
## Schema 与 model
| 能力 | 状态 | 边界 |
|---|---|---|
| object / array / map | 支持 | 递归容器和 inline schema |
| `properties` + `additionalProperties` | 支持 | extra entry 在 JSON 根 round trip |
| string/number/integer/bool/date/date-time | 支持 | format 可由 type mapping 覆盖 |
| enum | 支持 | string、number、boolean wire value;inline/component |
| enum 成员名映射 | 支持 | 配置及常见 `x-enum` extension |
| enum unknown fallback | 支持 | 可配置 `JsonKey.unknownEnumValue` 输出 |
| required / nullable / absent | 支持 | 反序列化区分缺失和不允许 null;普通 Freezed 字段不保留 presence 历史 |
| readOnly / writeOnly | 支持 | 必要时生成 request/response directional variant |
| `allOf` | 支持 | 合并 object property、required 和 additional property |
| discriminator `oneOf`/`anyOf` | 支持 | local 或已归一化 object branch |
| 无 discriminator `oneOf`/`anyOf` | 部分支持 | primitive/container/object;重叠 `oneOf` 匹配会拒绝 |
| custom schema/type/format | 支持 | external import;宿主代码负责 JSON 转换契约 |
| constraint metadata | 部分支持 | 保留 description/doc comment;不生成 runtime validator |
| presence/dirty tracking | 不支持 | 无 Kiota 风格 backing store |
## HTTP wire 语义
| 能力 | 状态 | 边界 |
|---|---|---|
| path/query/header/cookie parameter | 支持 | type、required/default 和 wire name |
| matrix / label / simple | 支持 | 校验 location/style 组合 |
| form / spaceDelimited / pipeDelimited / deepObject | 支持 | 校验 query/cookie 组合 |
| explode / `allowReserved` | 支持 | Retrofit 无法表达时生成 transport adapter |
| Parameter Object `content` | 部分支持 | 必须恰有一个 media type |
| JSON / `+json` request body | 支持 | required body 和 null-to-absent |
| text request body | 支持 | typed `@Body` |
| `multipart/form-data` | 支持 | binary、primitive、array 和 structured JSON part |
| `application/x-www-form-urlencoded` | 支持 | typed field;复杂 encoding 使用 adapter |
| binary upload | 支持 | `Uint8List` |
| binary download | 支持 | `Uint8List` 或 Dio `ResponseBody` stream |
| XML / Protobuf / 任意 custom codec | 不支持 | content-type error,不生成不安全 decoder |
## Response、server 与 security
| 能力 | 状态 | 边界 |
|---|---|---|
| 多个 2xx / 204 / `2XX` | 支持 | 确定性 primary response;不同 JSON type 可 warning 后变成 `Object?` |
| `default` / 4xx / 5xx | 支持 | 保留 error model/IR metadata;不作为 success |
| response header | 支持 | status、type、required flag 和 description |
| raw response metadata | 支持 | `response_mode: http_response` |
| binary + JSON 混合 success media | 支持诊断 | 单个 Retrofit 方法无法安全 decode 时 error |
| root/path/operation server | 支持 | server variable default/enum 校验;preferred server 成为 client default |
| 多 server runtime 切换 | 部分支持 | 保留 metadata;不生成环境选择 UI |
| API key / Bearer / Basic | 支持 metadata | 生成 scheme/operation requirement;凭证由宿主 interceptor/provider 提供 |
| OAuth2 / OpenID / mutual TLS | 部分支持 | 仅 scheme/scope metadata;不生成完整授权流程 |
| security OR/AND requirement | 支持 | list 项是 OR;同一 map 项中的 scheme 是 AND |
## 生成与生命周期
| 能力 | 状态 | 边界 |
|---|---|---|
| Retrofit + Freezed | 支持 | 当前版本唯一完整 renderer |
| passthrough / external generic wrapper | 支持 | 可配置标准 envelope 检测 |
| model/property/parameter/operation mapping | 支持 | collision 校验和安全 identifier |
| schema/type/import mapping | 支持 | 递归 Dart `List`/`Map` type expression |
| tag/path/operation/model filter | 支持 | exact、glob 和 `regex:` |
| dry-run | 支持 | 报告 would-write/would-delete |
| Dart fix / format | 支持 | 仅本轮改变的主生成文件 |
| build_runner build/watch | 支持 | 在配置的宿主项目目录执行 |
| unchanged skip | 支持 | spec/config/content 和实际文件 SHA-256 |
| stale cleanup | 支持 | manifest ownership、root containment、用户修改保护 |
| concurrent generation lock | 支持 | manifest 旁的操作系统 exclusive lock |
| external template / plugin renderer | 不支持 | IR/preset 已分层,但无公开 renderer SPI |
| 完整 SDK package scaffold | 不支持 | 在已有项目内生成 API/model |
## 验证覆盖
仓库测试覆盖:
- parser、IR、renderer 和 lifecycle 阶段;
- OpenAPI 3.0 YAML、OpenAPI 3.1 JSON/FastAPI fixture;
- enum/命名 mapping、parameter wire style、response、schema 语义、security
和 external/cyclic ref;
- reviewed SHA-256 golden;
- 生成到临时 Dart package,随后运行 build_runner、analysis 和 runtime test。
矩阵中的“支持”不代表超出所列边界的完整 OpenAPI/JSON Schema conformance。
引入新的 schema corpus 时,应先用最小 fixture 和 dry-run 验证。
诊断含义和处理方式见[诊断参考](DIAGNOSTICS.md)。
+254
View File
@@ -0,0 +1,254 @@
# 配置参考
[English](../CONFIGURATION.md) | [简体中文](CONFIGURATION.md)
`koi_swagger_parser` 使用 YAML 配置。完整注释示例见
[koi_swagger_parser.example.yaml](../../koi_swagger_parser.example.yaml)。
未知字段会产生 `config.unknown_key`;非法 scalar 类型和非法组合会在 OpenAPI
解析前报错。
## 查找顺序与路径语义
未传入 `--config` 时,加载器按以下顺序在工作目录查找:
1. `koi_swagger_parser.yaml`
2. `koi_swagger_parser.yml`
3. `generator_config.yaml`
4. `generator_config.yml`
所有相对 OpenAPI 文件 source、输出目录和显式 manifest 路径都以所选配置文件的
目录为基准。未找到配置文件时,以命令调用目录为基准。
显式传入配置文件且未指定 `--cwd` 时,其目录也会成为 build_runner 使用的宿主
项目工作目录。`--cwd` 可覆盖命令工作目录,但不会重新解释配置中的相对路径。
HTTP(S) source URL 保持 URL 语义。
## 顶层字段
| 字段 | 类型 | 必填 | 默认值 |
|---|---|---:|---|
| `input` | map | 是 | 无 |
| `output` | map | 否 | 见下文 |
| `generation` | map | 否 | 见下文 |
| `formatting` | map | 否 | 见下文 |
必须至少配置一个启用的 `input.swagger_urls` 项。
## `input`
### `input.swagger_urls`
类型:字符串或 source 对象的列表。默认:空,会报错。
```yaml
input:
swagger_urls:
- ./openapi.yaml
- url: https://example.com/openapi.json
enabled: true
- url: ./experimental.yaml
enabled: false
```
source 对象字段:
| 字段 | 类型 | 必填 | 默认值 | 含义 |
|---|---|---:|---|---|
| `url` | string | 是 | 无 | 本地路径、file URL 或 HTTP(S) URL |
| `enabled` | boolean | 否 | `true` | 是否参与生成 |
source 按配置顺序处理。多个根文档会确定性合并;operation 冲突报错,component
冲突会确定性改名并产生诊断。
## `output`
| 字段 | 类型 | 默认值 | 含义 |
|---|---|---|---|
| `base_dir` | string path | `./lib/src` | 生成代码公共根 |
| `api_dir` | string path | `<base_dir>/api` | API 输出根 |
| `models_dir` | string path | `<base_dir>/api_models` | model 输出根 |
| `included_tags` | string list | 未设置 | 包含匹配 tag 的 operation |
| `excluded_tags` | string list | 未设置 | 排除匹配 tag 的 operation |
| `included_paths` | string list | 未设置 | 包含匹配的 OpenAPI path |
| `excluded_paths` | string list | 未设置 | 排除匹配的 OpenAPI path |
| `included_operations` | string list | 未设置 | 包含匹配 operation |
| `excluded_operations` | string list | 未设置 | 排除匹配 operation |
| `included_models` | string list | 未设置 | 把匹配 schema 加为生成根 |
| `excluded_models` | string list | 未设置 | 排除匹配 model |
| `split_by_tags` | boolean | `true` | 按 operation tag 拆分 API 文件 |
| `extract_version_from_url` | boolean | `false` | 尝试从 source URL 提取分组版本 |
| `clean_stale_files` | boolean | `true` | 安全删除不再生成的自有文件 |
| `skip_if_unchanged` | boolean | `true` | spec、config 和内容未变时跳过写入 |
| `manifest_path` | string path | `<project>/.dart_tool/koi_swagger_parser/manifest.json` | 所有权/hash manifest |
过滤项支持:
- 精确字符串;
- `*`、`?`、`**` glob;
- `regex:<expression>`,使用 Dart 正则语法。
同一值同时匹配 include/exclude 时,exclude 优先;相同字面量同时出现在两边会
产生 warning。若保留的 operation 依赖被排除 model,生成会失败,不会输出无法
编译的客户端。
stale cleanup 仅处理旧 manifest 记录、仍位于当前 API/model 根、且内容仍匹配
旧 hash 的文件。manifest 应放在生成的 API/model 根之外。
## `generation.preset`
| 字段 | 类型 | 默认值 | 可选值 |
|---|---|---|---|
| `name` | string | `koi_flutter` | `koi_flutter` |
当前版本只有一个 renderer preset:Dio + Retrofit API 和
Freezed/json_serializable model。
## `generation.api`
| 字段 | 类型 | 默认值 | 可选值/含义 |
|---|---|---|---|
| `enabled` | boolean | `true` | 生成 API |
| `use_retrofit` | boolean | `true` | 启用 API 时必须为 `true` |
| `cancel_token_support` | boolean | `false` | 添加 Dio `CancelToken` 参数 |
| `response_mode` | string | `body` | `body`、`http_response` |
| `binary_response_mode` | string | `bytes` | `bytes`、`stream` |
`generation.api.enabled: true` 要求 `generation.models.enabled: true`,因为
当前 Retrofit renderer 会导入生成的 model index。
### `generation.api.client`
| 字段 | 类型 | 默认值 | 含义 |
|---|---|---|---|
| `class_name` | string | `ApiClient` | 生成的 client class 名 |
| `file_name` | string | `api_client` | 生成的 client 文件名 |
值会转换为安全的 Dart identifier/file name。
### `generation.api.response_wrapper`
| 字段 | 类型 | 默认值 | 可选值/含义 |
|---|---|---|---|
| `mode` | string | `passthrough` | `passthrough`、`external_generic` |
| `success_data_field` | string | `data` | envelope 中的成功 payload 字段 |
| `detect_standard_envelope` | boolean | `true` | 检测配置的 code/message/data 风格 envelope |
| `external_generic.import` | string | 无 | `external_generic` 模式必填 |
| `external_generic.class_name` | string | `BaseResult` | 外部 generic wrapper 类型 |
```yaml
generation:
api:
response_wrapper:
mode: external_generic
success_data_field: data
detect_standard_envelope: true
external_generic:
import: package:your_network/your_network.dart
class_name: BaseResult
```
兼容字段 `generation.api.base_result_import` 会在未显式设置 mode 时选择
`external_generic`,class name 为 `BaseResult`。新配置应优先使用结构化写法。
## `generation.models`
| 字段 | 类型 | 默认值 | 含义 |
|---|---|---|---|
| `enabled` | boolean | `true` | 生成 model |
| `class_prefix` | string | `Koi` | 生成 model 名称前缀 |
| `default_non_nullable_list_to_empty` | boolean | `true` | 符合条件的非空 list 默认使用空 list |
| `enum_unknown_fallback` | boolean | `false` | 生成并使用 `unknown` enum 成员 |
| `enum_unknown_default_case` | boolean | `false` | 上一字段的兼容别名 |
### `generation.models.json_serializable`
| 字段 | 类型 | 默认值 |
|---|---|---|
| `checked` | boolean | `true` |
| `explicit_to_json` | boolean | `true` |
| `include_if_null` | boolean | `false` |
这些值会在适用场景传给生成的 json_serializable annotation。
### Enum 映射
支持 list 和 map 两种写法:
```yaml
generation:
models:
enum_mappings:
OrderStatus:
- value: pending
name: waitingForPayment
description: 等待付款
- value: done
name: completed
Priority:
1: low
2:
name: high
description: 需要关注
```
外层 key 是 OpenAPI enum/schema 名称。wire `value` 可以是 string、number 或
boolean;`name` 必须是非空 Dart 成员名;`description` 可选。
兼容字段 `enum_key_mappings` 接受同样结构。两个字段映射同一 enum value 时,
`enum_mappings` 优先并产生 warning。vendor extension 优先级见
[README 的 Enum 映射](../../README.zh-CN.md#enum-映射)。
## `generation.mappings`
所有 mapping 段默认都是空 map。
| 字段 | 结构 | 含义 |
|---|---|---|
| `models` | `schema: DartName` | 重命名生成的 component model |
| `properties` | `schema: {wireProperty: dartField}` | 重命名指定 schema 的属性 |
| `parameters` | `operation: {wireParameter: dartParameter}` | 重命名指定 operation 的参数 |
| `operations` | `operation: dartMethod` | 重命名 operation |
| `schemas` | `schema: type-or-object` | 使用外部 Dart 类型,不生成该 schema |
| `types` | `openapiType[:format]: type-or-object` | 映射 OpenAPI scalar/type pattern |
| `imports` | `DartType: packageImport` | 为映射后的 Dart 类型提供 import |
operation key 可以是 `operationId` 或 `METHOD /path`。type mapping 先尝试
`type:format`,再尝试 `type`。
type-or-object 可以是 Dart 类型字符串,也可以是:
```yaml
type: UuidValue
import: package:uuid/uuid_value.dart
```
同一 mapping scope 内,两个 source name 不能指向同一个 Dart 名。相同 schema
不能同时出现在 `models` 和 `schemas`。同一个 Dart 类型不能解析出冲突 import。
这些组合都会报配置错误。
## `formatting`
| 字段 | 类型 | 默认值 | 可选值/含义 |
|---|---|---|---|
| `run_dart_fix` | boolean | `false` | 对本轮改变的主生成文件执行 `dart fix --apply` |
| `run_dart_format` | boolean | `true` | 格式化本轮改变的主生成文件 |
| `run_build_runner` | boolean | `false` | 主文件写入后运行 build_runner |
| `build_runner_mode` | string | `build` | `build`、`watch` |
| `delete_conflicting_outputs` | boolean | `true` | 向 build_runner 传入 `--delete-conflicting-outputs` |
CLI `--watch` 会在非 dry-run 生成后启动 build_runner watch,不受配置 mode
限制。watch 是长驻流程,适合交互式开发,不适合一次性 CI job。
## 组合规则
加载器会强制以下关系:
- API 和 model 不能同时关闭;
- API 当前依赖 model;
- API 当前要求 `use_retrofit: true`;
- `external_generic` response wrapper 需要非空 import;
- 未知 preset、enum/response/build-runner mode 会报错;
- 非法 filter 正则会报错;
- mapping target/import 冲突会报错。
稳定 code 和处理方式见[诊断参考](DIAGNOSTICS.md)。
+150
View File
@@ -0,0 +1,150 @@
# 诊断
[English](../DIAGNOSTICS.md) | [简体中文](DIAGNOSTICS.md)
配置、加载、解析、渲染和生命周期问题统一表示为 `KoiDiagnostic`:
```dart
class KoiDiagnostic {
final String code;
final DiagnosticSeverity severity; // info | warning | error
final String message;
final String? source;
final String? location;
}
```
`code` 是稳定的机器可读标识;message 文案可能随版本改进。`source` 表示配置
路径、文档 URL/文件或生成文件;`location` 通常是 source 内的 JSONPath、
JSON Pointer 或其他逻辑位置。
## Severity 行为
- `info`:正常决策,例如跳过未变化的生成;
- `warning`:输出仍然可靠,但发生改名、安全降级或明确省略的非阻塞能力;
- `error`:继续执行可能产生无效客户端,生成器抛出
`KoiDiagnosticException`。
CLI 默认输出 warning/error;`--verbose` 还会输出 info 和失败堆栈。
library 调用方可检查诊断:
```dart
try {
final result = await const KoiSwaggerParser().generate();
for (final item in result.diagnostics) {
print(item.format());
}
} on KoiDiagnosticException catch (error) {
for (final item in error.diagnostics) {
print(item.format());
}
}
```
自动化应匹配 `code`,不要匹配 message 文本。需要区分上下文时再断言 `source`
或 `location`。
## 常见 code
### 配置
| Code | 含义和处理 |
|---|---|
| `config.missing_sources` | 添加至少一个启用的 `input.swagger_urls`。 |
| `config.unknown_key` | 修正拼写或删除当前版本不认识的字段;warning。 |
| `config.invalid_section` | 把 scalar/list 改成预期 YAML map。 |
| `config.invalid_string` / `config.invalid_boolean` | 修正 YAML scalar 类型。 |
| `config.invalid_string_list` | include/exclude filter 必须是字符串列表。 |
| `config.invalid_filter_regex` | 修正 `regex:` 后的 Dart 表达式。 |
| `config.no_outputs_enabled` | API/model 不能同时关闭。 |
| `config.api_requires_models` | 当前 API renderer 依赖生成 model。 |
| `config.unsupported_api_renderer` | 保持 `use_retrofit: true`;目前没有其他公开 API renderer。 |
| `config.external_wrapper_import_required` | 添加 `external_generic.import`。 |
| `config.mapping_target_collision` | 为 mapping source 设置不同的 Dart 目标名。 |
| `config.import_mapping_conflict` | 同一映射 Dart 类型只能使用一个 import。 |
| `config.conflicting_schema_mapping` | 同一 schema 不能既重命名又由外部类型替换。 |
| `config.ambiguous_enum_mapping` | 使用类型明确、无歧义的 enum wire value。 |
### 文档与引用
| Code | 含义和处理 |
|---|---|
| `openapi.missing_version` / `openapi.unsupported_version` | 提供 OpenAPI 3.0.x/3.1.x 文档。 |
| `openapi.invalid_info` / `openapi.invalid_paths` | 修正文档对应关键结构。 |
| `openapi.source_load_failed` / `openapi.source_http_error` | 检查文件路径、URL、访问权限和响应状态。 |
| `openapi.unresolved_ref` | 本地 JSON Pointer 不存在。 |
| `openapi.unresolved_external_ref` | 外部资源/fragment 不存在或无法加载。 |
| `openapi.external_ref_not_bundled` | ref 归一化失败;检查前序 source/resolver 诊断。 |
| `openapi.external_component_renamed` | 外部 component 冲突已确定性改名;warning。 |
| `openapi.multisource_component_renamed` | root 间 component 冲突已确定性改名;warning。 |
| `openapi.multisource_operation_conflict` | 两个 root 定义相同 method/path;删除或拆分其中一个。 |
| `openapi.callbacks_not_supported` / `openapi.webhooks_not_supported` | 不生成反向调用 client;warning。 |
| `openapi.links_not_supported` | 不生成 Link Object 调用关系;warning。 |
### Parameter、request 与 response
| Code | 含义和处理 |
|---|---|
| `openapi.invalid_parameter_style` | 使用与 parameter location 匹配的 style。 |
| `openapi.deep_object_requires_object` | deepObject parameter 必须使用 object schema。 |
| `openapi.delimited_parameter_requires_array` | space/pipe-delimited parameter 必须使用 array schema。 |
| `openapi.parameter_content_count` | Parameter Object `content` 必须恰有一个 media type。 |
| `openapi.request_body_schema_missing` | 为选中的 request media type 添加 schema。 |
| `openapi.unsupported_request_content_type` | 使用已支持 request codec 或手写 endpoint。 |
| `openapi.unsupported_response_content_type` | 使用已支持 response codec 或手写 endpoint。 |
| `openapi.success_response_missing` | 添加显式 2xx response;`default` 不是 success。 |
| `openapi.multiple_success_response_types` | 多个成功 JSON body type 统一为 `Object?`;warning。 |
| `openapi.mixed_success_media_types` | 单个生成方法无法安全 decode binary/stream + JSON success。 |
| `openapi.path_parameter_forced_required` | 声明为 optional 的 path parameter 被强制为 required;warning。 |
### Schema 与命名
| Code | 含义和处理 |
|---|---|
| `openapi.property_read_write_only` | property 不能同时 read-only/write-only。 |
| `openapi.invalid_additional_properties` | 使用 boolean 或 Schema Object。 |
| `openapi.property_name_collision` | 自动 suffix 避免了 Dart field collision;warning。 |
| `openapi.operation_name_collision` | 自动 suffix 避免了 method collision;warning。 |
| `openapi.operation_argument_name_collision` | parameter 与 body、cancel token 或其他参数冲突。 |
| `openapi.duplicate_enum_member_name` | 多个 wire value 映射到同名,已添加 suffix。 |
| `openapi.enum_member_name_sanitized` | enum member name 已转换为合法 Dart identifier。 |
| `filter.required_model_excluded` | 选中 API 依赖被排除 model;包含该 model 或排除调用方。 |
| `filter.model_pattern_unmatched` | `included_models` pattern 没有匹配 schema。 |
### Server 与 security
| Code | 含义和处理 |
|---|---|
| `openapi.server_url_missing` | 添加非空 server URL。 |
| `openapi.server_variable_default_missing` | 为每个 server variable 提供 default。 |
| `openapi.server_variable_default_invalid` | 让 default 属于声明的 enum value。 |
| `openapi.server_variable_unresolved` | 定义 server URL 中的每个 variable。 |
| `openapi.unknown_security_scheme` | 在 components 中定义被引用 scheme。 |
| `openapi.unsupported_security_scheme` | scheme 只在文档边界内保留或被拒绝。 |
| `openapi.http_auth_requires_custom_interceptor` | 在宿主 Dio 层实现凭证注入;warning。 |
### 生成生命周期
| Code | 含义和处理 |
|---|---|
| `lifecycle.unchanged_skipped` | hash/file 一致,已安全跳过生成。 |
| `lifecycle.manifest_invalid` | 旧 manifest 损坏;重建输出但不信任其删除列表。 |
| `lifecycle.manifest_version_ignored` | manifest schema 不兼容,其删除列表被忽略。 |
| `lifecycle.output_outside_managed_roots` | renderer 输出越过 API/model 根,已拒绝。 |
| `lifecycle.duplicate_output_path` | 两个渲染文件以不同内容指向同一路径。 |
| `lifecycle.stale_path_outside_roots` | 旧路径位于当前 root 外,因此保留。 |
| `lifecycle.stale_file_modified` | stale 生成文件已被编辑,保留供人工处理。 |
| `lifecycle.generated_file_missing` | 主生成文件在 fix/format/build 期间消失。 |
| `lifecycle.control_file_not_stale` | lifecycle 控制文件永不作为 stale 输出处理。 |
## 设计原则
1. unresolved ref、非法 wire style 和不支持 codec 不会静默变成 `dynamic`;
2. 安全且可编译的自动改名可以 warning 后继续;
3. 不会破坏其他输出的不支持结构会 warning 后明确省略;
4. stale cleanup 在 path containment 或 hash ownership 不确定时保留文件;
5. 可用时诊断会带 component、operation、parameter 或文件上下文。
minor release 可以增加新 code。自动化应把未知 warning 视为需复核,把未知 error
视为阻塞。
+215
View File
@@ -0,0 +1,215 @@
# 从 swagger_generator_flutter 迁移
[English](../MIGRATION.md) | [简体中文](MIGRATION.md)
本指南面向使用 `swagger_generator_flutter` 的 `generator_config.yaml` 惯例。
迁移应在消费方 Dart/Flutter 仓库中进行,不依赖任何本机绝对路径。
## 建议顺序
1. 在独立分支保留旧生成结果;
2. 复制 [koi_swagger_parser.example.yaml](../../koi_swagger_parser.example.yaml),
第一轮只迁移 source、输出根和 response wrapper;
3. 第一次设置 `clean_stale_files: false` 并执行 `--dry-run`;
4. 生成到临时目录,对比 API signature、JSON key 和 import;
5. 迁移 enum、命名和 custom type mapping;
6. 在宿主项目运行 build_runner、`dart analyze` 和测试;
7. 审核输出后切换正式目录,并启用 `clean_stale_files: true`。
## 可直接沿用
| 旧字段 | 新字段或行为 |
|---|---|
| `input.swagger_urls` | 同名;支持 string/object、`enabled`、本地路径和 HTTP(S) source |
| `output.base_dir/api_dir/models_dir` | 同名 |
| `output.included_tags/excluded_tags` | 同名,并支持 glob/正则 |
| `output.split_by_tags` | 同名 |
| `generation.api.client.class_name/file_name` | 同名 |
| `generation.api.cancel_token_support` | 同名 |
| `generation.api.base_result_import` | 兼容输入;选择 `external_generic` + `BaseResult` |
| `generation.models.class_prefix` | 同名 |
| `generation.models.json_serializable.*` | 同名 |
| `generation.models.enum_key_mappings` | 仍然支持;建议迁移到 `enum_mappings` |
| `formatting.run_dart_format` | 同名 |
已有 enum mapping 不需要立即重写:
```yaml
generation:
models:
enum_key_mappings:
TaskType:
- value: 1
name: spotCheck
description: 抽查
```
两个字段映射同一 wire value 时,`enum_mappings` 优先。生成器也识别
`x-enumNames`、`x-enum-varnames` 和 `x-enum-values`。
## 需要改写
### Response wrapper
旧写法:
```yaml
generation:
api:
base_result_type: BaseResult
base_result_import: package:app/network/base_result.dart
```
推荐写法:
```yaml
generation:
api:
response_wrapper:
mode: external_generic
detect_standard_envelope: true
success_data_field: data
external_generic:
import: package:app/network/base_result.dart
class_name: BaseResult
```
`base_page_result_type/base_page_result_import` 没有自动一对一迁移。优先在
OpenAPI 中准确描述分页 schema;复用已有类型时使用
`generation.mappings.schemas`,或用 `passthrough` 保留服务端 envelope。
### 类型与 import mapping
旧顶层 `type_mapping` 和 `imports` 是全局配置。新配置把可校验 mapping 集中到
`generation.mappings`:
```yaml
generation:
mappings:
schemas:
Money:
type: Money
import: package:money2/money2.dart
types:
string:uuid:
type: UuidValue
import: package:uuid/uuid_value.dart
string:date-time: DateTime
imports:
UuidValue: package:uuid/uuid_value.dart
```
type 查找先尝试 `type:format`,再尝试 `type`。`schemas` 表示 schema 完全由
external type 提供,不再生成对应 Dart model。
### ignored directory/file
旧 `ignored_directories`/`ignored_files` 在渲染后跳过文件,可能留下引用缺失
model 的 API。应改为源语义 filter:
```yaml
output:
excluded_paths:
- /api/v1/legacy/**
excluded_operations:
- regex:^internal
excluded_models:
- Legacy*
```
保留的 operation 若依赖被排除 model,生成会以
`filter.required_model_excluded` 失败。没有任意丢弃渲染文件的设置。
### 版本提取
`/api/v1/`、`/v2/` 等 path 会自动分组。若还要尝试从 source URL 提取:
```yaml
output:
extract_version_from_url: true
```
旧 `generation.api.version_extraction.pattern/default_version` 没有一对一配置。
URL 提取失败会产生诊断并回到基于 path 的分组。
### build_runner
旧流程通常单独执行 build_runner,现在可以并入生成:
```yaml
formatting:
run_dart_format: true
run_build_runner: true
build_runner_mode: build
delete_conflicting_outputs: true
```
交互式开发可用:
```bash
dart run koi_swagger_parser generate --watch
```
watch 是长驻模式,不适合一次性 CI job。
## 不再读取的旧设置
这些字段会产生 `config.unknown_key` warning:
- `generator.name/version/author/copyright`;
- `input.validate_schema/strict_mode`;
- `output.api_file_suffix/model_file_suffix`;
- `generation.api.use_dio/parser/method_naming`;
- `generation.models.class_naming/field_naming/use_const_constructor`;
- 顶层 `validation.on_error/on_warning`;
- 顶层通用 import 排序/分组设置。
新生成器对会破坏 wire semantics 或编译的情况使用结构化 error,不提供
warn/ignore override。callbacks、links 等可安全省略的不支持结构使用 warning。
## 需审核的生成代码差异
- readOnly/writeOnly schema 可拆为 `FooRequest`/`FooResponse`;
- optional 但 schema 不允许 null 的字段仍用 nullable Dart argument 表示 absent,
同时使用 `disallowNullValue`;显式 JSON null 会被拒绝;
- 根 object 同时有 `properties` 和 `additionalProperties` 时,extra 会 flatten
到 JSON 根;
- `default` response 永不作为 success;
- 复杂 parameter style 可产生 public transport adapter + private Retrofit
delegate;
- API method 可返回 `HttpResponse<T>`,binary 输出可使用 bytes/stream;
- enum 包含 wire conversion,unknown fallback 使用内部 sentinel wire value;
- stale file 只通过 manifest ownership 删除;用户修改的 stale file 会保留。
## CLI 对照
旧:
```bash
dart run swagger_generator_flutter generate --all
```
新:
```bash
dart run koi_swagger_parser generate --all
dart run koi_swagger_parser generate --dry-run --verbose
```
旧 `--api`、`--models`、`--docs` 和 CLI tag override 不再保留。使用
`generation.*.enabled` 和 output filter。当前生成器不输出独立 API 文档站点。
## 验收清单
- dry-run 无 error 诊断;
- enum wire value 和 Dart member name 与旧契约一致;
- request/response directional model 包含预期字段;
- cookie、query、path 序列化产生等价请求;
- error/default response 不被 decode 成 success;
- external custom type 的 import 和 JSON conversion 正确;
- `dart run build_runner build --delete-conflicting-outputs` 成功;
- `dart analyze` 和宿主项目测试通过;
- 第二次生成报告 `lifecycle.unchanged_skipped`;
- 移除 operation 后只删除 manifest 拥有且未修改的文件。
对每个可用后端 fixture,除了对比生成源码,还应对比 runtime serialization。