From 0424a02731b30263ffd9eaafe78978d4330ed89a Mon Sep 17 00:00:00 2001 From: Rob Marsal Date: Mon, 21 Sep 2026 20:48:14 +0100 Subject: [PATCH] fix: keep the additional-properties catch-all on generated models --- .github/workflows/check.yaml | 32 +++++++++++++++++++++++++++++++- scripts/generate-local.sh | 24 ++++++++++++++++++++++++ scripts/postprocess.sh | 3 ++- 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index 43cbccc..9955c16 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -108,13 +108,43 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # `disallowAdditionalPropertiesIfNotPresent: false` only covers schemas that + # omit additionalProperties. For an explicit `additionalProperties: false` the + # generator reads the OpenAPI 3.1 boolean schema inconsistently, so the + # additional-properties catch-all silently comes and goes between spec + # revisions. `true` has only one reading. + # Keep in lockstep with scripts/generate-local.sh. + - name: Fetch and normalise OpenAPI spec + if: steps.version_check.outputs.should_generate == 'true' + run: | + curl -fsSL https://docs.reveng.ai/openapi.json -o openapi.json + python3 - <<'PY' + import json + + with open("openapi.json") as f: + spec = json.load(f) + + for schema in spec.get("components", {}).get("schemas", {}).values(): + if not isinstance(schema, dict) or schema.get("additionalProperties") is not False: + continue + if schema.get("properties"): + schema["additionalProperties"] = True + else: + # No properties means an empty response body; `true` would change + # its generated type, so drop the key instead. + del schema["additionalProperties"] + + with open("openapi.json", "w") as f: + json.dump(spec, f) + PY + - name: Generate Java SDK if: steps.version_check.outputs.should_generate == 'true' uses: openapi-generators/openapitools-generator-action@b729d184e6b3459572c37c0e37f88a832e69b552 # v1.5.0 with: generator: java generator-tag: 'v7.23.0' - openapi-url: https://docs.reveng.ai/openapi.json + openapi-file: openapi.json config-file: config.yml template-dir: templates # For the artifact version drop the `v` prefix as it's not usually used in Maven versions diff --git a/scripts/generate-local.sh b/scripts/generate-local.sh index fa7e82e..5745813 100755 --- a/scripts/generate-local.sh +++ b/scripts/generate-local.sh @@ -66,6 +66,30 @@ case "$SPEC" in ;; esac +# Normalise `additionalProperties: false` out of the spec. Keep in lockstep +# with the "Fetch and normalise OpenAPI spec" step in +# .github/workflows/check.yaml, which explains why. +python3 - "$SPEC_FILE" <<'PY' +import json +import sys + +with open(sys.argv[1]) as f: + spec = json.load(f) + +for schema in spec.get("components", {}).get("schemas", {}).values(): + if not isinstance(schema, dict) or schema.get("additionalProperties") is not False: + continue + if schema.get("properties"): + schema["additionalProperties"] = True + else: + # No properties means an empty response body; `true` would change its + # generated type, so drop the key instead. + del schema["additionalProperties"] + +with open(sys.argv[1], "w") as f: + json.dump(spec, f) +PY + SPEC_VERSION="$(jq -r '.info.version // empty' "$SPEC_FILE")" [ -n "$SPEC_VERSION" ] || { echo "spec has no info.version" >&2; exit 1; } echo "==> Spec info.version: $SPEC_VERSION" diff --git a/scripts/postprocess.sh b/scripts/postprocess.sh index cb53f47..caba526 100755 --- a/scripts/postprocess.sh +++ b/scripts/postprocess.sh @@ -28,7 +28,8 @@ rm -Rf gradlew.bat && mv java-client/gradlew.bat . rm -Rf pom.xml && mv java-client/pom.xml . rm -Rf settings.gradle && mv java-client/settings.gradle . rm -Rf README.md && mv java-client/README.md . -rm -Rf java-client +# openapi.json is the normalised spec CI generates from; it must not be committed. +rm -Rf java-client openapi.json # Ensure gradlew is executable chmod +x gradlew