diff --git a/packages/rtk-query-codegen-openapi/package.json b/packages/rtk-query-codegen-openapi/package.json index 2ba3f77f32..512168d890 100644 --- a/packages/rtk-query-codegen-openapi/package.json +++ b/packages/rtk-query-codegen-openapi/package.json @@ -76,9 +76,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-alpha.6", "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 0cf52b6130..880e484a78 100644 --- a/packages/rtk-query-codegen-openapi/src/generate.ts +++ b/packages/rtk-query-codegen-openapi/src/generate.ts @@ -1,17 +1,11 @@ import camelCase from 'lodash.camelcase'; import path from 'node:path'; -import ApiGenerator, { - getOperationName as _getOperationName, - createPropertyAssignment, - createQuestionToken, - getReferenceName, - isReference, - isValidIdentifier, - keywordType, - supportDeepObjects, -} from 'oazapfts/generate'; import type { OpenAPIV3 } from 'openapi-types'; import ts from 'typescript'; +import { UNSTABLE_cg as cg } from 'oazapfts'; +import { createContext, withMode, type OazapftsContext } from 'oazapfts/context'; +import { getTypeFromSchema, getTypeFromResponse, getResponseType, getSchemaFromContent, getOperationName as _getOperationName, preprocessComponents } from 'oazapfts/generate'; +import { resolve, resolveArray, isReference, getReferenceName } from '@oazapfts/resolve'; import type { ObjectPropertyDefinitions } from './codegen'; import { generateCreateApiCall, generateEndpointDefinition, generateImportNode, generateTagTypes } from './codegen'; import { generateReactHooks } from './generators/react-hooks'; @@ -24,12 +18,50 @@ import type { ParameterMatcher, TextMatcher, } from './types'; -import { capitalize, getOperationDefinitions, getV3Doc, removeUndefined, isQuery as testIsQuery } from './utils'; +import { + capitalize, + getOperationDefinitions, + getV3Doc, + removeUndefined, + isQuery as testIsQuery, +} from './utils'; import { factory } from './utils/factory'; +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,13 +354,13 @@ 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 || {}) + isDataResponse(status, includeDefault, resolve(response, ctx), responses || {}) ) .filter(([_1, _2, type]) => type !== keywordType.void) .map(([code, response, type]) => @@ -359,9 +387,8 @@ export async function generateApi( ).name ); - const operationParameters = apiGen.resolveArray(operation.parameters); - const pathItemParameters = apiGen - .resolveArray(pathItem.parameters) + 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( @@ -395,16 +422,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 +444,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 20c895219d..b926959040 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 98e3fb4c30..0d1afe7620 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6692,6 +6692,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.0.4 resolution: "@oazapfts/runtime@npm:1.0.4" @@ -7808,6 +7817,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" "@types/commander": "npm:^2.12.2" @@ -7824,7 +7834,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-alpha.6" openapi-types: "npm:^9.1.0" prettier: "npm:^3.2.5" pretty-quick: "npm:^4.0.0" @@ -21296,6 +21306,13 @@ __metadata: languageName: node linkType: hard +"lodash@npm:^4.17.23": + version: 4.17.23 + resolution: "lodash@npm:4.17.23" + checksum: 10/82504c88250f58da7a5a4289f57a4f759c44946c005dd232821c7688b5fcfbf4a6268f6a6cdde4b792c91edd2f3b5398c1d2a0998274432cff76def48735e233 + languageName: node + linkType: hard + "log-symbols@npm:^1.0.2": version: 1.0.2 resolution: "log-symbols@npm:1.0.2" @@ -23726,21 +23743,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-alpha.6": + version: 7.5.0-alpha.6 + resolution: "oazapfts@npm:7.5.0-alpha.6" 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/b82ac3a7d5a833cf36e061d732d4fbc4ebcc7b2c4dec9c13bbb5b0c4ff7b5972ae0cab1e44be0a2116878ca4fb5d66ea908776c80658194994e253bffab60452 languageName: node linkType: hard @@ -23956,6 +23973,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" @@ -30499,7 +30523,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: