[openapi-normalizer] fix: apply SIMPLIFY_BOOLEAN_ENUM to OpenAPI 3.1 specs

TE
1d ago
+107-5

Fixes #25287
Related: #25290 fixes the same instanceof problem for SIMPLIFY_ANYOF_STRING_AND_ENUM_STRING.

SIMPLIFY_BOOLEAN_ENUM (on by default) only handled instanceof BooleanSchema. In OpenAPI 3.1 documents, swagger-parser represents boolean schemas as JsonSchema, so type: boolean, enum: [true] was never simplified. Generators then produced string enums, e.g. the rust client emits an enum serialized as "true", which fails to deserialize {"vaccinated": true}.

Changes:

  • New ModelUtils.isSingleTypeSchema(schema, type): true when the schema has exactly the given type (type in 3.0, a single entry in types in 3.1). The same helper is added in #25288 and #25290, identical and in the same place, so the two PRs merge cleanly in either order.
  • processSimplifyBooleanEnum uses isSingleTypeSchema(schema, "boolean") && isEnumSchema(schema) instead of instanceof BooleanSchema. Multi-type 3.1 schemas such as type: [boolean, string] keep their enum, because there it applies to the whole union.
  • normalizeSimpleSchema, the path 3.1 JsonSchema instances take, now calls processSimplifyBooleanEnum after processNormalize31Spec. This covers type: [boolean, "null"] and const: true (which NORMALIZE_31SPEC turns into enum: [true]).

Not covered here: ADD_UNSIGNED_TO_INTEGER_WITH_INVALID_MAX_VALUE relies on instanceof IntegerSchema in the same way (both in the normalizeSchema dispatch and in the rule), so it probably also does nothing for 3.1 specs. SIMPLIFY_ANYOF_STRING_AND_ENUM_STRING has the same pattern (instanceof StringSchema) and is addressed in a separate PR. I kept this PR to the one rule.

Testing:

  • New OpenAPINormalizerTest#testOpenAPINormalizerSimplifyBooleanEnum31Spec with src/test/resources/3_1/simplifyBooleanEnum_test.yaml. It covers enum: [true], type: [boolean, "null"] with an enum, and const: true. It also checks that a string enum and a multi-type type: [boolean, string] enum are left alone. The test fails on master and passes with this change.
  • New ModelUtilsTest#testIsSingleTypeSchema.
  • OpenAPINormalizerTest, DefaultCodegenTest, DefaultGeneratorTest, ModelUtilsTest and RustClientCodegenTest all pass.
  • Regenerating all samples changes no files, since no existing 3.1 sample spec has a boolean enum.

PR checklist

  • Read the contribution guidelines.
  • Run the following to build the project and update samples:
    ./mvnw clean package || exit
    ./bin/generate-samples.sh ./bin/configs/*.yaml || exit
    ./bin/utils/export_docs_generators.sh || exit
    
    (For Windows users, please run the script in WSL)
    Commit all changed files.
    This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master.
    These must match the expectations made by your contribution.
    You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example ./bin/generate-samples.sh bin/configs/java*.
    IMPORTANT: Do NOT purge/delete any folders/files (e.g. tests) when regenerating the samples as manually written tests may be removed.
    Ran ./bin/generate-samples.sh ./bin/configs/*.yaml (all configs); no sample files changed. export_docs_generators.sh was not needed because no generator options changed.
  • If your PR is targeting a particular programming language, @mention the technical committee members, so they are more likely to review the pull request.

This is a core normalizer fix; it was found through the rust generators.
cc @frol @farcaller @richardwhiuk @paladinzh @jacob-pro @dsteeley


Summary by cubic

Fixes SIMPLIFY_BOOLEAN_ENUM so it applies to OpenAPI 3.1 specs, where boolean schemas are parsed as JsonSchema instead of BooleanSchema. Previously, boolean enums like type: boolean, enum: [true] were left untouched, and generators produced string enums (e.g., the rust client serialized "true"), which failed to deserialize booleans.

Bug Fixes

  • Uses ModelUtils.isSingleTypeSchema instead of instanceof BooleanSchema, and only simplifies single-type boolean schemas.
  • Runs processSimplifyBooleanEnum from normalizeSimpleSchema, the path 3.1 JsonSchema instances take, covering type: [boolean, "null"] and const: true (after NORMALIZE_31SPEC turns const into enum).
  • Leaves multi-type schemas like type: [boolean, string] untouched, and adds testOpenAPINormalizerSimplifyBooleanEnum31Spec covering all cases.