Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/loose-lies-fetch.md
Original file line number Diff line number Diff line change
@@ -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.
6 changes: 4 additions & 2 deletions src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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,
Expand Down
15 changes: 10 additions & 5 deletions src/schema-components-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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}`),
Expand Down
7 changes: 6 additions & 1 deletion src/schema-parser/schema-parser-fabric.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type {
ParsedSchema,
SchemaComponent,
SchemaTypeEnumContent,
SchemaTypeObjectContent,
SchemaTypePrimitiveContent,
Expand Down Expand Up @@ -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]),
Expand Down
20 changes: 18 additions & 2 deletions src/schema-routes/schema-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,10 @@ export class SchemaRoutes {
typeName,
schemaPath: [operationId],
});

if (schema?.typeData) {
schema.typeData.isExtractedRequestBody = true;
}
content = this.schemaParserFabric.getInlineParseContent({
$ref: schema.$ref,
});
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
});
Expand Down Expand Up @@ -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,
);
Expand Down Expand Up @@ -818,7 +834,7 @@ export class SchemaRoutes {
method,
usageSchema,
parsedSchemas,
) => {
): ParsedRoute => {
const { security: globalSecurity } = usageSchema;
const { moduleNameIndex, moduleNameFirstTag, extractRequestParams } =
this.config;
Expand Down
74 changes: 71 additions & 3 deletions types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,10 @@ export interface ParsedSchema<C> {
description?: string;
allFieldsAreOptional?: boolean;
content: C;
isExtractedRequestParams?: boolean;
isExtractedRequestBody?: boolean;
isExtractedResponseBody?: boolean;
isExtractedResponseError?: boolean;
}

export interface PathArgInfo {
Expand Down Expand Up @@ -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<string, any>;
};
isQueryBody?: boolean;
method?: string;
parameters?: Record<string, unknown>[];
path?: string;
pathParams?: Record<string, unknown>;
payload?: { name: string | null; optional?: boolean; type: string };
query?: Record<string, unknown>;
requestParams?: Record<string, unknown> | 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<string, any>;
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<string, any>;
// biome-ignore lint/suspicious/noExplicitAny: TODO
error?: Record<string, any>;
// biome-ignore lint/suspicious/noExplicitAny: TODO
full?: Record<string, any>;
};
// biome-ignore lint/suspicious/noExplicitAny: TODO
specificArgs?: Record<string, any>;
// biome-ignore lint/suspicious/noExplicitAny: TODO
queryObjectSchema?: Record<string, any>;
// biome-ignore lint/suspicious/noExplicitAny: TODO
pathObjectSchema?: Record<string, any>;
// biome-ignore lint/suspicious/noExplicitAny: TODO
headersObjectSchema?: Record<string, any>;
// biome-ignore lint/suspicious/noExplicitAny: TODO
responseBodySchema?: Record<string, any>;
requestBodySchema?: Record<string, any>;
specificArgNameResolver?: Record<string, any>;
request: ParsedRouteRequest;
response: ParsedRouteResponse;
routeName: RouteNameInfo;
raw: RawRouteInfo;
}
Expand Down