Release 0.2.0

Complete OpenAPI 3.0/3.1 generation, diagnostics, lifecycle safety, enum and naming mappings, bilingual documentation, examples, and pub.dev release validation.
This commit is contained in:
mr.koi
2026-07-31 16:47:39 +08:00
parent ea93f09154
commit 454e6c51d7
56 changed files with 14506 additions and 491 deletions
+132 -74
View File
@@ -1,114 +1,172 @@
# Architecture
`koi_swagger_parser` 采用“加载 -> 解析 -> IR -> 渲染 -> 落盘”的分层设计,避免把 OpenAPI 细节、Koi 业务约定、具体 Dart 输出模板揉在一起。
[English](ARCHITECTURE.md) | [Chinese (Simplified)](zh-CN/ARCHITECTURE.md)
## 1. 分层
The generation pipeline is deliberately split into stages:
### config
```text
configuration
-> source loader
-> external/local $ref resolver
-> multi-source merger
-> validation + parser
-> transport/schema IR
-> preset + Dart renderer
-> manifest lifecycle
-> fix / format / build_runner
```
负责读取与归一化配置:
The main invariant is that loading produces a self-contained document with
traceable dependencies; the parser represents OpenAPI semantics; the renderer
does not reinterpret the specification; and the lifecycle manages only files
declared in its manifest.
- 新配置:`koi_swagger_parser.yaml`
- 兼容配置:`generator_config.yaml`
- 路径归一化、默认值、旧字段兼容映射
- preset 解析(当前内置 `koi_flutter`)
## Configuration
### openapi
`lib/src/config/config.dart`:
负责:
- discovers `koi_swagger_parser.yaml` and compatible
`generator_config.yaml` names;
- resolves source and output paths against the configuration directory;
- chooses the host-project command directory from `--cwd` or the explicit
configuration directory;
- validates YAML scalar types before casting;
- parses enum, naming, type/import mappings, and filters;
- validates unsupported combinations, mapping collisions, regular
expressions, and enum mappings;
- supports migration from the legacy `base_result_import` setting.
- 本地/远程文档加载
- 多 source 深合并
- schema/ref 解析
- OpenAPI 3.1 nullable / anyOf / oneOf / additionalProperties / allOf 归一化
- multipart request body 与 discriminator union 识别
Unknown keys produce warnings. Invalid types and incomplete combinations
produce errors. See [Configuration](CONFIGURATION.md).
### ir
## Loader, resolver, and merger
产出与 OpenAPI 原文解耦的中间表示:
For each root source, `OpenApiReferenceResolver`:
- API operation
- request body / parameter
- model / enum
- Dart type graph
1. normalizes a local file, file URL, or HTTP(S) source;
2. caches each loaded document;
3. resolves local, relative, and remote `$ref` values;
4. bundles external components into the root document;
5. assigns deterministic names to component conflicts;
6. records dependencies and diagnoses unsupported cross-document cycles.
IR 是后续接入 `koi_blueprint_flutter` preset 的关键。
`OpenApiDocumentMerger` then merges roots in configuration order:
### generator
- a duplicate operation method/path is an error;
- components with the same name but different content are renamed
deterministically, and references from that source are rewritten;
- later inputs never silently overwrite conflicts;
- version, info, and path-parameter conflicts are reported.
只关心如何把 IR 渲染成 Dart 文件:
The parser should receive only root-document JSON Pointers. A remaining
external reference is treated as a resolver error.
- Retrofit API
- Freezed model
- enums
- index/client 聚合文件
## Validation and parsing
这一层不直接处理 OpenAPI 细节。
`OpenApiDocumentParser` handles:
### preset
- OpenAPI 3.0/3.1 version and core structure checks;
- operation, path, tag, and model filters;
- parameter location, style, explode, `allowReserved`, and `content`;
- request media types and response status, content, and headers;
- servers, security schemes, and security requirements;
- required, nullable, read-only/write-only, and `additionalProperties`
semantics;
- enum configuration/vendor-extension precedence;
- `allOf`, discriminated unions, and supported generic unions;
- custom mappings and Dart name collisions.
用于把 parser core 的 IR 和具体客户端技术栈解耦。当前先提供一个很薄的 preset 层:
A structure that cannot be rendered with reliable wire semantics must not
silently become `dynamic`. A safe but lossy continuation produces a warning;
for example, different successful JSON body types may converge on `Object?`.
A media type without a supported decoder, such as XML or Protobuf, produces an
error.
- `koi_flutter`:默认面向 Retrofit + Dio + Freezed
- 当前主要控制 multipart 二进制字段该生成为 `MultipartFile`、`File`、`Uint8List` 还是 `String`
- 后续可以继续扩展 blueprint preset、网络层 preset、命名/包装策略 preset
## Intermediate representation
## 2. Response Wrapper 设计
`lib/src/ir/ir.dart` is the parser/renderer contract. Its main nodes include:
parser core 不直接写死 `BaseResult`。
- `ApiOperation`: method, path, tag, version, server, and security data;
- `ApiParameter`: Dart/wire name, location, style, explode, and
`allowReserved`;
- `ApiRequestBody`: JSON, multipart, form-urlencoded, and binary requests;
- `ApiResponse`: status/default, body kind/type, content type, and headers;
- `ModelSpec`: object, enum, and union models with usage category and extras;
- `ModelField`: required/nullability/read-write flags and constraints;
- `DartType`: named, list, map, object, dynamic, and void type graphs.
当前支持两种模式:
Models are grouped as request, result, shared, or enum models. A component with
read-only/write-only properties can be projected into request and response
variants so that read-only values are not required in requests and
write-only secrets are not exposed in responses.
- `passthrough`
- 返回真实 OpenAPI response schema
- 适合你希望保留服务端 envelope 模型的场景
- `external_generic`
- 识别标准成功 envelope(默认 `code/message/data`)
- 将 `data` 提取为 inner type
- API 返回 `ExternalWrapper<Inner>`,例如 `BaseResult<KoiStoreResponse>`
A generic `oneOf` requires exactly one decoder match. A generic `anyOf` retains
the raw value and all matching cases. Object branches are prefiltered by
required keys and enum constraints before decoding.
这样做的意义是:
## Preset and renderer
- parser core 保持通用
- Koi 网络层包裹策略可配置
- 未来可以挂更多 wrapper preset,而不是继续堆服务端补丁
The current `koi_flutter` preset targets Dio, Retrofit, Freezed, and
json_serializable.
## 3. 模型分组
The renderer:
模型按使用语义归档:
- uses Retrofit annotations for parameter forms Retrofit can represent;
- emits a typed public adapter and private Retrofit delegate when a supported
style, cookie, or `allowReserved` combination needs custom serialization;
- renders Freezed object, enum, and union models;
- renders custom root-map read/write helpers for
`properties` plus `additionalProperties`;
- collects model dependencies and external type imports;
- groups API files by version and tag and emits client/index files.
- `request/`:只在请求体根节点使用
- `result/`:只在成功响应根节点使用
- `shared/`:被请求与响应共同依赖,或用于错误响应
- `enums/`:枚举单独分组
The renderer does not load references or decide whether an OpenAPI structure
is valid.
这比“按 swagger component 平铺”更符合 Koi 项目结构,也更利于未来蓝图模板整理。
## Generation lifecycle
## 4. 命名原则
Before writing, `GenerationLifecycle` calculates:
- 类名统一 PascalCase。
- 字段统一 camelCase,并保留 `@JsonKey(name: ...)` 对应 wire name。
- 文件名统一 snake_case。
- API 默认按 `version + tag` 分文件。
- the canonical merged-spec SHA-256;
- the normalized configuration SHA-256;
- each rendered path and content SHA-256.
命名转换统一收口到 util 层,避免每个阶段各写一套字符串处理逻辑。
The manifest records its schema/generator version, source roots, dependencies,
and the actual hash of each primary generated file. Writes use an operating
system exclusive lock and same-directory temporary-file rename.
## 5. 为什么新建项目而不是继续修补旧生成器
Safety invariants:
旧生成器的问题不是单点 bug,而是结构性耦合:
1. Renderer output must stay inside the configured API/model roots.
2. A stale candidate must come from the previous manifest.
3. Its path must still be inside the current managed roots.
4. Its current hash must equal the previous manifest hash before deletion.
5. A corrupt or unknown-version manifest is never trusted for deletion.
6. A failed fix, format, or build step does not write a success manifest.
7. Watch mode starts after the generation lock is released.
- OpenAPI 解析和业务输出绑定太深。
- nullable / anyOf / wrapper 规则分散在多个模板与解析阶段。
- 很难为未来 `koi_blueprint_flutter` 抽出复用 IR。
build_runner owns `.g.dart` and `.freezed.dart` files; they are not included in
this generator's stale-file manifest.
所以这里直接重新收口:
## Response wrappers
- parser core 面向 OpenAPI 语义
- Koi 输出作为 renderer/preset
- 兼容旧配置,但不延续旧架构负担
The parser does not hard-code `BaseResult`:
## 6. 近期路线
- `passthrough` returns the OpenAPI response schema;
- `external_generic` detects configured success envelopes and returns
`ExternalWrapper<Inner>`.
- 第一阶段:打通 Koi/FastAPI 场景(已完成)
- 第二阶段:补齐 multipart、discriminator union、preset 基础层(当前版本)
- 第三阶段:扩更强 union fallback、更多 preset,并服务 `koi_blueprint_flutter`
A `default` response remains an error response. `response_mode:
http_response` retains Dio status and headers. Error response models remain in
the IR and generated model set.
## Extension boundaries
The IR/preset/renderer split leaves room for a versioned renderer interface,
template overrides, semantic-diff tooling, and optional runtime adapters.
Those are design directions, not features of the current public release. The
current implementation boundaries are documented in
[Compatibility](COMPATIBILITY.md).
Additional ecosystem comparisons are kept as internal project research and
are not part of the published package.