From f597c226e2e6c7c40633ca801b2f7bc6e6bf7e77 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 7 Oct 2025 19:13:31 -0400 Subject: [PATCH 01/51] chore: migrates to scalar open types Signed-off-by: Vincent Biret --- packages/openapi3/package.json | 2 +- .../test/tsp-openapi3/context.test.ts | 2 +- .../tsp-openapi3/single-anyof-oneof.test.ts | 2 +- .../transform-component-schemas.test.ts | 2 +- .../union-anyof-with-null.test.ts | 2 +- pnpm-lock.yaml | 19 ++++++++++++++++--- 6 files changed, 21 insertions(+), 8 deletions(-) diff --git a/packages/openapi3/package.json b/packages/openapi3/package.json index 03deb28ae70..6b11123e718 100644 --- a/packages/openapi3/package.json +++ b/packages/openapi3/package.json @@ -63,8 +63,8 @@ ], "dependencies": { "@apidevtools/swagger-parser": "~12.0.0", + "@scalar/openapi-types": "^0.4.0", "@typespec/asset-emitter": "workspace:^", - "openapi-types": "~12.1.3", "yaml": "~2.8.0" }, "peerDependencies": { diff --git a/packages/openapi3/test/tsp-openapi3/context.test.ts b/packages/openapi3/test/tsp-openapi3/context.test.ts index aa060b74a3d..2f3ab0ea3df 100644 --- a/packages/openapi3/test/tsp-openapi3/context.test.ts +++ b/packages/openapi3/test/tsp-openapi3/context.test.ts @@ -1,5 +1,5 @@ import OpenAPIParser from "@apidevtools/swagger-parser"; -import { OpenAPI } from "openapi-types"; +import { OpenAPI } from "@scalar/openapi-types"; import { beforeAll, describe, expect, it } from "vitest"; import { createContext } from "../../src/cli/actions/convert/utils/context.js"; import { OpenAPI3Document } from "../../src/types.js"; diff --git a/packages/openapi3/test/tsp-openapi3/single-anyof-oneof.test.ts b/packages/openapi3/test/tsp-openapi3/single-anyof-oneof.test.ts index b18a48aa3cb..5eaa38b5d6e 100644 --- a/packages/openapi3/test/tsp-openapi3/single-anyof-oneof.test.ts +++ b/packages/openapi3/test/tsp-openapi3/single-anyof-oneof.test.ts @@ -1,5 +1,5 @@ import OpenAPIParser from "@apidevtools/swagger-parser"; -import { OpenAPI } from "openapi-types"; +import { OpenAPI } from "@scalar/openapi-types"; import { beforeAll, describe, expect, it } from "vitest"; import { generateDataType } from "../../src/cli/actions/convert/generators/generate-model.js"; import { TypeSpecDataTypes, TypeSpecModel } from "../../src/cli/actions/convert/interfaces.js"; diff --git a/packages/openapi3/test/tsp-openapi3/transform-component-schemas.test.ts b/packages/openapi3/test/tsp-openapi3/transform-component-schemas.test.ts index cb5cc5ef816..91cdd327927 100644 --- a/packages/openapi3/test/tsp-openapi3/transform-component-schemas.test.ts +++ b/packages/openapi3/test/tsp-openapi3/transform-component-schemas.test.ts @@ -1,5 +1,5 @@ import OpenAPIParser from "@apidevtools/swagger-parser"; -import { OpenAPI } from "openapi-types"; +import { OpenAPI } from "@scalar/openapi-types"; import { beforeAll, describe, expect, it } from "vitest"; import { TypeSpecModel } from "../../src/cli/actions/convert/interfaces.js"; import { transformComponentSchemas } from "../../src/cli/actions/convert/transforms/transform-component-schemas.js"; diff --git a/packages/openapi3/test/tsp-openapi3/union-anyof-with-null.test.ts b/packages/openapi3/test/tsp-openapi3/union-anyof-with-null.test.ts index de64e511884..b3561c05122 100644 --- a/packages/openapi3/test/tsp-openapi3/union-anyof-with-null.test.ts +++ b/packages/openapi3/test/tsp-openapi3/union-anyof-with-null.test.ts @@ -1,5 +1,5 @@ import OpenAPIParser from "@apidevtools/swagger-parser"; -import { OpenAPI } from "openapi-types"; +import { OpenAPI } from "@scalar/openapi-types"; import { beforeAll, describe, expect, it } from "vitest"; import { generateDataType } from "../../src/cli/actions/convert/generators/generate-model.js"; import { TypeSpecUnion } from "../../src/cli/actions/convert/interfaces.js"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 19ed15825d1..4ad457b1044 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1249,12 +1249,12 @@ importers: '@apidevtools/swagger-parser': specifier: ~12.0.0 version: 12.0.0(openapi-types@12.1.3) + '@scalar/openapi-types': + specifier: ^0.4.0 + version: 0.4.0 '@typespec/asset-emitter': specifier: workspace:^ version: link:../asset-emitter - openapi-types: - specifier: ~12.1.3 - version: 12.1.3 yaml: specifier: ~2.8.0 version: 2.8.1 @@ -5537,6 +5537,10 @@ packages: '@types/node': optional: true + '@scalar/openapi-types@0.4.0': + resolution: {integrity: sha512-vdFLzz7vETw6kS3bxWUHCBeFtJOnr1bk/+FXKyxKkd6TbcyT6USXkoelI704IaBdoXyOgGX10FD1IVrs4FwUow==} + engines: {node: '>=20'} + '@scarf/scarf@1.4.0': resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==} @@ -13338,6 +13342,9 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod@4.1.11: + resolution: {integrity: sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==} + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -17733,6 +17740,10 @@ snapshots: optionalDependencies: '@types/node': 24.3.1 + '@scalar/openapi-types@0.4.0': + dependencies: + zod: 4.1.11 + '@scarf/scarf@1.4.0': {} '@sec-ant/readable-stream@0.4.1': {} @@ -27411,4 +27422,6 @@ snapshots: zod@3.25.76: {} + zod@4.1.11: {} + zwitch@2.0.4: {} From b7bc26d3021de5b8e779f3ffa0ab83a6a239a455 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 7 Oct 2025 19:25:48 -0400 Subject: [PATCH 02/51] draft: package migration for parser Signed-off-by: Vincent Biret --- packages/openapi3/package.json | 2 +- .../src/cli/actions/convert/convert-file.ts | 2 +- .../src/cli/actions/convert/convert.ts | 2 +- .../src/cli/actions/convert/utils/context.ts | 2 +- .../test/tsp-openapi3/context.test.ts | 2 +- .../test/tsp-openapi3/generate-type.test.ts | 2 +- .../tsp-openapi3/http-part-methods.test.ts | 2 +- .../tsp-openapi3/single-anyof-oneof.test.ts | 2 +- .../transform-component-schemas.test.ts | 2 +- .../union-anyof-with-null.test.ts | 2 +- pnpm-lock.yaml | 109 ++++++++++-------- 11 files changed, 71 insertions(+), 58 deletions(-) diff --git a/packages/openapi3/package.json b/packages/openapi3/package.json index 6b11123e718..40f8c58504e 100644 --- a/packages/openapi3/package.json +++ b/packages/openapi3/package.json @@ -62,7 +62,7 @@ "!dist/test/**" ], "dependencies": { - "@apidevtools/swagger-parser": "~12.0.0", + "@scalar/openapi-parser": "^0.22.1", "@scalar/openapi-types": "^0.4.0", "@typespec/asset-emitter": "workspace:^", "yaml": "~2.8.0" diff --git a/packages/openapi3/src/cli/actions/convert/convert-file.ts b/packages/openapi3/src/cli/actions/convert/convert-file.ts index b5403a3685e..585c43bdb9a 100644 --- a/packages/openapi3/src/cli/actions/convert/convert-file.ts +++ b/packages/openapi3/src/cli/actions/convert/convert-file.ts @@ -1,4 +1,4 @@ -import OpenAPIParser from "@apidevtools/swagger-parser"; +import OpenAPIParser from "@scalar/openapi-parser"; import { formatTypeSpec, resolvePath } from "@typespec/compiler"; import { OpenAPI3Document } from "../../../types.js"; import { CliHost } from "../../types.js"; diff --git a/packages/openapi3/src/cli/actions/convert/convert.ts b/packages/openapi3/src/cli/actions/convert/convert.ts index 94425f6b9e8..fd9dfd87322 100644 --- a/packages/openapi3/src/cli/actions/convert/convert.ts +++ b/packages/openapi3/src/cli/actions/convert/convert.ts @@ -1,4 +1,4 @@ -import OpenAPIParser from "@apidevtools/swagger-parser"; +import OpenAPIParser from "@scalar/openapi-parser"; import { formatTypeSpec } from "@typespec/compiler"; import { OpenAPI3Document } from "../../../types.js"; import { generateMain } from "./generators/generate-main.js"; diff --git a/packages/openapi3/src/cli/actions/convert/utils/context.ts b/packages/openapi3/src/cli/actions/convert/utils/context.ts index 0bd593ddc5e..66e77065e10 100644 --- a/packages/openapi3/src/cli/actions/convert/utils/context.ts +++ b/packages/openapi3/src/cli/actions/convert/utils/context.ts @@ -1,4 +1,4 @@ -import type OpenAPIParser from "@apidevtools/swagger-parser"; +import type OpenAPIParser from "@scalar/openapi-parser"; import { OpenAPI3Document, OpenAPI3Encoding, OpenAPI3Schema, Refable } from "../../../../types.js"; import { Logger } from "../../../types.js"; import { SchemaToExpressionGenerator } from "../generators/generate-types.js"; diff --git a/packages/openapi3/test/tsp-openapi3/context.test.ts b/packages/openapi3/test/tsp-openapi3/context.test.ts index 2f3ab0ea3df..2e63949100d 100644 --- a/packages/openapi3/test/tsp-openapi3/context.test.ts +++ b/packages/openapi3/test/tsp-openapi3/context.test.ts @@ -1,4 +1,4 @@ -import OpenAPIParser from "@apidevtools/swagger-parser"; +import OpenAPIParser from "@scalar/openapi-parser"; import { OpenAPI } from "@scalar/openapi-types"; import { beforeAll, describe, expect, it } from "vitest"; import { createContext } from "../../src/cli/actions/convert/utils/context.js"; diff --git a/packages/openapi3/test/tsp-openapi3/generate-type.test.ts b/packages/openapi3/test/tsp-openapi3/generate-type.test.ts index da1abca12b1..76468cc4a68 100644 --- a/packages/openapi3/test/tsp-openapi3/generate-type.test.ts +++ b/packages/openapi3/test/tsp-openapi3/generate-type.test.ts @@ -1,4 +1,4 @@ -import OpenAPIParser from "@apidevtools/swagger-parser"; +import OpenAPIParser from "@scalar/openapi-parser"; import { formatTypeSpec } from "@typespec/compiler"; import { strictEqual } from "node:assert"; import { beforeAll, describe, it } from "vitest"; diff --git a/packages/openapi3/test/tsp-openapi3/http-part-methods.test.ts b/packages/openapi3/test/tsp-openapi3/http-part-methods.test.ts index f8c7e671fc5..c27bbe982b4 100644 --- a/packages/openapi3/test/tsp-openapi3/http-part-methods.test.ts +++ b/packages/openapi3/test/tsp-openapi3/http-part-methods.test.ts @@ -1,4 +1,4 @@ -import OpenAPIParser from "@apidevtools/swagger-parser"; +import OpenAPIParser from "@scalar/openapi-parser"; import { formatTypeSpec } from "@typespec/compiler"; import { strictEqual } from "node:assert"; import { beforeAll, describe, it } from "vitest"; diff --git a/packages/openapi3/test/tsp-openapi3/single-anyof-oneof.test.ts b/packages/openapi3/test/tsp-openapi3/single-anyof-oneof.test.ts index 5eaa38b5d6e..87e2985a461 100644 --- a/packages/openapi3/test/tsp-openapi3/single-anyof-oneof.test.ts +++ b/packages/openapi3/test/tsp-openapi3/single-anyof-oneof.test.ts @@ -1,4 +1,4 @@ -import OpenAPIParser from "@apidevtools/swagger-parser"; +import OpenAPIParser from "@scalar/openapi-parser"; import { OpenAPI } from "@scalar/openapi-types"; import { beforeAll, describe, expect, it } from "vitest"; import { generateDataType } from "../../src/cli/actions/convert/generators/generate-model.js"; diff --git a/packages/openapi3/test/tsp-openapi3/transform-component-schemas.test.ts b/packages/openapi3/test/tsp-openapi3/transform-component-schemas.test.ts index 91cdd327927..529a715d71c 100644 --- a/packages/openapi3/test/tsp-openapi3/transform-component-schemas.test.ts +++ b/packages/openapi3/test/tsp-openapi3/transform-component-schemas.test.ts @@ -1,4 +1,4 @@ -import OpenAPIParser from "@apidevtools/swagger-parser"; +import OpenAPIParser from "@scalar/openapi-parser"; import { OpenAPI } from "@scalar/openapi-types"; import { beforeAll, describe, expect, it } from "vitest"; import { TypeSpecModel } from "../../src/cli/actions/convert/interfaces.js"; diff --git a/packages/openapi3/test/tsp-openapi3/union-anyof-with-null.test.ts b/packages/openapi3/test/tsp-openapi3/union-anyof-with-null.test.ts index b3561c05122..524dd586d87 100644 --- a/packages/openapi3/test/tsp-openapi3/union-anyof-with-null.test.ts +++ b/packages/openapi3/test/tsp-openapi3/union-anyof-with-null.test.ts @@ -1,4 +1,4 @@ -import OpenAPIParser from "@apidevtools/swagger-parser"; +import OpenAPIParser from "@scalar/openapi-parser"; import { OpenAPI } from "@scalar/openapi-types"; import { beforeAll, describe, expect, it } from "vitest"; import { generateDataType } from "../../src/cli/actions/convert/generators/generate-model.js"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4ad457b1044..139cd5c85c3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1246,9 +1246,9 @@ importers: packages/openapi3: dependencies: - '@apidevtools/swagger-parser': - specifier: ~12.0.0 - version: 12.0.0(openapi-types@12.1.3) + '@scalar/openapi-parser': + specifier: ^0.22.1 + version: 0.22.1 '@scalar/openapi-types': specifier: ^0.4.0 version: 0.4.0 @@ -2697,22 +2697,6 @@ packages: '@antfu/utils@9.2.0': resolution: {integrity: sha512-Oq1d9BGZakE/FyoEtcNeSwM7MpDO2vUBi11RWBZXf75zPsbUVWmUs03EqkRFrcgbXyKTas0BdZWC1wcuSoqSAw==} - '@apidevtools/json-schema-ref-parser@14.0.1': - resolution: {integrity: sha512-Oc96zvmxx1fqoSEdUmfmvvb59/KDOnUoJ7s2t7bISyAn0XEz57LCCw8k2Y4Pf3mwKaZLMciESALORLgfe2frCw==} - engines: {node: '>= 16'} - - '@apidevtools/openapi-schemas@2.1.0': - resolution: {integrity: sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==} - engines: {node: '>=10'} - - '@apidevtools/swagger-methods@3.0.2': - resolution: {integrity: sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==} - - '@apidevtools/swagger-parser@12.0.0': - resolution: {integrity: sha512-WLJIWcfOXrSKlZEM+yhA2Xzatgl488qr1FoOxixYmtWapBzwSC0gVGq4WObr4hHClMIiFFdOBdixNkvWqkWIWA==} - peerDependencies: - openapi-types: '>=7' - '@arcanis/slice-ansi@1.1.1': resolution: {integrity: sha512-xguP2WR2Dv0gQ7Ykbdb7BNCnPnIPB94uTi0Z2NvkRBEnhbwjOQ7QyQKJXrVQg4qDpiD9hA5l5cCwy/z2OXgc3w==} @@ -5537,10 +5521,26 @@ packages: '@types/node': optional: true + '@scalar/helpers@0.0.11': + resolution: {integrity: sha512-EoAufzG0crQloYJxbCV8F+Y5vPyeeh1HMngGlXPT6oGSJPi6DiNA9wztqK3lvaBmSkJMh4VKIbejVqAXx1a0tg==} + engines: {node: '>=20'} + + '@scalar/json-magic@0.6.0': + resolution: {integrity: sha512-sy2yL7V8ZF7oUoMl46TjFbBfqZESDmKPfPXeyWeIcwKgjFwjM/FvLdGOOPNsYQ2tPZUCzg8QCNJk9QM+BKVyRg==} + engines: {node: '>=20'} + + '@scalar/openapi-parser@0.22.1': + resolution: {integrity: sha512-GJV7f9VLoyKbArMcWETbJ+xPqkopNBlb+oxEBSlSYYsoWc+p+sLAtuwObfJT8Ia+CStl5Y2ZRk/2DgCuy1vsaQ==} + engines: {node: '>=20'} + '@scalar/openapi-types@0.4.0': resolution: {integrity: sha512-vdFLzz7vETw6kS3bxWUHCBeFtJOnr1bk/+FXKyxKkd6TbcyT6USXkoelI704IaBdoXyOgGX10FD1IVrs4FwUow==} engines: {node: '>=20'} + '@scalar/openapi-upgrader@0.1.1': + resolution: {integrity: sha512-NlrQezuNGtejqS/SQshY9SjD4D66jiJcWgC2zfCS4muyfE2K85e0i6gegnKLZPSNrqoAvJyR78NPapme9z2/wg==} + engines: {node: '>=20'} + '@scarf/scarf@1.4.0': resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==} @@ -7196,9 +7196,6 @@ packages: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} - call-me-maybe@1.0.2: - resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==} - callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -9613,6 +9610,10 @@ packages: resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} engines: {'0': node >= 0.2.0} + jsonpointer@5.0.1: + resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==} + engines: {node: '>=0.10.0'} + jsonwebtoken@9.0.2: resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} engines: {node: '>=12', npm: '>=6'} @@ -9701,6 +9702,10 @@ packages: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} + leven@4.1.0: + resolution: {integrity: sha512-KZ9W9nWDT7rF7Dazg8xyLHGLrmpgq2nVNFUckhqdW3szVP6YhCpp/RAnpmVExA9JvrMynjwSLVrEj3AepHR6ew==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -10559,9 +10564,6 @@ packages: resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} engines: {node: '>=12'} - openapi-types@12.1.3: - resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} - optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -13273,6 +13275,11 @@ packages: resolution: {integrity: sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==} engines: {node: '>= 14'} + yaml@2.8.0: + resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==} + engines: {node: '>= 14.6'} + hasBin: true + yaml@2.8.1: resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} engines: {node: '>= 14.6'} @@ -13516,25 +13523,6 @@ snapshots: '@antfu/utils@9.2.0': {} - '@apidevtools/json-schema-ref-parser@14.0.1': - dependencies: - '@types/json-schema': 7.0.15 - js-yaml: 4.1.0 - - '@apidevtools/openapi-schemas@2.1.0': {} - - '@apidevtools/swagger-methods@3.0.2': {} - - '@apidevtools/swagger-parser@12.0.0(openapi-types@12.1.3)': - dependencies: - '@apidevtools/json-schema-ref-parser': 14.0.1 - '@apidevtools/openapi-schemas': 2.1.0 - '@apidevtools/swagger-methods': 3.0.2 - ajv: 8.17.1 - ajv-draft-04: 1.0.0(ajv@8.17.1) - call-me-maybe: 1.0.2 - openapi-types: 12.1.3 - '@arcanis/slice-ansi@1.1.1': dependencies: grapheme-splitter: 1.0.4 @@ -17740,10 +17728,33 @@ snapshots: optionalDependencies: '@types/node': 24.3.1 + '@scalar/helpers@0.0.11': {} + + '@scalar/json-magic@0.6.0': + dependencies: + '@scalar/helpers': 0.0.11 + yaml: 2.8.0 + + '@scalar/openapi-parser@0.22.1': + dependencies: + '@scalar/json-magic': 0.6.0 + '@scalar/openapi-types': 0.4.0 + '@scalar/openapi-upgrader': 0.1.1 + ajv: 8.17.1 + ajv-draft-04: 1.0.0(ajv@8.17.1) + ajv-formats: 3.0.1(ajv@8.17.1) + jsonpointer: 5.0.1 + leven: 4.1.0 + yaml: 2.8.0 + '@scalar/openapi-types@0.4.0': dependencies: zod: 4.1.11 + '@scalar/openapi-upgrader@0.1.1': + dependencies: + '@scalar/openapi-types': 0.4.0 + '@scarf/scarf@1.4.0': {} '@sec-ant/readable-stream@0.4.1': {} @@ -20144,8 +20155,6 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 - call-me-maybe@1.0.2: {} - callsites@3.1.0: {} camelcase-keys@6.2.2: @@ -23041,6 +23050,8 @@ snapshots: jsonparse@1.3.1: {} + jsonpointer@5.0.1: {} + jsonwebtoken@9.0.2: dependencies: jws: 3.2.2 @@ -23169,6 +23180,8 @@ snapshots: leven@3.1.0: {} + leven@4.1.0: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -24407,8 +24420,6 @@ snapshots: is-docker: 2.2.1 is-wsl: 2.2.0 - openapi-types@12.1.3: {} - optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -27354,6 +27365,8 @@ snapshots: yaml@2.2.2: {} + yaml@2.8.0: {} + yaml@2.8.1: {} yargs-parser@21.1.1: {} From 65b9d64ef4d648af35c214c8875b92bdaea73a7a Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 10 Oct 2025 15:06:55 -0400 Subject: [PATCH 03/51] chore: migrates document import logic to new package Signed-off-by: Vincent Biret --- .../src/cli/actions/convert/convert-file.ts | 10 +++-- .../src/cli/actions/convert/convert.ts | 19 +++++--- .../src/cli/actions/convert/utils/context.ts | 45 +++++++++++++++---- .../test/tsp-openapi3/context.test.ts | 16 ++++--- .../test/tsp-openapi3/generate-type.test.ts | 10 +++-- .../tsp-openapi3/http-part-methods.test.ts | 7 ++- .../tsp-openapi3/single-anyof-oneof.test.ts | 18 ++++---- .../transform-component-schemas.test.ts | 12 ++--- .../union-anyof-with-null.test.ts | 24 ++++++---- 9 files changed, 105 insertions(+), 56 deletions(-) diff --git a/packages/openapi3/src/cli/actions/convert/convert-file.ts b/packages/openapi3/src/cli/actions/convert/convert-file.ts index 585c43bdb9a..25e4c803f36 100644 --- a/packages/openapi3/src/cli/actions/convert/convert-file.ts +++ b/packages/openapi3/src/cli/actions/convert/convert-file.ts @@ -1,4 +1,4 @@ -import OpenAPIParser from "@scalar/openapi-parser"; +import { dereference } from "@scalar/openapi-parser"; import { formatTypeSpec, resolvePath } from "@typespec/compiler"; import { OpenAPI3Document } from "../../../types.js"; import { CliHost } from "../../types.js"; @@ -11,9 +11,11 @@ import { createContext } from "./utils/context.js"; export async function convertAction(host: CliHost, args: ConvertCliArgs) { // attempt to read the file const fullPath = resolvePath(process.cwd(), args.path); - const parser = new OpenAPIParser(); - const model = await parser.bundle(fullPath); - const context = createContext(parser, model as OpenAPI3Document, console, args.namespace); + const { specification } = await dereference(fullPath); + if (!specification) { + throw new Error("Failed to dereference OpenAPI document"); + } + const context = createContext(specification as OpenAPI3Document, console, args.namespace); const program = transform(context); let mainTsp: string; try { diff --git a/packages/openapi3/src/cli/actions/convert/convert.ts b/packages/openapi3/src/cli/actions/convert/convert.ts index fd9dfd87322..49d31d985fc 100644 --- a/packages/openapi3/src/cli/actions/convert/convert.ts +++ b/packages/openapi3/src/cli/actions/convert/convert.ts @@ -1,4 +1,4 @@ -import OpenAPIParser from "@scalar/openapi-parser"; +import { AnyObject, dereference } from "@scalar/openapi-parser"; import { formatTypeSpec } from "@typespec/compiler"; import { OpenAPI3Document } from "../../../types.js"; import { generateMain } from "./generators/generate-main.js"; @@ -20,14 +20,21 @@ export async function convertOpenAPI3Document( document: OpenAPI3Document, { disableExternalRefs, namespace }: ConvertOpenAPI3DocumentOptions = {}, ) { - const parser = new OpenAPIParser(); - const bundleOptions = disableExternalRefs + const dereferenceOptions = disableExternalRefs ? { - resolve: { external: false, http: false, file: false }, + onDereference: (data: { schema: AnyObject; ref: string }): void => { + if (data.ref.startsWith("#")) { + return; + } + throw new Error(`External $ref pointers are disabled, but found $ref: ${data.ref}`); + }, } : {}; - await parser.bundle(document as any, bundleOptions); - const context = createContext(parser, document, console, namespace); + const { specification } = await dereference(document, dereferenceOptions); + if (!specification) { + throw new Error("Failed to dereference OpenAPI document"); + } + const context = createContext(document, console, namespace); const program = transform(context); const content = generateMain(program, context); try { diff --git a/packages/openapi3/src/cli/actions/convert/utils/context.ts b/packages/openapi3/src/cli/actions/convert/utils/context.ts index 66e77065e10..be5ddcb8ec7 100644 --- a/packages/openapi3/src/cli/actions/convert/utils/context.ts +++ b/packages/openapi3/src/cli/actions/convert/utils/context.ts @@ -1,5 +1,10 @@ -import type OpenAPIParser from "@scalar/openapi-parser"; -import { OpenAPI3Document, OpenAPI3Encoding, OpenAPI3Schema, Refable } from "../../../../types.js"; +import { + OpenAPI3Document, + OpenAPI3Encoding, + OpenAPI3Schema, + OpenAPIDocument3_1, + Refable, +} from "../../../../types.js"; import { Logger } from "../../../types.js"; import { SchemaToExpressionGenerator } from "../generators/generate-types.js"; import { generateNamespaceName } from "./generate-namespace-name.js"; @@ -47,12 +52,7 @@ export interface Context { ): string; } -export type Parser = { - $refs: OpenAPIParser["$refs"]; -}; - export function createContext( - parser: Parser, openApi3Doc: OpenAPI3Document, logger?: Logger, namespace?: string, @@ -96,8 +96,35 @@ export function createContext( getSchemaByRef(ref) { return this.getByRef(ref); }, - getByRef(ref) { - return parser.$refs.get(ref) as any; + getByRef(ref: string): T | undefined { + const splitRef = ref.split("/"); + const componentKind = splitRef[2]; // #/components/schemas/Pet -> components + const componentName = splitRef[3]; // #/components/schemas/Pet -> Pet + switch (componentKind) { + case "schemas": + return openApi3Doc.components?.schemas?.[componentName] as T; + case "responses": + return openApi3Doc.components?.responses?.[componentName] as T; + case "parameters": + return openApi3Doc.components?.parameters?.[componentName] as T; + case "examples": + return openApi3Doc.components?.examples?.[componentName] as T; + case "requestBodies": + return openApi3Doc.components?.requestBodies?.[componentName] as T; + case "headers": + return openApi3Doc.components?.headers?.[componentName] as T; + case "securitySchemes": + return openApi3Doc.components?.securitySchemes?.[componentName] as T; + case "links": + return openApi3Doc.components?.links?.[componentName] as T; + case "callbacks": + return (openApi3Doc as unknown as OpenAPIDocument3_1).components?.callbacks?.[ + componentName + ] as T; + default: + this.logger.warn(`Unsupported component kind in $ref: ${ref}`); + return undefined; + } }, registerMultipartSchema(ref: string, encoding?: Record) { multipartSchemas.set(ref, encoding ?? null); diff --git a/packages/openapi3/test/tsp-openapi3/context.test.ts b/packages/openapi3/test/tsp-openapi3/context.test.ts index 2e63949100d..04e6c979fe4 100644 --- a/packages/openapi3/test/tsp-openapi3/context.test.ts +++ b/packages/openapi3/test/tsp-openapi3/context.test.ts @@ -1,24 +1,26 @@ -import OpenAPIParser from "@scalar/openapi-parser"; +import { dereference } from "@scalar/openapi-parser"; import { OpenAPI } from "@scalar/openapi-types"; import { beforeAll, describe, expect, it } from "vitest"; import { createContext } from "../../src/cli/actions/convert/utils/context.js"; import { OpenAPI3Document } from "../../src/types.js"; describe("tsp-openapi: Context methods", () => { - let parser: OpenAPIParser; let doc: OpenAPI.Document<{}>; beforeAll(async () => { - parser = new OpenAPIParser(); - doc = await parser.bundle({ + const { specification } = await dereference({ openapi: "3.0.0", info: { title: "Test", version: "1.0.0" }, paths: {}, }); + if (!specification) { + throw new Error("Failed to dereference OpenAPI document"); + } + doc = specification; }); it("should add a component encoding to the registry", () => { - const context = createContext(parser, doc as OpenAPI3Document); + const context = createContext(doc as OpenAPI3Document); const reference = "#/components/schemas/MySchema"; const encoding = { myProperty: { @@ -31,7 +33,7 @@ describe("tsp-openapi: Context methods", () => { }); it("should consider a component without encoding to be registered", () => { - const context = createContext(parser, doc as OpenAPI3Document); + const context = createContext(doc as OpenAPI3Document); const reference = "#/components/schemas/MySchema"; context.registerMultipartSchema(reference); expect(context.getMultipartSchemaEncoding(reference)).toBeUndefined(); @@ -39,7 +41,7 @@ describe("tsp-openapi: Context methods", () => { }); it("should NOT consider a component to be registered when it's not", () => { - const context = createContext(parser, doc as OpenAPI3Document); + const context = createContext(doc as OpenAPI3Document); const reference = "#/components/schemas/MySchema"; expect(context.getMultipartSchemaEncoding(reference)).toBeUndefined(); expect(context.isSchemaReferenceRegisteredForMultipartForm(reference)).toBe(false); diff --git a/packages/openapi3/test/tsp-openapi3/generate-type.test.ts b/packages/openapi3/test/tsp-openapi3/generate-type.test.ts index 76468cc4a68..28d59b3a473 100644 --- a/packages/openapi3/test/tsp-openapi3/generate-type.test.ts +++ b/packages/openapi3/test/tsp-openapi3/generate-type.test.ts @@ -1,4 +1,4 @@ -import OpenAPIParser from "@scalar/openapi-parser"; +import { dereference } from "@scalar/openapi-parser"; import { formatTypeSpec } from "@typespec/compiler"; import { strictEqual } from "node:assert"; import { beforeAll, describe, it } from "vitest"; @@ -279,13 +279,15 @@ const testScenarios: TestScenario[] = [ describe("tsp-openapi: generate-type", () => { let context: Context; beforeAll(async () => { - const parser = new OpenAPIParser(); - const doc = await parser.bundle({ + const { specification } = await dereference({ openapi: "3.0.0", info: { title: "Test", version: "1.0.0" }, paths: {}, }); - context = createContext(parser, doc as OpenAPI3Document); + if (!specification) { + throw new Error("Failed to dereference OpenAPI document"); + } + context = createContext(specification as OpenAPI3Document); }); testScenarios.forEach((t) => it(`${generateScenarioName(t)}`, async () => { diff --git a/packages/openapi3/test/tsp-openapi3/http-part-methods.test.ts b/packages/openapi3/test/tsp-openapi3/http-part-methods.test.ts index c27bbe982b4..2385b997675 100644 --- a/packages/openapi3/test/tsp-openapi3/http-part-methods.test.ts +++ b/packages/openapi3/test/tsp-openapi3/http-part-methods.test.ts @@ -1,4 +1,4 @@ -import OpenAPIParser from "@scalar/openapi-parser"; +import { dereference } from "@scalar/openapi-parser"; import { formatTypeSpec } from "@typespec/compiler"; import { strictEqual } from "node:assert"; import { beforeAll, describe, it } from "vitest"; @@ -15,13 +15,12 @@ describe("tsp-openapi: HTTP part generation methods", () => { let context: Context; beforeAll(async () => { - const parser = new OpenAPIParser(); - const doc = await parser.bundle({ + const { specification } = await dereference({ openapi: "3.0.0", info: { title: "Test", version: "1.0.0" }, paths: {}, }); - context = createContext(parser, doc as OpenAPI3Document); + context = createContext(specification as OpenAPI3Document); }); describe("basic HTTP part wrapping", () => { diff --git a/packages/openapi3/test/tsp-openapi3/single-anyof-oneof.test.ts b/packages/openapi3/test/tsp-openapi3/single-anyof-oneof.test.ts index 87e2985a461..7341516ad42 100644 --- a/packages/openapi3/test/tsp-openapi3/single-anyof-oneof.test.ts +++ b/packages/openapi3/test/tsp-openapi3/single-anyof-oneof.test.ts @@ -1,4 +1,4 @@ -import OpenAPIParser from "@scalar/openapi-parser"; +import { dereference } from "@scalar/openapi-parser"; import { OpenAPI } from "@scalar/openapi-types"; import { beforeAll, describe, expect, it } from "vitest"; import { generateDataType } from "../../src/cli/actions/convert/generators/generate-model.js"; @@ -8,12 +8,10 @@ import { createContext } from "../../src/cli/actions/convert/utils/context.js"; import { OpenAPI3Document } from "../../src/types.js"; describe("tsp-openapi: single anyOf/oneOf inline schema should produce model", () => { - let parser: OpenAPIParser; let doc: OpenAPI.Document<{}>; beforeAll(async () => { - parser = new OpenAPIParser(); - doc = await parser.bundle({ + const { specification } = await dereference({ openapi: "3.1.0", info: { title: "repro API", version: "1.0.0", description: "API for repro" }, servers: [{ url: "http://localhost:3000" }], @@ -87,10 +85,14 @@ describe("tsp-openapi: single anyOf/oneOf inline schema should produce model", ( }, }, }); + if (!specification) { + throw new Error("Failed to dereference OpenAPI document"); + } + doc = specification; }); it("should generate a model for anyOf with single inline schema", () => { - const context = createContext(parser, doc as OpenAPI3Document); + const context = createContext(doc as OpenAPI3Document); const types: TypeSpecDataTypes[] = []; transformComponentSchemas(context, types); @@ -115,7 +117,7 @@ describe("tsp-openapi: single anyOf/oneOf inline schema should produce model", ( }); it("should generate a model for oneOf with single inline schema", () => { - const context = createContext(parser, doc as OpenAPI3Document); + const context = createContext(doc as OpenAPI3Document); const types: TypeSpecDataTypes[] = []; transformComponentSchemas(context, types); @@ -132,7 +134,7 @@ describe("tsp-openapi: single anyOf/oneOf inline schema should produce model", ( }); it("should generate a model for anyOf with single inline schema + null", () => { - const context = createContext(parser, doc as OpenAPI3Document); + const context = createContext(doc as OpenAPI3Document); const types: TypeSpecDataTypes[] = []; transformComponentSchemas(context, types); @@ -152,7 +154,7 @@ describe("tsp-openapi: single anyOf/oneOf inline schema should produce model", ( }); it("should generate a model for oneOf with single inline schema + null", () => { - const context = createContext(parser, doc as OpenAPI3Document); + const context = createContext(doc as OpenAPI3Document); const types: TypeSpecDataTypes[] = []; transformComponentSchemas(context, types); diff --git a/packages/openapi3/test/tsp-openapi3/transform-component-schemas.test.ts b/packages/openapi3/test/tsp-openapi3/transform-component-schemas.test.ts index 529a715d71c..f894bbe1d78 100644 --- a/packages/openapi3/test/tsp-openapi3/transform-component-schemas.test.ts +++ b/packages/openapi3/test/tsp-openapi3/transform-component-schemas.test.ts @@ -1,4 +1,4 @@ -import OpenAPIParser from "@scalar/openapi-parser"; +import { dereference } from "@scalar/openapi-parser"; import { OpenAPI } from "@scalar/openapi-types"; import { beforeAll, describe, expect, it } from "vitest"; import { TypeSpecModel } from "../../src/cli/actions/convert/interfaces.js"; @@ -7,12 +7,10 @@ import { createContext } from "../../src/cli/actions/convert/utils/context.js"; import { OpenAPI3Document } from "../../src/types.js"; describe("tsp-openapi: transform component schemas", () => { - let parser: OpenAPIParser; let doc: OpenAPI.Document<{}>; beforeAll(async () => { - parser = new OpenAPIParser(); - doc = await parser.bundle({ + const { specification } = await dereference({ openapi: "3.0.0", info: { title: "Test", version: "1.0.0" }, paths: { @@ -52,10 +50,14 @@ describe("tsp-openapi: transform component schemas", () => { }, }, }); + if (!specification) { + throw new Error("Failed to dereference OpenAPI document"); + } + doc = specification; }); it("adds the encoding to the model when available", () => { - const context = createContext(parser, doc as OpenAPI3Document); + const context = createContext(doc as OpenAPI3Document); context.registerMultipartSchema("#/components/schemas/MyModel", { id: { contentType: "text/plain" }, name: { contentType: "text/plain" }, diff --git a/packages/openapi3/test/tsp-openapi3/union-anyof-with-null.test.ts b/packages/openapi3/test/tsp-openapi3/union-anyof-with-null.test.ts index 524dd586d87..82d33bde7ff 100644 --- a/packages/openapi3/test/tsp-openapi3/union-anyof-with-null.test.ts +++ b/packages/openapi3/test/tsp-openapi3/union-anyof-with-null.test.ts @@ -1,4 +1,4 @@ -import OpenAPIParser from "@scalar/openapi-parser"; +import { dereference } from "@scalar/openapi-parser"; import { OpenAPI } from "@scalar/openapi-types"; import { beforeAll, describe, expect, it } from "vitest"; import { generateDataType } from "../../src/cli/actions/convert/generators/generate-model.js"; @@ -8,12 +8,10 @@ import { createContext } from "../../src/cli/actions/convert/utils/context.js"; import { OpenAPI3Document } from "../../src/types.js"; describe("tsp-openapi: union anyOf with null", () => { - let parser: OpenAPIParser; let doc: OpenAPI.Document<{}>; beforeAll(async () => { - parser = new OpenAPIParser(); - doc = await parser.bundle({ + const { specification } = await dereference({ openapi: "3.1.0", info: { title: "Test", version: "1.0.0" }, paths: {}, @@ -68,10 +66,14 @@ on reasoning in a response.`, }, }, }); + if (!specification) { + throw new Error("Failed to dereference OpenAPI document"); + } + doc = specification; }); it("generates proper TypeSpec code with description and null", () => { - const context = createContext(parser, doc as OpenAPI3Document); + const context = createContext(doc as OpenAPI3Document); const types: TypeSpecUnion[] = []; transformComponentSchemas(context, types); @@ -98,7 +100,7 @@ on reasoning in a response.`, }); it("preserves description from oneOf members with constraints when one is null", () => { - const context = createContext(parser, doc as OpenAPI3Document); + const context = createContext(doc as OpenAPI3Document); const types: TypeSpecUnion[] = []; transformComponentSchemas(context, types); @@ -117,7 +119,7 @@ on reasoning in a response.`, }); it("handles reference + null anyOf correctly", () => { - const context = createContext(parser, doc as OpenAPI3Document); + const context = createContext(doc as OpenAPI3Document); const types: TypeSpecUnion[] = []; transformComponentSchemas(context, types); @@ -134,7 +136,7 @@ on reasoning in a response.`, }); it("handles oneOf with null type array properly", async () => { - const docWithTypeArray = await parser.bundle({ + const { specification } = await dereference({ openapi: "3.1.0", info: { title: "Test", version: "1.0.0" }, paths: {}, @@ -155,8 +157,12 @@ on reasoning in a response.`, }, }, }); + if (!specification) { + throw new Error("Failed to dereference OpenAPI document"); + } + const docWithTypeArray = specification; - const context = createContext(parser, docWithTypeArray as OpenAPI3Document); + const context = createContext(docWithTypeArray as OpenAPI3Document); const types: TypeSpecUnion[] = []; transformComponentSchemas(context, types); From da0374ff6b702ce3702bbd085c6fe4b602d1799c Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 10 Oct 2025 15:13:40 -0400 Subject: [PATCH 04/51] chore: removes old parser references Signed-off-by: Vincent Biret --- .../test/tsp-openapi3/missing-operation-id.test.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/packages/openapi3/test/tsp-openapi3/missing-operation-id.test.ts b/packages/openapi3/test/tsp-openapi3/missing-operation-id.test.ts index 894461077f5..4858f2fc6e2 100644 --- a/packages/openapi3/test/tsp-openapi3/missing-operation-id.test.ts +++ b/packages/openapi3/test/tsp-openapi3/missing-operation-id.test.ts @@ -1,15 +1,9 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import { transformPaths } from "../../src/cli/actions/convert/transforms/transform-paths.js"; -import { createContext, Parser } from "../../src/cli/actions/convert/utils/context.js"; +import { createContext } from "../../src/cli/actions/convert/utils/context.js"; import { convertOpenAPI3Document } from "../../src/index.js"; describe("Convert OpenAPI3 with missing operationId", () => { - const mockParser: Parser = { - $refs: { - get: () => undefined, - } as any, - }; - // Mock logger to capture warnings const mockLogger = { trace: vi.fn(), @@ -65,7 +59,7 @@ describe("Convert OpenAPI3 with missing operationId", () => { }, }; - const context = createContext(mockParser, openApiDoc as any, mockLogger); + const context = createContext(openApiDoc as any, mockLogger); const operations = transformPaths(openApiDoc.paths, context); // Should have 3 operations @@ -110,7 +104,7 @@ describe("Convert OpenAPI3 with missing operationId", () => { }, }; - const context = createContext(mockParser, openApiDoc as any, mockLogger); + const context = createContext(openApiDoc as any, mockLogger); const operations = transformPaths(openApiDoc.paths, context); // Should have 2 operations From 72577c62a7b5d969c119918e6235cc948e19b622 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 10 Oct 2025 15:16:54 -0400 Subject: [PATCH 05/51] docs: adds the chronus entry for the dependencies replacement --- .../changes/chore-parser-migration-2025-9-10-15-16-40.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .chronus/changes/chore-parser-migration-2025-9-10-15-16-40.md diff --git a/.chronus/changes/chore-parser-migration-2025-9-10-15-16-40.md b/.chronus/changes/chore-parser-migration-2025-9-10-15-16-40.md new file mode 100644 index 00000000000..1381cd4fdea --- /dev/null +++ b/.chronus/changes/chore-parser-migration-2025-9-10-15-16-40.md @@ -0,0 +1,7 @@ +--- +changeKind: internal +packages: + - "@typespec/openapi3" +--- + +[deps] swaps outdated and unmaintained openapi-types and swagger parser dependencies for drop in replacements \ No newline at end of file From 56d8671b039d0d1d27fc0e5da45fa54306a8476b Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 16 Oct 2025 15:09:20 -0400 Subject: [PATCH 06/51] chore: updates scalar dependencies to latest --- packages/openapi3/package.json | 4 +-- pnpm-lock.yaml | 48 +++++++++++++++++----------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/packages/openapi3/package.json b/packages/openapi3/package.json index 40f8c58504e..a59c0e6f6cb 100644 --- a/packages/openapi3/package.json +++ b/packages/openapi3/package.json @@ -62,8 +62,8 @@ "!dist/test/**" ], "dependencies": { - "@scalar/openapi-parser": "^0.22.1", - "@scalar/openapi-types": "^0.4.0", + "@scalar/openapi-parser": "^0.22.3", + "@scalar/openapi-types": "^0.5.0", "@typespec/asset-emitter": "workspace:^", "yaml": "~2.8.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 139cd5c85c3..7dcb029aa45 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1247,11 +1247,11 @@ importers: packages/openapi3: dependencies: '@scalar/openapi-parser': - specifier: ^0.22.1 - version: 0.22.1 + specifier: ^0.22.3 + version: 0.22.3 '@scalar/openapi-types': - specifier: ^0.4.0 - version: 0.4.0 + specifier: ^0.5.0 + version: 0.5.0 '@typespec/asset-emitter': specifier: workspace:^ version: link:../asset-emitter @@ -5521,24 +5521,24 @@ packages: '@types/node': optional: true - '@scalar/helpers@0.0.11': - resolution: {integrity: sha512-EoAufzG0crQloYJxbCV8F+Y5vPyeeh1HMngGlXPT6oGSJPi6DiNA9wztqK3lvaBmSkJMh4VKIbejVqAXx1a0tg==} + '@scalar/helpers@0.0.12': + resolution: {integrity: sha512-4NDmHShyi1hrVRsJCdRZT/FIpy+/5PFbVbQLRYX/pjpu5cYqHBj9s6n5RI6gGDXEBHAIFi63g9FC6Isgr66l1Q==} engines: {node: '>=20'} - '@scalar/json-magic@0.6.0': - resolution: {integrity: sha512-sy2yL7V8ZF7oUoMl46TjFbBfqZESDmKPfPXeyWeIcwKgjFwjM/FvLdGOOPNsYQ2tPZUCzg8QCNJk9QM+BKVyRg==} + '@scalar/json-magic@0.6.1': + resolution: {integrity: sha512-HJMPY5dUU3EXVS4EkjAFXo+uCrby/YFu/gljKDQnhYWRy5zQ0sJWrOEDcHS8nLoJRCIRD5tiVpCxnUnSb6OoAQ==} engines: {node: '>=20'} - '@scalar/openapi-parser@0.22.1': - resolution: {integrity: sha512-GJV7f9VLoyKbArMcWETbJ+xPqkopNBlb+oxEBSlSYYsoWc+p+sLAtuwObfJT8Ia+CStl5Y2ZRk/2DgCuy1vsaQ==} + '@scalar/openapi-parser@0.22.3': + resolution: {integrity: sha512-5Znbx9HVJb7EV9EJXJrUj+cs116QIBwX/hxkyaiLaaDL2w5S+z1rjtV+d0Jv7382FCtzAtfv/9llVuxZYPVqXA==} engines: {node: '>=20'} - '@scalar/openapi-types@0.4.0': - resolution: {integrity: sha512-vdFLzz7vETw6kS3bxWUHCBeFtJOnr1bk/+FXKyxKkd6TbcyT6USXkoelI704IaBdoXyOgGX10FD1IVrs4FwUow==} + '@scalar/openapi-types@0.5.0': + resolution: {integrity: sha512-HJBcLa+/mPP+3TCcQngj/iW5UqynRosOQdEETXjmdy6Ngw8wBjwIcT6C86J5jufJ6sI8++HYnt+e7pAvp5FO6A==} engines: {node: '>=20'} - '@scalar/openapi-upgrader@0.1.1': - resolution: {integrity: sha512-NlrQezuNGtejqS/SQshY9SjD4D66jiJcWgC2zfCS4muyfE2K85e0i6gegnKLZPSNrqoAvJyR78NPapme9z2/wg==} + '@scalar/openapi-upgrader@0.1.3': + resolution: {integrity: sha512-iROhcgy3vge6zsviPtoTLHale0nYt1PLhuMmJweQwJ0U23ZYyYhV5xgHtAd0OBEXuqT6rjYbJFvKOJZmJOwpNQ==} engines: {node: '>=20'} '@scarf/scarf@1.4.0': @@ -17728,18 +17728,18 @@ snapshots: optionalDependencies: '@types/node': 24.3.1 - '@scalar/helpers@0.0.11': {} + '@scalar/helpers@0.0.12': {} - '@scalar/json-magic@0.6.0': + '@scalar/json-magic@0.6.1': dependencies: - '@scalar/helpers': 0.0.11 + '@scalar/helpers': 0.0.12 yaml: 2.8.0 - '@scalar/openapi-parser@0.22.1': + '@scalar/openapi-parser@0.22.3': dependencies: - '@scalar/json-magic': 0.6.0 - '@scalar/openapi-types': 0.4.0 - '@scalar/openapi-upgrader': 0.1.1 + '@scalar/json-magic': 0.6.1 + '@scalar/openapi-types': 0.5.0 + '@scalar/openapi-upgrader': 0.1.3 ajv: 8.17.1 ajv-draft-04: 1.0.0(ajv@8.17.1) ajv-formats: 3.0.1(ajv@8.17.1) @@ -17747,13 +17747,13 @@ snapshots: leven: 4.1.0 yaml: 2.8.0 - '@scalar/openapi-types@0.4.0': + '@scalar/openapi-types@0.5.0': dependencies: zod: 4.1.11 - '@scalar/openapi-upgrader@0.1.1': + '@scalar/openapi-upgrader@0.1.3': dependencies: - '@scalar/openapi-types': 0.4.0 + '@scalar/openapi-types': 0.5.0 '@scarf/scarf@1.4.0': {} From 6693a2979ee08e316d6e55fd83df8cbdc910354e Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 20 Oct 2025 14:56:54 -0400 Subject: [PATCH 07/51] chore: fixes specification loading Signed-off-by: Vincent Biret --- .../src/cli/actions/convert/convert-file.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/openapi3/src/cli/actions/convert/convert-file.ts b/packages/openapi3/src/cli/actions/convert/convert-file.ts index 25e4c803f36..a1e52d9f11f 100644 --- a/packages/openapi3/src/cli/actions/convert/convert-file.ts +++ b/packages/openapi3/src/cli/actions/convert/convert-file.ts @@ -1,4 +1,6 @@ -import { dereference } from "@scalar/openapi-parser"; +import { dereference, load } from "@scalar/openapi-parser"; +import { fetchUrls } from "@scalar/openapi-parser/plugins/fetch-urls"; +import { readFiles } from "@scalar/openapi-parser/plugins/read-files"; import { formatTypeSpec, resolvePath } from "@typespec/compiler"; import { OpenAPI3Document } from "../../../types.js"; import { CliHost } from "../../types.js"; @@ -11,11 +13,14 @@ import { createContext } from "./utils/context.js"; export async function convertAction(host: CliHost, args: ConvertCliArgs) { // attempt to read the file const fullPath = resolvePath(process.cwd(), args.path); - const { specification } = await dereference(fullPath); - if (!specification) { + const { filesystem } = await load(fullPath, { + plugins: [fetchUrls(), readFiles()], + }); + const { schema } = await dereference(filesystem); + if (!schema) { throw new Error("Failed to dereference OpenAPI document"); } - const context = createContext(specification as OpenAPI3Document, console, args.namespace); + const context = createContext(schema as OpenAPI3Document, console, args.namespace); const program = transform(context); let mainTsp: string; try { From 93aa36789e4edc3a6124c803d4ce762df3e3b6c6 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 20 Oct 2025 15:05:39 -0400 Subject: [PATCH 08/51] chore: linting no deprecated Signed-off-by: Vincent Biret --- packages/openapi3/src/cli/actions/convert/convert-file.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/openapi3/src/cli/actions/convert/convert-file.ts b/packages/openapi3/src/cli/actions/convert/convert-file.ts index a1e52d9f11f..663634ad80c 100644 --- a/packages/openapi3/src/cli/actions/convert/convert-file.ts +++ b/packages/openapi3/src/cli/actions/convert/convert-file.ts @@ -13,6 +13,7 @@ import { createContext } from "./utils/context.js"; export async function convertAction(host: CliHost, args: ConvertCliArgs) { // attempt to read the file const fullPath = resolvePath(process.cwd(), args.path); + // eslint-disable-next-line @typescript-eslint/no-deprecated const { filesystem } = await load(fullPath, { plugins: [fetchUrls(), readFiles()], }); From 9dd13b4183b4ebb72e39df920db022f8f28fe70f Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 21 Oct 2025 13:30:57 -0400 Subject: [PATCH 09/51] chore: adds magic bundle dependency --- packages/openapi3/package.json | 1 + pnpm-lock.yaml | 3 +++ 2 files changed, 4 insertions(+) diff --git a/packages/openapi3/package.json b/packages/openapi3/package.json index a59c0e6f6cb..3fe3e6036b9 100644 --- a/packages/openapi3/package.json +++ b/packages/openapi3/package.json @@ -62,6 +62,7 @@ "!dist/test/**" ], "dependencies": { + "@scalar/json-magic": "^0.6.1", "@scalar/openapi-parser": "^0.22.3", "@scalar/openapi-types": "^0.5.0", "@typespec/asset-emitter": "workspace:^", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7dcb029aa45..685606bc1b7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1246,6 +1246,9 @@ importers: packages/openapi3: dependencies: + '@scalar/json-magic': + specifier: ^0.6.1 + version: 0.6.1 '@scalar/openapi-parser': specifier: ^0.22.3 version: 0.22.3 From d0d01053b4867ab019b93fe6307e1190e099e39c Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 21 Oct 2025 15:11:28 -0400 Subject: [PATCH 10/51] chore: updates from deprecated load method to bundle method Signed-off-by: Vincent Biret --- .../src/cli/actions/convert/convert-file.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/openapi3/src/cli/actions/convert/convert-file.ts b/packages/openapi3/src/cli/actions/convert/convert-file.ts index 663634ad80c..e2ae09f2adc 100644 --- a/packages/openapi3/src/cli/actions/convert/convert-file.ts +++ b/packages/openapi3/src/cli/actions/convert/convert-file.ts @@ -1,6 +1,7 @@ -import { dereference, load } from "@scalar/openapi-parser"; -import { fetchUrls } from "@scalar/openapi-parser/plugins/fetch-urls"; -import { readFiles } from "@scalar/openapi-parser/plugins/read-files"; +import { bundle } from "@scalar/json-magic/bundle"; +import { fetchUrls, parseJson, parseYaml } from "@scalar/json-magic/bundle/plugins/browser"; +import { readFiles } from "@scalar/json-magic/bundle/plugins/node"; +import { dereference } from "@scalar/openapi-parser"; import { formatTypeSpec, resolvePath } from "@typespec/compiler"; import { OpenAPI3Document } from "../../../types.js"; import { CliHost } from "../../types.js"; @@ -13,11 +14,11 @@ import { createContext } from "./utils/context.js"; export async function convertAction(host: CliHost, args: ConvertCliArgs) { // attempt to read the file const fullPath = resolvePath(process.cwd(), args.path); - // eslint-disable-next-line @typescript-eslint/no-deprecated - const { filesystem } = await load(fullPath, { - plugins: [fetchUrls(), readFiles()], + const data = await bundle(fullPath, { + plugins: [readFiles(), fetchUrls(), parseYaml(), parseJson()], + treeShake: false, }); - const { schema } = await dereference(filesystem); + const { schema } = await dereference(data); if (!schema) { throw new Error("Failed to dereference OpenAPI document"); } From baf11bc3a31b97db5d2edb2c84aee03e4a973842 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 27 Oct 2025 14:27:01 -0400 Subject: [PATCH 11/51] chore: removes dereference to avoid inlining of schemas Signed-off-by: Vincent Biret --- .../openapi3/src/cli/actions/convert/convert-file.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/openapi3/src/cli/actions/convert/convert-file.ts b/packages/openapi3/src/cli/actions/convert/convert-file.ts index e2ae09f2adc..a31c9cb742a 100644 --- a/packages/openapi3/src/cli/actions/convert/convert-file.ts +++ b/packages/openapi3/src/cli/actions/convert/convert-file.ts @@ -1,7 +1,7 @@ import { bundle } from "@scalar/json-magic/bundle"; import { fetchUrls, parseJson, parseYaml } from "@scalar/json-magic/bundle/plugins/browser"; import { readFiles } from "@scalar/json-magic/bundle/plugins/node"; -import { dereference } from "@scalar/openapi-parser"; +import { OpenAPI } from "@scalar/openapi-types"; import { formatTypeSpec, resolvePath } from "@typespec/compiler"; import { OpenAPI3Document } from "../../../types.js"; import { CliHost } from "../../types.js"; @@ -14,15 +14,14 @@ import { createContext } from "./utils/context.js"; export async function convertAction(host: CliHost, args: ConvertCliArgs) { // attempt to read the file const fullPath = resolvePath(process.cwd(), args.path); - const data = await bundle(fullPath, { + const data = (await bundle(fullPath, { plugins: [readFiles(), fetchUrls(), parseYaml(), parseJson()], treeShake: false, - }); - const { schema } = await dereference(data); - if (!schema) { + })) as OpenAPI.Document; + if (!data) { throw new Error("Failed to dereference OpenAPI document"); } - const context = createContext(schema as OpenAPI3Document, console, args.namespace); + const context = createContext(data as OpenAPI3Document, console, args.namespace); const program = transform(context); let mainTsp: string; try { From 5e95c6b4c0750d9bcbc010a4e358ca973510780b Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 27 Oct 2025 14:47:23 -0400 Subject: [PATCH 12/51] linting: error message Signed-off-by: Vincent Biret --- packages/openapi3/src/cli/actions/convert/convert-file.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/openapi3/src/cli/actions/convert/convert-file.ts b/packages/openapi3/src/cli/actions/convert/convert-file.ts index a31c9cb742a..5e150498bec 100644 --- a/packages/openapi3/src/cli/actions/convert/convert-file.ts +++ b/packages/openapi3/src/cli/actions/convert/convert-file.ts @@ -19,7 +19,7 @@ export async function convertAction(host: CliHost, args: ConvertCliArgs) { treeShake: false, })) as OpenAPI.Document; if (!data) { - throw new Error("Failed to dereference OpenAPI document"); + throw new Error("Failed to load OpenAPI document"); } const context = createContext(data as OpenAPI3Document, console, args.namespace); const program = transform(context); From bc4e839e35851fc0d2a83d8be75db4ba1ea5034c Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 27 Oct 2025 15:13:03 -0400 Subject: [PATCH 13/51] feat: adds new discriminator default mapping field Signed-off-by: Vincent Biret --- packages/openapi3/src/types.ts | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/packages/openapi3/src/types.ts b/packages/openapi3/src/types.ts index 88cf8b0a82b..73818b38deb 100644 --- a/packages/openapi3/src/types.ts +++ b/packages/openapi3/src/types.ts @@ -1,6 +1,25 @@ import { Diagnostic, Service } from "@typespec/compiler"; import { Contact, ExtensionKey, License } from "@typespec/openapi"; +/** +TODO checklist for @baywet: +- [ ] encoding https://github.com/BinkyLabs/OpenAPI.net/issues/51 +- [ ] media type components https://github.com/BinkyLabs/OpenAPI.net/issues/18 +- [x] discriminator defaultMapping https://github.com/BinkyLabs/OpenAPI.net/issues/3 +- [ ] response summary https://github.com/BinkyLabs/OpenAPI.net/issues/17 +- [ ] server name https://github.com/BinkyLabs/OpenAPI.net/issues/16 +- [ ] security scheme deprecated https://github.com/BinkyLabs/OpenAPI.net/issues/15 +- [ ] oauth flows https://github.com/BinkyLabs/OpenAPI.net/issues/14 +- [ ] examples data and serialized value https://github.com/BinkyLabs/OpenAPI.net/issues/12 +- [ ] xml fields https://github.com/BinkyLabs/OpenAPI.net/issues/11 +- [ ] media type item and prefix encoding https://github.com/BinkyLabs/OpenAPI.net/issues/10 +- [ ] document $self https://github.com/BinkyLabs/OpenAPI.net/issues/8 +- [ ] querystring parameter location https://github.com/BinkyLabs/OpenAPI.net/issues/7 +- [ ] cookie parameter style https://github.com/BinkyLabs/OpenAPI.net/issues/6 +- [ ] path item query and additional operations https://github.com/BinkyLabs/OpenAPI.net/issues/5 +- [ ] tag new fields https://github.com/BinkyLabs/OpenAPI.net/issues/4 +*/ + export type CommonOpenAPI3Schema = OpenAPI3Schema & OpenAPISchema3_1; export type SupportedOpenAPIDocuments = OpenAPI3Document | OpenAPIDocument3_1; @@ -373,6 +392,14 @@ export interface OpenAPI3Discriminator extends Extensions { mapping?: Record; } +export interface OpenAPIDiscriminator3_1 extends OpenAPI3Discriminator { + /** + * The default mapping value to be used when no other mapping matches. + * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields-21 + */ + defaultMapping?: string; +} + export type JsonType = "array" | "boolean" | "integer" | "number" | "object" | "string"; /** From de036c0f6e959ef1f40c3ba900afd53d978de644 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 27 Oct 2025 15:13:31 -0400 Subject: [PATCH 14/51] feat: add new OAS version in emitter options fix: default version for OAS emitter do not match valid values Signed-off-by: Vincent Biret --- packages/openapi3/src/lib.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/openapi3/src/lib.ts b/packages/openapi3/src/lib.ts index 12b5a1437b4..c2c8d6639b2 100644 --- a/packages/openapi3/src/lib.ts +++ b/packages/openapi3/src/lib.ts @@ -1,7 +1,7 @@ import { createTypeSpecLibrary, JSONSchemaType, paramMessage } from "@typespec/compiler"; export type FileType = "yaml" | "json"; -export type OpenAPIVersion = "3.0.0" | "3.1.0"; +export type OpenAPIVersion = "3.0.0" | "3.1.0" | "3.2.0"; export type ExperimentalParameterExamplesStrategy = "data" | "serialized"; export interface OpenAPI3EmitterOptions { /** @@ -44,7 +44,7 @@ export interface OpenAPI3EmitterOptions { * If more than one version is specified, then the output file * will be created inside a directory matching each specification version. * - * @default ["v3.0"] + * @default ["3.0.0"] * @internal */ "openapi-versions"?: OpenAPIVersion[]; From fd9b79ca69d2afd06177ef320fc0bb87df87dd36 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 27 Oct 2025 15:15:43 -0400 Subject: [PATCH 15/51] docs: adds the chronus entry for OAI 3.2.0 support --- .../changes/feat-oai-3-2-emission-2025-9-27-15-15-29.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .chronus/changes/feat-oai-3-2-emission-2025-9-27-15-15-29.md diff --git a/.chronus/changes/feat-oai-3-2-emission-2025-9-27-15-15-29.md b/.chronus/changes/feat-oai-3-2-emission-2025-9-27-15-15-29.md new file mode 100644 index 00000000000..8813c745190 --- /dev/null +++ b/.chronus/changes/feat-oai-3-2-emission-2025-9-27-15-15-29.md @@ -0,0 +1,7 @@ +--- +changeKind: feature +packages: + - "@typespec/openapi3" +--- + +added support for OpenAPI 3.2.0 emission \ No newline at end of file From afeff7519bc47e1fe2e40d46707904ac018aabd5 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 27 Oct 2025 15:20:51 -0400 Subject: [PATCH 16/51] feat: adds support for $self property in oai 3.2.0 Signed-off-by: Vincent Biret --- packages/openapi3/src/types.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/openapi3/src/types.ts b/packages/openapi3/src/types.ts index 73818b38deb..9deeb7d158f 100644 --- a/packages/openapi3/src/types.ts +++ b/packages/openapi3/src/types.ts @@ -13,7 +13,7 @@ TODO checklist for @baywet: - [ ] examples data and serialized value https://github.com/BinkyLabs/OpenAPI.net/issues/12 - [ ] xml fields https://github.com/BinkyLabs/OpenAPI.net/issues/11 - [ ] media type item and prefix encoding https://github.com/BinkyLabs/OpenAPI.net/issues/10 -- [ ] document $self https://github.com/BinkyLabs/OpenAPI.net/issues/8 +- [x] document $self https://github.com/BinkyLabs/OpenAPI.net/issues/8 - [ ] querystring parameter location https://github.com/BinkyLabs/OpenAPI.net/issues/7 - [ ] cookie parameter style https://github.com/BinkyLabs/OpenAPI.net/issues/6 - [ ] path item query and additional operations https://github.com/BinkyLabs/OpenAPI.net/issues/5 @@ -22,7 +22,7 @@ TODO checklist for @baywet: export type CommonOpenAPI3Schema = OpenAPI3Schema & OpenAPISchema3_1; -export type SupportedOpenAPIDocuments = OpenAPI3Document | OpenAPIDocument3_1; +export type SupportedOpenAPIDocuments = OpenAPI3Document | OpenAPIDocument3_1 | OpenAPIDocument3_2; export type Extensions = { [key in ExtensionKey]?: any; @@ -1163,3 +1163,13 @@ export interface OpenAPIComponents3_1 extends Extensions { callbacks?: Record>; pathItems?: Record; } + +export interface OpenAPIDocument3_2 extends Omit { + openapi: "3.2.0"; + /** + * This string MUST be in the form of a URI reference as defined. + * The $self field provides the self-assigned URI of this document, which also serves as its base URI. + * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields + */ + $self?: string; +} From 3fce7ef3f581b70fcc4c82bf4d6b3ada3725a303 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 27 Oct 2025 15:24:34 -0400 Subject: [PATCH 17/51] feat: adds support for server name in OAS 3.2.0 Signed-off-by: Vincent Biret --- packages/openapi3/src/types.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/openapi3/src/types.ts b/packages/openapi3/src/types.ts index 9deeb7d158f..65fa26e3fe0 100644 --- a/packages/openapi3/src/types.ts +++ b/packages/openapi3/src/types.ts @@ -7,7 +7,7 @@ TODO checklist for @baywet: - [ ] media type components https://github.com/BinkyLabs/OpenAPI.net/issues/18 - [x] discriminator defaultMapping https://github.com/BinkyLabs/OpenAPI.net/issues/3 - [ ] response summary https://github.com/BinkyLabs/OpenAPI.net/issues/17 -- [ ] server name https://github.com/BinkyLabs/OpenAPI.net/issues/16 +- [x] server name https://github.com/BinkyLabs/OpenAPI.net/issues/16 - [ ] security scheme deprecated https://github.com/BinkyLabs/OpenAPI.net/issues/15 - [ ] oauth flows https://github.com/BinkyLabs/OpenAPI.net/issues/14 - [ ] examples data and serialized value https://github.com/BinkyLabs/OpenAPI.net/issues/12 @@ -1164,7 +1164,7 @@ export interface OpenAPIComponents3_1 extends Extensions { pathItems?: Record; } -export interface OpenAPIDocument3_2 extends Omit { +export interface OpenAPIDocument3_2 extends Omit { openapi: "3.2.0"; /** * This string MUST be in the form of a URI reference as defined. @@ -1172,4 +1172,17 @@ export interface OpenAPIDocument3_2 extends Omit * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields */ $self?: string; + /** + * An array of Server Objects, which provide connectivity information to a target server. + * If the servers property is not provided, or is an empty array, the default value would be a Server Object with a url value of /. + */ + servers?: OpenAPIServer3_2[]; +} + +export interface OpenAPIServer3_2 extends OpenAPI3Server { + /** + * An optional unique string to refer to the host designated by the URL. + * @see https://spec.openapis.org/oas/latest#fixed-fields-3 + */ + name?: string; } From 07ff406468793f691e61f3b702d70fde51e79b96 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 27 Oct 2025 15:38:16 -0400 Subject: [PATCH 18/51] feat: adds support for media type components in 3.2 feat: adds support for item/prefix encoding and encoding fields for media type and encoding in 3.2.0 Signed-off-by: Vincent Biret --- packages/openapi3/src/types.ts | 58 +++++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/packages/openapi3/src/types.ts b/packages/openapi3/src/types.ts index 65fa26e3fe0..05309464699 100644 --- a/packages/openapi3/src/types.ts +++ b/packages/openapi3/src/types.ts @@ -3,8 +3,8 @@ import { Contact, ExtensionKey, License } from "@typespec/openapi"; /** TODO checklist for @baywet: -- [ ] encoding https://github.com/BinkyLabs/OpenAPI.net/issues/51 -- [ ] media type components https://github.com/BinkyLabs/OpenAPI.net/issues/18 +- [x] encoding https://github.com/BinkyLabs/OpenAPI.net/issues/51 +- [x] media type components https://github.com/BinkyLabs/OpenAPI.net/issues/18 - [x] discriminator defaultMapping https://github.com/BinkyLabs/OpenAPI.net/issues/3 - [ ] response summary https://github.com/BinkyLabs/OpenAPI.net/issues/17 - [x] server name https://github.com/BinkyLabs/OpenAPI.net/issues/16 @@ -12,12 +12,13 @@ TODO checklist for @baywet: - [ ] oauth flows https://github.com/BinkyLabs/OpenAPI.net/issues/14 - [ ] examples data and serialized value https://github.com/BinkyLabs/OpenAPI.net/issues/12 - [ ] xml fields https://github.com/BinkyLabs/OpenAPI.net/issues/11 -- [ ] media type item and prefix encoding https://github.com/BinkyLabs/OpenAPI.net/issues/10 +- [x] media type item and prefix encoding https://github.com/BinkyLabs/OpenAPI.net/issues/10 - [x] document $self https://github.com/BinkyLabs/OpenAPI.net/issues/8 - [ ] querystring parameter location https://github.com/BinkyLabs/OpenAPI.net/issues/7 - [ ] cookie parameter style https://github.com/BinkyLabs/OpenAPI.net/issues/6 - [ ] path item query and additional operations https://github.com/BinkyLabs/OpenAPI.net/issues/5 - [ ] tag new fields https://github.com/BinkyLabs/OpenAPI.net/issues/4 +- [ ] all references to media type and encoding need to be recursively updated in the 3.2 structure */ export type CommonOpenAPI3Schema = OpenAPI3Schema & OpenAPISchema3_1; @@ -1164,7 +1165,8 @@ export interface OpenAPIComponents3_1 extends Extensions { pathItems?: Record; } -export interface OpenAPIDocument3_2 extends Omit { +export interface OpenAPIDocument3_2 + extends Omit { openapi: "3.2.0"; /** * This string MUST be in the form of a URI reference as defined. @@ -1177,12 +1179,58 @@ export interface OpenAPIDocument3_2 extends Omit; + /** + * Applies nested Encoding Objects in the same manner as the {@link OpenAPIMediaType3_2.prefixEncoding} field + * @see https://spec.openapis.org/oas/v3.2.0#common-fixed-fields-0 + */ + prefixEncoding?: OpenAPIEncoding3_2[]; + /** + * Applies nested Encoding Objects in the same manner as the {@link OpenAPIMediaType3_2.itemEncoding} field + * @see https://spec.openapis.org/oas/v3.2.0#common-fixed-fields-0 + */ + itemEncoding?: OpenAPIEncoding3_2; +} + +export interface OpenAPIMediaType3_2 extends OpenAPI3MediaType { + /** + * A map between a property name and its encoding information + * @see https://spec.openapis.org/oas/v3.2.0#fixed-fields-11 + */ + encoding?: Record; + /** + * An array of positional encoding information + * @see https://spec.openapis.org/oas/v3.2.0#fixed-fields-11 + */ + prefixEncoding?: OpenAPIEncoding3_2[]; + /** + * A single Encoding Object that provides encoding information for multiple array items + * @see https://spec.openapis.org/oas/v3.2.0#fixed-fields-11 + */ + itemEncoding?: OpenAPIEncoding3_2; +} + +export interface OpenAPIComponents3_2 extends OpenAPIComponents3_1 { + /** + * An object to hold reusable {@link OpenAPIMediaType3_2} objects + * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields-5 + */ + mediaTypes?: Record; +} From e8daf3650c5c048a010d62fa785c38191deafe46 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 27 Oct 2025 15:48:29 -0400 Subject: [PATCH 19/51] feat: adds response summary field for OAI 3.2.0 Signed-off-by: Vincent Biret --- packages/openapi3/src/types.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/openapi3/src/types.ts b/packages/openapi3/src/types.ts index 05309464699..ff8d0be5668 100644 --- a/packages/openapi3/src/types.ts +++ b/packages/openapi3/src/types.ts @@ -6,7 +6,7 @@ TODO checklist for @baywet: - [x] encoding https://github.com/BinkyLabs/OpenAPI.net/issues/51 - [x] media type components https://github.com/BinkyLabs/OpenAPI.net/issues/18 - [x] discriminator defaultMapping https://github.com/BinkyLabs/OpenAPI.net/issues/3 -- [ ] response summary https://github.com/BinkyLabs/OpenAPI.net/issues/17 +- [x] response summary https://github.com/BinkyLabs/OpenAPI.net/issues/17 - [x] server name https://github.com/BinkyLabs/OpenAPI.net/issues/16 - [ ] security scheme deprecated https://github.com/BinkyLabs/OpenAPI.net/issues/15 - [ ] oauth flows https://github.com/BinkyLabs/OpenAPI.net/issues/14 @@ -19,6 +19,7 @@ TODO checklist for @baywet: - [ ] path item query and additional operations https://github.com/BinkyLabs/OpenAPI.net/issues/5 - [ ] tag new fields https://github.com/BinkyLabs/OpenAPI.net/issues/4 - [ ] all references to media type and encoding need to be recursively updated in the 3.2 structure +- [ ] all references to response need to be recursively updated in the 3.2 structure */ export type CommonOpenAPI3Schema = OpenAPI3Schema & OpenAPISchema3_1; @@ -1227,10 +1228,21 @@ export interface OpenAPIMediaType3_2 extends OpenAPI3MediaType { itemEncoding?: OpenAPIEncoding3_2; } -export interface OpenAPIComponents3_2 extends OpenAPIComponents3_1 { +export interface OpenAPIComponents3_2 extends Omit { /** * An object to hold reusable {@link OpenAPIMediaType3_2} objects * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields-5 */ mediaTypes?: Record; + responses?: Record>; +} + +export interface OpenAPIResponse3_2 extends Omit { + /** A map containing descriptions of potential response payloads. The key is a media type or media type range and the value describes it. For responses that match multiple keys, only the most specific key is applicable. e.g. text/plain overrides text/* */ + content?: Record; + /** + * A short summary of the meaning of the response. + * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields-14 + */ + summary?: string; } From 7bdc156f8717076df2da37c09b241abf6d4f3c76 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 28 Oct 2025 10:25:12 -0400 Subject: [PATCH 20/51] chore: adds placehold for specs props Signed-off-by: Vincent Biret --- packages/openapi3/src/openapi-spec-mappings.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/openapi3/src/openapi-spec-mappings.ts b/packages/openapi3/src/openapi-spec-mappings.ts index 504754aeed6..ad8efbb2cc4 100644 --- a/packages/openapi3/src/openapi-spec-mappings.ts +++ b/packages/openapi3/src/openapi-spec-mappings.ts @@ -75,6 +75,16 @@ export function getOpenApiSpecProps(specVersion: OpenAPIVersion): OpenApiSpecSpe getRawBinarySchema: getRawBinarySchema3_1, isRawBinarySchema: isRawBinarySchema3_1, }; + case "3.2.0": + return { + applyEncoding: applyEncoding3_1, + createRootDoc(program, serviceType, serviceVersion) { + return createRoot(program, serviceType, specVersion, serviceVersion); + }, + createSchemaEmitter: createSchemaEmitter3_1, + getRawBinarySchema: getRawBinarySchema3_1, + isRawBinarySchema: isRawBinarySchema3_1, + }; } } From 142f6d1ca9820a55f6e0414673dcfcec27365065 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 28 Oct 2025 10:25:53 -0400 Subject: [PATCH 21/51] tests: adds helper for OAI 3.2.0 Signed-off-by: Vincent Biret --- packages/openapi3/test/works-for.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/openapi3/test/works-for.ts b/packages/openapi3/test/works-for.ts index a4dfa9e64a9..fc20a374cd0 100644 --- a/packages/openapi3/test/works-for.ts +++ b/packages/openapi3/test/works-for.ts @@ -11,6 +11,7 @@ import { export const OpenAPISpecHelpers: Record = { "3.0.0": createSpecHelpers("3.0.0"), "3.1.0": createSpecHelpers("3.1.0"), + "3.2.0": createSpecHelpers("3.2.0"), }; export type ObjectSchemaIndexer = "additionalProperties" | "unevaluatedProperties"; From e8d1ec35e3dd031d2decea8c73e368cd7f7b8e65 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 28 Oct 2025 10:36:10 -0400 Subject: [PATCH 22/51] feat: adds support for OAI 3.2.0 security scheme deprecated field Signed-off-by: Vincent Biret --- packages/openapi3/src/types.ts | 41 ++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/packages/openapi3/src/types.ts b/packages/openapi3/src/types.ts index ff8d0be5668..d5ddbd83f2f 100644 --- a/packages/openapi3/src/types.ts +++ b/packages/openapi3/src/types.ts @@ -8,7 +8,7 @@ TODO checklist for @baywet: - [x] discriminator defaultMapping https://github.com/BinkyLabs/OpenAPI.net/issues/3 - [x] response summary https://github.com/BinkyLabs/OpenAPI.net/issues/17 - [x] server name https://github.com/BinkyLabs/OpenAPI.net/issues/16 -- [ ] security scheme deprecated https://github.com/BinkyLabs/OpenAPI.net/issues/15 +- [x] security scheme deprecated https://github.com/BinkyLabs/OpenAPI.net/issues/15 - [ ] oauth flows https://github.com/BinkyLabs/OpenAPI.net/issues/14 - [ ] examples data and serialized value https://github.com/BinkyLabs/OpenAPI.net/issues/12 - [ ] xml fields https://github.com/BinkyLabs/OpenAPI.net/issues/11 @@ -1228,13 +1228,19 @@ export interface OpenAPIMediaType3_2 extends OpenAPI3MediaType { itemEncoding?: OpenAPIEncoding3_2; } -export interface OpenAPIComponents3_2 extends Omit { +export interface OpenAPIComponents3_2 + extends Omit { /** * An object to hold reusable {@link OpenAPIMediaType3_2} objects * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields-5 */ mediaTypes?: Record; responses?: Record>; + /** + * An object to hold reusable {@link OpenAPISecurityScheme3_2} objects + * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields-5 + */ + securitySchemes?: Record>; } export interface OpenAPIResponse3_2 extends Omit { @@ -1246,3 +1252,34 @@ export interface OpenAPIResponse3_2 extends Omit { */ summary?: string; } + +export interface OpenAPISecuritySchemeBase3_2 extends OpenAPI3SecuritySchemeBase { + /** + * Specifies that a security scheme is deprecated and SHOULD be transitioned out of usage. + * + * @see https://spec.openapis.org/oas/v3.2.0.html#security-scheme-object + */ + deprecated?: boolean; +} + +export interface OpenAPIApiKeySecurityScheme3_2 + extends OpenAPISecuritySchemeBase3_2, + OpenAPI3ApiKeySecurityScheme {} + +export interface OpenAPIOAuth2SecurityScheme3_2 + extends OpenAPISecuritySchemeBase3_2, + OpenAPI3OAuth2SecurityScheme {} + +export interface OpenAPIOpenIdConnectSecurityScheme3_2 + extends OpenAPISecuritySchemeBase3_2, + OpenAPI3OpenIdConnectSecurityScheme {} + +export interface OpenAPIHttpSecurityScheme3_2 + extends OpenAPISecuritySchemeBase3_2, + OpenAPI3HttpSecurityScheme {} + +export type OpenAPISecurityScheme3_2 = + | OpenAPIApiKeySecurityScheme3_2 + | OpenAPIOAuth2SecurityScheme3_2 + | OpenAPIOpenIdConnectSecurityScheme3_2 + | OpenAPIHttpSecurityScheme3_2; From a8b6fc8a617d6806d38b29ecbeba9e336ca19b01 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 28 Oct 2025 10:44:30 -0400 Subject: [PATCH 23/51] feat: adds support for device auth flow in OAI 3.2.0 Signed-off-by: Vincent Biret --- packages/openapi3/src/types.ts | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/openapi3/src/types.ts b/packages/openapi3/src/types.ts index d5ddbd83f2f..9adeda0297f 100644 --- a/packages/openapi3/src/types.ts +++ b/packages/openapi3/src/types.ts @@ -9,7 +9,7 @@ TODO checklist for @baywet: - [x] response summary https://github.com/BinkyLabs/OpenAPI.net/issues/17 - [x] server name https://github.com/BinkyLabs/OpenAPI.net/issues/16 - [x] security scheme deprecated https://github.com/BinkyLabs/OpenAPI.net/issues/15 -- [ ] oauth flows https://github.com/BinkyLabs/OpenAPI.net/issues/14 +- [x] oauth flows https://github.com/BinkyLabs/OpenAPI.net/issues/14 - [ ] examples data and serialized value https://github.com/BinkyLabs/OpenAPI.net/issues/12 - [ ] xml fields https://github.com/BinkyLabs/OpenAPI.net/issues/11 - [x] media type item and prefix encoding https://github.com/BinkyLabs/OpenAPI.net/issues/10 @@ -1268,7 +1268,10 @@ export interface OpenAPIApiKeySecurityScheme3_2 export interface OpenAPIOAuth2SecurityScheme3_2 extends OpenAPISecuritySchemeBase3_2, - OpenAPI3OAuth2SecurityScheme {} + Omit { + /** An object containing configuration information for the flow types supported. */ + flows: OpenAPIOAuthFlows3_2; +} export interface OpenAPIOpenIdConnectSecurityScheme3_2 extends OpenAPISecuritySchemeBase3_2, @@ -1283,3 +1286,26 @@ export type OpenAPISecurityScheme3_2 = | OpenAPIOAuth2SecurityScheme3_2 | OpenAPIOpenIdConnectSecurityScheme3_2 | OpenAPIHttpSecurityScheme3_2; + +/** Configuration for the OAuth Client Credentials flow. Previously called application in OpenAPI 2.0. */ +export interface OpenAPIDeviceAuthorizationFlow3_2 extends OpenAPI3OAuth2Flow { + /** The token URL to be used for this flow. This MUST be in the form of a URL. */ + tokenUrl: string; + /** + * The device authorization URL to be used for this flow. This MUST be in the form of a URL. + * @see https://spec.openapis.org/oas/v3.2.0.html#oauth-flow-object + */ + deviceAuthorizationUrl: string; +} +/** + * Allows configuration of the supported OAuth Flows. + * + * @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.2.0.md#oauthFlowsObject + */ +export interface OpenAPIOAuthFlows3_2 extends OpenAPI3OAuthFlows { + /** + * Configuration for the OAuth 2.0 Device Authorization Grant flow. + * @see https://spec.openapis.org/oas/v3.2.0.html#oauth-flows-object + */ + deviceAuthorization?: OpenAPIDeviceAuthorizationFlow3_2; +} From 2fd692ac1b32ebdcb78e9a3bfcbfa5c1a7fa550e Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 28 Oct 2025 10:51:45 -0400 Subject: [PATCH 24/51] feat: adds support for OAI 3.2.0 example string and data value fields Signed-off-by: Vincent Biret --- packages/openapi3/src/types.ts | 41 +++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/packages/openapi3/src/types.ts b/packages/openapi3/src/types.ts index 9adeda0297f..09c27890fd1 100644 --- a/packages/openapi3/src/types.ts +++ b/packages/openapi3/src/types.ts @@ -10,7 +10,7 @@ TODO checklist for @baywet: - [x] server name https://github.com/BinkyLabs/OpenAPI.net/issues/16 - [x] security scheme deprecated https://github.com/BinkyLabs/OpenAPI.net/issues/15 - [x] oauth flows https://github.com/BinkyLabs/OpenAPI.net/issues/14 -- [ ] examples data and serialized value https://github.com/BinkyLabs/OpenAPI.net/issues/12 +- [x] examples data and serialized value https://github.com/BinkyLabs/OpenAPI.net/issues/12 - [ ] xml fields https://github.com/BinkyLabs/OpenAPI.net/issues/11 - [x] media type item and prefix encoding https://github.com/BinkyLabs/OpenAPI.net/issues/10 - [x] document $self https://github.com/BinkyLabs/OpenAPI.net/issues/8 @@ -20,6 +20,7 @@ TODO checklist for @baywet: - [ ] tag new fields https://github.com/BinkyLabs/OpenAPI.net/issues/4 - [ ] all references to media type and encoding need to be recursively updated in the 3.2 structure - [ ] all references to response need to be recursively updated in the 3.2 structure +- [ ] all references to example need to be recursively updated in the 3.2 structure (only parameter left) */ export type CommonOpenAPI3Schema = OpenAPI3Schema & OpenAPISchema3_1; @@ -1210,7 +1211,7 @@ export interface OpenAPIEncoding3_2 extends OpenAPI3Encoding { itemEncoding?: OpenAPIEncoding3_2; } -export interface OpenAPIMediaType3_2 extends OpenAPI3MediaType { +export interface OpenAPIMediaType3_2 extends Omit { /** * A map between a property name and its encoding information * @see https://spec.openapis.org/oas/v3.2.0#fixed-fields-11 @@ -1226,21 +1227,33 @@ export interface OpenAPIMediaType3_2 extends OpenAPI3MediaType { * @see https://spec.openapis.org/oas/v3.2.0#fixed-fields-11 */ itemEncoding?: OpenAPIEncoding3_2; + + /** Examples with title */ + examples?: Record>; } export interface OpenAPIComponents3_2 - extends Omit { + extends Omit { /** * An object to hold reusable {@link OpenAPIMediaType3_2} objects * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields-5 */ - mediaTypes?: Record; + mediaTypes?: Record>; + /** + * An object to hold reusable {@link OpenAPIResponse3_2} objects + * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields-5 + */ responses?: Record>; /** * An object to hold reusable {@link OpenAPISecurityScheme3_2} objects * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields-5 */ securitySchemes?: Record>; + /** + * An object to hold reusable {@link OpenAPIExample3_2} objects + * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields-5 + */ + examples?: Record>; } export interface OpenAPIResponse3_2 extends Omit { @@ -1300,7 +1313,7 @@ export interface OpenAPIDeviceAuthorizationFlow3_2 extends OpenAPI3OAuth2Flow { /** * Allows configuration of the supported OAuth Flows. * - * @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.2.0.md#oauthFlowsObject + * @see https://spec.openapis.org/oas/v3.2.0.html#oauthFlowsObject */ export interface OpenAPIOAuthFlows3_2 extends OpenAPI3OAuthFlows { /** @@ -1309,3 +1322,21 @@ export interface OpenAPIOAuthFlows3_2 extends OpenAPI3OAuthFlows { */ deviceAuthorization?: OpenAPIDeviceAuthorizationFlow3_2; } + +/** + * Allows sharing examples for operation responses. + * + * @see https://spec.openapis.org/oas/v3.2.0.html#example-object + */ +export interface OpenAPIExample3_2 extends OpenAPI3Example { + /** + * An example of the data structure that MUST be valid according to the relevant schema. + * @see https://spec.openapis.org/oas/v3.2.0.html#example-object + */ + dataValue?: unknown; + /** + * An example of the serialized form of the value, including encoding and escaping + * @see https://spec.openapis.org/oas/v3.2.0.html#example-object + */ + serializedValue?: string; +} From ce18be7918ce1c05f20dcdb6385e9aae85385ed8 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 28 Oct 2025 11:07:22 -0400 Subject: [PATCH 25/51] feat: adds support for xml new fields in OAI 3.2.0 Signed-off-by: Vincent Biret --- .../convert/generators/generate-types.ts | 11 +--- packages/openapi3/src/index.ts | 1 - packages/openapi3/src/schema-emitter.ts | 9 +-- packages/openapi3/src/types.ts | 55 +++++++++++++++---- .../openapi3/test/nullable-properties.test.ts | 4 +- 5 files changed, 51 insertions(+), 29 deletions(-) diff --git a/packages/openapi3/src/cli/actions/convert/generators/generate-types.ts b/packages/openapi3/src/cli/actions/convert/generators/generate-types.ts index ab790dec0f9..990e9b7e11a 100644 --- a/packages/openapi3/src/cli/actions/convert/generators/generate-types.ts +++ b/packages/openapi3/src/cli/actions/convert/generators/generate-types.ts @@ -1,10 +1,5 @@ import { printIdentifier } from "@typespec/compiler"; -import { - OpenAPI3Encoding, - OpenAPI3Schema, - OpenAPI3SchemaProperty, - Refable, -} from "../../../../types.js"; +import { OpenAPI3Encoding, OpenAPI3Schema, Refable } from "../../../../types.js"; import { Context } from "../utils/context.js"; import { getDecoratorsForSchema, @@ -466,7 +461,7 @@ export class SchemaToExpressionGenerator { } export function isReferencedEnumType( - propSchema: OpenAPI3SchemaProperty, + propSchema: Refable, context: Context, ): boolean { let isEnumType = false; @@ -486,7 +481,7 @@ export function isReferencedEnumType( } export function isReferencedUnionType( - propSchema: OpenAPI3SchemaProperty, + propSchema: Refable, context: Context, ): boolean { let isUnionType = false; diff --git a/packages/openapi3/src/index.ts b/packages/openapi3/src/index.ts index 59bb9a9f59e..a8ee7183636 100644 --- a/packages/openapi3/src/index.ts +++ b/packages/openapi3/src/index.ts @@ -49,7 +49,6 @@ export type { OpenAPI3Response, OpenAPI3Responses, OpenAPI3Schema, - OpenAPI3SchemaProperty, OpenAPI3SecurityScheme, OpenAPI3SecuritySchemeBase, OpenAPI3Server, diff --git a/packages/openapi3/src/schema-emitter.ts b/packages/openapi3/src/schema-emitter.ts index 9a569b7611d..be6c77e9294 100644 --- a/packages/openapi3/src/schema-emitter.ts +++ b/packages/openapi3/src/schema-emitter.ts @@ -71,12 +71,7 @@ import { JsonSchemaModule } from "./json-schema.js"; import { OpenAPI3EmitterOptions, reportDiagnostic } from "./lib.js"; import { ResolvedOpenAPI3EmitterOptions } from "./openapi.js"; import { getSchemaForStdScalars } from "./std-scalar-schemas.js"; -import { - CommonOpenAPI3Schema, - OpenAPI3Schema, - OpenAPI3SchemaProperty, - OpenAPISchema3_1, -} from "./types.js"; +import { CommonOpenAPI3Schema, OpenAPI3Schema, OpenAPISchema3_1, Refable } from "./types.js"; import { ensureValidComponentFixedFieldKey, getDefaultValue, @@ -363,7 +358,7 @@ export class OpenAPI3SchemaEmitterBase< return requiredProps.length > 0 ? requiredProps : undefined; } - modelProperties(model: Model): EmitterOutput> { + modelProperties(model: Model): EmitterOutput>> { const program = this.emitter.getProgram(); const props = new ObjectBuilder(); const visibility = this.emitter.getContext().visibility; diff --git a/packages/openapi3/src/types.ts b/packages/openapi3/src/types.ts index 09c27890fd1..5acd4accaba 100644 --- a/packages/openapi3/src/types.ts +++ b/packages/openapi3/src/types.ts @@ -11,7 +11,7 @@ TODO checklist for @baywet: - [x] security scheme deprecated https://github.com/BinkyLabs/OpenAPI.net/issues/15 - [x] oauth flows https://github.com/BinkyLabs/OpenAPI.net/issues/14 - [x] examples data and serialized value https://github.com/BinkyLabs/OpenAPI.net/issues/12 -- [ ] xml fields https://github.com/BinkyLabs/OpenAPI.net/issues/11 +- [x] xml fields https://github.com/BinkyLabs/OpenAPI.net/issues/11 - [x] media type item and prefix encoding https://github.com/BinkyLabs/OpenAPI.net/issues/10 - [x] document $self https://github.com/BinkyLabs/OpenAPI.net/issues/8 - [ ] querystring parameter location https://github.com/BinkyLabs/OpenAPI.net/issues/7 @@ -21,9 +21,10 @@ TODO checklist for @baywet: - [ ] all references to media type and encoding need to be recursively updated in the 3.2 structure - [ ] all references to response need to be recursively updated in the 3.2 structure - [ ] all references to example need to be recursively updated in the 3.2 structure (only parameter left) +- [ ] all references to schema need to be recursively updated in the 3.2 structure (all/any/one/additionalProperties...) */ -export type CommonOpenAPI3Schema = OpenAPI3Schema & OpenAPISchema3_1; +export type CommonOpenAPI3Schema = OpenAPI3Schema & OpenAPISchema3_1 & OpenAPISchema3_2; export type SupportedOpenAPIDocuments = OpenAPI3Document | OpenAPIDocument3_1 | OpenAPIDocument3_2; @@ -395,7 +396,7 @@ export interface OpenAPI3Discriminator extends Extensions { mapping?: Record; } -export interface OpenAPIDiscriminator3_1 extends OpenAPI3Discriminator { +export interface OpenAPIDiscriminator3_2 extends OpenAPI3Discriminator { /** * The default mapping value to be used when no other mapping matches. * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields-21 @@ -405,11 +406,6 @@ export interface OpenAPIDiscriminator3_1 extends OpenAPI3Discriminator { export type JsonType = "array" | "boolean" | "integer" | "number" | "object" | "string"; -/** - * Autorest allows a few properties to be next to $ref of a property. - */ -export type OpenAPI3SchemaProperty = Ref | OpenAPI3Schema; - export type OpenAPI3XmlSchema = Extensions & { name?: string; namespace?: string; @@ -605,7 +601,7 @@ export type OpenAPI3Schema = Extensions & { * order of the instance properties MAY be in any order. * */ - properties?: Record; + properties?: Record>; /** indicates that additional unlisted properties can exist in this schema */ additionalProperties?: boolean | Refable; @@ -1211,7 +1207,9 @@ export interface OpenAPIEncoding3_2 extends OpenAPI3Encoding { itemEncoding?: OpenAPIEncoding3_2; } -export interface OpenAPIMediaType3_2 extends Omit { +export interface OpenAPIMediaType3_2 extends Omit { + /** The schema defining the content of the request, response, or parameter. */ + schema?: Refable; /** * A map between a property name and its encoding information * @see https://spec.openapis.org/oas/v3.2.0#fixed-fields-11 @@ -1233,7 +1231,7 @@ export interface OpenAPIMediaType3_2 extends Omit } export interface OpenAPIComponents3_2 - extends Omit { + extends Omit { /** * An object to hold reusable {@link OpenAPIMediaType3_2} objects * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields-5 @@ -1254,6 +1252,11 @@ export interface OpenAPIComponents3_2 * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields-5 */ examples?: Record>; + /** + * An object to hold reusable {@link OpenAPISchema3_2} objects + * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields-5 + */ + schemas?: Record>; } export interface OpenAPIResponse3_2 extends Omit { @@ -1340,3 +1343,33 @@ export interface OpenAPIExample3_2 extends OpenAPI3Example { */ serializedValue?: string; } + +export interface OpenAPISchema3_2 extends Omit { + /** + * Adds additional metadata to describe the XML representation of this property. + * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields-20 + */ + xml?: OpenAPIXmlSchema3_2; + /** + * + * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields-20 + */ + discriminator?: OpenAPIDiscriminator3_2; +} + +export interface OpenAPIXmlSchema3_2 extends Omit { + /** + * @deprecated This property is deprecated and SHOULD be replaced by the {@link nodeType} property. + */ + attribute?: boolean; + /** + * @deprecated This property is deprecated and SHOULD be replaced by the {@link nodeType} property. + */ + wrapped?: boolean; + /** + * The type of the XML DOM node. + * @see https://spec.openapis.org/oas/v3.2.0.html#xml-node-types + * @default "none" + */ + nodeType?: "element" | "attribute" | "text" | "cdata" | "comment" | "none"; +} diff --git a/packages/openapi3/test/nullable-properties.test.ts b/packages/openapi3/test/nullable-properties.test.ts index 7fe703ec4b2..d67b23c066e 100644 --- a/packages/openapi3/test/nullable-properties.test.ts +++ b/packages/openapi3/test/nullable-properties.test.ts @@ -1,6 +1,6 @@ import { deepStrictEqual, ok } from "assert"; import { describe, expect, it } from "vitest"; -import { OpenAPI3SchemaProperty, OpenAPISchema3_1, Refable } from "../src/types.js"; +import { OpenAPI3Schema, OpenAPISchema3_1, Refable } from "../src/types.js"; import { worksFor } from "./works-for.js"; worksFor(["3.0.0"], ({ oapiForModel, openApiFor }) => { @@ -43,7 +43,7 @@ worksFor(["3.0.0"], ({ oapiForModel, openApiFor }) => { }); describe("when used in circular references", () => { - async function expectInCircularReference(ref: string, value: OpenAPI3SchemaProperty) { + async function expectInCircularReference(ref: string, value: Refable) { const res = await openApiFor( ` model Test { From ddb8c23c6dae65aec4b17c707de74f005aebe701 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 28 Oct 2025 11:53:28 -0400 Subject: [PATCH 26/51] tests: updates assertion to match new type information Signed-off-by: Vincent Biret --- packages/openapi3/test/get-openapi.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/openapi3/test/get-openapi.test.ts b/packages/openapi3/test/get-openapi.test.ts index 9b389fd099d..1d0757f8862 100644 --- a/packages/openapi3/test/get-openapi.test.ts +++ b/packages/openapi3/test/get-openapi.test.ts @@ -22,7 +22,10 @@ it("can get openapi as an object", async () => { const output = await getOpenAPI3(program, { "omit-unreachable-types": false }); const documentRecord = output[0]; ok(!documentRecord.versioned, "should not be versioned"); - strictEqual(documentRecord.document.components!.schemas!["Item"].type, "object"); + const itemSchema = documentRecord.document.components!.schemas!["Item"]; + ok(itemSchema, "should have Item schema"); + ok("type" in itemSchema, "Item schema should have type"); + strictEqual(itemSchema.type, "object"); }); it("has diagnostics", async () => { From f1a629472261b57efefd4ebe8c0f7434b3ac73b3 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 28 Oct 2025 12:15:24 -0400 Subject: [PATCH 27/51] feat: adds support for query and additional operations in OAI 3.2.0 Signed-off-by: Vincent Biret --- packages/openapi3/src/types.ts | 60 ++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 7 deletions(-) diff --git a/packages/openapi3/src/types.ts b/packages/openapi3/src/types.ts index 5acd4accaba..9252eba19b1 100644 --- a/packages/openapi3/src/types.ts +++ b/packages/openapi3/src/types.ts @@ -16,7 +16,7 @@ TODO checklist for @baywet: - [x] document $self https://github.com/BinkyLabs/OpenAPI.net/issues/8 - [ ] querystring parameter location https://github.com/BinkyLabs/OpenAPI.net/issues/7 - [ ] cookie parameter style https://github.com/BinkyLabs/OpenAPI.net/issues/6 -- [ ] path item query and additional operations https://github.com/BinkyLabs/OpenAPI.net/issues/5 +- [x] path item query and additional operations https://github.com/BinkyLabs/OpenAPI.net/issues/5 - [ ] tag new fields https://github.com/BinkyLabs/OpenAPI.net/issues/4 - [ ] all references to media type and encoding need to be recursively updated in the 3.2 structure - [ ] all references to response need to be recursively updated in the 3.2 structure @@ -151,15 +151,21 @@ export interface OpenAPI3Tag extends Extensions { externalDocs?: OpenAPI3ExternalDocs; } -export type HttpMethod = "get" | "put" | "post" | "delete" | "options" | "head" | "patch" | "trace"; +export type OpenAPI2HttpMethod = "get" | "put" | "post" | "delete" | "options" | "head" | "patch"; + +export type OpenAPI3HttpMethod = OpenAPI2HttpMethod | "trace"; + +export type OpenAPIHttpMethod3_2 = OpenAPI3HttpMethod | "query"; + +export type HttpMethod = OpenAPI2HttpMethod | OpenAPI3HttpMethod | OpenAPIHttpMethod3_2; /** * Describes the operations available on a single path. A Path Item may be empty, due to ACL constraints. The path itself is still exposed to the documentation viewer but they will not know which operations and parameters are available. * - * @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#pathItemObject + * @see https://spec.openapis.org/oas/v3.0.4.html#fixed-fields-6 */ export type OpenAPI3PathItem = { - [method in HttpMethod]?: OpenAPI3Operation; + [method in OpenAPI3HttpMethod]?: OpenAPI3Operation; } & { parameters?: OpenAPI3Parameter[] } & Extensions; export interface OpenAPI3Components extends Extensions { @@ -721,7 +727,7 @@ export type OpenAPI3Header = OpenAPI3ParameterBase & { export type OpenAPI3Operation = Extensions & { description?: string; summary?: string; - responses?: any; + responses?: Refable; tags?: string[]; operationId?: string; requestBody?: Refable; @@ -1164,7 +1170,7 @@ export interface OpenAPIComponents3_1 extends Extensions { } export interface OpenAPIDocument3_2 - extends Omit { + extends Omit { openapi: "3.2.0"; /** * This string MUST be in the form of a URI reference as defined. @@ -1172,6 +1178,14 @@ export interface OpenAPIDocument3_2 * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields */ $self?: string; + /** The available paths and operations for the API */ + paths: Record; + + /** + * The incoming webhooks that may be received as part of this API. + * The key name is a unique string to refer to each webhook. + */ + webhooks?: Record; /** * An array of Server Objects, which provide connectivity information to a target server. * If the servers property is not provided, or is an empty array, the default value would be a Server Object with a url value of /. @@ -1231,7 +1245,10 @@ export interface OpenAPIMediaType3_2 extends Omit { + extends Omit< + OpenAPIComponents3_1, + "responses" | "securitySchemes" | "examples" | "schemas" | "callbacks" | "pathItems" + > { /** * An object to hold reusable {@link OpenAPIMediaType3_2} objects * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields-5 @@ -1257,8 +1274,22 @@ export interface OpenAPIComponents3_2 * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields-5 */ schemas?: Record>; + /** + * An object to hold reusable {@link OpenAPIPathItem3_2} objects + * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields-5 + */ + callbacks?: Record>>; + /** + * An object to hold reusable {@link OpenAPIPathItem3_2} objects + * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields-5 + */ + pathItems?: Record>; } +export type OpenAPIResponses3_2 = { + [status: OpenAPI3StatusCode]: Refable; +} & Extensions; + export interface OpenAPIResponse3_2 extends Omit { /** A map containing descriptions of potential response payloads. The key is a media type or media type range and the value describes it. For responses that match multiple keys, only the most specific key is applicable. e.g. text/plain overrides text/* */ content?: Record; @@ -1373,3 +1404,18 @@ export interface OpenAPIXmlSchema3_2 extends Omit & { + responses?: Refable; +}; + +/** + * Describes the operations available on a single path. A Path Item may be empty, due to ACL constraints. The path itself is still exposed to the documentation viewer but they will not know which operations and parameters are available. + * + * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields-6 + */ +export type OpenAPIPathItem3_2 = Omit & { + [method in OpenAPIHttpMethod3_2]?: OpenAPIOperation3_2; +} & { + additionalOperations?: Record; +}; From 683ca371347e80548ed9004c11dac643351a188e Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 28 Oct 2025 12:32:36 -0400 Subject: [PATCH 28/51] feat: adds support for query string parameter in OAI 3.2.0 Signed-off-by: Vincent Biret --- packages/openapi3/src/types.ts | 48 +++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/packages/openapi3/src/types.ts b/packages/openapi3/src/types.ts index 9252eba19b1..17cc36fb3e3 100644 --- a/packages/openapi3/src/types.ts +++ b/packages/openapi3/src/types.ts @@ -14,7 +14,7 @@ TODO checklist for @baywet: - [x] xml fields https://github.com/BinkyLabs/OpenAPI.net/issues/11 - [x] media type item and prefix encoding https://github.com/BinkyLabs/OpenAPI.net/issues/10 - [x] document $self https://github.com/BinkyLabs/OpenAPI.net/issues/8 -- [ ] querystring parameter location https://github.com/BinkyLabs/OpenAPI.net/issues/7 +- [x] querystring parameter location https://github.com/BinkyLabs/OpenAPI.net/issues/7 - [ ] cookie parameter style https://github.com/BinkyLabs/OpenAPI.net/issues/6 - [x] path item query and additional operations https://github.com/BinkyLabs/OpenAPI.net/issues/5 - [ ] tag new fields https://github.com/BinkyLabs/OpenAPI.net/issues/4 @@ -663,6 +663,8 @@ export type OpenAPI3ParameterBase = Extensions & { example?: any; examples?: Record>; + /** A map containing the representations for the parameter. The key is the media type and the value describes it. The map MUST only contain one entry. */ + content?: Record; }; export type OpenAPI3QueryParameter = OpenAPI3ParameterBase & { @@ -1247,7 +1249,13 @@ export interface OpenAPIMediaType3_2 extends Omit { /** * An object to hold reusable {@link OpenAPIMediaType3_2} objects @@ -1284,6 +1292,11 @@ export interface OpenAPIComponents3_2 * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields-5 */ pathItems?: Record>; + /** + * An object to hold reusable {@link OpenAPIParameter3_2} objects + * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields-5 + */ + parameters?: Record>; } export type OpenAPIResponses3_2 = { @@ -1405,8 +1418,9 @@ export interface OpenAPIXmlSchema3_2 extends Omit & { +export type OpenAPIOperation3_2 = Omit & { responses?: Refable; + parameters?: Refable[]; }; /** @@ -1414,8 +1428,34 @@ export type OpenAPIOperation3_2 = Omit & { * * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields-6 */ -export type OpenAPIPathItem3_2 = Omit & { +export type OpenAPIPathItem3_2 = Omit & { [method in OpenAPIHttpMethod3_2]?: OpenAPIOperation3_2; } & { additionalOperations?: Record; + parameters?: Refable[]; +}; +export type OpenAPIParameterBase3_2 = Omit & { + /** A map containing the representations for the parameter. The key is the media type and the value describes it. The map MUST only contain one entry. */ + content?: Record>; + examples?: Record>; +}; +export type OpenAPIQueryStringParameter3_2 = Omit< + OpenAPIParameterBase3_2, + "style" | "explode" | "allowReserved" | "schema" +> & { + /** Name of the parameter. */ + name: string; + in: "querystring"; }; +export type OpenAPIHeaderParameter3_2 = Omit & + OpenAPIParameterBase3_2; +export type OpenAPIPathParameter3_2 = Omit & + OpenAPIParameterBase3_2; +export type OpenAPIQueryParameter3_2 = Omit & + OpenAPIParameterBase3_2; +export type OpenAPIParameter3_2 = + | OpenAPIHeaderParameter3_2 + | OpenAPIQueryParameter3_2 + | OpenAPIQueryStringParameter3_2 + | OpenAPIPathParameter3_2; +export type OpenAPIParameterType3_2 = OpenAPIParameter3_2["in"]; From 1c23a30a0d6547ca513e0631432a85d855d0663b Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 28 Oct 2025 12:40:28 -0400 Subject: [PATCH 29/51] feat: adds new tag fields for OAI 3.2.0 Signed-off-by: Vincent Biret --- packages/openapi3/src/types.ts | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/packages/openapi3/src/types.ts b/packages/openapi3/src/types.ts index 17cc36fb3e3..a95bb9adb35 100644 --- a/packages/openapi3/src/types.ts +++ b/packages/openapi3/src/types.ts @@ -17,7 +17,7 @@ TODO checklist for @baywet: - [x] querystring parameter location https://github.com/BinkyLabs/OpenAPI.net/issues/7 - [ ] cookie parameter style https://github.com/BinkyLabs/OpenAPI.net/issues/6 - [x] path item query and additional operations https://github.com/BinkyLabs/OpenAPI.net/issues/5 -- [ ] tag new fields https://github.com/BinkyLabs/OpenAPI.net/issues/4 +- [x] tag new fields https://github.com/BinkyLabs/OpenAPI.net/issues/4 - [ ] all references to media type and encoding need to be recursively updated in the 3.2 structure - [ ] all references to response need to be recursively updated in the 3.2 structure - [ ] all references to example need to be recursively updated in the 3.2 structure (only parameter left) @@ -1172,7 +1172,10 @@ export interface OpenAPIComponents3_1 extends Extensions { } export interface OpenAPIDocument3_2 - extends Omit { + extends Omit< + OpenAPIDocument3_1, + "openapi" | "servers" | "components" | "webhooks" | "paths" | "tags" + > { openapi: "3.2.0"; /** * This string MUST be in the form of a URI reference as defined. @@ -1193,6 +1196,13 @@ export interface OpenAPIDocument3_2 * If the servers property is not provided, or is an empty array, the default value would be a Server Object with a url value of /. */ servers?: OpenAPIServer3_2[]; + /** + * A list of tags used by the specification with additional metadata. + * The order of the tags can be used to reflect on their order by the parsing tools. + * Not all tags that are used by the Operation Object must be declared. + * The tags that are not declared MAY be organized randomly or based on the tools' logic. Each tag name in the list MUST be unique. + */ + tags?: OpenAPITag3_2[]; /** An element to hold various schemas for the specification. */ components?: OpenAPIComponents3_2; } @@ -1459,3 +1469,21 @@ export type OpenAPIParameter3_2 = | OpenAPIQueryStringParameter3_2 | OpenAPIPathParameter3_2; export type OpenAPIParameterType3_2 = OpenAPIParameter3_2["in"]; + +export interface OpenAPITag3_2 extends OpenAPI3Tag { + /** + * A short summary of the tag, used for display purposes. + * @see https://spec.openapis.org/oas/latest#fixed-fields-18 + */ + summary?: string; + /** + * The name of a tag that this tag is nested under. The named tag MUST exist in the API description, and circular references between parent and child tags MUST NOT be used. + * @see https://spec.openapis.org/oas/latest#fixed-fields-18 + */ + parent?: string; + /** + * A machine-readable string to categorize what sort of tag it is + * @see https://spec.openapis.org/oas/latest#fixed-fields-18 + */ + kind?: string; +} From 398e2e76c0fe9beb99826ec1970a48b0e51e9337 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 28 Oct 2025 12:56:53 -0400 Subject: [PATCH 30/51] tests: aligns structure with updated types Signed-off-by: Vincent Biret --- .../convert/transforms/transform-paths.ts | 6 ++- .../cli/actions/convert/utils/decorators.ts | 24 ++++++---- packages/openapi3/src/types.ts | 2 +- packages/openapi3/test/examples.test.ts | 44 ++++++++++++++++--- 4 files changed, 59 insertions(+), 17 deletions(-) diff --git a/packages/openapi3/src/cli/actions/convert/transforms/transform-paths.ts b/packages/openapi3/src/cli/actions/convert/transforms/transform-paths.ts index 7c466787a79..dbabd72b548 100644 --- a/packages/openapi3/src/cli/actions/convert/transforms/transform-paths.ts +++ b/packages/openapi3/src/cli/actions/convert/transforms/transform-paths.ts @@ -3,6 +3,8 @@ import { OpenAPI3Parameter, OpenAPI3PathItem, OpenAPI3RequestBody, + OpenAPIParameter3_2, + OpenAPIPathItem3_2, Refable, } from "../../../../types.js"; import { @@ -23,7 +25,7 @@ import { supportedHttpMethods } from "../utils/supported-http-methods.js"; * @returns */ export function transformPaths( - paths: Record, + paths: Record | Record, context: Context, ): TypeSpecOperation[] { const operations: TypeSpecOperation[] = []; @@ -111,7 +113,7 @@ function dedupeParameters( } function transformOperationParameter( - parameter: Refable, + parameter: Refable | Refable, ): Refable { if ("$ref" in parameter) { return { $ref: parameter.$ref }; diff --git a/packages/openapi3/src/cli/actions/convert/utils/decorators.ts b/packages/openapi3/src/cli/actions/convert/utils/decorators.ts index 302fc5f1189..1f1747f447e 100644 --- a/packages/openapi3/src/cli/actions/convert/utils/decorators.ts +++ b/packages/openapi3/src/cli/actions/convert/utils/decorators.ts @@ -4,7 +4,9 @@ import { Extensions, OpenAPI3Parameter, OpenAPI3Schema, + OpenAPIParameter3_2, OpenAPISchema3_1, + OpenAPISchema3_2, Refable, } from "../../../../types.js"; import { TSValue, TypeSpecDecorator } from "../interfaces.js"; @@ -40,11 +42,13 @@ function isExtensionKey(key: string): key is ExtensionKey { return key.startsWith("x-"); } -export function getParameterDecorators(parameter: OpenAPI3Parameter) { +export function getParameterDecorators(parameter: OpenAPI3Parameter | OpenAPIParameter3_2) { const decorators: TypeSpecDecorator[] = []; decorators.push(...getExtensions(parameter)); - decorators.push(...getDecoratorsForSchema(parameter.schema)); + if ("schema" in parameter && parameter.schema) { + decorators.push(...getDecoratorsForSchema(parameter.schema)); + } const locationDecorator = getLocationDecorator(parameter); if (locationDecorator) decorators.push(locationDecorator); @@ -52,7 +56,9 @@ export function getParameterDecorators(parameter: OpenAPI3Parameter) { return decorators; } -function getLocationDecorator(parameter: OpenAPI3Parameter): TypeSpecDecorator | undefined { +function getLocationDecorator( + parameter: OpenAPI3Parameter | OpenAPIParameter3_2, +): TypeSpecDecorator | undefined { if (!validLocations.includes(parameter.in)) return; const decorator: TypeSpecDecorator = { @@ -60,13 +66,15 @@ function getLocationDecorator(parameter: OpenAPI3Parameter): TypeSpecDecorator | args: [], }; + if (!("explode" in parameter)) return decorator; + let decoratorArgs: TypeSpecDecorator["args"][0] | undefined; switch (parameter.in) { case "header": - decoratorArgs = getHeaderArgs(parameter); + decoratorArgs = getHeaderArgs(parameter.explode ?? false); break; case "query": - decoratorArgs = getQueryArgs(parameter); + decoratorArgs = getQueryArgs({ explode: parameter.explode ?? false, style: parameter.style }); break; } @@ -98,7 +106,7 @@ export function normalizeObjectValueToTSValueExpression(value: any): string { } else return `${JSON.stringify(value)}`; } -function getQueryArgs(parameter: OpenAPI3Parameter): TSValue | undefined { +function getQueryArgs(parameter: { explode: boolean; style?: string }): TSValue | undefined { const queryOptions = getNormalizedQueryOptions(parameter); return createTSValueFromObjectValue(queryOptions); } @@ -140,7 +148,7 @@ function getNormalizedQueryOptions({ return queryOptions; } -function getHeaderArgs({ explode }: OpenAPI3Parameter): TSValue | undefined { +function getHeaderArgs(explode: boolean): TSValue | undefined { if (explode === true) { return createTSValue(`#{ explode: true }`); } @@ -149,7 +157,7 @@ function getHeaderArgs({ explode }: OpenAPI3Parameter): TSValue | undefined { } export function getDecoratorsForSchema( - schema: Refable, + schema: Refable, ): TypeSpecDecorator[] { const decorators: TypeSpecDecorator[] = []; diff --git a/packages/openapi3/src/types.ts b/packages/openapi3/src/types.ts index a95bb9adb35..87bcf20131f 100644 --- a/packages/openapi3/src/types.ts +++ b/packages/openapi3/src/types.ts @@ -1315,7 +1315,7 @@ export type OpenAPIResponses3_2 = { export interface OpenAPIResponse3_2 extends Omit { /** A map containing descriptions of potential response payloads. The key is a media type or media type range and the value describes it. For responses that match multiple keys, only the most specific key is applicable. e.g. text/plain overrides text/* */ - content?: Record; + content?: Record>; /** * A short summary of the meaning of the response. * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields-14 diff --git a/packages/openapi3/test/examples.test.ts b/packages/openapi3/test/examples.test.ts index df10f71f725..b18e1e59aa9 100644 --- a/packages/openapi3/test/examples.test.ts +++ b/packages/openapi3/test/examples.test.ts @@ -1,3 +1,4 @@ +import { ok } from "assert/strict"; import { describe, expect, it } from "vitest"; import { OpenAPI3Document, OpenAPI3Parameter, OpenAPI3RequestBody } from "../src/types.js"; import { openApiFor } from "./test-host.js"; @@ -115,7 +116,12 @@ worksFor(["3.0.0", "3.1.0"], ({ openApiFor }) => { op getPet(): {name: string, age: int32}; `, ); - expect(res.paths["/"].get?.responses[200].content["application/json"].example).toEqual({ + ok(res.paths["/"].get); + ok(res.paths["/"].get.responses); + ok("200" in res.paths["/"].get.responses); + ok("content" in res.paths["/"].get.responses["200"]); + ok(res.paths["/"].get.responses["200"].content); + expect(res.paths["/"].get?.responses["200"].content["application/json"].example).toEqual({ name: "Fluffy", age: 2, }); @@ -141,13 +147,21 @@ worksFor(["3.0.0", "3.1.0"], ({ openApiFor }) => { }; `, ); - expect(res.paths["/"].get?.responses[200].content["application/json"].examples).toEqual({ + ok(res.paths["/"].get); + ok(res.paths["/"].get.responses); + ok("200" in res.paths["/"].get.responses); + ok("content" in res.paths["/"].get.responses["200"]); + ok(res.paths["/"].get.responses["200"].content); + expect(res.paths["/"].get?.responses["200"].content["application/json"].examples).toEqual({ Ok: { summary: "Ok", value: { name: "Fluffy", age: 2 }, }, }); - expect(res.paths["/"].get?.responses[404].content["application/json"].examples).toEqual({ + ok("404" in res.paths["/"].get.responses); + ok("content" in res.paths["/"].get.responses["404"]); + ok(res.paths["/"].get.responses["404"].content); + expect(res.paths["/"].get?.responses["404"].content["application/json"].examples).toEqual({ "Not found": { summary: "Not found", value: { @@ -178,12 +192,20 @@ worksFor(["3.0.0", "3.1.0"], ({ openApiFor }) => { `, ); - expect(res.paths["/"].get?.responses[200].content["application/json"].examples).toEqual({ + ok(res.paths["/"].get); + ok(res.paths["/"].get.responses); + ok("200" in res.paths["/"].get.responses); + ok("content" in res.paths["/"].get.responses["200"]); + ok(res.paths["/"].get.responses["200"].content); + expect(res.paths["/"].get?.responses["200"].content["application/json"].examples).toEqual({ Ok: { summary: "Ok", value: { data: "Ok" }, }, }); + ok("4XX" in res.paths["/"].get.responses); + ok("content" in res.paths["/"].get.responses["4XX"]); + ok(res.paths["/"].get.responses["4XX"].content); expect(res.paths["/"].get?.responses["4XX"].content["application/json"].examples).toEqual({ "Not found": { summary: "Not found", @@ -209,7 +231,12 @@ worksFor(["3.0.0", "3.1.0"], ({ openApiFor }) => { op getPet(): {@body pet: {name: string, age: int32}}; `, ); - expect(res.paths["/"].get?.responses[200].content["application/json"].example).toEqual({ + ok(res.paths["/"].get); + ok(res.paths["/"].get.responses); + ok("200" in res.paths["/"].get.responses); + ok("content" in res.paths["/"].get.responses["200"]); + ok(res.paths["/"].get.responses["200"].content); + expect(res.paths["/"].get?.responses["200"].content["application/json"].example).toEqual({ name: "Fluffy", age: 2, }); @@ -229,7 +256,12 @@ worksFor(["3.0.0", "3.1.0"], ({ openApiFor }) => { op getPet(): {@bodyRoot pet: {name: string, age: int32}}; `, ); - expect(res.paths["/"].get?.responses[200].content["application/json"].example).toEqual({ + ok(res.paths["/"].get); + ok(res.paths["/"].get.responses); + ok("200" in res.paths["/"].get.responses); + ok("content" in res.paths["/"].get.responses["200"]); + ok(res.paths["/"].get.responses["200"].content); + expect(res.paths["/"].get?.responses["200"].content["application/json"].example).toEqual({ name: "Fluffy", age: 2, }); From 013b1517519e53cfcce1ddd4bfd6aa799d8a06bc Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 28 Oct 2025 13:02:11 -0400 Subject: [PATCH 31/51] tests: further typing alignment Signed-off-by: Vincent Biret --- .../test/tsp-openapi3/missing-operation-id.test.ts | 7 +++++-- packages/openapi3/test/tsp-openapi3/paths.test.ts | 11 +++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/openapi3/test/tsp-openapi3/missing-operation-id.test.ts b/packages/openapi3/test/tsp-openapi3/missing-operation-id.test.ts index 4858f2fc6e2..a597725c14c 100644 --- a/packages/openapi3/test/tsp-openapi3/missing-operation-id.test.ts +++ b/packages/openapi3/test/tsp-openapi3/missing-operation-id.test.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import { transformPaths } from "../../src/cli/actions/convert/transforms/transform-paths.js"; import { createContext } from "../../src/cli/actions/convert/utils/context.js"; -import { convertOpenAPI3Document } from "../../src/index.js"; +import { convertOpenAPI3Document, JsonSchemaType } from "../../src/index.js"; describe("Convert OpenAPI3 with missing operationId", () => { // Mock logger to capture warnings @@ -29,7 +29,10 @@ describe("Convert OpenAPI3 with missing operationId", () => { description: "Success", content: { "application/json": { - schema: { type: "array", items: { type: "string" } }, + schema: { + type: "array" as JsonSchemaType, + items: { type: "string" as JsonSchemaType }, + }, }, }, }, diff --git a/packages/openapi3/test/tsp-openapi3/paths.test.ts b/packages/openapi3/test/tsp-openapi3/paths.test.ts index 3e0d967b590..cd5f26e1ab6 100644 --- a/packages/openapi3/test/tsp-openapi3/paths.test.ts +++ b/packages/openapi3/test/tsp-openapi3/paths.test.ts @@ -668,7 +668,7 @@ model Foo { operationId: "getFoo", parameters: [], responses: { - [statusCode]: {}, + [statusCode]: {} as OpenAPI3Response, }, }, }, @@ -765,8 +765,9 @@ model Foo { parameters: [], responses: { [statusCode]: { + description: "Test Response", headers: { foo: { schema: { type: "string" } } }, - }, + } as OpenAPI3Response, }, }, }, @@ -816,8 +817,9 @@ model Foo { parameters: [], responses: { [statusCode]: { + description: "Test Response", content: { "application/json": { schema: { type: "string" } } }, - }, + } as OpenAPI3Response, }, }, }, @@ -939,8 +941,9 @@ model Foo { parameters: [], responses: { [statusCode]: { + description: "Test Response", content: { "application/json": { schema: { $ref: "#/components/schemas/Foo" } } }, - }, + } as OpenAPI3Response, }, }, }, From 93d310383e2551f571e3f1e4d65591e2d75fcce4 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 28 Oct 2025 13:08:16 -0400 Subject: [PATCH 32/51] feat: adds support for cookie style parameter in OAI 3.2.0 Signed-off-by: Vincent Biret --- packages/openapi3/src/types.ts | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/packages/openapi3/src/types.ts b/packages/openapi3/src/types.ts index 87bcf20131f..f535a87ee11 100644 --- a/packages/openapi3/src/types.ts +++ b/packages/openapi3/src/types.ts @@ -15,7 +15,7 @@ TODO checklist for @baywet: - [x] media type item and prefix encoding https://github.com/BinkyLabs/OpenAPI.net/issues/10 - [x] document $self https://github.com/BinkyLabs/OpenAPI.net/issues/8 - [x] querystring parameter location https://github.com/BinkyLabs/OpenAPI.net/issues/7 -- [ ] cookie parameter style https://github.com/BinkyLabs/OpenAPI.net/issues/6 +- [x] cookie parameter style https://github.com/BinkyLabs/OpenAPI.net/issues/6 - [x] path item query and additional operations https://github.com/BinkyLabs/OpenAPI.net/issues/5 - [x] tag new fields https://github.com/BinkyLabs/OpenAPI.net/issues/4 - [ ] all references to media type and encoding need to be recursively updated in the 3.2 structure @@ -703,10 +703,23 @@ export type OpenAPI3HeaderParameter = OpenAPI3ParameterBase & { */ style?: "simple"; }; +export type OpenAPI3CookieParameter = OpenAPI3ParameterBase & { + /** Name of the parameter. */ + name: string; + in: "cookie"; + /** + * Describes how the parameter value will be serialized depending on the type of the parameter value. + * + * Default value for header parameters is simple. + * @see https://github.com/OAI/OpenAPI-Specification/blob/3.0.3/versions/3.0.2.md#style-values + */ + style?: "form"; +}; export type OpenAPI3Parameter = | OpenAPI3HeaderParameter | OpenAPI3QueryParameter - | OpenAPI3PathParameter; + | OpenAPI3PathParameter + | OpenAPI3CookieParameter; export type OpenAPI3ParameterType = OpenAPI3Parameter["in"]; /** @@ -1457,6 +1470,19 @@ export type OpenAPIQueryStringParameter3_2 = Omit< name: string; in: "querystring"; }; +export type OpenAPICookieParameter3_2 = Omit< + OpenAPI3CookieParameter, + "content" | "examples" | "style" +> & + OpenAPIParameterBase3_2 & { + /** + * Describes how the parameter value will be serialized depending on the type of the parameter value. + * + * Default value for header parameters is simple. + * @see https://spec.openapis.org/oas/latest#fixed-fields-9 + */ + style?: "form" | "cookie"; + }; export type OpenAPIHeaderParameter3_2 = Omit & OpenAPIParameterBase3_2; export type OpenAPIPathParameter3_2 = Omit & @@ -1467,7 +1493,8 @@ export type OpenAPIParameter3_2 = | OpenAPIHeaderParameter3_2 | OpenAPIQueryParameter3_2 | OpenAPIQueryStringParameter3_2 - | OpenAPIPathParameter3_2; + | OpenAPIPathParameter3_2 + | OpenAPICookieParameter3_2; export type OpenAPIParameterType3_2 = OpenAPIParameter3_2["in"]; export interface OpenAPITag3_2 extends OpenAPI3Tag { From e92b9a688eaca460ccbc5abfad2e17d1e878909a Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 28 Oct 2025 13:10:37 -0400 Subject: [PATCH 33/51] fix: schema mapping for parameters import Signed-off-by: Vincent Biret --- .../src/cli/actions/convert/transforms/transform-paths.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/openapi3/src/cli/actions/convert/transforms/transform-paths.ts b/packages/openapi3/src/cli/actions/convert/transforms/transform-paths.ts index dbabd72b548..730adb58e78 100644 --- a/packages/openapi3/src/cli/actions/convert/transforms/transform-paths.ts +++ b/packages/openapi3/src/cli/actions/convert/transforms/transform-paths.ts @@ -125,7 +125,7 @@ function transformOperationParameter( doc: parameter.description, decorators: getParameterDecorators(parameter), isOptional: !parameter.required, - schema: parameter.schema, + schema: "schema" in parameter ? parameter.schema : {}, }; } From 35df978562674812dd886df1b5baa4a03a8e9201 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 29 Oct 2025 11:47:34 -0400 Subject: [PATCH 34/51] chore: aligns usage of 3.2.0 media type Signed-off-by: Vincent Biret --- packages/openapi3/src/types.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/openapi3/src/types.ts b/packages/openapi3/src/types.ts index f535a87ee11..6cff9d4acee 100644 --- a/packages/openapi3/src/types.ts +++ b/packages/openapi3/src/types.ts @@ -18,7 +18,7 @@ TODO checklist for @baywet: - [x] cookie parameter style https://github.com/BinkyLabs/OpenAPI.net/issues/6 - [x] path item query and additional operations https://github.com/BinkyLabs/OpenAPI.net/issues/5 - [x] tag new fields https://github.com/BinkyLabs/OpenAPI.net/issues/4 -- [ ] all references to media type and encoding need to be recursively updated in the 3.2 structure +- [x] all references to media type and encoding need to be recursively updated in the 3.2 structure - [ ] all references to response need to be recursively updated in the 3.2 structure - [ ] all references to example need to be recursively updated in the 3.2 structure (only parameter left) - [ ] all references to schema need to be recursively updated in the 3.2 structure (all/any/one/additionalProperties...) @@ -1279,6 +1279,7 @@ export interface OpenAPIComponents3_2 | "callbacks" | "pathItems" | "parameters" + | "requestBodies" > { /** * An object to hold reusable {@link OpenAPIMediaType3_2} objects @@ -1295,6 +1296,11 @@ export interface OpenAPIComponents3_2 * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields-5 */ securitySchemes?: Record>; + /** + * An object to hold reusable {@link OpenAPIRequestBody3_2} objects + * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields-5 + */ + requestBodies?: Record>; /** * An object to hold reusable {@link OpenAPIExample3_2} objects * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields-5 @@ -1441,9 +1447,13 @@ export interface OpenAPIXmlSchema3_2 extends Omit & { +export type OpenAPIOperation3_2 = Omit< + OpenAPI3Operation, + "responses" | "parameters" | "requestBody" +> & { responses?: Refable; parameters?: Refable[]; + requestBody?: Refable; }; /** @@ -1514,3 +1524,8 @@ export interface OpenAPITag3_2 extends OpenAPI3Tag { */ kind?: string; } + +export interface OpenAPIRequestBody3_2 extends Omit { + /** A map containing descriptions of potential request payloads. The key is a media type or media type range and the value describes it. For requests that match multiple keys, only the most specific key is applicable. e.g. text/plain overrides text/* */ + content?: Record>; +} From 888f97e23167433eeb585cd883c8b2d1d652fb8c Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 29 Oct 2025 11:50:48 -0400 Subject: [PATCH 35/51] chore: validate response and example 3.2 are referenced all the way up Signed-off-by: Vincent Biret --- packages/openapi3/src/types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/openapi3/src/types.ts b/packages/openapi3/src/types.ts index 6cff9d4acee..2bc528d83eb 100644 --- a/packages/openapi3/src/types.ts +++ b/packages/openapi3/src/types.ts @@ -19,8 +19,8 @@ TODO checklist for @baywet: - [x] path item query and additional operations https://github.com/BinkyLabs/OpenAPI.net/issues/5 - [x] tag new fields https://github.com/BinkyLabs/OpenAPI.net/issues/4 - [x] all references to media type and encoding need to be recursively updated in the 3.2 structure -- [ ] all references to response need to be recursively updated in the 3.2 structure -- [ ] all references to example need to be recursively updated in the 3.2 structure (only parameter left) +- [x] all references to response need to be recursively updated in the 3.2 structure +- [x] all references to example need to be recursively updated in the 3.2 structure (only parameter left) - [ ] all references to schema need to be recursively updated in the 3.2 structure (all/any/one/additionalProperties...) */ From bb135317c4a65b54a3e8fb0cd811a2e03a9a58b3 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 29 Oct 2025 12:16:57 -0400 Subject: [PATCH 36/51] chore: aligns schema references for OAI 3.2.0 Signed-off-by: Vincent Biret --- packages/openapi3/src/types.ts | 63 ++++++++++++++-------------------- 1 file changed, 26 insertions(+), 37 deletions(-) diff --git a/packages/openapi3/src/types.ts b/packages/openapi3/src/types.ts index 2bc528d83eb..791f6d076f2 100644 --- a/packages/openapi3/src/types.ts +++ b/packages/openapi3/src/types.ts @@ -1,29 +1,6 @@ import { Diagnostic, Service } from "@typespec/compiler"; import { Contact, ExtensionKey, License } from "@typespec/openapi"; -/** -TODO checklist for @baywet: -- [x] encoding https://github.com/BinkyLabs/OpenAPI.net/issues/51 -- [x] media type components https://github.com/BinkyLabs/OpenAPI.net/issues/18 -- [x] discriminator defaultMapping https://github.com/BinkyLabs/OpenAPI.net/issues/3 -- [x] response summary https://github.com/BinkyLabs/OpenAPI.net/issues/17 -- [x] server name https://github.com/BinkyLabs/OpenAPI.net/issues/16 -- [x] security scheme deprecated https://github.com/BinkyLabs/OpenAPI.net/issues/15 -- [x] oauth flows https://github.com/BinkyLabs/OpenAPI.net/issues/14 -- [x] examples data and serialized value https://github.com/BinkyLabs/OpenAPI.net/issues/12 -- [x] xml fields https://github.com/BinkyLabs/OpenAPI.net/issues/11 -- [x] media type item and prefix encoding https://github.com/BinkyLabs/OpenAPI.net/issues/10 -- [x] document $self https://github.com/BinkyLabs/OpenAPI.net/issues/8 -- [x] querystring parameter location https://github.com/BinkyLabs/OpenAPI.net/issues/7 -- [x] cookie parameter style https://github.com/BinkyLabs/OpenAPI.net/issues/6 -- [x] path item query and additional operations https://github.com/BinkyLabs/OpenAPI.net/issues/5 -- [x] tag new fields https://github.com/BinkyLabs/OpenAPI.net/issues/4 -- [x] all references to media type and encoding need to be recursively updated in the 3.2 structure -- [x] all references to response need to be recursively updated in the 3.2 structure -- [x] all references to example need to be recursively updated in the 3.2 structure (only parameter left) -- [ ] all references to schema need to be recursively updated in the 3.2 structure (all/any/one/additionalProperties...) -*/ - export type CommonOpenAPI3Schema = OpenAPI3Schema & OpenAPISchema3_1 & OpenAPISchema3_2; export type SupportedOpenAPIDocuments = OpenAPI3Document | OpenAPIDocument3_1 | OpenAPIDocument3_2; @@ -657,7 +634,7 @@ export type OpenAPI3ParameterBase = Extensions & { /** When this is true, parameter values of type array or object generate separate parameters for each value of the array or key-value pair of the map. For other types of parameters this property has no effect. When style is form, the default value is true. For all other styles, the default value is false. */ explode?: boolean; - schema: OpenAPI3Schema; + schema?: Refable; /** A free-form property to include an example of an instance for this schema. To represent examples that cannot be naturally represented in JSON or YAML, a string value can be used to contain the example with escaping where necessary. */ example?: any; @@ -1417,18 +1394,26 @@ export interface OpenAPIExample3_2 extends OpenAPI3Example { serializedValue?: string; } -export interface OpenAPISchema3_2 extends Omit { - /** - * Adds additional metadata to describe the XML representation of this property. - * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields-20 - */ - xml?: OpenAPIXmlSchema3_2; - /** - * - * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields-20 - */ - discriminator?: OpenAPIDiscriminator3_2; -} +export type OpenAPISchema3_2 = JsonSchema< + { + /** + * Adds additional metadata to describe the XML representation of this property. + * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields-20 + */ + xml?: OpenAPIXmlSchema3_2; + /** + * + * @see https://spec.openapis.org/oas/v3.2.0.html#fixed-fields-20 + */ + discriminator?: OpenAPIDiscriminator3_2; + /** + * Additional external documentation for this schema. + * + * @see https://spec.openapis.org/oas/v3.1.1.html#external-documentation-object + */ + externalDocs?: OpenAPI3ExternalDocs; + } & Extensions +>; export interface OpenAPIXmlSchema3_2 extends Omit { /** @@ -1467,10 +1452,14 @@ export type OpenAPIPathItem3_2 = Omit; parameters?: Refable[]; }; -export type OpenAPIParameterBase3_2 = Omit & { +export type OpenAPIParameterBase3_2 = Omit< + OpenAPI3ParameterBase, + "content" | "examples" | "schema" +> & { /** A map containing the representations for the parameter. The key is the media type and the value describes it. The map MUST only contain one entry. */ content?: Record>; examples?: Record>; + schema?: Refable; }; export type OpenAPIQueryStringParameter3_2 = Omit< OpenAPIParameterBase3_2, From 4d7cd09a7a381a76715d52149618d5a9defcd7a7 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 29 Oct 2025 12:25:07 -0400 Subject: [PATCH 37/51] feat: adds 3_2 types exports Signed-off-by: Vincent Biret --- packages/openapi3/src/index.ts | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/packages/openapi3/src/index.ts b/packages/openapi3/src/index.ts index a8ee7183636..c504a61eef8 100644 --- a/packages/openapi3/src/index.ts +++ b/packages/openapi3/src/index.ts @@ -60,9 +60,40 @@ export type { OpenAPI3VersionedDocumentRecord, OpenAPI3VersionedServiceRecord, OpenAPI3XmlSchema, + OpenAPIApiKeySecurityScheme3_2, OpenAPIComponents3_1, + OpenAPIComponents3_2, + OpenAPICookieParameter3_2, + OpenAPIDeviceAuthorizationFlow3_2, OpenAPIDocument3_1, + OpenAPIDocument3_2, + OpenAPIEncoding3_2, + OpenAPIExample3_2, + OpenAPIHeaderParameter3_2, + OpenAPIHttpMethod3_2, + OpenAPIHttpSecurityScheme3_2, + OpenAPIMediaType3_2, + OpenAPIOAuth2SecurityScheme3_2, + OpenAPIOAuthFlows3_2, + OpenAPIOpenIdConnectSecurityScheme3_2, + OpenAPIOperation3_2, + OpenAPIParameter3_2, + OpenAPIParameterBase3_2, + OpenAPIParameterType3_2, + OpenAPIPathItem3_2, + OpenAPIPathParameter3_2, + OpenAPIQueryParameter3_2, + OpenAPIQueryStringParameter3_2, + OpenAPIRequestBody3_2, + OpenAPIResponse3_2, + OpenAPIResponses3_2, OpenAPISchema3_1, + OpenAPISchema3_2, + OpenAPISecurityScheme3_2, + OpenAPISecuritySchemeBase3_2, + OpenAPIServer3_2, + OpenAPITag3_2, + OpenAPIXmlSchema3_2, Ref, Refable, SupportedOpenAPIDocuments, From b274c748dbd3441ddb4047ccc88b8354e91475a1 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 29 Oct 2025 13:01:02 -0400 Subject: [PATCH 38/51] fix: types misalignments Signed-off-by: Vincent Biret --- .../generate-response-expressions.ts | 6 ++--- .../src/cli/actions/convert/interfaces.ts | 23 +++++++++++-------- .../transform-component-parameters.ts | 4 ++-- .../convert/transforms/transform-paths.ts | 9 ++++---- packages/openapi3/src/index.ts | 1 + packages/openapi3/src/openapi.ts | 8 ++++++- packages/openapi3/src/types.ts | 4 +++- 7 files changed, 35 insertions(+), 20 deletions(-) diff --git a/packages/openapi3/src/cli/actions/convert/generators/generate-response-expressions.ts b/packages/openapi3/src/cli/actions/convert/generators/generate-response-expressions.ts index 2218f67c52f..cf2ddd0a2d3 100644 --- a/packages/openapi3/src/cli/actions/convert/generators/generate-response-expressions.ts +++ b/packages/openapi3/src/cli/actions/convert/generators/generate-response-expressions.ts @@ -359,10 +359,10 @@ function convertHeaderToProperty( return { name: normalizedName, - decorators: [headerDecorator, ...getDecoratorsForSchema(header.schema)], - doc: props.header.description ?? header.description ?? header.schema.description, + decorators: [headerDecorator, ...(header.schema ? getDecoratorsForSchema(header.schema) : [])], + doc: props.header.description ?? header.description ?? header.schema?.description, isOptional: !header.required, - schema: header.schema, + schema: header.schema ?? {}, }; } diff --git a/packages/openapi3/src/cli/actions/convert/interfaces.ts b/packages/openapi3/src/cli/actions/convert/interfaces.ts index 7a61c49561a..43e51a4cb1e 100644 --- a/packages/openapi3/src/cli/actions/convert/interfaces.ts +++ b/packages/openapi3/src/cli/actions/convert/interfaces.ts @@ -1,5 +1,10 @@ import { Contact, License } from "@typespec/openapi"; -import { OpenAPI3Encoding, OpenAPI3Responses, OpenAPI3Schema, Refable } from "../../../types.js"; +import { + OpenAPI3Encoding, + OpenAPI3Responses, + Refable, + SupportedOpenAPISchema, +} from "../../../types.js"; export interface TypeSpecProgram { serviceInfo: TypeSpecServiceInfo; @@ -87,7 +92,7 @@ export interface TypeSpecModel extends TypeSpecDeclaration { kind: "model"; properties: TypeSpecModelProperty[]; - additionalProperties?: Refable; + additionalProperties?: Refable; /** * Note: Only one of `extends` or `is` should be specified. */ @@ -99,7 +104,7 @@ export interface TypeSpecModel extends TypeSpecDeclaration { /** * Defaults to 'object' */ - type?: OpenAPI3Schema["type"]; + type?: SupportedOpenAPISchema["type"]; spread?: string[]; @@ -120,17 +125,17 @@ export interface TypeSpecAlias extends Pick; + schema: Refable; } export interface TypeSpecOperation extends TypeSpecDeclaration { @@ -160,7 +165,7 @@ export interface TypeSpecOperationParameter { doc?: string; decorators: TypeSpecDecorator[]; isOptional: boolean; - schema: Refable; + schema: Refable; } export interface TypeSpecRequestBody { @@ -168,5 +173,5 @@ export interface TypeSpecRequestBody { doc?: string; isOptional: boolean; encoding?: Record; - schema?: Refable; + schema?: Refable; } diff --git a/packages/openapi3/src/cli/actions/convert/transforms/transform-component-parameters.ts b/packages/openapi3/src/cli/actions/convert/transforms/transform-component-parameters.ts index 9fc2317affc..c14044c74d8 100644 --- a/packages/openapi3/src/cli/actions/convert/transforms/transform-component-parameters.ts +++ b/packages/openapi3/src/cli/actions/convert/transforms/transform-component-parameters.ts @@ -48,8 +48,8 @@ function getModelPropertyFromParameter(parameter: OpenAPI3Parameter): TypeSpecMo return { name: printIdentifier(parameter.name), isOptional: !parameter.required, - doc: parameter.description ?? parameter.schema.description, + doc: parameter.description ?? parameter.schema?.description, decorators: getParameterDecorators(parameter), - schema: parameter.schema, + schema: parameter.schema ?? {}, }; } diff --git a/packages/openapi3/src/cli/actions/convert/transforms/transform-paths.ts b/packages/openapi3/src/cli/actions/convert/transforms/transform-paths.ts index 730adb58e78..1a59d4fe71a 100644 --- a/packages/openapi3/src/cli/actions/convert/transforms/transform-paths.ts +++ b/packages/openapi3/src/cli/actions/convert/transforms/transform-paths.ts @@ -5,6 +5,7 @@ import { OpenAPI3RequestBody, OpenAPIParameter3_2, OpenAPIPathItem3_2, + OpenAPIRequestBody3_2, Refable, } from "../../../../types.js"; import { @@ -125,7 +126,7 @@ function transformOperationParameter( doc: parameter.description, decorators: getParameterDecorators(parameter), isOptional: !parameter.required, - schema: "schema" in parameter ? parameter.schema : {}, + schema: "schema" in parameter ? (parameter.schema ?? {}) : {}, }; } @@ -243,7 +244,7 @@ function splitOperationByContentType( } function transformRequestBodies( - requestBodies: Refable | undefined, + requestBodies: Refable | Refable | undefined, context: Context, ): TypeSpecRequestBody[] { if (!requestBodies) { @@ -267,8 +268,8 @@ function transformRequestBodies( contentType, isOptional: !requestBodies.required, doc: description ?? requestBodies.description, - encoding: contentBody.encoding, - schema: contentBody.schema, + encoding: "encoding" in contentBody ? contentBody.encoding : undefined, + schema: "schema" in contentBody ? contentBody.schema : {}, }); } diff --git a/packages/openapi3/src/index.ts b/packages/openapi3/src/index.ts index c504a61eef8..c65e0b5de96 100644 --- a/packages/openapi3/src/index.ts +++ b/packages/openapi3/src/index.ts @@ -97,6 +97,7 @@ export type { Ref, Refable, SupportedOpenAPIDocuments, + SupportedOpenAPISchema, } from "./types.js"; /** @internal */ diff --git a/packages/openapi3/src/openapi.ts b/packages/openapi3/src/openapi.ts index 14d6c598db0..e0ecaeee982 100644 --- a/packages/openapi3/src/openapi.ts +++ b/packages/openapi3/src/openapi.ts @@ -1543,7 +1543,13 @@ function createOAPIEmitter( ): OpenAPI3Parameter { if (target.schema) { const schema = target.schema; - if (schema.enum && apply.schema.enum) { + if ( + "enum" in schema && + schema.enum && + apply.schema && + "enum" in apply.schema && + apply.schema.enum + ) { schema.enum = [...new Set([...schema.enum, ...apply.schema.enum])]; } target.schema = schema; diff --git a/packages/openapi3/src/types.ts b/packages/openapi3/src/types.ts index 791f6d076f2..957f5bbb006 100644 --- a/packages/openapi3/src/types.ts +++ b/packages/openapi3/src/types.ts @@ -3,6 +3,8 @@ import { Contact, ExtensionKey, License } from "@typespec/openapi"; export type CommonOpenAPI3Schema = OpenAPI3Schema & OpenAPISchema3_1 & OpenAPISchema3_2; +export type SupportedOpenAPISchema = OpenAPI3Schema | OpenAPISchema3_1 | OpenAPISchema3_2; + export type SupportedOpenAPIDocuments = OpenAPI3Document | OpenAPIDocument3_1 | OpenAPIDocument3_2; export type Extensions = { @@ -1516,5 +1518,5 @@ export interface OpenAPITag3_2 extends OpenAPI3Tag { export interface OpenAPIRequestBody3_2 extends Omit { /** A map containing descriptions of potential request payloads. The key is a media type or media type range and the value describes it. For requests that match multiple keys, only the most specific key is applicable. e.g. text/plain overrides text/* */ - content?: Record>; + content: Record>; } From 374d2c4b81a62d8d9c5a75e7c9fc58ff6aaf1993 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 29 Oct 2025 13:02:57 -0400 Subject: [PATCH 39/51] docs: outdated 3.0 links Signed-off-by: Vincent Biret --- packages/openapi3/src/types.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/openapi3/src/types.ts b/packages/openapi3/src/types.ts index 957f5bbb006..2a890391948 100644 --- a/packages/openapi3/src/types.ts +++ b/packages/openapi3/src/types.ts @@ -181,7 +181,7 @@ export type OpenAPI3Response = Extensions & { /** * Each Media Type Object provides schema and examples for the media type identified by its key. * - * @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#mediaTypeObject + * @see https://spec.openapis.org/oas/v3.0.4.html#media-type-object */ export type OpenAPI3MediaType = Extensions & { /** The schema defining the content of the request, response, or parameter. */ @@ -200,7 +200,7 @@ export type OpenAPI3MediaType = Extensions & { /** * A single encoding definition applied to a single schema property. * - * see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#encodingObject + * @see https://spec.openapis.org/oas/v3.0.4.html#encoding-object */ export type OpenAPI3Encoding = Extensions & { /** the Content-Type for encoding a specific property. Default value depends on the property type: for string with format being binary – application/octet-stream; for other primitive types – text/plain; for object - application/json; for array – the default is defined based on the inner type. The value can be a specific media type (e.g. application/json), a wildcard media type (e.g. image/*), or a comma-separated list of the two types. */ @@ -222,7 +222,7 @@ export type OpenAPI3Encoding = Extensions & { /** * Describes a single request body. * - * @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#request-body-object + * @see https://spec.openapis.org/oas/v3.0.4.html#request-body-object */ export type OpenAPI3RequestBody = Extensions & { /** A brief description of the request body. This could contain examples of use. CommonMark syntax MAY be used for rich text representation. */ @@ -244,7 +244,7 @@ export type OpenAPI3SecurityScheme = /** * defines a security scheme that can be used by the operations. Supported schemes are HTTP authentication, an API key (either as a header, a cookie parameter or as a query parameter), OAuth2's common flows (implicit, password, application and access code) as defined in RFC6749, and OpenID Connect Discovery. * - * @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#security-scheme-object + * @see https://spec.openapis.org/oas/v3.0.4.html#security-scheme-object */ export interface OpenAPI3SecuritySchemeBase extends Extensions { /** A short description for security scheme. CommonMark syntax MAY be used for rich text representation. */ @@ -293,7 +293,7 @@ export interface OpenAPI3OAuth2SecurityScheme extends OpenAPI3SecuritySchemeBase /** * Allows configuration of the supported OAuth Flows. * - * @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#oauthFlowsObject + * @see https://spec.openapis.org/oas/v3.0.4.html#oauth-flows-object */ export interface OpenAPI3OAuthFlows extends Extensions { /** Configuration for the OAuth Implicit flow */ @@ -708,7 +708,7 @@ export type OpenAPI3ParameterType = OpenAPI3Parameter["in"]; * in MUST NOT be specified, it is implicitly in header. * All traits that are affected by the location MUST be applicable to a location of header (for example, style). * - * @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#header-object + * @see https://spec.openapis.org/oas/v3.0.4.html#header-object */ export type OpenAPI3Header = OpenAPI3ParameterBase & { /** Describes how the parameter value will be serialized depending on the type of the parameter value From 5d28db68376c50e2eebd7d85bd14fb7acf500f61 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 29 Oct 2025 13:04:24 -0400 Subject: [PATCH 40/51] docs: additional outdated links updates Signed-off-by: Vincent Biret --- packages/openapi3/src/types.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/openapi3/src/types.ts b/packages/openapi3/src/types.ts index 2a890391948..74b7c1eb28c 100644 --- a/packages/openapi3/src/types.ts +++ b/packages/openapi3/src/types.ts @@ -367,7 +367,7 @@ export type OpenAPI3Link = /** * Allows sharing examples for operation responses. * - * @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#exampleObject + * @see https://spec.openapis.org/oas/v2.0.html#example-object */ export interface OpenAPI3Example { summary?: string; @@ -654,7 +654,7 @@ export type OpenAPI3QueryParameter = OpenAPI3ParameterBase & { * Describes how the parameter value will be serialized depending on the type of the parameter value. * * Default value for query parameters is form. - * @see https://github.com/OAI/OpenAPI-Specification/blob/3.0.3/versions/3.0.2.md#style-values + * @see https://spec.openapis.org/oas/v3.0.4.html#style-values */ style?: "form" | "spaceDelimited" | "pipeDelimited" | "deepObject"; }; @@ -666,7 +666,7 @@ export type OpenAPI3PathParameter = OpenAPI3ParameterBase & { * Describes how the parameter value will be serialized depending on the type of the parameter value. * * Default value for path parameters is simple. - * @see https://github.com/OAI/OpenAPI-Specification/blob/3.0.3/versions/3.0.2.md#style-values + * @see https://spec.openapis.org/oas/v3.0.4.html#style-values */ style?: "simple" | "label" | "matrix"; }; @@ -678,7 +678,7 @@ export type OpenAPI3HeaderParameter = OpenAPI3ParameterBase & { * Describes how the parameter value will be serialized depending on the type of the parameter value. * * Default value for header parameters is simple. - * @see https://github.com/OAI/OpenAPI-Specification/blob/3.0.3/versions/3.0.2.md#style-values + * @see https://spec.openapis.org/oas/v3.0.4.html#style-values */ style?: "simple"; }; @@ -690,7 +690,7 @@ export type OpenAPI3CookieParameter = OpenAPI3ParameterBase & { * Describes how the parameter value will be serialized depending on the type of the parameter value. * * Default value for header parameters is simple. - * @see https://github.com/OAI/OpenAPI-Specification/blob/3.0.3/versions/3.0.2.md#style-values + * @see https://spec.openapis.org/oas/v3.0.4.html#style-values */ style?: "form"; }; From 63377dd743bb1f95c74147075dc34cb448f56d58 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 29 Oct 2025 13:16:49 -0400 Subject: [PATCH 41/51] fix: additional types misalignments corrections Signed-off-by: Vincent Biret --- .../convert/generators/generate-model.ts | 6 +- .../convert/generators/generate-types.ts | 64 +++++++++++-------- .../src/cli/actions/convert/utils/context.ts | 8 +-- 3 files changed, 46 insertions(+), 32 deletions(-) diff --git a/packages/openapi3/src/cli/actions/convert/generators/generate-model.ts b/packages/openapi3/src/cli/actions/convert/generators/generate-model.ts index 3fd2f219106..c66c37ae286 100644 --- a/packages/openapi3/src/cli/actions/convert/generators/generate-model.ts +++ b/packages/openapi3/src/cli/actions/convert/generators/generate-model.ts @@ -1,5 +1,5 @@ import { printIdentifier } from "@typespec/compiler"; -import { OpenAPI3Encoding, OpenAPI3Schema, Refable } from "../../../../types.js"; +import { OpenAPI3Encoding, Refable, SupportedOpenAPISchema } from "../../../../types.js"; import { TypeSpecAlias, TypeSpecDataTypes, @@ -96,7 +96,7 @@ function generateUnion(union: TypeSpecUnion, context: Context): string { const schema = union.schema; - const getVariantName = (member: Refable) => { + const getVariantName = (member: Refable) => { if (union.schema.discriminator === undefined) { return ""; } @@ -159,7 +159,7 @@ function generateUnion(union: TypeSpecUnion, context: Context): string { } } - if (schema.nullable) { + if ("nullable" in schema && schema.nullable) { definitions.push("null,"); } diff --git a/packages/openapi3/src/cli/actions/convert/generators/generate-types.ts b/packages/openapi3/src/cli/actions/convert/generators/generate-types.ts index 990e9b7e11a..2942246cc09 100644 --- a/packages/openapi3/src/cli/actions/convert/generators/generate-types.ts +++ b/packages/openapi3/src/cli/actions/convert/generators/generate-types.ts @@ -1,5 +1,10 @@ import { printIdentifier } from "@typespec/compiler"; -import { OpenAPI3Encoding, OpenAPI3Schema, Refable } from "../../../../types.js"; +import { + OpenAPI3Encoding, + OpenAPISchema3_2, + Refable, + SupportedOpenAPISchema, +} from "../../../../types.js"; import { Context } from "../utils/context.js"; import { getDecoratorsForSchema, @@ -12,7 +17,7 @@ export class SchemaToExpressionGenerator { constructor(public rootNamespace: string) {} public generateTypeFromRefableSchema( - schema: Refable, + schema: Refable, callingScope: string[], isHttpPart = false, encoding?: Record, @@ -24,7 +29,7 @@ export class SchemaToExpressionGenerator { : this.getTypeFromSchema(schema, callingScope, isHttpPart, encoding, context); } - public generateArrayType(schema: OpenAPI3Schema, callingScope: string[]): string { + public generateArrayType(schema: SupportedOpenAPISchema, callingScope: string[]): string { const items = schema.items; if (!items) { return "unknown[]"; @@ -80,7 +85,7 @@ export class SchemaToExpressionGenerator { } private getTypeFromSchema( - schema: OpenAPI3Schema, + schema: SupportedOpenAPISchema, callingScope: string[], isHttpPart = false, encoding?: Record, @@ -98,7 +103,7 @@ export class SchemaToExpressionGenerator { } else { // Create a schema with a single type to reuse existing type extraction logic // Remove type array, nullable, and default to avoid double-processing - const singleTypeSchema: OpenAPI3Schema = { + const singleTypeSchema: SupportedOpenAPISchema = { ...schema, type: t as any, nullable: undefined, @@ -149,7 +154,7 @@ export class SchemaToExpressionGenerator { type = this.generateArrayType(schema, callingScope); } - if (schema.nullable) { + if ("nullable" in schema && schema.nullable) { type += ` | null`; } @@ -165,7 +170,7 @@ export class SchemaToExpressionGenerator { } private generateDefaultValue( - schema: OpenAPI3Schema, + schema: SupportedOpenAPISchema, callingScope: string[], context?: Context, ): string | undefined { @@ -219,9 +224,9 @@ export class SchemaToExpressionGenerator { return undefined; // Return undefined to indicate no default found } - private getAllOfType(schema: OpenAPI3Schema, callingScope: string[]): string { + private getAllOfType(schema: SupportedOpenAPISchema, callingScope: string[]): string { const requiredProps: string[] = schema.required || []; - let properties: Record> = {}; + let properties: Record> = {}; const baseTypes: string[] = []; for (const member of schema.allOf || []) { @@ -256,7 +261,9 @@ export class SchemaToExpressionGenerator { } } - private stripDefaultsFromSchema(schema: Refable): Refable { + private stripDefaultsFromSchema( + schema: Refable, + ): Refable { if ("$ref" in schema) { return schema; } @@ -269,24 +276,31 @@ export class SchemaToExpressionGenerator { strippedSchema.items = this.stripDefaultsFromSchema(strippedSchema.items); } + // in the next blocks the casts are ugly but safe because the stripped schema is going to be the same as the original one if (strippedSchema.anyOf) { - strippedSchema.anyOf = strippedSchema.anyOf.map((item) => this.stripDefaultsFromSchema(item)); + strippedSchema.anyOf = strippedSchema.anyOf.map((item) => + this.stripDefaultsFromSchema(item), + ) as unknown as OpenAPISchema3_2[]; } if (strippedSchema.oneOf) { - strippedSchema.oneOf = strippedSchema.oneOf.map((item) => this.stripDefaultsFromSchema(item)); + strippedSchema.oneOf = strippedSchema.oneOf.map((item) => + this.stripDefaultsFromSchema(item), + ) as unknown as OpenAPISchema3_2[]; } if (strippedSchema.allOf) { - strippedSchema.allOf = strippedSchema.allOf.map((item) => this.stripDefaultsFromSchema(item)); + strippedSchema.allOf = strippedSchema.allOf.map((item) => + this.stripDefaultsFromSchema(item), + ) as unknown as OpenAPISchema3_2[]; } if (strippedSchema.properties) { - const strippedProperties: Record> = {}; + const strippedProperties: Record> = {}; for (const [key, prop] of Object.entries(strippedSchema.properties)) { strippedProperties[key] = this.stripDefaultsFromSchema(prop); } - strippedSchema.properties = strippedProperties; + strippedSchema.properties = strippedProperties as unknown as Record; } if ( @@ -301,7 +315,7 @@ export class SchemaToExpressionGenerator { return strippedSchema; } - private getAnyOfType(schema: OpenAPI3Schema, callingScope: string[]): string { + private getAnyOfType(schema: SupportedOpenAPISchema, callingScope: string[]): string { const definitions: string[] = []; for (const item of schema.anyOf ?? []) { @@ -313,7 +327,7 @@ export class SchemaToExpressionGenerator { return definitions.join(" | "); } - private getOneOfType(schema: OpenAPI3Schema, callingScope: string[]): string { + private getOneOfType(schema: SupportedOpenAPISchema, callingScope: string[]): string { const definitions: string[] = []; for (const item of schema.oneOf ?? []) { @@ -403,7 +417,7 @@ export class SchemaToExpressionGenerator { public static readonly decoratorNamesToExcludeForParts: string[] = ["minValue", "maxValue"]; private getObjectType( - schema: OpenAPI3Schema, + schema: SupportedOpenAPISchema, callingScope: string[], isHttpPart = false, encoding?: Record, @@ -461,7 +475,7 @@ export class SchemaToExpressionGenerator { } export function isReferencedEnumType( - propSchema: Refable, + propSchema: Refable, context: Context, ): boolean { let isEnumType = false; @@ -481,7 +495,7 @@ export function isReferencedEnumType( } export function isReferencedUnionType( - propSchema: Refable, + propSchema: Refable, context: Context, ): boolean { let isUnionType = false; @@ -504,7 +518,7 @@ export function isReferencedUnionType( return isUnionType; } -export function getTypeSpecPrimitiveFromSchema(schema: OpenAPI3Schema): string | undefined { +export function getTypeSpecPrimitiveFromSchema(schema: SupportedOpenAPISchema): string | undefined { if (schema.type === "boolean") { return "boolean"; } else if ((schema as any).type === "null") { @@ -519,7 +533,7 @@ export function getTypeSpecPrimitiveFromSchema(schema: OpenAPI3Schema): string | return; } -function getIntegerType(schema: OpenAPI3Schema): string { +function getIntegerType(schema: SupportedOpenAPISchema): string { const format = schema.format ?? ""; switch (format) { case "int8": @@ -538,7 +552,7 @@ function getIntegerType(schema: OpenAPI3Schema): string { } } -function getNumberType(schema: OpenAPI3Schema): string { +function getNumberType(schema: SupportedOpenAPISchema): string { const format = schema.format ?? ""; switch (format) { case "decimal": @@ -554,7 +568,7 @@ function getNumberType(schema: OpenAPI3Schema): string { } } -function getStringType(schema: OpenAPI3Schema): string { +function getStringType(schema: SupportedOpenAPISchema): string { const format = schema.format ?? ""; let type = "string"; switch (format) { @@ -583,6 +597,6 @@ function getStringType(schema: OpenAPI3Schema): string { return type; } -function getEnum(schemaEnum: (string | number | boolean)[]): string { +function getEnum(schemaEnum: (string | number | boolean | null)[]): string { return schemaEnum.map((e) => JSON.stringify(e)).join(" | "); } diff --git a/packages/openapi3/src/cli/actions/convert/utils/context.ts b/packages/openapi3/src/cli/actions/convert/utils/context.ts index be5ddcb8ec7..a6cd8804a95 100644 --- a/packages/openapi3/src/cli/actions/convert/utils/context.ts +++ b/packages/openapi3/src/cli/actions/convert/utils/context.ts @@ -1,9 +1,9 @@ import { OpenAPI3Document, OpenAPI3Encoding, - OpenAPI3Schema, OpenAPIDocument3_1, Refable, + SupportedOpenAPISchema, } from "../../../../types.js"; import { Logger } from "../../../types.js"; import { SchemaToExpressionGenerator } from "../generators/generate-types.js"; @@ -15,13 +15,13 @@ export interface Context { readonly logger: Logger; generateTypeFromRefableSchema( - schema: Refable, + schema: Refable, callingScope: string[], isHttpPart?: boolean, encoding?: Record, ): string; getRefName(ref: string, callingScope: string[]): string; - getSchemaByRef(ref: string): OpenAPI3Schema | undefined; + getSchemaByRef(ref: string): SupportedOpenAPISchema | undefined; getByRef(ref: string): T | undefined; /** @@ -80,7 +80,7 @@ export function createContext( return schemaExpressionGenerator.getRefName(ref, callingScope); }, generateTypeFromRefableSchema( - schema: Refable, + schema: Refable, callingScope: string[], isHttpPart = false, encoding?: Record, From c36ce50c3ab093582ba5965dfe115a41032486cb Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 29 Oct 2025 13:24:12 -0400 Subject: [PATCH 42/51] tests: adds unit tests for OAI 3.2.0 import Signed-off-by: Vincent Biret --- .../src/cli/actions/convert/convert.ts | 4 +- .../src/cli/actions/convert/utils/context.ts | 6 +- .../tsp-openapi3/convert-openapi3-doc.test.ts | 488 +++++++++--------- 3 files changed, 252 insertions(+), 246 deletions(-) diff --git a/packages/openapi3/src/cli/actions/convert/convert.ts b/packages/openapi3/src/cli/actions/convert/convert.ts index 49d31d985fc..66219b881a4 100644 --- a/packages/openapi3/src/cli/actions/convert/convert.ts +++ b/packages/openapi3/src/cli/actions/convert/convert.ts @@ -1,6 +1,6 @@ import { AnyObject, dereference } from "@scalar/openapi-parser"; import { formatTypeSpec } from "@typespec/compiler"; -import { OpenAPI3Document } from "../../../types.js"; +import { SupportedOpenAPIDocuments } from "../../../types.js"; import { generateMain } from "./generators/generate-main.js"; import { transform } from "./transforms/transforms.js"; import { createContext } from "./utils/context.js"; @@ -17,7 +17,7 @@ export interface ConvertOpenAPI3DocumentOptions { } export async function convertOpenAPI3Document( - document: OpenAPI3Document, + document: SupportedOpenAPIDocuments, { disableExternalRefs, namespace }: ConvertOpenAPI3DocumentOptions = {}, ) { const dereferenceOptions = disableExternalRefs diff --git a/packages/openapi3/src/cli/actions/convert/utils/context.ts b/packages/openapi3/src/cli/actions/convert/utils/context.ts index a6cd8804a95..792287dedfd 100644 --- a/packages/openapi3/src/cli/actions/convert/utils/context.ts +++ b/packages/openapi3/src/cli/actions/convert/utils/context.ts @@ -1,8 +1,8 @@ import { - OpenAPI3Document, OpenAPI3Encoding, OpenAPIDocument3_1, Refable, + SupportedOpenAPIDocuments, SupportedOpenAPISchema, } from "../../../../types.js"; import { Logger } from "../../../types.js"; @@ -10,7 +10,7 @@ import { SchemaToExpressionGenerator } from "../generators/generate-types.js"; import { generateNamespaceName } from "./generate-namespace-name.js"; export interface Context { - readonly openApi3Doc: OpenAPI3Document; + readonly openApi3Doc: SupportedOpenAPIDocuments; readonly rootNamespace: string; readonly logger: Logger; @@ -53,7 +53,7 @@ export interface Context { } export function createContext( - openApi3Doc: OpenAPI3Document, + openApi3Doc: SupportedOpenAPIDocuments, logger?: Logger, namespace?: string, ): Context { diff --git a/packages/openapi3/test/tsp-openapi3/convert-openapi3-doc.test.ts b/packages/openapi3/test/tsp-openapi3/convert-openapi3-doc.test.ts index bbd71908f42..422b2f7b0d8 100644 --- a/packages/openapi3/test/tsp-openapi3/convert-openapi3-doc.test.ts +++ b/packages/openapi3/test/tsp-openapi3/convert-openapi3-doc.test.ts @@ -3,302 +3,308 @@ import { strictEqual } from "node:assert"; import { describe, it } from "vitest"; import { convertOpenAPI3Document } from "../../src/index.js"; -it("should convert an OpenAPI3 document to a formatted TypeSpec program", async () => { - const tsp = await convertOpenAPI3Document({ - info: { - title: "Test Service", - version: "1.0.0", - }, - openapi: "3.0.0", - paths: {}, - components: { - schemas: { - Foo: { - type: "string", - }, - }, - }, - }); - - strictEqual( - tsp, - await formatTypeSpec( - ` - import "@typespec/http"; - import "@typespec/openapi"; - import "@typespec/openapi3"; - - using Http; - using OpenAPI; - - @service(#{ title: "Test Service" }) - @info(#{ version: "1.0.0" }) - namespace TestService; - - scalar Foo extends string; - `, - { printWidth: 100, tabWidth: 2 }, - ), - ); -}); - -it("converts an OpenAPI 3 document with an empty schema to a valid TypeSpec representation", async () => { - const tsp = await convertOpenAPI3Document({ - info: { - title: "Test", - version: "0.0.0", - }, - openapi: "3.0.0", - paths: {}, - components: { - schemas: { - Foo: {}, - }, - }, - }); - - strictEqual( - tsp, - await formatTypeSpec( - ` - import "@typespec/http"; - import "@typespec/openapi"; - import "@typespec/openapi3"; - - using Http; - using OpenAPI; - - @service(#{ - title: "Test", - }) - @info(#{ - version: "0.0.0", - }) - namespace Test; - - scalar Foo; - `, - { printWidth: 100, tabWidth: 2 }, - ), - ); -}); +const versions = ["3.0.0", "3.1.0", "3.2.0"] as const; -describe("Union types with multiple defaults", () => { - it("should select first default for union types with multiple defaults", async () => { +describe.each(versions)("convertOpenAPI3Document v%s", (version) => { + it("should convert an OpenAPI3 document to a formatted TypeSpec program", async () => { const tsp = await convertOpenAPI3Document({ - openapi: "3.0.0", info: { - title: "(title)", - version: "0.0.0", + title: "Test Service", + version: "1.0.0", }, - tags: [], + openapi: version, paths: {}, components: { schemas: { Foo: { - type: "object", - required: ["bar"], - properties: { - bar: { - anyOf: [ - { - type: "string", - default: "life", - }, - { - type: "array", - items: { - type: "string", - }, - default: ["life"], - }, - { - type: "number", - default: 42, - }, - ], - }, - }, + type: "string", }, }, }, }); - // Should generate valid TypeSpec syntax strictEqual( - tsp.includes('bar: string | string[] | numeric = "life";'), - true, - "Expected 'bar: string | string[] | numeric = \"life\";' but got: " + tsp, - ); + tsp, + await formatTypeSpec( + ` + import "@typespec/http"; + import "@typespec/openapi"; + import "@typespec/openapi3"; - // Should NOT generate the invalid syntax - strictEqual( - tsp.includes('string = "life"| string[]'), - false, - "Should not contain the invalid syntax from the issue. Got: " + tsp, - ); + using Http; + using OpenAPI; - // Should NOT generate the invalid syntax - strictEqual( - tsp.includes("= 42,"), - false, - "Should not contain number default concatenated. Got: " + tsp, + @service(#{ title: "Test Service" }) + @info(#{ version: "1.0.0" }) + namespace TestService; + + scalar Foo extends string; + `, + { printWidth: 100, tabWidth: 2 }, + ), ); }); -}); -describe("String escaping", () => { - it("should escape ${...} in string literals to prevent interpolation", async () => { + it("converts an OpenAPI 3 document with an empty schema to a valid TypeSpec representation", async () => { const tsp = await convertOpenAPI3Document({ - openapi: "3.0.0", info: { - title: "(title)", + title: "Test", version: "0.0.0", }, - tags: [], + openapi: version, paths: {}, components: { schemas: { - Foo: { - type: "object", - required: ["foo"], - properties: { - foo: { - type: "string", - title: "${asdf}", - }, - }, - }, + Foo: {}, }, }, }); - // Should escape ${...} in strings to prevent interpolation strictEqual( - tsp.includes('@summary("\\${asdf}")'), - true, - "Expected '\\${asdf}' to be escaped but got: " + tsp, - ); + tsp, + await formatTypeSpec( + ` + import "@typespec/http"; + import "@typespec/openapi"; + import "@typespec/openapi3"; - // Should NOT contain unescaped ${...} - strictEqual( - tsp.includes('@summary("${asdf}")'), - false, - "Should not contain unescaped ${asdf}. Got: " + tsp, + using Http; + using OpenAPI; + + @service(#{ + title: "Test", + }) + @info(#{ + version: "0.0.0", + }) + namespace Test; + + scalar Foo; + `, + { printWidth: 100, tabWidth: 2 }, + ), ); }); - it("should escape multiple ${...} occurrences in string literals", async () => { - const tsp = await convertOpenAPI3Document({ - openapi: "3.0.0", - info: { - title: "(title)", - version: "0.0.0", - }, - tags: [], - paths: {}, - components: { - schemas: { - Foo: { - type: "object", - required: ["bar"], - properties: { - bar: { - type: "string", - description: "Value is ${foo} and ${bar}", + describe("Union types with multiple defaults", () => { + it("should select first default for union types with multiple defaults", async () => { + const tsp = await convertOpenAPI3Document({ + openapi: version, + info: { + title: "(title)", + version: "0.0.0", + }, + tags: [], + paths: {}, + components: { + schemas: { + Foo: { + type: "object", + required: ["bar"], + properties: { + bar: { + anyOf: [ + { + type: "string", + default: "life", + }, + { + type: "array", + items: { + type: "string", + }, + default: ["life"], + }, + { + type: "number", + default: 42, + }, + ], + }, }, }, }, }, - }, - }); + }); - // Should escape all ${...} in strings - strictEqual( - tsp.includes("\\${foo}") && tsp.includes("\\${bar}"), - true, - "Expected all ${...} to be escaped but got: " + tsp, - ); + // Should generate valid TypeSpec syntax + strictEqual( + tsp.includes('bar: string | string[] | numeric = "life";'), + true, + "Expected 'bar: string | string[] | numeric = \"life\";' but got: " + tsp, + ); + + // Should NOT generate the invalid syntax + strictEqual( + tsp.includes('string = "life"| string[]'), + false, + "Should not contain the invalid syntax from the issue. Got: " + tsp, + ); + + // Should NOT generate the invalid syntax + strictEqual( + tsp.includes("= 42,"), + false, + "Should not contain number default concatenated. Got: " + tsp, + ); + }); }); -}); -describe("OpenAPI 3.1 anyOf with null conversion", () => { - it("should convert anyOf with ref + null to proper union with null", async () => { - const tsp = await convertOpenAPI3Document({ - openapi: "3.1.0", - info: { - title: "Test Service", - version: "0.0.0", - }, - paths: {}, - components: { - schemas: { - Bar: { - type: "object", - required: ["bar"], - properties: { - bar: { - anyOf: [{ $ref: "#/components/schemas/Foo" }, { type: "null" as any }], + describe("String escaping", () => { + it("should escape ${...} in string literals to prevent interpolation", async () => { + const tsp = await convertOpenAPI3Document({ + openapi: version, + info: { + title: "(title)", + version: "0.0.0", + }, + tags: [], + paths: {}, + components: { + schemas: { + Foo: { + type: "object", + required: ["foo"], + properties: { + foo: { + type: "string", + title: "${asdf}", + }, }, }, }, - Foo: { - type: "object", - required: ["foo"], - properties: { - foo: { - type: "string", + }, + }); + + // Should escape ${...} in strings to prevent interpolation + strictEqual( + tsp.includes('@summary("\\${asdf}")'), + true, + "Expected '\\${asdf}' to be escaped but got: " + tsp, + ); + + // Should NOT contain unescaped ${...} + strictEqual( + tsp.includes('@summary("${asdf}")'), + false, + "Should not contain unescaped ${asdf}. Got: " + tsp, + ); + }); + + it("should escape multiple ${...} occurrences in string literals", async () => { + const tsp = await convertOpenAPI3Document({ + openapi: version, + info: { + title: "(title)", + version: "0.0.0", + }, + tags: [], + paths: {}, + components: { + schemas: { + Foo: { + type: "object", + required: ["bar"], + properties: { + bar: { + type: "string", + description: "Value is ${foo} and ${bar}", + }, }, }, }, }, - }, - } as any); + }); - // Should contain "bar: Foo | null;" instead of "bar: Foo | unknown;" - strictEqual( - tsp.includes("bar: Foo | null;"), - true, - "Expected 'bar: Foo | null;' but got: " + tsp, - ); - strictEqual( - tsp.includes("bar: Foo | unknown;"), - false, - "Should not contain 'bar: Foo | unknown;'", - ); + // Should escape all ${...} in strings + strictEqual( + tsp.includes("\\${foo}") && tsp.includes("\\${bar}"), + true, + "Expected all ${...} to be escaped but got: " + tsp, + ); + }); }); - it("should convert plain null type to null", async () => { - const tsp = await convertOpenAPI3Document({ - openapi: "3.1.0", - info: { - title: "Test Service", - version: "0.0.0", - }, - paths: {}, - components: { - schemas: { - NullOnly: { - type: "null" as any, + if (version !== "3.0.0") { + describe("OpenAPI 3.1 anyOf with null conversion", () => { + it("should convert anyOf with ref + null to proper union with null", async () => { + const tsp = await convertOpenAPI3Document({ + openapi: version, + info: { + title: "Test Service", + version: "0.0.0", + }, + paths: {}, + components: { + schemas: { + Bar: { + type: "object", + required: ["bar"], + properties: { + bar: { + anyOf: [{ $ref: "#/components/schemas/Foo" }, { type: "null" as any }], + }, + }, + }, + Foo: { + type: "object", + required: ["foo"], + properties: { + foo: { + type: "string", + }, + }, + }, + }, }, - Bar: { - type: "object", - required: ["nullProp"], - properties: { - nullProp: { - $ref: "#/components/schemas/NullOnly", + } as any); + + // Should contain "bar: Foo | null;" instead of "bar: Foo | unknown;" + strictEqual( + tsp.includes("bar: Foo | null;"), + true, + "Expected 'bar: Foo | null;' but got: " + tsp, + ); + strictEqual( + tsp.includes("bar: Foo | unknown;"), + false, + "Should not contain 'bar: Foo | unknown;'", + ); + }); + + it("should convert plain null type to null", async () => { + const tsp = await convertOpenAPI3Document({ + openapi: version, + info: { + title: "Test Service", + version: "0.0.0", + }, + paths: {}, + components: { + schemas: { + NullOnly: { + type: "null" as any, + }, + Bar: { + type: "object", + required: ["nullProp"], + properties: { + nullProp: { + $ref: "#/components/schemas/NullOnly", + }, + }, }, }, }, - }, - }, - } as any); + } as any); - // Should contain proper null handling - strictEqual( - tsp.includes("scalar NullOnly extends null;"), - true, - "Expected 'scalar NullOnly extends null;' but got: " + tsp, - ); - }); + // Should contain proper null handling + strictEqual( + tsp.includes("scalar NullOnly extends null;"), + true, + "Expected 'scalar NullOnly extends null;' but got: " + tsp, + ); + }); + }); + } }); From 9961466aed6bccca9526b828ceaaab43088fb406 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 29 Oct 2025 13:32:34 -0400 Subject: [PATCH 43/51] tests: duplicates tests for 3.1.0 to 3.2.0 Signed-off-by: Vincent Biret --- packages/openapi3/test/additional-properties.test.ts | 6 +++--- packages/openapi3/test/array.test.ts | 4 ++-- packages/openapi3/test/circular-references.test.ts | 4 ++-- packages/openapi3/test/component.test.ts | 4 ++-- packages/openapi3/test/discriminator.test.ts | 4 ++-- packages/openapi3/test/documentation.test.ts | 4 ++-- packages/openapi3/test/enums.test.ts | 4 ++-- packages/openapi3/test/examples.test.ts | 4 ++-- packages/openapi3/test/file.test.ts | 4 ++-- packages/openapi3/test/info.test.ts | 4 ++-- packages/openapi3/test/metadata.test.ts | 4 ++-- packages/openapi3/test/models.test.ts | 4 ++-- packages/openapi3/test/multipart.test.ts | 4 ++-- packages/openapi3/test/no-service-found.test.ts | 4 ++-- packages/openapi3/test/openapi-output.test.ts | 4 ++-- packages/openapi3/test/operation-id.test.ts | 4 ++-- packages/openapi3/test/overloads.test.ts | 4 ++-- packages/openapi3/test/parameters.test.ts | 4 ++-- packages/openapi3/test/primitive-types.test.ts | 4 ++-- packages/openapi3/test/record.test.ts | 4 ++-- packages/openapi3/test/response-descriptions.test.ts | 4 ++-- packages/openapi3/test/return-types.test.ts | 4 ++-- packages/openapi3/test/scalar-constraints.test.ts | 6 +++--- packages/openapi3/test/security.test.ts | 4 ++-- packages/openapi3/test/servers.test.ts | 4 ++-- packages/openapi3/test/shared-routes.test.ts | 4 ++-- packages/openapi3/test/status-codes.test.ts | 4 ++-- packages/openapi3/test/string-template.test.ts | 4 ++-- packages/openapi3/test/tagmetadata.test.ts | 4 ++-- packages/openapi3/test/union-schema.test.ts | 4 ++-- packages/openapi3/test/versioning.test.ts | 4 ++-- packages/openapi3/test/works-for.ts | 2 ++ packages/openapi3/test/xml-models.test.ts | 4 ++-- 33 files changed, 68 insertions(+), 66 deletions(-) diff --git a/packages/openapi3/test/additional-properties.test.ts b/packages/openapi3/test/additional-properties.test.ts index 0942e720653..91474c55490 100644 --- a/packages/openapi3/test/additional-properties.test.ts +++ b/packages/openapi3/test/additional-properties.test.ts @@ -1,9 +1,9 @@ import { deepStrictEqual, ok } from "assert"; import { describe, it } from "vitest"; import { OpenAPI3EmitterOptions } from "../src/lib.js"; -import { worksFor } from "./works-for.js"; +import { supportedVersions, worksFor } from "./works-for.js"; -worksFor(["3.0.0", "3.1.0"], ({ oapiForModel, objectSchemaIndexer }) => { +worksFor(supportedVersions, ({ oapiForModel, objectSchemaIndexer }) => { describe("extends Record", () => { it(`doesn't set ${objectSchemaIndexer} on model itself`, async () => { const res = await oapiForModel("Pet", `model Pet extends Record {};`); @@ -181,7 +181,7 @@ worksFor(["3.1.0"], ({ oapiForModel }) => { }); }); -worksFor(["3.0.0", "3.1.0"], ({ oapiForModel: baseOapiForMopdel, objectSchemaIndexer }) => { +worksFor(supportedVersions, ({ oapiForModel: baseOapiForMopdel, objectSchemaIndexer }) => { const oapiForModel = async (name: string, model: string, options?: OpenAPI3EmitterOptions) => { return baseOapiForMopdel(name, model, { ...options, "seal-object-schemas": true }); }; diff --git a/packages/openapi3/test/array.test.ts b/packages/openapi3/test/array.test.ts index fcb57bbbe55..6e33222a247 100644 --- a/packages/openapi3/test/array.test.ts +++ b/packages/openapi3/test/array.test.ts @@ -1,8 +1,8 @@ import { deepStrictEqual, ok, strictEqual } from "assert"; import { it } from "vitest"; -import { worksFor } from "./works-for.js"; +import { supportedVersions, worksFor } from "./works-for.js"; -worksFor(["3.0.0", "3.1.0"], ({ oapiForModel, openApiFor }) => { +worksFor(supportedVersions, ({ oapiForModel, openApiFor }) => { it("defines array inline", async () => { const res = await oapiForModel( "Pet", diff --git a/packages/openapi3/test/circular-references.test.ts b/packages/openapi3/test/circular-references.test.ts index b64934437f6..95034f997ff 100644 --- a/packages/openapi3/test/circular-references.test.ts +++ b/packages/openapi3/test/circular-references.test.ts @@ -1,8 +1,8 @@ import { deepStrictEqual } from "assert"; import { it } from "vitest"; -import { worksFor } from "./works-for.js"; +import { supportedVersions, worksFor } from "./works-for.js"; -worksFor(["3.0.0", "3.1.0"], ({ oapiForModel }) => { +worksFor(supportedVersions, ({ oapiForModel }) => { it("can reference itself via a property", async () => { const res = await oapiForModel( "Pet", diff --git a/packages/openapi3/test/component.test.ts b/packages/openapi3/test/component.test.ts index c347d5bfd85..d082666d249 100644 --- a/packages/openapi3/test/component.test.ts +++ b/packages/openapi3/test/component.test.ts @@ -1,7 +1,7 @@ import { expectDiagnostics } from "@typespec/compiler/testing"; import { describe, expect, it } from "vitest"; import { OpenAPI3Document } from "../src/types.js"; -import { worksFor } from "./works-for.js"; +import { supportedVersions, worksFor } from "./works-for.js"; interface DiagnosticCheck { expectedDiagInvalidKey: string; @@ -236,7 +236,7 @@ const testCases: Case[] = [ }, ]; -worksFor(["3.0.0", "3.1.0"], async (specHelpers) => { +worksFor(supportedVersions, async (specHelpers) => { describe("Invalid component key", () => { it.each(testCases)( "$title should report diagnostics and replace by valid key", diff --git a/packages/openapi3/test/discriminator.test.ts b/packages/openapi3/test/discriminator.test.ts index e6629d155e9..f585d5ff126 100644 --- a/packages/openapi3/test/discriminator.test.ts +++ b/packages/openapi3/test/discriminator.test.ts @@ -1,9 +1,9 @@ import { expectDiagnostics } from "@typespec/compiler/testing"; import { deepStrictEqual, ok } from "assert"; import { it } from "vitest"; -import { worksFor } from "./works-for.js"; +import { supportedVersions, worksFor } from "./works-for.js"; -worksFor(["3.0.0", "3.1.0"], ({ checkFor, openApiFor }) => { +worksFor(supportedVersions, ({ checkFor, openApiFor }) => { it("discriminator can be simple literals", async () => { const openApi = await openApiFor(` @discriminator("kind") diff --git a/packages/openapi3/test/documentation.test.ts b/packages/openapi3/test/documentation.test.ts index 61213d28d2d..0894c623b65 100644 --- a/packages/openapi3/test/documentation.test.ts +++ b/packages/openapi3/test/documentation.test.ts @@ -1,8 +1,8 @@ import { deepStrictEqual, strictEqual } from "assert"; import { it } from "vitest"; -import { worksFor } from "./works-for.js"; +import { supportedVersions, worksFor } from "./works-for.js"; -worksFor(["3.0.0", "3.1.0"], ({ openApiFor }) => { +worksFor(supportedVersions, ({ openApiFor }) => { it("supports summary and description", async () => { const openApi = await openApiFor(` @summary("This is a summary") diff --git a/packages/openapi3/test/enums.test.ts b/packages/openapi3/test/enums.test.ts index 849c5dc94c5..3d8a325a441 100644 --- a/packages/openapi3/test/enums.test.ts +++ b/packages/openapi3/test/enums.test.ts @@ -1,9 +1,9 @@ import { expectDiagnostics } from "@typespec/compiler/testing"; import { deepStrictEqual, strictEqual } from "assert"; import { it } from "vitest"; -import { worksFor } from "./works-for.js"; +import { supportedVersions, worksFor } from "./works-for.js"; -worksFor(["3.0.0", "3.1.0"], ({ diagnoseOpenApiFor, oapiForModel }) => { +worksFor(supportedVersions, ({ diagnoseOpenApiFor, oapiForModel }) => { it("throws diagnostics for empty enum definitions", async () => { const diagnostics = await diagnoseOpenApiFor(`enum PetType {}`); diff --git a/packages/openapi3/test/examples.test.ts b/packages/openapi3/test/examples.test.ts index b18e1e59aa9..eb159fe0a2f 100644 --- a/packages/openapi3/test/examples.test.ts +++ b/packages/openapi3/test/examples.test.ts @@ -2,7 +2,7 @@ import { ok } from "assert/strict"; import { describe, expect, it } from "vitest"; import { OpenAPI3Document, OpenAPI3Parameter, OpenAPI3RequestBody } from "../src/types.js"; import { openApiFor } from "./test-host.js"; -import { worksFor } from "./works-for.js"; +import { supportedVersions, worksFor } from "./works-for.js"; describe("schema examples", () => { it("apply example on model", async () => { @@ -54,7 +54,7 @@ describe("schema examples", () => { }); }); -worksFor(["3.0.0", "3.1.0"], ({ openApiFor }) => { +worksFor(supportedVersions, ({ openApiFor }) => { it("set example on the request body", async () => { const res: OpenAPI3Document = await openApiFor( ` diff --git a/packages/openapi3/test/file.test.ts b/packages/openapi3/test/file.test.ts index 40f45fb1b74..3a9437e02a8 100644 --- a/packages/openapi3/test/file.test.ts +++ b/packages/openapi3/test/file.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; -import { worksFor } from "./works-for.js"; +import { supportedVersions, worksFor } from "./works-for.js"; -worksFor(["3.0.0", "3.1.0"], ({ openApiFor, version }) => { +worksFor(supportedVersions, ({ openApiFor, version }) => { function getRawBinarySchema(contentMediaType?: T) { if (version === "3.0.0") { return { type: "string", format: "binary" } as const; diff --git a/packages/openapi3/test/info.test.ts b/packages/openapi3/test/info.test.ts index 26dc6cdba05..0211617a3e9 100644 --- a/packages/openapi3/test/info.test.ts +++ b/packages/openapi3/test/info.test.ts @@ -1,8 +1,8 @@ import { deepStrictEqual, strictEqual } from "assert"; import { it } from "vitest"; -import { worksFor } from "./works-for.js"; +import { supportedVersions, worksFor } from "./works-for.js"; -worksFor(["3.0.0", "3.1.0"], ({ openApiFor }) => { +worksFor(supportedVersions, ({ openApiFor }) => { it("set the service title with @service", async () => { const res = await openApiFor( ` diff --git a/packages/openapi3/test/metadata.test.ts b/packages/openapi3/test/metadata.test.ts index 8d26eea79d0..727bd7c79f2 100644 --- a/packages/openapi3/test/metadata.test.ts +++ b/packages/openapi3/test/metadata.test.ts @@ -1,8 +1,8 @@ import { deepStrictEqual } from "assert"; import { it } from "vitest"; -import { worksFor } from "./works-for.js"; +import { supportedVersions, worksFor } from "./works-for.js"; -worksFor(["3.0.0", "3.1.0"], ({ openApiFor }) => { +worksFor(supportedVersions, ({ openApiFor }) => { it("will expose all properties on unreferenced models but filter properties on referenced models", async () => { const res = await openApiFor(` model M { diff --git a/packages/openapi3/test/models.test.ts b/packages/openapi3/test/models.test.ts index 63254c9917b..1f25eeccea4 100644 --- a/packages/openapi3/test/models.test.ts +++ b/packages/openapi3/test/models.test.ts @@ -2,9 +2,9 @@ import { DiagnosticTarget } from "@typespec/compiler"; import { expectDiagnostics } from "@typespec/compiler/testing"; import { deepStrictEqual, ok, strictEqual } from "assert"; import { describe, expect, it } from "vitest"; -import { worksFor } from "./works-for.js"; +import { supportedVersions, worksFor } from "./works-for.js"; -worksFor(["3.0.0", "3.1.0"], ({ diagnoseOpenApiFor, oapiForModel, openApiFor }) => { +worksFor(supportedVersions, ({ diagnoseOpenApiFor, oapiForModel, openApiFor }) => { it("defines models", async () => { const res = await oapiForModel( "Foo", diff --git a/packages/openapi3/test/multipart.test.ts b/packages/openapi3/test/multipart.test.ts index 3a988a7355c..1858f48e0f7 100644 --- a/packages/openapi3/test/multipart.test.ts +++ b/packages/openapi3/test/multipart.test.ts @@ -1,9 +1,9 @@ import { deepStrictEqual } from "assert"; import { describe, expect, it } from "vitest"; import { OpenAPI3Encoding, OpenAPI3Schema, OpenAPISchema3_1 } from "../src/types.js"; -import { worksFor } from "./works-for.js"; +import { supportedVersions, worksFor } from "./works-for.js"; -worksFor(["3.0.0", "3.1.0"], ({ openApiFor }) => { +worksFor(supportedVersions, ({ openApiFor }) => { it("create dedicated model for multipart", async () => { const res = await openApiFor( ` diff --git a/packages/openapi3/test/no-service-found.test.ts b/packages/openapi3/test/no-service-found.test.ts index 0b1e8607523..d2b639a8d53 100644 --- a/packages/openapi3/test/no-service-found.test.ts +++ b/packages/openapi3/test/no-service-found.test.ts @@ -1,8 +1,8 @@ import { expectDiagnosticEmpty, expectDiagnostics } from "@typespec/compiler/testing"; import { it } from "vitest"; -import { worksFor } from "./works-for.js"; +import { supportedVersions, worksFor } from "./works-for.js"; -worksFor(["3.0.0", "3.1.0"], ({ diagnoseOpenApiFor }) => { +worksFor(supportedVersions, ({ diagnoseOpenApiFor }) => { it("does not emit warning if a non-service namespace has no routes", async () => { const diagnostics = await diagnoseOpenApiFor( ` diff --git a/packages/openapi3/test/openapi-output.test.ts b/packages/openapi3/test/openapi-output.test.ts index 1f8e686a628..650e5cb86bf 100644 --- a/packages/openapi3/test/openapi-output.test.ts +++ b/packages/openapi3/test/openapi-output.test.ts @@ -1,8 +1,8 @@ import { deepStrictEqual, ok, strictEqual } from "assert"; import { describe, it } from "vitest"; -import { worksFor } from "./works-for.js"; +import { supportedVersions, worksFor } from "./works-for.js"; -worksFor(["3.0.0", "3.1.0"], ({ oapiForModel, openApiFor, openapiWithOptions }) => { +worksFor(supportedVersions, ({ oapiForModel, openApiFor, openapiWithOptions }) => { describe("openapi3: types included", () => { it("emit unreferenced types by default", async () => { const output = await openapiWithOptions( diff --git a/packages/openapi3/test/operation-id.test.ts b/packages/openapi3/test/operation-id.test.ts index 60870bfb231..0be1bc348f6 100644 --- a/packages/openapi3/test/operation-id.test.ts +++ b/packages/openapi3/test/operation-id.test.ts @@ -1,6 +1,6 @@ import { expect, it } from "vitest"; import { OpenAPI3Document } from "../src/types.js"; -import { worksFor } from "./works-for.js"; +import { supportedVersions, worksFor } from "./works-for.js"; interface Case { description: string; @@ -31,7 +31,7 @@ const testCases: Case[] = [ }, ]; -worksFor(["3.0.0", "3.1.0"], ({ openApiFor }) => { +worksFor(supportedVersions, ({ openApiFor }) => { it.each(testCases)("$description", async (c: Case) => { const res: OpenAPI3Document = await openApiFor(c.code); expect(res.paths["/{id}"].get?.operationId).toBe(c.expectedOperationId); diff --git a/packages/openapi3/test/overloads.test.ts b/packages/openapi3/test/overloads.test.ts index 5d8ea33f66a..256a1a4d6d6 100644 --- a/packages/openapi3/test/overloads.test.ts +++ b/packages/openapi3/test/overloads.test.ts @@ -1,9 +1,9 @@ import { deepStrictEqual, ok, strictEqual } from "assert"; import { beforeEach, describe, it } from "vitest"; import { OpenAPI3Document, OpenAPI3RequestBody } from "../src/types.js"; -import { worksFor } from "./works-for.js"; +import { supportedVersions, worksFor } from "./works-for.js"; -worksFor(["3.0.0", "3.1.0"], ({ openApiFor }) => { +worksFor(supportedVersions, ({ openApiFor }) => { describe("overloads use same endpoint", () => { let res: OpenAPI3Document; beforeEach(async () => { diff --git a/packages/openapi3/test/parameters.test.ts b/packages/openapi3/test/parameters.test.ts index b0007a25975..6ca60fd0f1d 100644 --- a/packages/openapi3/test/parameters.test.ts +++ b/packages/openapi3/test/parameters.test.ts @@ -2,9 +2,9 @@ import { expectDiagnostics } from "@typespec/compiler/testing"; import { deepStrictEqual, ok, strictEqual } from "assert"; import { describe, expect, it } from "vitest"; import { OpenAPI3PathParameter, OpenAPI3QueryParameter } from "../src/types.js"; -import { worksFor } from "./works-for.js"; +import { supportedVersions, worksFor } from "./works-for.js"; -worksFor(["3.0.0", "3.1.0"], ({ diagnoseOpenApiFor, openApiFor }) => { +worksFor(supportedVersions, ({ diagnoseOpenApiFor, openApiFor }) => { describe("query parameters", () => { async function getQueryParam(code: string): Promise { const res = await openApiFor(code); diff --git a/packages/openapi3/test/primitive-types.test.ts b/packages/openapi3/test/primitive-types.test.ts index 5720882727f..bd5d856f26d 100644 --- a/packages/openapi3/test/primitive-types.test.ts +++ b/packages/openapi3/test/primitive-types.test.ts @@ -1,9 +1,9 @@ import { deepStrictEqual, ok, strictEqual } from "assert"; import { describe, it } from "vitest"; import { OpenAPI3Schema } from "../src/types.js"; -import { worksFor } from "./works-for.js"; +import { supportedVersions, worksFor } from "./works-for.js"; -worksFor(["3.0.0", "3.1.0"], ({ oapiForModel, openApiFor }) => { +worksFor(supportedVersions, ({ oapiForModel, openApiFor }) => { describe("handle TypeSpec intrinsic types", () => { const cases = [ ["unknown", {}], diff --git a/packages/openapi3/test/record.test.ts b/packages/openapi3/test/record.test.ts index 090f3684445..fb8ef22ab60 100644 --- a/packages/openapi3/test/record.test.ts +++ b/packages/openapi3/test/record.test.ts @@ -1,8 +1,8 @@ import { deepStrictEqual, ok } from "assert"; import { it } from "vitest"; -import { worksFor } from "./works-for.js"; +import { supportedVersions, worksFor } from "./works-for.js"; -worksFor(["3.0.0", "3.1.0"], ({ oapiForModel, objectSchemaIndexer }) => { +worksFor(supportedVersions, ({ oapiForModel, objectSchemaIndexer }) => { it("defines record inline", async () => { const res = await oapiForModel( "Pet", diff --git a/packages/openapi3/test/response-descriptions.test.ts b/packages/openapi3/test/response-descriptions.test.ts index 4935047cc62..3bab4ef1034 100644 --- a/packages/openapi3/test/response-descriptions.test.ts +++ b/packages/openapi3/test/response-descriptions.test.ts @@ -1,8 +1,8 @@ import { strictEqual } from "assert"; import { it } from "vitest"; -import { worksFor } from "./works-for.js"; +import { supportedVersions, worksFor } from "./works-for.js"; -worksFor(["3.0.0", "3.1.0"], ({ openApiFor }) => { +worksFor(supportedVersions, ({ openApiFor }) => { it("use a default message by status code if not specified", async () => { const res = await openApiFor( ` diff --git a/packages/openapi3/test/return-types.test.ts b/packages/openapi3/test/return-types.test.ts index 37942038fcc..bb9b3f3b0a4 100644 --- a/packages/openapi3/test/return-types.test.ts +++ b/packages/openapi3/test/return-types.test.ts @@ -1,9 +1,9 @@ import { expectDiagnosticEmpty, expectDiagnostics } from "@typespec/compiler/testing"; import { deepStrictEqual, ok, strictEqual } from "assert"; import { describe, expect, it } from "vitest"; -import { worksFor } from "./works-for.js"; +import { supportedVersions, worksFor } from "./works-for.js"; -worksFor(["3.0.0", "3.1.0"], ({ checkFor, openApiFor, objectSchemaIndexer }) => { +worksFor(supportedVersions, ({ checkFor, openApiFor, objectSchemaIndexer }) => { it("model used with @body and without shouldn't conflict if it contains no metadata", async () => { const res = await openApiFor( ` diff --git a/packages/openapi3/test/scalar-constraints.test.ts b/packages/openapi3/test/scalar-constraints.test.ts index dd5654ee835..21f96fa2cdf 100644 --- a/packages/openapi3/test/scalar-constraints.test.ts +++ b/packages/openapi3/test/scalar-constraints.test.ts @@ -1,7 +1,7 @@ /* eslint-disable vitest/no-identical-title */ import { deepStrictEqual, strictEqual } from "assert"; import { describe, it } from "vitest"; -import { worksFor } from "./works-for.js"; +import { supportedVersions, worksFor } from "./works-for.js"; describe("numeric constraints", () => { const scalarNumberTypes = [ @@ -19,7 +19,7 @@ describe("numeric constraints", () => { "safeint", ]; - worksFor(["3.0.0", "3.1.0"], ({ oapiForModel }) => { + worksFor(supportedVersions, ({ oapiForModel }) => { describe("@minValue/@maxValue/@multipleOf", () => { for (const numType of scalarNumberTypes) { it(numType, async () => { @@ -148,7 +148,7 @@ describe("string constraints", () => { @pattern("a|b") @format("ipv4")`; - worksFor(["3.0.0", "3.1.0"], ({ oapiForModel }) => { + worksFor(supportedVersions, ({ oapiForModel }) => { it("on scalar declaration", async () => { const schemas = await oapiForModel( "Test", diff --git a/packages/openapi3/test/security.test.ts b/packages/openapi3/test/security.test.ts index c759f15699c..b4c6ebfe37b 100644 --- a/packages/openapi3/test/security.test.ts +++ b/packages/openapi3/test/security.test.ts @@ -1,9 +1,9 @@ import { expectDiagnostics } from "@typespec/compiler/testing"; import { deepStrictEqual } from "assert"; import { expect, it } from "vitest"; -import { worksFor } from "./works-for.js"; +import { supportedVersions, worksFor } from "./works-for.js"; -worksFor(["3.0.0", "3.1.0"], ({ diagnoseOpenApiFor, openApiFor }) => { +worksFor(supportedVersions, ({ diagnoseOpenApiFor, openApiFor }) => { it("set a basic auth", async () => { const res = await openApiFor( ` diff --git a/packages/openapi3/test/servers.test.ts b/packages/openapi3/test/servers.test.ts index 3b6ebb7d8f2..b6ea1a508ad 100644 --- a/packages/openapi3/test/servers.test.ts +++ b/packages/openapi3/test/servers.test.ts @@ -1,9 +1,9 @@ import { expectDiagnostics } from "@typespec/compiler/testing"; import { deepStrictEqual } from "assert"; import { it } from "vitest"; -import { worksFor } from "./works-for.js"; +import { supportedVersions, worksFor } from "./works-for.js"; -worksFor(["3.0.0", "3.1.0"], ({ diagnoseOpenApiFor, openApiFor }) => { +worksFor(supportedVersions, ({ diagnoseOpenApiFor, openApiFor }) => { it("set a basic server(url)", async () => { const res = await openApiFor( ` diff --git a/packages/openapi3/test/shared-routes.test.ts b/packages/openapi3/test/shared-routes.test.ts index 07ae0e981a1..dfa8e2113c0 100644 --- a/packages/openapi3/test/shared-routes.test.ts +++ b/packages/openapi3/test/shared-routes.test.ts @@ -1,9 +1,9 @@ import { expectDiagnostics } from "@typespec/compiler/testing"; import { deepStrictEqual } from "assert"; import { it } from "vitest"; -import { worksFor } from "./works-for.js"; +import { supportedVersions, worksFor } from "./works-for.js"; -worksFor(["3.0.0", "3.1.0"], ({ diagnoseOpenApiFor, openApiFor }) => { +worksFor(supportedVersions, ({ diagnoseOpenApiFor, openApiFor }) => { it("emits warning for routes containing query parameters", async () => { const diagnostics = await diagnoseOpenApiFor( ` diff --git a/packages/openapi3/test/status-codes.test.ts b/packages/openapi3/test/status-codes.test.ts index c5abe8abb4c..94375016c3a 100644 --- a/packages/openapi3/test/status-codes.test.ts +++ b/packages/openapi3/test/status-codes.test.ts @@ -1,9 +1,9 @@ import { expectDiagnostics } from "@typespec/compiler/testing"; import { deepStrictEqual } from "assert"; import { it } from "vitest"; -import { worksFor } from "./works-for.js"; +import { supportedVersions, worksFor } from "./works-for.js"; -worksFor(["3.0.0", "3.1.0"], ({ diagnoseOpenApiFor, openApiFor }) => { +worksFor(supportedVersions, ({ diagnoseOpenApiFor, openApiFor }) => { async function expectStatusCodes(code: string, statusCodes: string[]) { const res = await openApiFor(code); deepStrictEqual(Object.keys(res.paths["/"].get.responses), statusCodes); diff --git a/packages/openapi3/test/string-template.test.ts b/packages/openapi3/test/string-template.test.ts index e89d72cad8d..9394f85b761 100644 --- a/packages/openapi3/test/string-template.test.ts +++ b/packages/openapi3/test/string-template.test.ts @@ -1,9 +1,9 @@ import { expectDiagnostics } from "@typespec/compiler/testing"; import { deepStrictEqual } from "assert"; import { describe, it } from "vitest"; -import { worksFor } from "./works-for.js"; +import { supportedVersions, worksFor } from "./works-for.js"; -worksFor(["3.0.0", "3.1.0"], ({ emitOpenApiWithDiagnostics, openApiFor }) => { +worksFor(supportedVersions, ({ emitOpenApiWithDiagnostics, openApiFor }) => { describe("handle interpolating literals", () => { it("string", async () => { const schemas = await openApiFor(` diff --git a/packages/openapi3/test/tagmetadata.test.ts b/packages/openapi3/test/tagmetadata.test.ts index 8b7e4ea1c08..9f2ef92929b 100644 --- a/packages/openapi3/test/tagmetadata.test.ts +++ b/packages/openapi3/test/tagmetadata.test.ts @@ -1,8 +1,8 @@ import { deepStrictEqual } from "assert"; import { it } from "vitest"; -import { worksFor } from "./works-for.js"; +import { supportedVersions, worksFor } from "./works-for.js"; -worksFor(["3.0.0", "3.1.0"], ({ openApiFor }) => { +worksFor(supportedVersions, ({ openApiFor }) => { const testCases: [string, string, string, any][] = [ [ "set tag metadata", diff --git a/packages/openapi3/test/union-schema.test.ts b/packages/openapi3/test/union-schema.test.ts index bec01a0ae90..f4cffca50b8 100644 --- a/packages/openapi3/test/union-schema.test.ts +++ b/packages/openapi3/test/union-schema.test.ts @@ -1,9 +1,9 @@ import { expectDiagnostics } from "@typespec/compiler/testing"; import { deepStrictEqual, ok, strictEqual } from "assert"; import { describe, expect, it } from "vitest"; -import { worksFor } from "./works-for.js"; +import { supportedVersions, worksFor } from "./works-for.js"; -worksFor(["3.0.0", "3.1.0"], ({ diagnoseOpenApiFor, oapiForModel, openApiFor }) => { +worksFor(supportedVersions, ({ diagnoseOpenApiFor, oapiForModel, openApiFor }) => { describe("discriminated unions", () => { it("use object envelope", async () => { const res = await openApiFor( diff --git a/packages/openapi3/test/versioning.test.ts b/packages/openapi3/test/versioning.test.ts index 4a9ccbbc4f7..e1f4108390d 100644 --- a/packages/openapi3/test/versioning.test.ts +++ b/packages/openapi3/test/versioning.test.ts @@ -2,9 +2,9 @@ import { expectDiagnostics } from "@typespec/compiler/testing"; import { deepStrictEqual, ok, strictEqual } from "assert"; import { describe, it } from "vitest"; import { ApiTester, openApiForVersions } from "./test-host.js"; -import { worksFor } from "./works-for.js"; +import { supportedVersions, worksFor } from "./works-for.js"; -worksFor(["3.0.0", "3.1.0"], ({ openApiFor, version: specVersion }) => { +worksFor(supportedVersions, ({ openApiFor, version: specVersion }) => { const TesterWithVersioning = ApiTester.importLibraries() .using("Http", "Rest", "Versioning") .emit("@typespec/openapi3", { "openapi-versions": [specVersion] }); diff --git a/packages/openapi3/test/works-for.ts b/packages/openapi3/test/works-for.ts index fc20a374cd0..b344e22d626 100644 --- a/packages/openapi3/test/works-for.ts +++ b/packages/openapi3/test/works-for.ts @@ -55,3 +55,5 @@ export function worksFor(versions: OpenAPIVersion[], cb: WorksForCb) { cb(specHelpers); }); } + +export const supportedVersions = Object.keys(OpenAPISpecHelpers) as OpenAPIVersion[]; diff --git a/packages/openapi3/test/xml-models.test.ts b/packages/openapi3/test/xml-models.test.ts index e5ce5eb56ed..a6014e856fc 100644 --- a/packages/openapi3/test/xml-models.test.ts +++ b/packages/openapi3/test/xml-models.test.ts @@ -2,9 +2,9 @@ import { expectDiagnostics } from "@typespec/compiler/testing"; import { deepStrictEqual } from "assert"; import { describe, expect, it } from "vitest"; import { SimpleTester } from "./test-host.js"; -import { worksFor } from "./works-for.js"; +import { supportedVersions, worksFor } from "./works-for.js"; -worksFor(["3.0.0", "3.1.0"], ({ emitOpenApiWithDiagnostics, oapiForModel }) => { +worksFor(supportedVersions, ({ emitOpenApiWithDiagnostics, oapiForModel }) => { describe("@name", () => { it("set xml.name for schema", async () => { const res = await oapiForModel( From 154d15716c9757175622a199e728c85ba9e46d25 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 29 Oct 2025 13:39:19 -0400 Subject: [PATCH 44/51] fix: types misalignments Signed-off-by: Vincent Biret --- .../transforms/transform-component-parameters.ts | 14 +++++++++----- .../cli/actions/convert/transforms/transforms.ts | 4 ++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/openapi3/src/cli/actions/convert/transforms/transform-component-parameters.ts b/packages/openapi3/src/cli/actions/convert/transforms/transform-component-parameters.ts index c14044c74d8..4d264ca40e4 100644 --- a/packages/openapi3/src/cli/actions/convert/transforms/transform-component-parameters.ts +++ b/packages/openapi3/src/cli/actions/convert/transforms/transform-component-parameters.ts @@ -1,5 +1,5 @@ import { printIdentifier } from "@typespec/compiler"; -import { OpenAPI3Parameter } from "../../../../types.js"; +import { OpenAPI3Parameter, OpenAPIParameter3_2 } from "../../../../types.js"; import { TypeSpecDataTypes, TypeSpecModelProperty } from "../interfaces.js"; import { Context } from "../utils/context.js"; import { getParameterDecorators } from "../utils/decorators.js"; @@ -22,6 +22,7 @@ export function transformComponentParameters( for (const name of Object.keys(parameters)) { const parameter = parameters[name]; + if ("$ref" in parameter) continue; transformComponentParameter(dataTypes, name, parameter); } } @@ -29,7 +30,7 @@ export function transformComponentParameters( function transformComponentParameter( dataTypes: TypeSpecDataTypes[], key: string, - parameter: OpenAPI3Parameter, + parameter: OpenAPI3Parameter | OpenAPIParameter3_2, ): void { const { name, scope } = getScopeAndName(key); // Parameters should live in the root Parameters namespace @@ -44,12 +45,15 @@ function transformComponentParameter( }); } -function getModelPropertyFromParameter(parameter: OpenAPI3Parameter): TypeSpecModelProperty { +function getModelPropertyFromParameter( + parameter: OpenAPI3Parameter | OpenAPIParameter3_2, +): TypeSpecModelProperty { + const parameterSchema = "schema" in parameter ? (parameter.schema ?? {}) : {}; return { name: printIdentifier(parameter.name), isOptional: !parameter.required, - doc: parameter.description ?? parameter.schema?.description, + doc: parameter.description ?? parameterSchema.description, decorators: getParameterDecorators(parameter), - schema: parameter.schema ?? {}, + schema: parameterSchema, }; } diff --git a/packages/openapi3/src/cli/actions/convert/transforms/transforms.ts b/packages/openapi3/src/cli/actions/convert/transforms/transforms.ts index 92376d16dfa..7f49706d62e 100644 --- a/packages/openapi3/src/cli/actions/convert/transforms/transforms.ts +++ b/packages/openapi3/src/cli/actions/convert/transforms/transforms.ts @@ -1,8 +1,8 @@ import { - OpenAPI3Document, OpenAPI3PathItem, OpenAPI3RequestBody, Refable, + SupportedOpenAPIDocuments, } from "../../../../types.js"; import { TypeSpecModel, TypeSpecProgram } from "../interfaces.js"; import { Context } from "../utils/context.js"; @@ -36,7 +36,7 @@ export function transform(context: Context): TypeSpecProgram { * Scans all operations in the OpenAPI document to identify schemas used in multipart forms * and registers them with their encoding information before model generation. */ -function scanForMultipartSchemas(openapi: OpenAPI3Document, context: Context): void { +function scanForMultipartSchemas(openapi: SupportedOpenAPIDocuments, context: Context): void { if (!openapi.paths) return; for (const path of Object.values(openapi.paths)) { From 31a37249b188f5612ee0dd5a4999e6e63cc423d3 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Wed, 29 Oct 2025 13:46:45 -0400 Subject: [PATCH 45/51] fix: more types misalignments Signed-off-by: Vincent Biret --- .../transforms/transform-component-schemas.ts | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/packages/openapi3/src/cli/actions/convert/transforms/transform-component-schemas.ts b/packages/openapi3/src/cli/actions/convert/transforms/transform-component-schemas.ts index 07038dcff74..373c6abb11f 100644 --- a/packages/openapi3/src/cli/actions/convert/transforms/transform-component-schemas.ts +++ b/packages/openapi3/src/cli/actions/convert/transforms/transform-component-schemas.ts @@ -1,5 +1,5 @@ import { printIdentifier } from "@typespec/compiler"; -import { OpenAPI3Schema, OpenAPISchema3_1, Refable } from "../../../../types.js"; +import { Refable, SupportedOpenAPISchema } from "../../../../types.js"; import { TypeSpecDataTypes, TypeSpecDecorator, @@ -24,6 +24,7 @@ export function transformComponentSchemas(context: Context, models: TypeSpecData for (const name of Object.keys(schemas)) { const schema = schemas[name]; + if ("$ref" in schema) continue; transformComponentSchema(models, name, context, schema); } @@ -32,7 +33,7 @@ export function transformComponentSchemas(context: Context, models: TypeSpecData types: TypeSpecDataTypes[], name: string, context: Context, - schema: OpenAPI3Schema, + schema: SupportedOpenAPISchema, ): void { const kind = getTypeSpecKind(schema); switch (kind) { @@ -52,7 +53,7 @@ export function transformComponentSchemas(context: Context, models: TypeSpecData function populateAlias( types: TypeSpecDataTypes[], rawName: string, - schema: Refable, + schema: Refable, ): void { if (!("$ref" in schema)) { return; @@ -69,7 +70,11 @@ export function transformComponentSchemas(context: Context, models: TypeSpecData }); } - function populateEnum(types: TypeSpecDataTypes[], name: string, schema: OpenAPI3Schema): void { + function populateEnum( + types: TypeSpecDataTypes[], + name: string, + schema: SupportedOpenAPISchema, + ): void { const tsEnum: TypeSpecEnum = { kind: "enum", ...getScopeAndName(name), @@ -85,7 +90,7 @@ export function transformComponentSchemas(context: Context, models: TypeSpecData types: TypeSpecDataTypes[], rawName: string, context: Context, - schema: OpenAPI3Schema, + schema: SupportedOpenAPISchema, ): void { const { name, scope } = getScopeAndName(rawName); @@ -125,7 +130,11 @@ export function transformComponentSchemas(context: Context, models: TypeSpecData }); } - function populateUnion(types: TypeSpecDataTypes[], name: string, schema: OpenAPI3Schema): void { + function populateUnion( + types: TypeSpecDataTypes[], + name: string, + schema: SupportedOpenAPISchema, + ): void { // Extract description and decorators from meaningful union members const unionMetadata = extractUnionMetadata(schema); @@ -144,7 +153,7 @@ export function transformComponentSchemas(context: Context, models: TypeSpecData * Extracts meaningful description and decorators from union members. * Handles anyOf/oneOf with null, and type arrays like ["string", "null"]. */ - function extractUnionMetadata(schema: OpenAPI3Schema | OpenAPISchema3_1): { + function extractUnionMetadata(schema: SupportedOpenAPISchema): { description?: string; decorators: TypeSpecDecorator[]; } { @@ -184,7 +193,11 @@ export function transformComponentSchemas(context: Context, models: TypeSpecData return { decorators: [] }; } - function populateScalar(types: TypeSpecDataTypes[], name: string, schema: OpenAPI3Schema): void { + function populateScalar( + types: TypeSpecDataTypes[], + name: string, + schema: SupportedOpenAPISchema, + ): void { types.push({ kind: "scalar", ...getScopeAndName(name), @@ -199,7 +212,7 @@ export function transformComponentSchemas(context: Context, models: TypeSpecData properties: TypeSpecModelProperty[]; spread: string[]; } - function getAllOfDetails(schema: OpenAPI3Schema, callingScope: string[]): AllOfDetails { + function getAllOfDetails(schema: SupportedOpenAPISchema, callingScope: string[]): AllOfDetails { const details: AllOfDetails = { spread: [], properties: [], @@ -246,7 +259,7 @@ export function transformComponentSchemas(context: Context, models: TypeSpecData return details; } - function getModelIs(schema: OpenAPI3Schema, callingScope: string[]): string | undefined { + function getModelIs(schema: SupportedOpenAPISchema, callingScope: string[]): string | undefined { if (schema.type !== "array") { return; } @@ -260,7 +273,7 @@ export function transformComponentSchemas(context: Context, models: TypeSpecData * returns that member's schema merged with any properties from the parent schema. * Otherwise, returns the original schema. */ -function unwrapSingleAnyOfOneOf(schema: OpenAPI3Schema): OpenAPI3Schema { +function unwrapSingleAnyOfOneOf(schema: SupportedOpenAPISchema): SupportedOpenAPISchema { const unionMembers = schema.anyOf || schema.oneOf; if (!unionMembers) { return schema; @@ -284,7 +297,7 @@ function unwrapSingleAnyOfOneOf(schema: OpenAPI3Schema): OpenAPI3Schema { // Priority: member properties > parent properties // Use member's description if available, otherwise use parent's description return { - ...schema, + ...(schema as object), ...member, description: member.description || schema.description, anyOf: undefined, @@ -299,7 +312,7 @@ function unwrapSingleAnyOfOneOf(schema: OpenAPI3Schema): OpenAPI3Schema { function getModelPropertiesFromObjectSchema({ properties, required = [], -}: OpenAPI3Schema): TypeSpecModelProperty[] { +}: SupportedOpenAPISchema): TypeSpecModelProperty[] { if (!properties) return []; const modelProperties: TypeSpecModelProperty[] = []; @@ -318,18 +331,18 @@ function getModelPropertiesFromObjectSchema({ return modelProperties; } -function getTypeSpecKind(schema: OpenAPI3Schema): TypeSpecDataTypes["kind"] { +function getTypeSpecKind(schema: SupportedOpenAPISchema): TypeSpecDataTypes["kind"] { if ("$ref" in schema) { return "alias"; } - if (schema.enum && schema.type === "string" && !schema.nullable) { + if (schema.enum && schema.type === "string" && (!("nullable" in schema) || !schema.nullable)) { return "enum"; } else if ( schema.anyOf || schema.oneOf || schema.enum || - schema.nullable || + ("nullable" in schema && schema.nullable) || (Array.isArray(schema.type) && schema.type.includes("null")) ) { // Check if anyOf/oneOf has a single meaningful inline object schema From 217c8695b920760b0ebf17feb42676ea9623c540 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 30 Oct 2025 09:00:27 -0400 Subject: [PATCH 46/51] feat: adds version 3.2.0 to the playground --- packages/openapi3/src/lib.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/openapi3/src/lib.ts b/packages/openapi3/src/lib.ts index c2c8d6639b2..fd3ae387cb0 100644 --- a/packages/openapi3/src/lib.ts +++ b/packages/openapi3/src/lib.ts @@ -167,16 +167,18 @@ const EmitterOptionsSchema: JSONSchemaType = { ].join("\n"), }, "openapi-versions": { + title: "OpenAPI Versions", type: "array", items: { type: "string", - enum: ["3.0.0", "3.1.0"], + enum: ["3.0.0", "3.1.0", "3.2.0"], nullable: true, description: "The versions of OpenAPI to emit. Defaults to `[3.0.0]`", }, nullable: true, uniqueItems: true, minItems: 1, + default: ["3.0.0"], }, "new-line": { type: "string", From 450f3eb80708c07835cad419a3efaf1b21476ab3 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 30 Oct 2025 10:57:35 -0400 Subject: [PATCH 47/51] fix: regression explode true should be default for query parameters Signed-off-by: Vincent Biret --- packages/openapi3/src/cli/actions/convert/utils/decorators.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/openapi3/src/cli/actions/convert/utils/decorators.ts b/packages/openapi3/src/cli/actions/convert/utils/decorators.ts index 1f1747f447e..3a725cfdb68 100644 --- a/packages/openapi3/src/cli/actions/convert/utils/decorators.ts +++ b/packages/openapi3/src/cli/actions/convert/utils/decorators.ts @@ -66,15 +66,13 @@ function getLocationDecorator( args: [], }; - if (!("explode" in parameter)) return decorator; - let decoratorArgs: TypeSpecDecorator["args"][0] | undefined; switch (parameter.in) { case "header": decoratorArgs = getHeaderArgs(parameter.explode ?? false); break; case "query": - decoratorArgs = getQueryArgs({ explode: parameter.explode ?? false, style: parameter.style }); + decoratorArgs = getQueryArgs({ explode: parameter.explode ?? true, style: parameter.style }); break; } From c24bf480cc336054671e1e7d3cfe324a0145e969 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 30 Oct 2025 11:49:14 -0400 Subject: [PATCH 48/51] tests: updates test value with added description Signed-off-by: Vincent Biret --- packages/openapi3/test/tsp-openapi3/paths.test.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/openapi3/test/tsp-openapi3/paths.test.ts b/packages/openapi3/test/tsp-openapi3/paths.test.ts index cd5f26e1ab6..26802c7ad6d 100644 --- a/packages/openapi3/test/tsp-openapi3/paths.test.ts +++ b/packages/openapi3/test/tsp-openapi3/paths.test.ts @@ -786,9 +786,12 @@ model Foo { @info(#{ version: "1.0.0" }) namespace TestService; - @route("/") @get op getFoo(): GeneratedHelpers.DefaultResponse; + @route("/") @get op getFoo(): GeneratedHelpers.DefaultResponse< + Description = "Test Response", + Headers = { + @header foo?: string; + } + >; namespace GeneratedHelpers { @doc(Description) @@ -838,7 +841,10 @@ model Foo { @info(#{ version: "1.0.0" }) namespace TestService; - @route("/") @get op getFoo(): GeneratedHelpers.DefaultResponse; + @route("/") @get op getFoo(): GeneratedHelpers.DefaultResponse< + Description = "Test Response", + Body = string + >; namespace GeneratedHelpers { @doc(Description) From 6760fc4082e2f1d02cff3e3f7803420234b7fe9a Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 30 Oct 2025 12:09:24 -0400 Subject: [PATCH 49/51] fix: regression for alias imports Signed-off-by: Vincent Biret --- .../transforms/transform-component-schemas.ts | 52 ++++++++++++------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/packages/openapi3/src/cli/actions/convert/transforms/transform-component-schemas.ts b/packages/openapi3/src/cli/actions/convert/transforms/transform-component-schemas.ts index 373c6abb11f..81950d0dd25 100644 --- a/packages/openapi3/src/cli/actions/convert/transforms/transform-component-schemas.ts +++ b/packages/openapi3/src/cli/actions/convert/transforms/transform-component-schemas.ts @@ -24,7 +24,6 @@ export function transformComponentSchemas(context: Context, models: TypeSpecData for (const name of Object.keys(schemas)) { const schema = schemas[name]; - if ("$ref" in schema) continue; transformComponentSchema(models, name, context, schema); } @@ -33,7 +32,7 @@ export function transformComponentSchemas(context: Context, models: TypeSpecData types: TypeSpecDataTypes[], name: string, context: Context, - schema: SupportedOpenAPISchema, + schema: Refable, ): void { const kind = getTypeSpecKind(schema); switch (kind) { @@ -73,8 +72,9 @@ export function transformComponentSchemas(context: Context, models: TypeSpecData function populateEnum( types: TypeSpecDataTypes[], name: string, - schema: SupportedOpenAPISchema, + schema: Refable, ): void { + if ("$ref" in schema) return; const tsEnum: TypeSpecEnum = { kind: "enum", ...getScopeAndName(name), @@ -90,7 +90,7 @@ export function transformComponentSchemas(context: Context, models: TypeSpecData types: TypeSpecDataTypes[], rawName: string, context: Context, - schema: SupportedOpenAPISchema, + schema: Refable, ): void { const { name, scope } = getScopeAndName(rawName); @@ -112,18 +112,20 @@ export function transformComponentSchemas(context: Context, models: TypeSpecData decorators: [...getDecoratorsForSchema(effectiveSchema)], doc: effectiveSchema.description || schema.description, properties: [ - ...getModelPropertiesFromObjectSchema(effectiveSchema), + ...("$ref" in effectiveSchema ? [] : getModelPropertiesFromObjectSchema(effectiveSchema)), ...allOfDetails.properties, ], additionalProperties: - effectiveSchema.additionalProperties === true - ? {} // Use empty object to represent Record - : typeof effectiveSchema.additionalProperties === "object" - ? effectiveSchema.additionalProperties - : undefined, + "additionalProperties" in effectiveSchema + ? effectiveSchema.additionalProperties === true + ? {} // Use empty object to represent Record + : typeof effectiveSchema.additionalProperties === "object" + ? effectiveSchema.additionalProperties + : undefined + : undefined, extends: allOfDetails.extends, is: isParent, - type: effectiveSchema.type, + type: "type" in effectiveSchema ? effectiveSchema.type : undefined, spread: allOfDetails.spread, isModelReferencedAsMultipartRequestBody, encoding, @@ -133,8 +135,9 @@ export function transformComponentSchemas(context: Context, models: TypeSpecData function populateUnion( types: TypeSpecDataTypes[], name: string, - schema: SupportedOpenAPISchema, + schema: Refable, ): void { + if ("$ref" in schema) return; // Extract description and decorators from meaningful union members const unionMetadata = extractUnionMetadata(schema); @@ -196,14 +199,14 @@ export function transformComponentSchemas(context: Context, models: TypeSpecData function populateScalar( types: TypeSpecDataTypes[], name: string, - schema: SupportedOpenAPISchema, + schema: Refable, ): void { types.push({ kind: "scalar", ...getScopeAndName(name), decorators: getDecoratorsForSchema(schema), doc: schema.description, - schema, + schema: "$ref" in schema ? {} : schema, }); } @@ -212,13 +215,16 @@ export function transformComponentSchemas(context: Context, models: TypeSpecData properties: TypeSpecModelProperty[]; spread: string[]; } - function getAllOfDetails(schema: SupportedOpenAPISchema, callingScope: string[]): AllOfDetails { + function getAllOfDetails( + schema: Refable, + callingScope: string[], + ): AllOfDetails { const details: AllOfDetails = { spread: [], properties: [], }; - if (!schema.allOf) { + if ("$ref" in schema || !schema.allOf) { return details; } @@ -259,8 +265,11 @@ export function transformComponentSchemas(context: Context, models: TypeSpecData return details; } - function getModelIs(schema: SupportedOpenAPISchema, callingScope: string[]): string | undefined { - if (schema.type !== "array") { + function getModelIs( + schema: Refable, + callingScope: string[], + ): string | undefined { + if ("$ref" in schema || schema.type !== "array") { return; } return context.generateTypeFromRefableSchema(schema, callingScope); @@ -273,7 +282,10 @@ export function transformComponentSchemas(context: Context, models: TypeSpecData * returns that member's schema merged with any properties from the parent schema. * Otherwise, returns the original schema. */ -function unwrapSingleAnyOfOneOf(schema: SupportedOpenAPISchema): SupportedOpenAPISchema { +function unwrapSingleAnyOfOneOf( + schema: Refable, +): Refable { + if ("$ref" in schema) return schema; const unionMembers = schema.anyOf || schema.oneOf; if (!unionMembers) { return schema; @@ -331,7 +343,7 @@ function getModelPropertiesFromObjectSchema({ return modelProperties; } -function getTypeSpecKind(schema: SupportedOpenAPISchema): TypeSpecDataTypes["kind"] { +function getTypeSpecKind(schema: Refable): TypeSpecDataTypes["kind"] { if ("$ref" in schema) { return "alias"; } From c27060b0ba7d56d4b89050f2ece97c7fd778eb92 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 30 Oct 2025 12:56:05 -0400 Subject: [PATCH 50/51] chore: adds missing v2 http method export Signed-off-by: Vincent Biret --- packages/openapi3/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/openapi3/src/index.ts b/packages/openapi3/src/index.ts index c65e0b5de96..9e00ffa9ab2 100644 --- a/packages/openapi3/src/index.ts +++ b/packages/openapi3/src/index.ts @@ -17,6 +17,7 @@ export type { JsonSchema, JsonSchemaType, JsonType, + OpenAPI2HttpMethod, OpenAPI3ApiKeySecurityScheme, OpenAPI3AuthorizationCodeOAuthFlow, OpenAPI3ClientCredentialsFlow, From 7cdc4432b487adfd2a728f6c60418b432644b2d3 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Thu, 30 Oct 2025 13:43:22 -0400 Subject: [PATCH 51/51] chore: removes http2 methods as requested during review Signed-off-by: Vincent Biret --- packages/openapi3/src/index.ts | 1 - packages/openapi3/src/types.ts | 14 ++++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/openapi3/src/index.ts b/packages/openapi3/src/index.ts index 9e00ffa9ab2..c65e0b5de96 100644 --- a/packages/openapi3/src/index.ts +++ b/packages/openapi3/src/index.ts @@ -17,7 +17,6 @@ export type { JsonSchema, JsonSchemaType, JsonType, - OpenAPI2HttpMethod, OpenAPI3ApiKeySecurityScheme, OpenAPI3AuthorizationCodeOAuthFlow, OpenAPI3ClientCredentialsFlow, diff --git a/packages/openapi3/src/types.ts b/packages/openapi3/src/types.ts index 74b7c1eb28c..0c3f4eab932 100644 --- a/packages/openapi3/src/types.ts +++ b/packages/openapi3/src/types.ts @@ -130,13 +130,19 @@ export interface OpenAPI3Tag extends Extensions { externalDocs?: OpenAPI3ExternalDocs; } -export type OpenAPI2HttpMethod = "get" | "put" | "post" | "delete" | "options" | "head" | "patch"; - -export type OpenAPI3HttpMethod = OpenAPI2HttpMethod | "trace"; +export type OpenAPI3HttpMethod = + | "get" + | "put" + | "post" + | "delete" + | "options" + | "head" + | "patch" + | "trace"; export type OpenAPIHttpMethod3_2 = OpenAPI3HttpMethod | "query"; -export type HttpMethod = OpenAPI2HttpMethod | OpenAPI3HttpMethod | OpenAPIHttpMethod3_2; +export type HttpMethod = OpenAPI3HttpMethod | OpenAPIHttpMethod3_2; /** * Describes the operations available on a single path. A Path Item may be empty, due to ACL constraints. The path itself is still exposed to the documentation viewer but they will not know which operations and parameters are available.