-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
feat(codegen): migrate to oazapfts v7 #5228
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
Suto-Michimasa
wants to merge
10
commits into
reduxjs:master
Choose a base branch
from
Suto-Michimasa:feat/codegen-oazapfts-v7-enum-style
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+115
−49
Draft
Changes from 6 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
d3486b0
chore: update oazapfts to v7.3.0 and adjust compatibility layer
Suto-Michimasa 6f8e661
delete enumStyle and focus migrate v7
Suto-Michimasa fbef0b9
chore: downgrade oazapfts to v7.0.0 in package.json and yarn.lock
Suto-Michimasa 903e461
chore: update oazapfts and refactor code generation
Suto-Michimasa 5ef9867
refactor: simplify discriminator handling in schema processing
Suto-Michimasa 4f32e53
refactor: remove makeDiscriminatorPropertiesRequired function and cle…
Suto-Michimasa 68a3625
chore: update oazapfts to v7.5.0-alpha.5 and refine code generation
Suto-Michimasa bfabf8f
refactor: streamline operation name handling and improve TypeScript v…
Suto-Michimasa f214959
refactor: improve context creation in API generation
Suto-Michimasa 973b27a
chore: update oazapfts to v7.5.0-alpha.6 and enhance code generation
Suto-Michimasa File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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, type OnlyMode } from 'oazapfts/context'; | ||
| import { getTypeFromSchema, getResponseType, getSchemaFromContent } 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,103 @@ import type { | |
| ParameterMatcher, | ||
| TextMatcher, | ||
| } from './types'; | ||
| import { capitalize, getOperationDefinitions, getV3Doc, removeUndefined, isQuery as testIsQuery } from './utils'; | ||
| import { | ||
| capitalize, | ||
| getOperationDefinitions, | ||
| getOperationName as _getOperationName, | ||
| getV3Doc, | ||
| removeUndefined, | ||
| isQuery as testIsQuery, | ||
| } from './utils'; | ||
| import { factory } from './utils/factory'; | ||
|
|
||
| const { createPropertyAssignment, createQuestionToken, keywordType, isValidIdentifier } = cg; | ||
|
|
||
| const generatedApiName = 'injectedRtkApi'; | ||
| const v3DocCache: Record<string, OpenAPIV3.Document> = {}; | ||
|
|
||
| function supportDeepObjects( | ||
| params: OpenAPIV3.ParameterObject[] | ||
| ): OpenAPIV3.ParameterObject[] { | ||
| const res: OpenAPIV3.ParameterObject[] = []; | ||
| const merged: Record<string, any> = {}; | ||
| 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<string, any>, | ||
| }, | ||
| }; | ||
| res.push(obj); | ||
| } | ||
| obj.schema.properties[prop] = p.schema; | ||
| } | ||
| return res; | ||
| } | ||
|
|
||
| function preprocessComponents(ctx: OazapftsContext): void { | ||
| const schemas = (ctx.spec as any).components?.schemas; | ||
| if (!schemas) return; | ||
|
|
||
| const prefix = '#/components/schemas/'; | ||
|
|
||
| for (const name of Object.keys(schemas)) { | ||
| const schema = schemas[name]; | ||
| if (isReference(schema) || typeof schema === 'boolean') continue; | ||
| if (schema.discriminator && !schema.oneOf && !schema.anyOf) { | ||
| ctx.discriminatingSchemas.add(schema); | ||
| } | ||
| } | ||
|
|
||
| for (const name of Object.keys(schemas)) { | ||
| const schema = schemas[name]; | ||
| if (isReference(schema) || typeof schema === 'boolean' || !schema.allOf) continue; | ||
|
|
||
| for (const childSchema of schema.allOf) { | ||
| if (!isReference(childSchema)) continue; | ||
| const resolved = resolve<OpenAPIV3.SchemaObject>(childSchema, ctx); | ||
| if (!ctx.discriminatingSchemas.has(resolved as any)) continue; | ||
|
|
||
| const refBasename = childSchema.$ref.split('/').pop()!; | ||
| const discriminatingSchema = schemas[refBasename]; | ||
| if (isReference(discriminatingSchema)) continue; | ||
|
|
||
| const discriminator = discriminatingSchema.discriminator; | ||
| if (!discriminator) continue; | ||
|
|
||
| const refs = Object.values(discriminator.mapping || {}); | ||
| if (refs.includes(prefix + name)) continue; | ||
|
|
||
| if (!discriminator.mapping) { | ||
| discriminator.mapping = {}; | ||
| } | ||
| discriminator.mapping[name] = prefix + name; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| function getTypeFromResponse( | ||
| ctx: OazapftsContext, | ||
| response: OpenAPIV3.ResponseObject | OpenAPIV3.ReferenceObject, | ||
| onlyMode?: OnlyMode | ||
| ): ts.TypeNode { | ||
| const resolved = resolve(response, ctx); | ||
| if (!resolved.content) return keywordType.void; | ||
| const schema = getSchemaFromContent(resolved.content); | ||
| return getTypeFromSchema(onlyMode ? withMode(ctx, onlyMode) : ctx, schema); | ||
| } | ||
|
||
|
|
||
| function defaultIsDataResponse(code: string, includeDefault: boolean) { | ||
| if (includeDefault && code === 'default') { | ||
| return true; | ||
|
|
@@ -89,9 +174,9 @@ function withQueryComment<T extends ts.Node>(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 +186,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 +259,14 @@ export async function generateApi( | |
| ) { | ||
| const v3Doc = (v3DocCache[spec] ??= await getV3Doc(spec, httpResolverOptions)); | ||
|
|
||
| const apiGen = new ApiGenerator(v3Doc, { | ||
| unionUndefined, | ||
| const ctx = createContext(v3Doc as any, { | ||
| useEnumType, | ||
| unionUndefined, | ||
| 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 +340,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,21 +400,21 @@ 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 || {}) | ||
| .map( | ||
| ([code, response]) => | ||
| [ | ||
| code, | ||
| apiGen.resolve(response), | ||
| apiGen.getTypeFromResponse(response, 'readOnly') || | ||
| resolve(response, ctx), | ||
| getTypeFromResponse(ctx, response, '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 +441,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 +476,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 +498,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, | ||
| }; | ||
|
|
||
2 changes: 1 addition & 1 deletion
2
packages/rtk-query-codegen-openapi/src/generators/react-hooks.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
25 changes: 25 additions & 0 deletions
25
packages/rtk-query-codegen-openapi/src/utils/getOperationName.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import lodashCamelCase from 'lodash.camelcase'; | ||
| import { UNSTABLE_cg as cg } from 'oazapfts'; | ||
|
|
||
| const { isValidIdentifier } = cg; | ||
|
|
||
| export function getOperationName( | ||
| verb: string, | ||
| path: string, | ||
| operationId?: string | ||
| ): string { | ||
| if (operationId) { | ||
| const normalized = operationId.replace(/[^\w\s]/g, ' '); | ||
| let camelCased = lodashCamelCase(normalized); | ||
| if (camelCased) { | ||
| camelCased = camelCased.replace(/^[^a-zA-Z_$]+/, ''); | ||
| if (camelCased && isValidIdentifier(camelCased)) { | ||
| return camelCased; | ||
| } | ||
| } | ||
| } | ||
| const pathStr = path | ||
| .replace(/\{(.+?)\}/, 'by $1') | ||
| .replace(/\{(.+?)\}/, 'and $1'); | ||
| return lodashCamelCase(`${verb} ${pathStr}`); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
7.5.0-alpha.5 does this within createContext