Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 64 additions & 1 deletion src/generator/03-entities/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, PropertyMetaData>;
createProperties: InterfaceProperty[];
source: string;

filterInterfaceName: string;
Expand Down Expand Up @@ -128,13 +131,21 @@ export const generateEntities = (context: OpenApiContext): Map<string, Generated
name: entityName,
interfaceName: entityInterfaceName,
properties,
createProperties: entityInterfaceProperties.filter((prop) => !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,
Expand All @@ -144,3 +155,55 @@ export const generateEntities = (context: OpenApiContext): Map<string, Generated

return entities;
};

export const generateCreateEntityStatements = (
entities: Map<string, GeneratedEntity>,
enums: Map<string, GeneratedEnum>
): 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;
};
5 changes: 3 additions & 2 deletions src/generator/04-services/endpoints/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()}>`
Expand Down
4 changes: 2 additions & 2 deletions src/generator/04-services/endpoints/generic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/generator/04-services/endpoints/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
],
Expand Down
5 changes: 3 additions & 2 deletions src/generator/04-services/utils/generateContentType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}

Expand Down
5 changes: 3 additions & 2 deletions src/generator/04-services/utils/generateRequestBodyType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import { isReferenceObject } from '@utils/openapi/guards';

export const generateRequestBodyType = (
{ requestBody }: OpenAPIV3.OperationObject,
requestBodies: Map<string, OpenAPIV3.RequestBodyObject>
requestBodies: Map<string, OpenAPIV3.RequestBodyObject>,
isDeepPartialType?: boolean
): AnyType => {
const requestBodyObject =
requestBody && isReferenceObject(requestBody) ? requestBodies.get(getRefName(requestBody)) : requestBody;
return generateContentType(requestBodyObject);
return generateContentType(requestBodyObject, undefined, isDeepPartialType);
};
8 changes: 6 additions & 2 deletions src/generator/generate.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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)
Expand Down
18 changes: 11 additions & 7 deletions src/utils/openapi/convertToTypeScriptType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 => ({
Expand Down Expand Up @@ -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':
Expand All @@ -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');
}
Expand Down
Loading