degenerate 0.5.0
degenerate: ^0.5.0 copied to clipboard
Generate typesafe Dart API clients from OpenAPI 3.x specs. Produces idiomatic models, serialization, and HTTP clients with pluggable adapters.
Changelog #
Important user-facing changes only. For full details see commit log.
0.5.0 #
Presence-aware models, lossless overlapping unions, directional schemas, complete response types, and streaming middleware.
Upgrade the generator, degenerate_runtime, and your HTTP adapter together to
0.5.x, then regenerate your client. The generator requires Dart 3.11.1 or later;
generated clients and runtime packages require Dart 3.8.0 or later. See the
migration guide
for the upgrade steps and examples.
Breaking changes #
- Optional nullable fields are now
Omittable<T?>(#21): an optional field whose spec type is nullable (nullable: true/type: [T, 'null']) has three legal wire states — absent, explicit null, value — which a plainT?cannot express. Those fields now generate asOmittable<T?>(new indegenerate_runtime), defaulting toOmittable.absent():- Construct with
Omittable(value); clear withOmittable(null); omit by leaving the parameter out. - Read with
field.value/field.isPresent;fromJson/toJsonround-trip all three states exactly. copyWithtakes theOmittabledirectly for these fields instead of a thunk.- Migration:
model.field→model.field.value,copyWith(field: () => v)→copyWith(field: Omittable(v)). Or pass--omittable=offto restore the previous plain-T?output (Dartnullserializes as omitted). - Review comparisons, null checks, interpolation, and arguments passed to
dynamicAPIs even if they still compile. Wrappers must be unwrapped before using their payloads. - Preserve omit-on-null behavior in existing request helpers with
value == null ? const Omittable.absent() : Omittable(value). Unconditionally wrapping a nullable argument sends an explicit null instead of omitting the field. --omittable=alladditionally wraps optional non-nullable fields, for servers that accept null-clears the spec doesn't declare.- Generated code now requires the matching
degenerate_runtimeversion (which addsOmittable). - Fields declared directly on discriminated-union variants follow the same presence rules.
- Construct with
- Schema defaults no longer imply presence: optional defaulted fields remain nullable or omittable and absent fields stay absent in
toJson(). ReadfieldOrDefaultfor the declared fallback; required fields remain required even when they declare defaults. anyOfuses lossless classes: replace single-variant.a()/.b()construction with named, omittable typed views. All matching views are available, decoded JSON round-trips unchanged, and unknown shapes are retained. Caller-created views merge on serialization and reject conflicting values.- Request and response schemas respect
readOnly/writeOnly: affected operation types useNameRequest/NameResponsemodels, including nested and recursive references. Migrate request construction to the generated request type and response annotations to the response type. - Named response variants (#5): operations with multiple response shapes return sealed success/error classes with typed variants per status and media type. Body-or-empty responses have payload-free variants for empty statuses. Operations with one shape keep plain
ApiResult<T, E>. Match variants such asPostAuthSuccess200(:final data)instead of inspectingOneOf.value; anUnknownvariant retains the full response when no declared status or default applies. Decoding respects exact statuses, ranges, and content types. Declared2xxresponses determine success types;defaultsupplies fallback errors and contributes to success only when no2xxis declared. - Inline response model names include status codes: the primary success keeps
PostAuthResponse; additional statuses use names such asPostAuthResponse201andPostAuthResponseDefaultinstead ofPostAuthResponse2andPostAuthResponsedefault. Update explicit type annotations after regeneration. - Interceptors return
StreamedApiResponse:Handler, custominterceptmethods, andretryWhencallbacks now use the streamed response. Status-based auth refresh, retry, logging, and cached responses work for streaming operations. Wrap buffered cached responses withStreamedApiResponse.fromResponse; body inspection consumes the stream and requires wrapping the buffered response before forwarding. - Previously dropped schema details now appear in generated types (#14, #16):
allOfmodels retain inherited and overridden properties and required fields; partial discriminator mappings retain unmapped variants; inline enum names avoid collisions. Collections preserve nullable item types in fields,copyWith, and API results. Regeneration can therefore add required constructor arguments, union variants, and nullable type arguments.
Bug fixes #
- Nullable collection items decode correctly (#14, thanks @josher8a): lists and maps containing null items no longer throw during
fromJson, including API response payloads. - Parameter values use their wire formats (#19): dates use ISO 8601, bytes use base64, and durations use milliseconds. Object and array query parameters respect their style and explode settings; simple-style path parameters encode values while preserving separators. Nullable parameters, fields, and collection items are guarded before conversion.
- Spec text is escaped throughout generated source (#15, #18, thanks @josher8a): quotes, dollar signs, backslashes, and line breaks in paths, descriptions, field names, media types, and discriminator/security metadata no longer corrupt Dart literals or escape comments into source code.
- Schema composition preserves data (#16): nested
allOfchains and variant-specific fields survive merging, explicit discriminator mappings keep the remaining implicit variants, and conflicting mappings emit diagnostics for unreachable variants. - Stable additional-properties hashes: equal overflow maps hash equally regardless of insertion order, and repeated reads of an unchanged model's hash remain stable.
- Recursive
OneOfserialization: collection variants serialize nested models, enums, dates, URIs, big integers, durations, and bytes through their wire codecs. - JSON string responses decode their JSON value, removing the surrounding wire quotes.
- Object query and path parameters unwrap omittable fields: serializers send scalar values and omit absent or null fields across query styles and path explode modes.
- Nullable binary fields serialize as base64: optional, required nullable, and collection element byte values encode correctly, including explicit nulls.
- Schemas named
Omittableavoid runtime name collisions: generated model names receive a suffix, with references updated consistently. - Required nullable fields always serialize their key (#21): a field that is both
requiredand nullable (nullable: true/type: [T, 'null']) dropped its key fromtoJson()when the value was null. A required field must always be present on the wire, so it now serializes as an explicit"field": null. canParseaccepts explicit null for required nullable fields: a payload like{"mode": null}failedcanParseeven when the spec declaresmoderequired and nullable; the generated type check now tolerates null for nullable types.- Binary fields compare by content:
Uint8List(format: binary/byte) fields used identity==/hashCode, so byte-identical models compared unequal. They now uselistEquals/Object.hashAlllike list fields.
Improvements #
- Updated code emission: upgraded to
code_builder4.12 and its structured control-flow builders for response dispatch. Raw generated output may have spacing changes; rundart formaton the generated directory to apply your project's formatting preferences.
0.4.2 #
Bug fixes for enum parameter serialization, generated security helpers, and union parsing; anyOf + discriminator support.
Bug fixes #
- Enum path parameters interpolate their wire value (#7): a path parameter whose schema is a
$refstring enum produced debug strings likeColor(red)in request URLs. Path interpolation now uses the enum's underlying value, and every other parameter serialization site (query, header, cookie, deepObject fields, list items, multipart text fields) was hardened so non-string enums and non-string-backed extension types no longer generate uncompilable code. - apiKey schemes missing
inornameno longer emit uncompilable code (#8): the generator defaults a missingnameto the scheme key and a missing/invalidintoheader(each with a CLI warning), and thewith<Scheme>facade helper is only emitted when its matchingapply<Scheme>method exists. - Security requirement lists are explicitly typed (#9): operations opting out via
security: []emitted an untyped[]inferred asList<dynamic>; all requirement lists now carry an explicit<ApiSecurityRequirement>type argument. OneOf.parsefalls through onError, not justException(#10, thanks @josher8a): a variant'sfromJsonthat discriminates by casting (e.g.json['x'] as String) throws aTypeErrorwhen the payload is a different variant; trial-parsing previously aborted instead of trying the next variant.- Union variant names no longer collide with their referenced model: when the derived variant name
<UnionName><PascalCase(value)>matches the referenced schema's own class name (e.g. OpenAI's Realtime events), the wrapper now uses a$separator (RealtimeClientEvent$SessionUpdate), fixing ambiguous barrel exports and a self-recursivefromJson.
Improvements #
anyOf+discriminatorgenerates sealed unions (#10, thanks @josher8a): schemas usinganyOfwith a discriminator (allowed by the OpenAPI spec) now lower to the same sealed-class hierarchy asoneOfwhen every variant is a$ref, instead of an untaggedOneOfN. Affected schemas in regenerated clients change shape accordingly (e.g. OpenAI'sResponsesClientEvent).
0.4.1 #
Generated code is now lint-clean under very_good_analysis.
Breaking changes #
OneOf.parsecallback signature: callback parameter type changed fromObject?toObject(withjson!assertion in runtime). Callers passing nullable values to parse callbacks will need to add a!assertion.@immutableannotation: re-exported fromdegenerate_runtime. Generated model, enum, and union variant classes are now annotated@immutable.
Improvements #
- Lint-clean generated output: generated Dart code now passes
dart analyzewith zero issues undervery_good_analysisfor all tested specs (Stripe, GitHub, Cloudflare, Twilio, etc.).- Constructors ordered before fields/methods (
sort_constructors_first) - Required named parameters before optional (
always_put_required_named_parameters_first) - Files end with exactly one trailing newline (
eol_at_end_of_file) - Imports/exports sorted alphabetically (
directives_ordering) - Security scheme literals omit redundant default values and use
const(avoid_redundant_argument_values,prefer_const_constructors) - Raw strings and double-quoted strings used to avoid unnecessary escapes (
use_raw_strings,avoid_escaping_inner_quotes) - Identity map transforms like
.map((k, v) => MapEntry(k, v))are elided (unnecessary_lambdas) - Simple closures like
(e) => base64Encode(e)emit tearoffs (unnecessary_lambdas) - Empty model
fromJsonuses_for unused parameter andconstreturn (avoid_unused_constructor_parameters,prefer_const_constructors) - Doc comment
[references]escaped to avoid false Dart doc links (comment_references) - Fenced code blocks in doc comments get a language specifier (
missing_code_block_language_in_doc_comment) - Form-urlencoded body list literals are explicitly typed (
inference_failure_on_collection_literal) - Untagged union
fromJsoncasts accessor toMap<String, dynamic>for object variants (argument_type_not_assignable) - Extension type multipart serialization skips redundant
.toString()whentoJson()already returnsString(noop_primitive_operations)
- Constructors ordered before fields/methods (
0.4.0 #
Critical fixes for discriminated unions, streaming, and runtime ergonomics.
Breaking changes #
OneOftypes simplified: the sealed subclass hierarchy (OneOf2A,OneOf2B, etc.) is replaced by a singlefinal classper arity. Code usingis OneOf2Achecks must switch to matching on.valuetype instead. Named constructors.a(),.b(),.c()etc. provide concise construction with Dart's implicit context syntax (e.g.model: .b(.gpt4oMini)).
Bug fixes #
- Discriminator enum value extraction: inferred discriminator mappings now extract the actual enum value from referenced schemas (e.g.
'system'fromrole: enum: [system]) instead of using schema names. Fixes OpenAIChatCompletionRequestMessagedispatch and similar patterns. - Streaming interceptor bypass:
executeStreamingandexecuteJsonlStreamingnow run through the full interceptor chain (auth, retry, logging) before sending. Previously interceptors were completely bypassed for streaming requests, causing silent auth failures. - Mid-stream error wrapping: network errors during SSE/JSONL streaming (SocketException, etc.) are now wrapped in
ApiStreamErrorinstead of propagating as raw platform exceptions.
Improvements #
- OneOf variant deduplication: inline unions with duplicate variant types (e.g.
OneOf3<A, A, B>) are collapsed to remove redundant variants (OneOf2<A, B>). - Shorter discriminated union variant names: variant class names now use the discriminator enum value (
ChatCompletionRequestMessageSystem) instead of the full schema name (ChatCompletionRequestMessageChatCompletionRequestSystemMessage). - Added
example.mdwith real-world usage patterns (OpenAI, streaming, Riverpod, envelope unwrapping).
0.3.0 #
Major feature release: allOf composition, additionalProperties, streaming, and OAS 3.2 support.
New features #
additionalPropertiessupport: objects withadditionalPropertiesnow generate a typed overflowMap<String, T>field. Extra keys are preserved during fromJson/toJson round-trips instead of being silently dropped. Supports typed values, dynamic, and recursive self-references.allOfcomposition:allOfschemas that combine a$refwith extra inline properties now produce a merged object with all fields from both the ref target and the inline schema, instead of silently dropping the extra properties.--unwrap-fieldsCLI option: unwrap response envelopes by extracting a named field.--unwrap-fields=resultreturns theresulttype directly instead of the full{errors, messages, success, result}envelope — matching what Stainless generates for Cloudflare SDKs.- OAS 3.2
querymethod: the HTTP QUERY method (safe method with request body) is now a first-class operation. - OAS 3.2
additionalOperations: custom HTTP methods (COPY, LOCK, PURGE, etc.) via the path item'sadditionalOperationsmap. - Typed streaming via
itemSchema: SSE and JSONL responses withitemSchemanow returnStream<T>with typed deserialization instead ofvoid. - JSONL streaming:
application/jsonlandapplication/x-ndjsonmedia types are now supported with a dedicatedexecuteJsonlStreamingruntime method. - Integer and number enums: enum schemas with
type: integerortype: numbernow generate typed enums withint/doublevalues, not just string enums. content-based parameter schemas: parameters using the OAS 3.xcontentfield (instead ofschema) now have their schema correctly extracted.- OAuth2
deviceAuthorization: the device authorization flow type anddeviceAuthorizationUrlare now supported.
Bug fixes #
- String literal escaping:
$,\n,\r,\t, and Unicode bidi/zero-width characters are now properly escaped in generated string literals. - Object member name conflicts: fields and methods named
toString,hashCode,runtimeType, ornoSuchMethodare prefixed with$to avoid conflicts withObjectmembers. - Discriminator mapping keys: inferred
oneOfdiscriminator mappings now use the original schema names as switch keys, not the Dart type names. - Discriminator
toJsoncollision: union varianttoJsonspreads the variant first and sets the discriminator key last, so the discriminator always wins over fields with the same name. type: "null"handling:type: "null"now maps to nullabledynamicinstead ofString.canParsefor optional-only models: models with no required fields now check for at least one known property key instead of returningtrueunconditionally.- Latin character transliteration: accented characters are transliterated to ASCII (
café→cafe) instead of being stripped (caf). - Enum casing: leading
+/-before letters in enum values no longer corrupts casing (+NaN→naN, notNaN). - Query/header parameter key escaping:
$and special characters in parameter names are properly escaped in generated map key strings.
0.2.1 #
- Fix required-but-nullable query parameters generating invalid assignments (
String?assigned toMap<String, String>) - Add totem-web.json to snapshot testing
0.2.0 #
Breaking changes to CLI defaults and output structure.
- New default output: generated code goes to
lib/<name>/(waslib/src/generated/). The-nflag sets the package name (default:api_client) and is appended to the-obase directory. - Workspace mode (
--workspace): generates a standalone Dart package withpubspec.yamltopackages/<name>/(customizable with-o). Includesresolution: workspacefor Dart workspaces. - Flattened directory structure: removed the
src/directory from generated output. Models, APIs, client, and barrel file are now siblings (e.g.models/,apis/,client/,<name>.dart). Workspace mode wraps these underlib/. - No pubspec in default mode: the CLI prints the dependencies you need to add to your own
pubspec.yamlafter generation. - Post-generation instructions: the CLI now prints setup instructions (dependencies for default mode, workspace + dependency config for workspace mode).
- Unformatted output: generated code is no longer annotated with
// dart format off. Rundart formaton the output to apply your project's formatting preferences. - Dropped
collectiondependency: replacedListEqualitywith a built-inlistEqualsindegenerate_runtime. One fewer dependency for generated code. - Simplified file headers: generated files now contain only
// GENERATED CODE - DO NOT MODIFY BY HAND.
0.1.5 #
- Fix published package containing
workspacefield in pubspec.yaml, which causeddart run build_runnerfailures for users
0.1.4 #
- Add stdin pipe support (
-i -): generate fromcurl,cat, or any command (e.g.curl https://example.com/spec.json | degenerate -i - -o lib/src/api -n my_api)
0.1.3 #
- Fix runtime crash for required+nullable fields in fromJson (
type 'Null' is not a subtype of type 'String' in type cast) - Fix enum values with special characters (e.g.
[DONE]) generating PascalCase instead of lowerCamelCase
0.1.2 #
- Add GitHub Actions CI
- Add release script (
make release) - Make runtime packages publishable to pub.dev (version constraints instead of path deps)
0.1.1 #
- Fix README image on pub.dev
- Add GitHub Actions CI
- Add release script (
make release) - Make runtime packages publishable to pub.dev (version constraints instead of path deps)
0.1.0 #
- Initial version.