From f9db1244ff48e13c36b6f9c27427708aa4ccb8ce Mon Sep 17 00:00:00 2001 From: Lubos Date: Sun, 22 Jun 2025 20:31:33 +0800 Subject: [PATCH] fix(parser): handle JSON pointers --- .../test/openapi-ts.config.ts | 2 +- .../openapi-ts-tests/test/spec/3.1.x/bug.yaml | 22 +++++++++++++++++++ .../src/openApi/3.1.x/parser/index.ts | 4 ++++ packages/openapi-ts/src/utils/ref.ts | 11 +++++++++- 4 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 packages/openapi-ts-tests/test/spec/3.1.x/bug.yaml diff --git a/packages/openapi-ts-tests/test/openapi-ts.config.ts b/packages/openapi-ts-tests/test/openapi-ts.config.ts index ab7c6563e..d32c19dd1 100644 --- a/packages/openapi-ts-tests/test/openapi-ts.config.ts +++ b/packages/openapi-ts-tests/test/openapi-ts.config.ts @@ -60,7 +60,7 @@ export default defineConfig(() => { // 'invalid', // 'servers-entry.yaml', // ), - path: path.resolve(__dirname, 'spec', '3.1.x', 'content-types.yaml'), + path: path.resolve(__dirname, 'spec', '3.1.x', 'bug.yaml'), // path: path.resolve(__dirname, 'spec', 'v3-transforms.json'), // path: 'http://localhost:4000/', // path: 'https://get.heyapi.dev/', diff --git a/packages/openapi-ts-tests/test/spec/3.1.x/bug.yaml b/packages/openapi-ts-tests/test/spec/3.1.x/bug.yaml new file mode 100644 index 000000000..1fd4e5ce1 --- /dev/null +++ b/packages/openapi-ts-tests/test/spec/3.1.x/bug.yaml @@ -0,0 +1,22 @@ +openapi: 3.1.0 +info: + title: Sample API + version: 1.0.0 +paths: + /foo: + get: + responses: + '200': + description: OK + content: + application/json: + schema: + type: string + post: + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/paths/~1foo/get/responses/200/content/application~1json/schema' diff --git a/packages/openapi-ts/src/openApi/3.1.x/parser/index.ts b/packages/openapi-ts/src/openApi/3.1.x/parser/index.ts index f89841f27..5f4f63698 100644 --- a/packages/openapi-ts/src/openApi/3.1.x/parser/index.ts +++ b/packages/openapi-ts/src/openApi/3.1.x/parser/index.ts @@ -58,6 +58,7 @@ export const parseV3_1_X = (context: IR.Context) => { for (const name in context.spec.components.securitySchemes) { const securityOrReference = context.spec.components.securitySchemes[name]!; + // TODO: safe pointer to path const securitySchemeObject = '$ref' in securityOrReference ? context.resolveRef(securityOrReference.$ref) @@ -68,6 +69,7 @@ export const parseV3_1_X = (context: IR.Context) => { for (const name in context.spec.components.parameters) { const $ref = `#/components/parameters/${name}`; const parameterOrReference = context.spec.components.parameters[name]!; + // TODO: safe pointer to path const parameter = '$ref' in parameterOrReference ? context.resolveRef(parameterOrReference.$ref) @@ -84,6 +86,7 @@ export const parseV3_1_X = (context: IR.Context) => { const $ref = `#/components/requestBodies/${name}`; const requestBodyOrReference = context.spec.components.requestBodies[name]!; + // TODO: safe pointer to path const requestBody = '$ref' in requestBodyOrReference ? context.resolveRef(requestBodyOrReference.$ref) @@ -113,6 +116,7 @@ export const parseV3_1_X = (context: IR.Context) => { for (const path in context.spec.paths) { const pathItem = context.spec.paths[path as keyof PathsObject]!; + // TODO: safe pointer to path const finalPathItem = pathItem.$ref ? { ...context.resolveRef(pathItem.$ref), diff --git a/packages/openapi-ts/src/utils/ref.ts b/packages/openapi-ts/src/utils/ref.ts index 87a803afa..dac3d6e12 100644 --- a/packages/openapi-ts/src/utils/ref.ts +++ b/packages/openapi-ts/src/utils/ref.ts @@ -1,3 +1,6 @@ +const jsonPointerSlash = /~1/g; +const jsonPointerTilde = /~0/g; + export const irRef = '#/ir/'; export const isRefOpenApiComponent = ($ref: string): boolean => { @@ -20,7 +23,11 @@ export const refToName = ($ref: string): string => { const refToParts = ($ref: string): string[] => { // Remove the leading `#` and split by `/` to traverse the object const parts = $ref.replace(/^#\//, '').split('/'); - return parts; + const cleanParts = parts.map((part) => + part.replace(jsonPointerSlash, '/').replace(jsonPointerTilde, '~'), + ); + console.log($ref, cleanParts); + return cleanParts; }; export const resolveRef = ({ @@ -36,8 +43,10 @@ export const resolveRef = ({ let current = spec; + console.log(spec.paths['/foo'].get.responses['200']); for (const part of parts) { const p = part as keyof typeof current; + console.log(p); if (current[p] === undefined) { throw new Error(`Reference not found: ${$ref}`); }