diff --git a/.changeset/loose-lies-fetch.md b/.changeset/loose-lies-fetch.md new file mode 100644 index 000000000..274dc71ca --- /dev/null +++ b/.changeset/loose-lies-fetch.md @@ -0,0 +1,5 @@ +--- +"swagger-typescript-api": patch +--- + +Improve type safety by adding proper types to SchemaComponent and introducing flags for extracted elements such as request parameters, request body, response body, and response errors. diff --git a/src/configuration.ts b/src/configuration.ts index e76ad495d..bd273c148 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -5,6 +5,8 @@ import * as typescript from "typescript"; import type { ExtractingOptions, GenerateApiConfiguration, + Hooks, + SchemaComponent, } from "../types/index.js"; import { ComponentTypeNameResolver } from "./component-type-name-resolver.js"; import * as CONSTANTS from "./constants.js"; @@ -86,11 +88,11 @@ export class CodeGenConfig { }; routeNameDuplicatesMap = new Map(); prettierOptions = { ...CONSTANTS.PRETTIER_OPTIONS }; - hooks = { + hooks: Hooks = { onPreBuildRoutePath: (_routePath: unknown) => void 0, onBuildRoutePath: (_routeData: unknown) => void 0, onInsertPathParam: (_pathParam: unknown) => void 0, - onCreateComponent: (schema: unknown) => schema, + onCreateComponent: (schema: SchemaComponent) => schema, onPreParseSchema: ( _originalSchema: unknown, _typeName: unknown, diff --git a/src/schema-components-map.ts b/src/schema-components-map.ts index 7c9d13752..308a14e9b 100644 --- a/src/schema-components-map.ts +++ b/src/schema-components-map.ts @@ -21,11 +21,16 @@ export class SchemaComponentsMap { return ref.split("/"); }; - createComponent($ref: string, rawTypeData: string) { + createComponent( + $ref: string, + rawTypeData: SchemaComponent["rawTypeData"], + ): SchemaComponent { const parsed = this.parseRef($ref); - const typeName = parsed[parsed.length - 1]; - const componentName = parsed[parsed.length - 2]; - const componentSchema = { + const typeName = parsed[parsed.length - 1]!; + const componentName = parsed[ + parsed.length - 2 + ] as SchemaComponent["componentName"]; + const componentSchema: SchemaComponent = { $ref, typeName, rawTypeData, @@ -52,7 +57,7 @@ export class SchemaComponentsMap { return this._data; } - filter(...componentNames: string[]) { + filter(...componentNames: (string[] | string)[]) { return this._data.filter((it) => componentNames.some((componentName) => it.$ref.startsWith(`#/components/${componentName}`), diff --git a/src/schema-parser/schema-parser-fabric.ts b/src/schema-parser/schema-parser-fabric.ts index bdf9ffc0a..1e1ad4efd 100644 --- a/src/schema-parser/schema-parser-fabric.ts +++ b/src/schema-parser/schema-parser-fabric.ts @@ -1,5 +1,6 @@ import type { ParsedSchema, + SchemaComponent, SchemaTypeEnumContent, SchemaTypeObjectContent, SchemaTypePrimitiveContent, @@ -63,7 +64,11 @@ export class SchemaParserFabric { return parser.schema; }; - createParsedComponent = ({ typeName, schema, schemaPath }) => { + createParsedComponent = ({ + typeName, + schema, + schemaPath, + }): SchemaComponent => { const schemaCopy = structuredClone(schema); const customComponent = this.schemaComponentsMap.createComponent( this.schemaComponentsMap.createRef(["components", "schemas", typeName]), diff --git a/src/schema-routes/schema-routes.ts b/src/schema-routes/schema-routes.ts index b443999f9..b8bf8f243 100644 --- a/src/schema-routes/schema-routes.ts +++ b/src/schema-routes/schema-routes.ts @@ -597,6 +597,10 @@ export class SchemaRoutes { typeName, schemaPath: [operationId], }); + + if (schema?.typeData) { + schema.typeData.isExtractedRequestBody = true; + } content = this.schemaParserFabric.getInlineParseContent({ $ref: schema.$ref, }); @@ -670,10 +674,16 @@ export class SchemaRoutes { }, ); - return this.schemaParserFabric.createParsedComponent({ + const component = this.schemaParserFabric.createParsedComponent({ typeName: generatedTypeName, schema: schema, }); + + if (component.typeData) { + component.typeData.isExtractedRequestParams = true; + } + + return component; } return schema; @@ -705,6 +715,9 @@ export class SchemaRoutes { schemaPath: [routeInfo.operationId], }); successResponse.schema.contentKind = contentKind; + if (successResponse.schema.typeData) { + successResponse.schema.typeData.isExtractedResponseBody = true; + } successResponse.type = this.schemaParserFabric.getInlineParseContent({ $ref: successResponse.schema.$ref, }); @@ -756,6 +769,9 @@ export class SchemaRoutes { { ...schema }, ); responseBodyInfo.error.schemas = [component]; + if (component.typeData) { + component.typeData.isExtractedResponseError = true; + } responseBodyInfo.error.type = this.typeNameFormatter.format( component.typeName, ); @@ -818,7 +834,7 @@ export class SchemaRoutes { method, usageSchema, parsedSchemas, - ) => { + ): ParsedRoute => { const { security: globalSecurity } = usageSchema; const { moduleNameIndex, moduleNameFirstTag, extractRequestParams } = this.config; diff --git a/types/index.ts b/types/index.ts index 6ef977318..17fdf6e90 100644 --- a/types/index.ts +++ b/types/index.ts @@ -442,6 +442,10 @@ export interface ParsedSchema { description?: string; allFieldsAreOptional?: boolean; content: C; + isExtractedRequestParams?: boolean; + isExtractedRequestBody?: boolean; + isExtractedResponseBody?: boolean; + isExtractedResponseError?: boolean; } export interface PathArgInfo { @@ -514,12 +518,76 @@ export type RawRouteInfo = { consumes?: string[]; }; +export interface ParsedRouteRequest { + contentTypes?: string[]; + formData?: boolean; + headers?: { + name: string | null; + optional: boolean | undefined; + type: Record; + }; + isQueryBody?: boolean; + method?: string; + parameters?: Record[]; + path?: string; + pathParams?: Record; + payload?: { name: string | null; optional?: boolean; type: string }; + query?: Record; + requestParams?: Record | null; + security?: boolean; +} + +export interface ParsedRouteResponse { + contentTypes?: string[]; + errorType?: string; + fullTypes?: string; + type?: string; +} + export interface ParsedRoute { id: string; - jsDocLines: string; namespace: string; - request: Request; - response: Response; + // biome-ignore lint/suspicious/noExplicitAny: TODO + routeParams?: Record; + requestBodyInfo?: { + // biome-ignore lint/suspicious/noExplicitAny: TODO + paramName: any; + // biome-ignore lint/suspicious/noExplicitAny: TODO + contentTypes: any[]; + contentKind: string; + // biome-ignore lint/suspicious/noExplicitAny: TODO + schema: any; + // biome-ignore lint/suspicious/noExplicitAny: TODO + type: any; + // biome-ignore lint/suspicious/noExplicitAny: TODO + required: any; + }; + responseBodyInfo?: { + // biome-ignore lint/suspicious/noExplicitAny: TODO + contentTypes: any[]; + // biome-ignore lint/suspicious/noExplicitAny: TODO + responses: any[]; + // biome-ignore lint/suspicious/noExplicitAny: TODO + success?: Record; + // biome-ignore lint/suspicious/noExplicitAny: TODO + error?: Record; + // biome-ignore lint/suspicious/noExplicitAny: TODO + full?: Record; + }; + // biome-ignore lint/suspicious/noExplicitAny: TODO + specificArgs?: Record; + // biome-ignore lint/suspicious/noExplicitAny: TODO + queryObjectSchema?: Record; + // biome-ignore lint/suspicious/noExplicitAny: TODO + pathObjectSchema?: Record; + // biome-ignore lint/suspicious/noExplicitAny: TODO + headersObjectSchema?: Record; + // biome-ignore lint/suspicious/noExplicitAny: TODO + responseBodySchema?: Record; + requestBodySchema?: Record; + specificArgNameResolver?: Record; + request: ParsedRouteRequest; + response: ParsedRouteResponse; routeName: RouteNameInfo; raw: RawRouteInfo; }