Skip to content

fix: convert numeric exclusiveMinimum/exclusiveMaximum for OpenAPI 3.0 and Swagger 2.0 - #962

Open
lprnmns wants to merge 2 commits into
fastify:mainfrom
lprnmns:fix/openapi30-exclusive-bounds
Open

lprnmns wants to merge 2 commits into
fastify:mainfrom
lprnmns:fix/openapi30-exclusive-bounds

Conversation

@lprnmns

@lprnmns lprnmns commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Route schemas are validated by Ajv as JSON Schema draft 7, where exclusiveMinimum/exclusiveMaximum are numbers. OpenAPI 3.0 (the default document version) uses the draft 4 form, where they are booleans that make minimum/maximum exclusive. They are currently copied through unchanged, so a schema such as

querystring: { type: 'object', properties: { page: { type: 'integer', exclusiveMinimum: 0 } } }

produces an invalid OpenAPI 3.0 document:

#/paths/~1r/get/parameters/0/schema/exclusiveMinimum must be boolean

For OpenAPI 3.0, convertJsonSchemaToOpenapi3 now rewrites a numeric bound next to the existing type: 'null' → nullable conversion: exclusiveMinimum: n becomes minimum: n, exclusiveMinimum: true. When the schema also has minimum/maximum, the stricter bound is kept, so { minimum: 5, exclusiveMinimum: 0 } stays { minimum: 5 }. OpenAPI 3.1+ documents are left as they are, since 3.1 uses the numeric form.

Swagger 2.0 (the non-openapi mode) has the same boolean form, so the conversion also runs there: in replaceUnsupported for body and response schemas, and on query/header/path parameters (including items). The helper lives in lib/util/convert-exclusive-bounds.js.

Checklist

Tests: two new tests in test/spec/openapi/schema.test.js, covering the 3.0 conversion (including the cases where minimum/maximum is stricter) and checking that 3.1 is unchanged. Both validate the document with @apidevtools/swagger-parser. The 3.0 test fails on main. npm run test (unit tests with 100% coverage, and tstyche) and ESLint pass.

@gurgunday

Copy link
Copy Markdown
Member

Swagger 2.0 also uses the boolean form, so with the default (non-openapi) mode, { type: 'integer', exclusiveMinimum: 0 } produces an invalid document on this branch

@lprnmns

lprnmns commented Oct 22, 2026

Copy link
Copy Markdown
Contributor Author

Good catch, thanks. Swagger 2.0 uses the same boolean form, so I added the conversion there too (361026b):

  • The helper moved to lib/util/convert-exclusive-bounds.js and is shared by both specs.
  • In Swagger mode it runs in replaceUnsupported, next to the const → enum conversion, which covers body and response schemas. It also runs on query, header and path parameters, including their items.

The new Swagger test (query param, array items, body, response) fails Swagger.validate without the change. npm test (301 tests, 100% coverage) and lint pass.

One thing I left as is: in Swagger mode, schemas registered with addSchema and emitted under definitions don't go through replaceUnsupported today (so const isn't converted there either). I didn't change that here, but I can if you'd like.

@lprnmns lprnmns changed the title fix(openapi): convert numeric exclusiveMinimum/exclusiveMaximum for OpenAPI 3.0 fix: convert numeric exclusiveMinimum/exclusiveMaximum for OpenAPI 3.0 and Swagger 2.0 Oct 22, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants