Complete OpenAPI 3.0/3.1 generation, diagnostics, lifecycle safety, enum and naming mappings, bilingual documentation, examples, and pub.dev release validation.
173 lines
6.7 KiB
Markdown
173 lines
6.7 KiB
Markdown
# Architecture
|
|
|
|
[English](ARCHITECTURE.md) | [Chinese (Simplified)](zh-CN/ARCHITECTURE.md)
|
|
|
|
The generation pipeline is deliberately split into stages:
|
|
|
|
```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.
|
|
|
|
## Configuration
|
|
|
|
`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.
|
|
|
|
Unknown keys produce warnings. Invalid types and incomplete combinations
|
|
produce errors. See [Configuration](CONFIGURATION.md).
|
|
|
|
## Loader, resolver, and merger
|
|
|
|
For each root source, `OpenApiReferenceResolver`:
|
|
|
|
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.
|
|
|
|
`OpenApiDocumentMerger` then merges roots in configuration order:
|
|
|
|
- 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.
|
|
|
|
The parser should receive only root-document JSON Pointers. A remaining
|
|
external reference is treated as a resolver error.
|
|
|
|
## Validation and parsing
|
|
|
|
`OpenApiDocumentParser` handles:
|
|
|
|
- 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.
|
|
|
|
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.
|
|
|
|
## Intermediate representation
|
|
|
|
`lib/src/ir/ir.dart` is the parser/renderer contract. Its main nodes include:
|
|
|
|
- `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.
|
|
|
|
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
|
|
|
|
The current `koi_flutter` preset targets Dio, Retrofit, Freezed, and
|
|
json_serializable.
|
|
|
|
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.
|
|
|
|
The renderer does not load references or decide whether an OpenAPI structure
|
|
is valid.
|
|
|
|
## Generation lifecycle
|
|
|
|
Before writing, `GenerationLifecycle` calculates:
|
|
|
|
- the canonical merged-spec SHA-256;
|
|
- the normalized configuration SHA-256;
|
|
- each rendered path and content SHA-256.
|
|
|
|
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.
|
|
|
|
Safety invariants:
|
|
|
|
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.
|
|
|
|
build_runner owns `.g.dart` and `.freezed.dart` files; they are not included in
|
|
this generator's stale-file manifest.
|
|
|
|
## Response wrappers
|
|
|
|
The parser does not hard-code `BaseResult`:
|
|
|
|
- `passthrough` returns the OpenAPI response schema;
|
|
- `external_generic` detects configured success envelopes and returns
|
|
`ExternalWrapper<Inner>`.
|
|
|
|
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.
|