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:
+132
-74
@@ -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.
|
||||
|
||||
@@ -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).
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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 交付。
|
||||
@@ -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`。
|
||||
@@ -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)。
|
||||
@@ -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)。
|
||||
@@ -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
|
||||
视为阻塞。
|
||||
@@ -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。
|
||||
Reference in New Issue
Block a user