diff --git a/packages/rtk-query-codegen-openapi/package.json b/packages/rtk-query-codegen-openapi/package.json index b1487d57c2..d6e02416ed 100644 --- a/packages/rtk-query-codegen-openapi/package.json +++ b/packages/rtk-query-codegen-openapi/package.json @@ -80,9 +80,10 @@ }, "dependencies": { "@apidevtools/swagger-parser": "^10.1.1", + "@oazapfts/resolve": "^1.0.0", "commander": "^6.2.0", "lodash.camelcase": "^4.3.0", - "oazapfts": "^6.4.0", + "oazapfts": "7.5.0", "prettier": "^3.2.5", "semver": "^7.3.5", "swagger2openapi": "^7.0.4", diff --git a/packages/rtk-query-codegen-openapi/src/generate.ts b/packages/rtk-query-codegen-openapi/src/generate.ts index 6e8a4d3717..220b163d7c 100644 --- a/packages/rtk-query-codegen-openapi/src/generate.ts +++ b/packages/rtk-query-codegen-openapi/src/generate.ts @@ -1,14 +1,16 @@ +import { getReferenceName, isReference, resolve, resolveArray } from '@oazapfts/resolve'; import camelCase from 'lodash.camelcase'; import path from 'node:path'; -import ApiGenerator, { +import { UNSTABLE_cg as cg } from 'oazapfts'; +import type { OazapftsContext } from 'oazapfts/context'; +import { createContext, withMode } from 'oazapfts/context'; +import { getOperationName as _getOperationName, - createPropertyAssignment, - createQuestionToken, - getReferenceName, - isReference, - isValidIdentifier, - keywordType, - supportDeepObjects, + getResponseType, + getSchemaFromContent, + getTypeFromResponse, + getTypeFromSchema, + preprocessComponents, } from 'oazapfts/generate'; import type { OpenAPIV3 } from 'openapi-types'; import ts from 'typescript'; @@ -24,12 +26,42 @@ import type { ParameterMatcher, TextMatcher, } from './types'; -import { capitalize, getOperationDefinitions, getV3Doc, removeUndefined, isQuery as testIsQuery } from './utils'; import { factory } from './utils/factory'; +import { capitalize, getOperationDefinitions, getV3Doc, removeUndefined, isQuery as testIsQuery } from './utils/index'; + +const { createPropertyAssignment, createQuestionToken, keywordType, isValidIdentifier } = cg; const generatedApiName = 'injectedRtkApi'; const v3DocCache: Record = {}; +function supportDeepObjects(params: OpenAPIV3.ParameterObject[]): OpenAPIV3.ParameterObject[] { + const res: OpenAPIV3.ParameterObject[] = []; + const merged: Record = {}; + for (const p of params) { + const m = /^(.+?)\[(.*?)\]/.exec(p.name); + if (!m) { + res.push(p); + continue; + } + const [, name, prop] = m; + let obj = merged[name]; + if (!obj) { + obj = merged[name] = { + name, + in: p.in, + style: 'deepObject', + schema: { + type: 'object', + properties: {} as Record, + }, + }; + res.push(obj); + } + obj.schema.properties[prop] = p.schema; + } + return res; +} + function defaultIsDataResponse(code: string, includeDefault: boolean) { if (includeDefault && code === 'default') { return true; @@ -89,9 +121,9 @@ function withQueryComment(node: T, def: QueryArgDefinition, h function getPatternFromProperty( property: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject, - apiGen: ApiGenerator + ctx: OazapftsContext ): string | null { - const resolved = apiGen.resolve(property); + const resolved = resolve(property, ctx); if (!resolved || typeof resolved !== 'object' || !('pattern' in resolved)) return null; if (resolved.type !== 'string') return null; const pattern = resolved.pattern; @@ -101,15 +133,15 @@ function getPatternFromProperty( function generateRegexConstantsForType( typeName: string, schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject, - apiGen: ApiGenerator + ctx: OazapftsContext ): ts.VariableStatement[] { - const resolvedSchema = apiGen.resolve(schema); + const resolvedSchema = resolve(schema, ctx); if (!resolvedSchema || !('properties' in resolvedSchema) || !resolvedSchema.properties) return []; const constants: ts.VariableStatement[] = []; for (const [propertyName, property] of Object.entries(resolvedSchema.properties)) { - const pattern = getPatternFromProperty(property, apiGen); + const pattern = getPatternFromProperty(property, ctx); if (!pattern) continue; const constantName = camelCase(`${typeName} ${propertyName} Pattern`); @@ -174,17 +206,13 @@ export async function generateApi( ) { const v3Doc = (v3DocCache[spec] ??= await getV3Doc(spec, httpResolverOptions)); - const apiGen = new ApiGenerator(v3Doc, { + const ctx = createContext(v3Doc, { unionUndefined, useEnumType, mergeReadWriteOnly, useUnknown, }); - - // temporary workaround for https://github.com/oazapfts/oazapfts/issues/491 - if (apiGen.spec.components?.schemas) { - apiGen.preprocessComponents(apiGen.spec.components.schemas); - } + preprocessComponents(ctx); const operationDefinitions = getOperationDefinitions(v3Doc).filter(operationMatches(filterEndpoints)); @@ -258,18 +286,18 @@ export async function generateApi( ), ...Object.values(interfaces), ...(outputRegexConstants - ? apiGen.aliases.flatMap((alias) => { + ? ctx.aliases.flatMap((alias) => { if (!ts.isInterfaceDeclaration(alias) && !ts.isTypeAliasDeclaration(alias)) return [alias]; const typeName = alias.name.escapedText.toString(); const schema = v3Doc.components?.schemas?.[typeName]; if (!schema) return [alias]; - const regexConstants = generateRegexConstantsForType(typeName, schema, apiGen); + const regexConstants = generateRegexConstantsForType(typeName, schema, ctx); return regexConstants.length > 0 ? [alias, ...regexConstants] : [alias]; }) - : apiGen.aliases), - ...apiGen.enumAliases, + : ctx.aliases), + ...ctx.enumAliases, ...(hooks ? [ generateReactHooks({ @@ -318,7 +346,7 @@ export async function generateApi( const tags = tag ? getTags({ verb, pathItem }) : undefined; const isQuery = testIsQuery(verb, overrides); - const returnsJson = apiGen.getResponseType(responses) === 'json'; + const returnsJson = getResponseType(ctx, responses) === 'json'; let ResponseType: ts.TypeNode = factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword); if (returnsJson) { const returnTypes = Object.entries(responses || {}) @@ -326,14 +354,12 @@ export async function generateApi( ([code, response]) => [ code, - apiGen.resolve(response), - apiGen.getTypeFromResponse(response, 'readOnly') || + resolve(response, ctx), + getTypeFromResponse(response, withMode(ctx, 'readOnly')) || factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword), ] as const ) - .filter(([status, response]) => - isDataResponse(status, includeDefault, apiGen.resolve(response), responses || {}) - ) + .filter(([status, response]) => isDataResponse(status, includeDefault, resolve(response, ctx), responses || {})) .filter(([_1, _2, type]) => type !== keywordType.void) .map(([code, response, type]) => ts.addSyntheticLeadingComment( @@ -359,10 +385,10 @@ export async function generateApi( ).name ); - const operationParameters = apiGen.resolveArray(operation.parameters); - const pathItemParameters = apiGen - .resolveArray(pathItem.parameters) - .filter((pp) => !operationParameters.some((op) => op.name === pp.name && op.in === pp.in)); + const operationParameters = resolveArray(ctx, operation.parameters); + const pathItemParameters = resolveArray(ctx, pathItem.parameters).filter( + (pp) => !operationParameters.some((op) => op.name === pp.name && op.in === pp.in) + ); const parameters = supportDeepObjects([...pathItemParameters, ...operationParameters]).filter( argumentMatches(overrides?.parameterFilter) @@ -395,16 +421,16 @@ export async function generateApi( origin: 'param', name, originalName: param.name, - type: apiGen.getTypeFromSchema(isReference(param) ? param : param.schema, undefined, 'writeOnly'), + type: getTypeFromSchema(withMode(ctx, 'writeOnly'), isReference(param) ? param : param.schema), required: param.required, param, }; } if (requestBody) { - const body = apiGen.resolve(requestBody); - const schema = apiGen.getSchemaFromContent(body.content); - const type = apiGen.getTypeFromSchema(schema); + const body = resolve(requestBody, ctx); + const schema = getSchemaFromContent(body.content); + const type = getTypeFromSchema(ctx, schema); const schemaName = camelCase( (type as any).name || getReferenceName(schema) || @@ -417,7 +443,7 @@ export async function generateApi( origin: 'body', name, originalName: schemaName, - type: apiGen.getTypeFromSchema(schema, undefined, 'writeOnly'), + type: getTypeFromSchema(withMode(ctx, 'writeOnly'), schema), required: true, body, }; diff --git a/packages/rtk-query-codegen-openapi/test/generateEndpoints.test.ts b/packages/rtk-query-codegen-openapi/test/generateEndpoints.test.ts index c452a4253b..d08c5e48bf 100644 --- a/packages/rtk-query-codegen-openapi/test/generateEndpoints.test.ts +++ b/packages/rtk-query-codegen-openapi/test/generateEndpoints.test.ts @@ -768,6 +768,20 @@ describe('openapi spec', () => { }); }); +describe('useEnumType option', () => { + it('generates TypeScript enums when useEnumType is true', async () => { + const api = await generateEndpoints({ + unionUndefined: true, + schemaFile: resolve(__dirname, 'fixtures/petstore.json'), + apiFile: './fixtures/emptyApi.ts', + useEnumType: true, + filterEndpoints: ['findPetsByStatus'], + }); + + expect(api).toMatch(/enum\s+\w+/); + }); +}); + describe('query parameters', () => { it('parameters overridden in swagger should also be overridden in the code', async () => { const api = await generateEndpoints({ diff --git a/yarn.lock b/yarn.lock index afcd412f1a..3118a17919 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7043,6 +7043,15 @@ __metadata: languageName: node linkType: hard +"@oazapfts/resolve@npm:^1.0.0": + version: 1.0.0 + resolution: "@oazapfts/resolve@npm:1.0.0" + dependencies: + openapi-types: "npm:^12.1.3" + checksum: 10/2dfc1af464172fa3b987ed83cbd33159638bc219f6fa96e017c9c007ca02c9ce861a048475353e5bb62166074994934c519b493959b1b18f8fe49c6d5a012d4c + languageName: node + linkType: hard + "@oazapfts/runtime@npm:^1.0.3": version: 1.2.0 resolution: "@oazapfts/runtime@npm:1.2.0" @@ -8365,6 +8374,7 @@ __metadata: "@babel/core": "npm:^7.12.10" "@babel/preset-env": "npm:^7.12.11" "@babel/preset-typescript": "npm:^7.12.7" + "@oazapfts/resolve": "npm:^1.0.0" "@oazapfts/runtime": "npm:^1.0.3" "@reduxjs/toolkit": "npm:^1.6.0" "@tanstack/intent": "npm:^0.0.19" @@ -8382,7 +8392,7 @@ __metadata: lodash.camelcase: "npm:^4.3.0" msw: "npm:^2.1.5" node-fetch: "npm:^3.3.2" - oazapfts: "npm:^6.4.0" + oazapfts: "npm:7.5.0" openapi-types: "npm:^9.1.0" prettier: "npm:^3.2.5" pretty-quick: "npm:^4.0.0" @@ -22013,7 +22023,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.7.0, lodash@npm:~4.18.1": +"lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.17.23, lodash@npm:^4.7.0, lodash@npm:~4.18.1": version: 4.18.1 resolution: "lodash@npm:4.18.1" checksum: 10/306fea53dfd39dad1f03d45ba654a2405aebd35797b673077f401edb7df2543623dc44b9effbb98f69b32152295fff725a4cec99c684098947430600c6af0c3f @@ -24510,21 +24520,21 @@ __metadata: languageName: node linkType: hard -"oazapfts@npm:^6.4.0": - version: 6.4.0 - resolution: "oazapfts@npm:6.4.0" +"oazapfts@npm:7.5.0": + version: 7.5.0 + resolution: "oazapfts@npm:7.5.0" dependencies: "@apidevtools/swagger-parser": "npm:^12.1.0" - lodash: "npm:^4.17.21" + "@oazapfts/resolve": "npm:^1.0.0" + lodash: "npm:^4.17.23" minimist: "npm:^1.2.8" - swagger2openapi: "npm:^7.0.8" tapable: "npm:^2.3.0" typescript: "npm:^5.9.3" peerDependencies: - "@oazapfts/runtime": "*" + "@oazapfts/runtime": ^1.2.0 bin: - oazapfts: cli.js - checksum: 10/c52d1a8d786e41b5c228d6e2a52923ac58fde86eef44c1fb31dc65171788ae7c36d6b23ab53acea91fcc20a3b6121051bc7e90a6ebb633db54380562a8a16664 + oazapfts: dist/cli.js + checksum: 10/349ea06e3347c4a842004a798853418617734050e61ac9d2b4461ab9709af13a5e4f66b389d9884e5f737e06af344b8f8d788ba0afc90efadf7ca0924fc80473 languageName: node linkType: hard @@ -24759,6 +24769,13 @@ __metadata: languageName: node linkType: hard +"openapi-types@npm:^12.1.3": + version: 12.1.3 + resolution: "openapi-types@npm:12.1.3" + checksum: 10/9d1d7ed848622b63d0a4c3f881689161b99427133054e46b8e3241e137f1c78bb0031c5d80b420ee79ac2e91d2e727ffd6fc13c553d1b0488ddc8ad389dcbef8 + languageName: node + linkType: hard + "openapi-types@npm:^9.1.0": version: 9.3.1 resolution: "openapi-types@npm:9.3.1" @@ -31247,7 +31264,7 @@ __metadata: languageName: node linkType: hard -"swagger2openapi@npm:^7.0.4, swagger2openapi@npm:^7.0.8": +"swagger2openapi@npm:^7.0.4": version: 7.0.8 resolution: "swagger2openapi@npm:7.0.8" dependencies: @@ -32894,8 +32911,8 @@ __metadata: linkType: hard "vite@npm:^6.0.0 || ^7.0.0 || ^8.0.0": - version: 8.0.3 - resolution: "vite@npm:8.0.3" + version: 8.0.5 + resolution: "vite@npm:8.0.5" dependencies: fsevents: "npm:~2.3.3" lightningcss: "npm:^1.32.0" @@ -32906,7 +32923,7 @@ __metadata: peerDependencies: "@types/node": ^20.19.0 || >=22.12.0 "@vitejs/devtools": ^0.1.0 - esbuild: ^0.27.0 + esbuild: ^0.27.0 || ^0.28.0 jiti: ">=1.21.0" less: ^4.0.0 sass: ^1.70.0 @@ -32946,7 +32963,7 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: 10/745b791cb71297ac3877af061da44751d93f198413426bbb76a1f8384d76d4162a6ad739b2bcdf5fb966cd1295db59412614aee60738e40e1c99cee561e682f0 + checksum: 10/eca1ddd6309193a80a8c82607e672df3fecff88d3639382c6eaaf4c3094accd0412c16e2f1cc4a25c835f055133325eaa0355e484d4c253ffb015a7d83d68250 languageName: node linkType: hard