Skip to content

generate-openapi-client silently emits an all-any client: pinned generator 6.2.1 cannot read the now-3.1 spec #130

Description

@Kyzgor
  • I'm submitting a ...
    [x] bug report
    [ ] feature request
    [ ] question about the decisions made in the repository
    [ ] question about how to use this project

  • Summary

Running yarn generate-openapi-client today no longer regenerates the typed client; it silently produces an all-any one. The pinned generator (6.2.1) predates OpenAPI 3.1, but https://api.permit.io/v2/openapi.json is now 3.1.0, so 6.2.1 fails to resolve most schemas and emits untyped output. Because the script passes --skip-validate-spec, the run still exits 0. The committed src/openapi/ is clean and typed, but it can't be reproduced from the live spec anymore, so new schema fields (role inheritance via RoleCreate.extends, and anything else added recently) can't be regenerated cleanly.

  • Other information (e.g. detailed explanation, stack traces, related issues, suggestions how to fix, links for us to have context, eg. StackOverflow, personal fork, etc.)

Reproduction (clean checkout, yarn install, then yarn generate-openapi-client):

The pin is 6.2.1:

// openapitools.json
"generator-cli": { "version": "6.2.1" }

and the script targets the live spec with validation off:

openapi-generator-cli generate -i https://api.permit.io/v2/openapi.json -g typescript-axios ... --skip-validate-spec

The live spec declares "openapi": "3.1.0", which 6.2.1's parser can't read. The run logs 82 resolver errors across 15 distinct schemas, all with the same Jackson root cause:

[main] ERROR i.s.v.p.reference.ReferenceVisitor - Error resolving schema #/components/schemas/ResourceRead
...
java.lang.IllegalArgumentException: Cannot deserialize value of type `java.lang.Boolean` from Object value (token `JsonToken.START_OBJECT`)

Affected schemas include ResourceRead, ResourceCreate, ResourceReplace, ResourceUpdate, ElementsConfigRead/Create/Update, MappingRule(Update), and the OPAL data models. With --skip-validate-spec set, the generator exits 0 and writes files anyway, but every member that depended on an unresolved schema degrades to any. A fresh regen degrades 247 of 319 generated type files to all-any.

RoleCreate shows the failure cleanly. The two blocks below are the same live spec fed to 6.2.1, differing only in the top-level openapi version string (so they isolate the version bug from any unrelated drift; keys are quoted because the script later runs yarn fix:prettier). With the version header rewritten to 3.0.3, 6.2.1 types every property correctly:

export interface RoleCreate {
  'key': string;
  'name': string;
  'description'?: string;
  'permissions'?: Array<string>;
  'attributes'?: object;
  'extends'?: Array<string>;
  'granted_to'?: GrantedTo;
  'v1compat_settings'?: object;
  'v1compat_attributes'?: object;
  'v1compat_is_built_in'?: boolean;
}

Left at 3.1.0, the byte-identical content collapses to any under an index signature, exit 0:

export interface RoleCreate {
  [key: string]: any;

  'key': any;
  'name': any;
  'description'?: any;
  'permissions'?: any;
  'attributes'?: any;
  'extends'?: any;
  'granted_to'?: any;
  'v1compat_settings'?: any;
  'v1compat_attributes'?: any;
  'v1compat_is_built_in'?: any;
}

(The committed src/openapi/types/role-create.ts has the typed shape but only 6 of these properties, with no extends. It was generated against an older 3.0 spec, before extends and the v1compat_* fields were added, which is the regeneration gap this issue is about.)

Root cause

The generator is pinned to 6.2.1, which predates OpenAPI 3.1. The spec moved from 3.0 to 3.1, so 6.2.1 can't model it. The Boolean from Object resolver errors are the visible symptom but not the main driver: a clean schema that logs no resolver error still degrades. A minimal one-property 3.1 spec with no $ref (or RoleCreate itself) still turns every property into any; the codegen receives an effectively empty schema for it, while the byte-identical 3.0.3 content types correctly. So 6.2.1 does not recover a usable type from the 2020-12 representation. (The exact internal accessor that drops the type is a likely-but-unconfirmed mechanism; the observable above is what I verified.) --skip-validate-spec then turns what should be a hard failure into a silent exit 0. The committed src/openapi/ was generated by 6.2.1 against an earlier 3.0 spec, which is why it's still clean, but that path can't be reproduced today.

Options (directions to weigh, not a recommendation; I'd implement whichever you prefer)

A. Downconvert the spec to 3.0 before generation, keep 6.2.1. Rewrite the top-level openapi header from 3.1.0 to 3.0.3 ahead of the unchanged generator. Smallest change, and it restores typed output for the spec as it is today (verified: the current spec re-declared 3.0.3 produces 0 all-any). Caveat, verified locally: this only changes the version string, it does not transform 3.1 / JSON-Schema-2020-12 constructs. A property typed ["string","null"] (nullable union), a const, or an array with items: {type: ["string","null"]} still degrades to any (exit 0) after the downconvert. So it's a minimal bridge that holds only while the spec stays 3.0-representable; the moment a genuinely-3.1 type lands, it silently regresses to the same any.

B. Move to a 3.1-capable generator (e.g. 7.12.0). This is the only lever that models 3.1 natively, so it closes the failure for the constructs above instead of relying on the spec staying 3.0-shaped. Not every 7.x is usable: 7.1.0 and 7.5.0 both fail with a NullPointerException (7.1.0 writes a partial, still-mistyped client, e.g. RoleCreate.key: any, before exiting non-zero; 7.5.0 exits non-zero and writes nothing). 7.12.0 is the first that types the client. Trade-offs: a 6.x to 7.x bump changes some typescript-axios output (worth a careful diff of the regenerated client), and 7.12.0 has a small residual of its own, 2 of 350 types still come out all-any (derived-role-rule-create, derived-role-block-edit), a separate and narrower matter.

C. Add a codegen check in CI, whichever lever you pick. Regenerate the client and assert it still carries named types (no [key: string]: any). This is the gap that kept the breakage invisible: nothing runs generate-openapi-client in CI today, and an all-any client compiles and lints clean (strict doesn't flag explicit any). Two things make it a real guard rather than a snapshot check. Run it against the live spec (or a fixture you refresh on a schedule) so it can see drift, and note it needs the Java / openapi-generator toolchain available in CI.

On --skip-validate-spec: dropping it would surface the failure, but heads-up that the served spec also has two unrelated authoring issues, a duplicate Bulk Operations tag and a tuple-form (array) items nested in OPALUpdateCallback that isn't valid OpenAPI 3.0, so validation currently fails fast on those too. The codegen check above is a cleaner fail-loud signal.

Separately, regenerating against the live spec also surfaces drift since the committed client was last built (e.g. src/api/elements.ts references an AuthenticationApi the live API no longer exposes), so a full re-baseline is probably its own change rather than part of this fix.

Happy to send a PR for whichever direction you prefer.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions