dart_openapi_generator 0.3.0
dart_openapi_generator: ^0.3.0 copied to clipboard
Native-Dart, OpenAPI 3.x → Dart code generator. Generates idiomatic Dio-based model classes and API client services via build_runner. No JVM. No Mustache. Generated code looks hand-written.
Changelog #
All notable changes to dart_openapi_generator will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
0.3.0 - 2026-08-18 #
Fixed #
oneOfbranches that are$refs to component schemas are no longer stolen from the rest of the spec. AoneOfvariant was assumed to be a type dedicated to that one union, so every variant was emitted inside the wrapper's file and skipped in the standalone-file loop. For a$refbranch that assumption is wrong:#/components/schemas/Customeris a shared component, and other schemas reference it. The result was thatmodels/customer.dartwas never written while every model with a$ref: Customerproperty still imported it (uri_does_not_exist,undefined_class), and that a component used by two unions — the request and the response of one endpoint, typically — was emitted twice with two different supertypes (ambiguous_exporton the barrel). A component is now emitted exactly once and reused: the reproduction from the bug report goes from 5 analysis errors to none.- Anonymous
oneOfbranches no longer crash the generator. An inline (non-$ref) variant was looked up in the name registry under a synthesised name that was never registered, throwingStateError. Those names are registered now, which also gives them the usual duplicate-name detection. - Top-level
typedef Name = List<Item>;schemas now import their item type. The alias was emitted without the import it needs. - Path parameters that are not strings no longer break compilation.
Uri.encodeComponenttakes aString, but the generated service passed the parameter as-is for everything except enums, so{ "type": "integer" }on a path parameter emittedUri.encodeComponent(id)and failed withargument_type_not_assignable. The Dart type was always correct — only the URL serialization was missing. The only workaround was to declare the parameter as a string in the spec. Non-string parameters are now converted (id.toString()), and generated types serialize throughtoJson()first so their wire value is used. String parameters are emitted exactly as before. An array path parameter converts as well but warns, since OpenAPIstyle/explodeare not implemented. - Synthesised
oneOfvariant class names no longer collide with a real component schema. The class generated for a non-object branch is named<Wrapper><TypeSuffix>(e.g.FooString); a spec that also declares a component with that exact name produced two classes with the same name in two files —ambiguous_exporton the barrel, the very failure this release fixes. The synthesised name now yields and is disambiguated automatically (FooString2), so the user's component always keeps its name. - A
oneOfbranch whose$refcannot be resolved to a component schema no longer crashes the build. It used to reach the name registry unregistered and throw a rawStateErrorwith an internal message. The branch is now skipped with a warning naming the unresolved target, and the rest of the union is still generated. - A list response whose items are a polymorphic
oneOfno longer throws at runtime. Each item was decoded withWrapper.fromJson(e as Map<String, dynamic>); an element matching the union's non-object branch — the only reason that branch exists — failed the cast. The cast is gone; the generatedfromJsonalready acceptsObject?.
Changed (breaking) #
- A
oneOfwrapper and its$refbranches are emitted into the same file, and the branchesimplementsthe wrapper instead of extending it. Dart only allows asealedtype to be implemented from within its own library, and asealedwrapper is what makesswitchexhaustiveness work. Keeping both properties — reusable standalone branches and an exhaustive union — requires them to share a library. Two unions that share a branch are therefore emitted together. Consequences for existing code: pattern matching (case Circle c:) is unaffected, but a class that used toextendits wrapper now implements it, and models move between files. Class names never change, so import the generated barrel (generated.dart) rather than individual model files; the internal file layout is a function of the spec'soneOfgraph and is not part of the public contract. oneOfwithout adiscriminatornow decodes instead of throwing.fromJsonused to throwUnimplementedErrorunconditionally, which made those endpoints undecodable. It now tries the variants and returns the first that decodes, ordered most-specific-first — by number of required properties, then declared properties, then spec order. That order matters:Customer{id}is a subset ofDriver{id, license}, and trying the narrow variant first would swallow every payload of the wide one. Two variants that accept the same payload remain genuinely ambiguous — add adiscriminator.propertyNameto decide explicitly.- Path and query parameters with
format: date-timeare now typedDateTime, notString. The models already mapped that format toDateTime, so the same schema had two different Dart types depending on where it appeared. Parameters are now serialized withtoIso8601String()before URL encoding. Known limitation: thedate_time_converter: timestampoption is not yet honoured on parameters — it applies to model properties only.
Changed #
- The non-200 primary-response warning is aggregated. One warning per operation ("using 201 as primary response") produced dozens of lines on a large spec, for behaviour that is correct and needs no attention. A single line now reports the count and the operations concerned.
- Object-typed path parameters warn. They were interpolated as
toJson().toString(), writing a Dart map literal into the URL; OpenAPIstyle/explodeserialization is not implemented, so the case is now flagged like the array one. - A discriminator
mappingpointing at an unregistered schema raises a readableOpenApiParseExceptioninstead of a rawStateError. - Generated unions without a discriminator carry their own dartdoc, stating the order in which variants are tried, that a variant's parse errors are swallowed, and that
discriminator.propertyNameis the way to decide explicitly. Previously that contract lived only in this changelog. - A collapsed single-branch
oneOfkeeps the wrapper'sdescriptionwhen the branch is inline and declares none of its own.
Added #
oneOfbranches that are not objects are supported. Array, primitive and enum branches used to be dropped with a warning, leaving the union without a case for them. They are now emitted as a generated class holding the decodedvalue(e.g.final class GetPointsResponsePointList extends GetPointsResponse { final List<Point> value; }). A union with such a branch cannot promise a JSON object, so itstoJson/fromJsonwiden toObject?and the service call site stops typing the Dio response — unions of objects keep the narrowerMap<String, dynamic>signature and are unchanged.- A single-branch
oneOfwritten inline is collapsed to its branch.{ oneOf: [ { type: string } ] }on a path parameter is aString; it no longer produces asealedwrapper with one case. A named single-branchoneOfis left alone — collapsing it would make the component disappear.
0.2.0 - 2026-07-19 #
Added #
- The barrel file is now named after
output_dir's last path segment instead of a hardcodedgenerated.dart— e.g.output_dir: "lib/services/network/petstore_client"producespetstore_client.dart. Keeps multiple generated clients in the same app collision-free.
Changed #
- Repository flattened:
packages/dart_openapi_generator/no longer exists — the package now lives at the repo root (lib/,test/,build.yaml,pubspec.yaml). The repo root is both the published package and the pub workspace root, withexample/as its only member.
Fixed #
- Single-pass
build_runner build. The builder used to write generated files via rawdart:io, invisible to build_runner's asset graph — a downstream builder (e.g. a consumer's ownsource_gen-based builder importing a generated type) could never resolve them on the first build, only on a second, identical run. The builder factory now computes the spec's exact output file list synchronously, before build_runner freezes its asset graph, andbuild()writes viabuildStep.writeAsStringagainst those declared paths — so generated files are tracked like any other asset and resolve correctly in a single pass.
Changed (breaking) #
dart_openapi_generator_annotationsis removed. There is no more@OpenApiGeneratorDart annotation — configuration (input_spec,output_dir,client_name,date_time_converter,debug_logging) now lives in theoptions:block of your project'sbuild.yaml. This is a direct consequence of the single-pass fix: config must be readable synchronously in the builder factory, before any Dart element resolution is possible.RemoteSpec(HTTPS spec URLs) is removed. Only local spec files are supported — the builder factory reads the spec synchronously, and there is no synchronous HTTP client in Dart.- The bespoke spec-unchanged cache (
CacheManager,skipIfSpecIsUnchanged,cachePath) is removed. build_runner's own incremental build engine (asset digests) now does this job for free, correctly, once output is tracked in its asset graph — the manual MD5/manifest cache only existed to work around the old dart:io write. - Manual
cleanOutput/manifest-based deletion is removed. Stale-output cleanup is handled by build_runner's own tracked-asset lifecycle and--delete-conflicting-outputs. dart_openapi_generatorno longer depends onanalyzer,source_gen,http, orcrypto— none of those are needed once annotation discovery and the HTTP-basedRemoteSpecloader are gone. This removes the package's only major-version-churn-prone dependency (analyzer).
0.1.1 - 2026-06-09 #
Fixed #
hashCodegeneration: useObject.hashAllwhen a model has more than 20 fields —Object.hashaccepts at most 20 positional arguments_isGeneratedType: anonymous inline schemas (name == null) no longer treated as generated types, preventing spurious import references in generated code- Enum path parameters: serialize via
.toJson().toString()beforeUri.encodeComponentto emit the correct wire value - Parser: empty
schema: {}in request body or response content now yieldsnullinstead of a broken anonymous schema, so the generator correctly emitsFuture<void>