diff --git a/src/generator/03-entities/index.ts b/src/generator/03-entities/index.ts index a85d70e..7470036 100644 --- a/src/generator/03-entities/index.ts +++ b/src/generator/03-entities/index.ts @@ -7,11 +7,14 @@ import { ExtendedSchema, isEnumSchemaObject, isReferenceObject } from '@utils/op import { OpenAPIV3 } from 'openapi-types'; import { logger } from '@logger'; import { OpenApiContext } from '@utils/weclapp/extractContext'; +import { GeneratedEnum } from '@generator/02-enums'; +import { pascalCase } from 'change-case'; export interface GeneratedEntity { name: string; interfaceName: string; properties: Map; + createProperties: InterfaceProperty[]; source: string; filterInterfaceName: string; @@ -128,13 +131,21 @@ export const generateEntities = (context: OpenApiContext): Map !prop.readonly), source: generateStatements( generateInterface(entityInterfaceName, entityInterfaceProperties, parentEntityInterfaceName) ), filterInterfaceName: entityFilterInterfaceName, filterSource: generateStatements( - generateInterface(entityFilterInterfaceName, entityFilterInterfaceProperties, parentEntityFilterInterfaceName) + generateInterface( + entityFilterInterfaceName, + entityFilterInterfaceProperties.map((prop) => ({ + ...prop, + required: !!(prop.required ?? prop.type?.endsWith('[]')) || prop.type !== 'string' + })), + parentEntityFilterInterfaceName + ) ), parentName: parentEntityName, @@ -144,3 +155,55 @@ export const generateEntities = (context: OpenApiContext): Map, + enums: Map +): string[] => { + const isPrimitiveOrEnumOrObject = (propType?: string) => { + return ( + !propType || + enums.has(propType) || + propType.includes('|') || + propType === 'string' || + propType === 'number' || + propType === 'boolean' || + propType === '{}' + ); + }; + + const transformCreateEntityInterfaceProps = (props: InterfaceProperty[]) => { + return props.map((prop) => { + if (isPrimitiveOrEnumOrObject(prop.type)) { + return prop; + } + + if (prop.type?.endsWith('[]')) { + const typeWithoutBrackets = prop.type.replace(/^\((.*?)\)\[\]$/, '$1'); + return isPrimitiveOrEnumOrObject(typeWithoutBrackets) + ? prop + : { ...prop, type: `(${typeWithoutBrackets}_Create)[]` }; + } + + return { ...prop, type: `${prop.type}_Create` }; + }); + }; + + const createEntityStatements: string[] = []; + + const generateCreateEntityStatement = (entity: GeneratedEntity, entityName: string): string => { + const name = `${pascalCase(entityName)}_Create`; + const parentName = entity.parentName ? `${pascalCase(entity.parentName)}_Create` : undefined; + const interfaceProperties = Array.from(transformCreateEntityInterfaceProps(entity.createProperties)); + + return generateStatements(generateInterface(name, interfaceProperties, parentName)); + }; + + entities.forEach((entity, name) => { + if (entity.properties.entries().filter(([, meta]) => !meta.enum)) { + createEntityStatements.push(generateCreateEntityStatement(entity, name)); + } + }); + + return createEntityStatements; +}; diff --git a/src/generator/04-services/endpoints/create.ts b/src/generator/04-services/endpoints/create.ts index 6f96d3c..403d55b 100644 --- a/src/generator/04-services/endpoints/create.ts +++ b/src/generator/04-services/endpoints/create.ts @@ -13,13 +13,14 @@ export const generateCreateEndpoint: ServiceFunctionGenerator = ({ context, options }): GeneratedServiceFunction => { + const endpointName = pascalCase(endpoint.service); const functionName = 'create'; - const functionTypeName = `${pascalCase(endpoint.service)}Service_${pascalCase(functionName)}`; + const functionTypeName = `${endpointName}Service_${pascalCase(functionName)}`; const functionTypeSource = generateArrowFunctionType({ type: functionTypeName, params: [ - `data: DeepPartial<${generateRequestBodyType(operationObject, context.requestBodies).toString()}>`, + `data: ${generateRequestBodyType(operationObject, context.requestBodies).toString()}_Create`, 'requestOptions?: RequestOptions' ], returns: `${resolveResponseType(options.target)}<${generateResponseType(operationObject, context.responses).toString()}>` diff --git a/src/generator/04-services/endpoints/generic.ts b/src/generator/04-services/endpoints/generic.ts index feaa439..08cfc74 100644 --- a/src/generator/04-services/endpoints/generic.ts +++ b/src/generator/04-services/endpoints/generic.ts @@ -33,11 +33,11 @@ export const generateGenericEndpoint = const params = createObjectType({ params: operationObject.parameters && - convertToTypeScriptType(resolveParameters(operationObject.parameters, context.parameters)), + convertToTypeScriptType(resolveParameters(operationObject.parameters, context.parameters), true), body: method === 'get' ? undefined - : wrapBody(generateRequestBodyType(operationObject, context.requestBodies), options.target) + : wrapBody(generateRequestBodyType(operationObject, context.requestBodies, true), options.target) }); const responseBody = generateResponseType(operationObject, context.responses); diff --git a/src/generator/04-services/endpoints/update.ts b/src/generator/04-services/endpoints/update.ts index e35c2ab..12e581b 100644 --- a/src/generator/04-services/endpoints/update.ts +++ b/src/generator/04-services/endpoints/update.ts @@ -20,7 +20,7 @@ export const generateUpdateEndpoint: ServiceFunctionGenerator = ({ type: functionTypeName, params: [ 'id: string', - `data: DeepPartial<${generateRequestBodyType(operationObject, context.requestBodies).toString()}>`, + `data: ${generateRequestBodyType(operationObject, context.requestBodies).toString()}`, 'options?: UpdateQuery', 'requestOptions?: RequestOptions' ], diff --git a/src/generator/04-services/utils/generateContentType.ts b/src/generator/04-services/utils/generateContentType.ts index 6eca5bf..33d4b7b 100644 --- a/src/generator/04-services/utils/generateContentType.ts +++ b/src/generator/04-services/utils/generateContentType.ts @@ -8,14 +8,15 @@ import { OpenAPIV3 } from 'openapi-types'; export const generateContentType = ( body?: OpenAPIV3.RequestBodyObject | OpenAPIV3.ResponseObject, - fallback: string = 'unknown' + fallback: string = 'unknown', + isDeepPartialType?: boolean ): AnyType => { if (!body?.content) return createRawType(fallback); const types: AnyType[] = []; for (const { schema } of Object.values(body.content)) { if (schema) { - types.push(convertToTypeScriptType(schema)); + types.push(convertToTypeScriptType(schema, isDeepPartialType)); } } diff --git a/src/generator/04-services/utils/generateRequestBodyType.ts b/src/generator/04-services/utils/generateRequestBodyType.ts index 4580c0f..6640c40 100644 --- a/src/generator/04-services/utils/generateRequestBodyType.ts +++ b/src/generator/04-services/utils/generateRequestBodyType.ts @@ -5,9 +5,10 @@ import { isReferenceObject } from '@utils/openapi/guards'; export const generateRequestBodyType = ( { requestBody }: OpenAPIV3.OperationObject, - requestBodies: Map + requestBodies: Map, + isDeepPartialType?: boolean ): AnyType => { const requestBodyObject = requestBody && isReferenceObject(requestBody) ? requestBodies.get(getRefName(requestBody)) : requestBody; - return generateContentType(requestBodyObject); + return generateContentType(requestBodyObject, undefined, isDeepPartialType); }; diff --git a/src/generator/generate.ts b/src/generator/generate.ts index 36d5a76..db1641f 100644 --- a/src/generator/generate.ts +++ b/src/generator/generate.ts @@ -1,7 +1,7 @@ import { Target } from '../target'; import { generateBase } from '@generator/01-base'; import { generateEnums } from '@generator/02-enums'; -import { generateEntities } from '@generator/03-entities'; +import { generateCreateEntityStatements, generateEntities } from '@generator/03-entities'; import { generateServices } from '@generator/04-services'; import { generateMaps } from '@generator/05-maps'; import { generateBlockComment } from '@ts/generateComment'; @@ -26,13 +26,17 @@ export const generate = (doc: OpenAPIV3.Document, options: GeneratorOptions): st const base = generateBase(doc.info.version, options); const enums = generateEnums(context); const entities = generateEntities(context); + const createEntityStatements = generateCreateEntityStatements(entities, enums); const services = generateServices(entities, context, options); const maps = generateMaps(enums, entities, services, context, options); return generateStatements( generateBlockComment('BASE', base), generateBlockComment('ENUMS', generateStatements(...[...enums.values()].map((v) => v.source))), - generateBlockComment('ENTITIES', generateStatements(...[...entities.values()].map((v) => v.source))), + generateBlockComment( + 'ENTITIES', + generateStatements(...[...entities.values()].map((v) => v.source), ...createEntityStatements) + ), generateBlockComment('FILTERS', generateStatements(...[...entities.values()].map((v) => v.filterSource))), generateBlockComment('SERVICES', generateStatements(...[...services.values()].map((v) => v.source))), generateBlockComment('MAPS', maps) diff --git a/src/utils/openapi/convertToTypeScriptType.ts b/src/utils/openapi/convertToTypeScriptType.ts index b8cb201..8e02a00 100644 --- a/src/utils/openapi/convertToTypeScriptType.ts +++ b/src/utils/openapi/convertToTypeScriptType.ts @@ -36,9 +36,9 @@ export interface ObjectType extends Type { export type PropertyPropagationOption = 'ignore' | 'propagate' | 'force'; -export const createReferenceType = (value: string): ReferenceType => ({ +export const createReferenceType = (value: string, isDeepPartialType?: boolean): ReferenceType => ({ type: 'reference', - toString: () => loosePascalCase(value) + toString: () => (isDeepPartialType ? `DeepPartial<${loosePascalCase(value)}>` : loosePascalCase(value)) }); export const createRawType = (value: string): RawType => ({ @@ -90,12 +90,13 @@ export const getRefName = (obj: OpenAPIV3.ReferenceObject) => { }; export const convertToTypeScriptType = ( - schema: OpenAPIV3.SchemaObject | OpenAPIV3.ParameterObject[] | OpenAPIV3.ReferenceObject + schema: OpenAPIV3.SchemaObject | OpenAPIV3.ParameterObject[] | OpenAPIV3.ReferenceObject, + isDeepPartialType?: boolean ): AnyType => { if (Array.isArray(schema)) { - return convertToTypeScriptType(convertParametersToSchemaObject(schema)); + return convertToTypeScriptType(convertParametersToSchemaObject(schema), isDeepPartialType); } else if (isReferenceObject(schema)) { - return createReferenceType(getRefName(schema)); + return createReferenceType(getRefName(schema), isDeepPartialType); } else { switch (schema.type) { case 'integer': @@ -113,13 +114,16 @@ export const convertToTypeScriptType = ( const { properties = {}, required = [] } = schema; return createObjectType( Object.fromEntries( - Object.entries(properties).map(([prop, propSchema]) => [prop, convertToTypeScriptType(propSchema)]) + Object.entries(properties).map(([prop, propSchema]) => [ + prop, + convertToTypeScriptType(propSchema, isDeepPartialType) + ]) ), required ); } case 'array': - return createArrayType(convertToTypeScriptType(schema.items)); + return createArrayType(convertToTypeScriptType(schema.items, isDeepPartialType)); default: return createRawType('unknown'); }