[core] fix: SIMPLIFY_ANYOF_STRING_AND_ENUM_STRING for OpenAPI 3.1 specs

TE
1d ago
+147-6

Fixes #25289
Related: #25288 fixes the same instanceof problem for SIMPLIFY_BOOLEAN_ENUM.

SIMPLIFY_ANYOF_STRING_AND_ENUM_STRING used instanceof StringSchema to find the string and enum branches of an anyOf. In 3.1 specs the parser creates JsonSchema instances, so the rule never applied and open enums such as anyOf: [{type: string}, {type: string, enum: [a, b]}] were not simplified.

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.
  • processSimplifyAnyOfStringAndEnumString checks both branches with isSingleTypeSchema(s, "string") instead of instanceof StringSchema, and uses isEnum.
  • The rule now simplifies only when exactly one branch has an enum. If both branches are string enums, the anyOf is left unchanged. Before, it collapsed to the first branch and dropped the values of the second. This also changes 3.0, but only when SIMPLIFY_ONEOF_ANYOF_ENUM is disabled, since that rule runs first and merges both enums by default.
  • The unneeded (StringSchema) casts are removed (getEnum() is on Schema).

Not covered:

  • 3.1 type arrays such as ["string", "null"] are still not simplified. Stripping null first is a job for NORMALIZE_31SPEC.
  • format is not considered. In 3.0 this is a small change: a string branch with a format such as uuid or date (parsed as a StringSchema subclass other than StringSchema itself) used to be skipped and is now treated like any other string branch, the same as in 3.1. The rule is opt-in and no sample enables it.
  • In 3.1, SIMPLIFY_ONEOF_ANYOF_ENUM does not merge an anyOf of two string enums yet (EnumUtils compares against getType(), which is null for JsonSchema). With this change that anyOf is kept as is rather than losing values.
  • The rule stays opt-in. No default changes.

Testing:

  • New OpenAPINormalizerTest#testOpenAPINormalizerSimplifyAnyOfStringAndEnumString31Spec with a new spec src/test/resources/3_1/simplifyAnyOfStringAndEnumString_test.yaml. It covers a $ref enum branch, an inline enum branch, and an anyOf of two non-enum strings, which must stay unchanged. The test fails on master and passes with this change.
  • New OpenAPINormalizerTest#testOpenAPINormalizerSimplifyAnyOfStringAndEnumStringKeepsTwoEnums31Spec: an anyOf of two string enums must keep both branches. It fails without the both-enum guard.
  • New ModelUtilsTest#testIsSingleTypeSchema.
  • OpenAPINormalizerTest (74 tests) and the cppboostbeast tests (whose normalizer overrides this method) pass.
  • No sample config enables this rule, so sample output is unchanged. All configs were regenerated with no changes.

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.
    Regenerated all configs with ./bin/generate-samples.sh ./bin/configs/*.yaml. Nothing changed. export_docs_generators.sh was not needed (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.

cc @frol @farcaller @richardwhiuk @paladinzh @jacob-pro @dsteeley


Summary by cubic

Fixes the SIMPLIFY_ANYOF_STRING_AND_ENUM_STRING normalizer rule so it also simplifies open string enums in OpenAPI 3.1 specs.

The rule detected string/enum branches with instanceof StringSchema, but the 3.1 parser builds JsonSchema instances, so the rule silently never ran on 3.1 specs. A new helper, ModelUtils.isSingleTypeSchema, accepts a JsonSchema whose types is exactly ["string"] while keeping the 3.0 StringSchema check unchanged, and the now-unneeded (StringSchema) casts were removed since getEnum() lives on Schema. The rule now only simplifies when exactly one branch has an enum; an anyOf of two string enums stays intact instead of collapsing to the first branch and dropping values. The rule stays opt-in.

Known limitations:

  • 3.1 type arrays such as ["string", "null"] remain unsimplified; stripping null is left to NORMALIZE_31SPEC.
  • 3.0 schemas with a format still don't match (the parser creates a non-StringSchema subclass), and the 3.1 check ignores format.