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
5 changes: 5 additions & 0 deletions .changeset/shaggy-otters-walk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"swagger-typescript-api": minor
---

Add support for multiple request/response types to be defined as unions
100 changes: 81 additions & 19 deletions src/schema-routes/schema-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,16 +331,55 @@ export class SchemaRoutes {

/* content: { "multipart/form-data": { schema: {...} }, "application/json": { schema: {...} } } */

/* for example: dataType = "multipart/form-data" */
const contentTypes = Object.keys(content);

// if there's only one content type, return it
if (contentTypes.length === 1 && content[contentTypes[0]]?.schema) {
return {
...content[contentTypes[0]].schema,
dataType: contentTypes[0],
};
}

// Check if there are multiple media types with schemas
const schemasWithDataTypes = [];
for (const dataType in content) {
if (content[dataType]?.schema) {
return {
schemasWithDataTypes.push({
...content[dataType].schema,
dataType,
};
});
}
}

// If there's only one schema, return it directly
if (schemasWithDataTypes.length === 1) {
return schemasWithDataTypes[0];
}

// If there are multiple schemas with different structures, create a oneOf schema to generate a union type
if (schemasWithDataTypes.length > 1) {
// Check if all schemas are structurally the same
// If they are, just return the first one
const firstSchema = schemasWithDataTypes[0];
const allSchemasAreSame = schemasWithDataTypes.every((schema) =>
lodash.isEqual(
lodash.omit(schema, "dataType"),
lodash.omit(firstSchema, "dataType"),
),
);

if (allSchemasAreSame) {
return firstSchema;
}

// Otherwise, create a union type
return {
oneOf: schemasWithDataTypes,
dataType: schemasWithDataTypes[0].dataType, // Use the first dataType for compatibility
};
}

return null;
};

Expand All @@ -357,24 +396,47 @@ export class SchemaRoutes {
this.schemaParserFabric.schemaUtils.getSchemaRefType(requestInfo);

if (schema) {
const content = this.schemaParserFabric.getInlineParseContent(
schema,
typeName,
[operationId],
);
const foundedSchemaByName = parsedSchemas.find(
(parsedSchema) =>
this.typeNameFormatter.format(parsedSchema.name) === content,
);
const foundSchemaByContent = parsedSchemas.find((parsedSchema) =>
lodash.isEqual(parsedSchema.content, content),
);
// If we have a oneOf schema (multiple media types), handle it specially
if (schema.oneOf) {
// Process each schema in the oneOf array
const unionTypes = schema.oneOf.map((subSchema) => {
return this.schemaParserFabric.getInlineParseContent(
subSchema,
typeName,
[operationId],
);
});

const foundSchema = foundedSchemaByName || foundSchemaByContent;
// Filter out any duplicates or Any types
const filteredTypes =
this.schemaParserFabric.schemaUtils.filterSchemaContents(
unionTypes,
(content) => content !== this.config.Ts.Keyword.Any,
);

return foundSchema
? this.typeNameFormatter.format(foundSchema.name)
: content;
// Create a union type
return this.config.Ts.UnionType(filteredTypes);
} else {
// Handle single schema as before
const content = this.schemaParserFabric.getInlineParseContent(
schema,
typeName,
[operationId],
);
const foundedSchemaByName = parsedSchemas.find(
(parsedSchema) =>
this.typeNameFormatter.format(parsedSchema.name) === content,
);
const foundSchemaByContent = parsedSchemas.find((parsedSchema) =>
lodash.isEqual(parsedSchema.content, content),
);

const foundSchema = foundedSchemaByName || foundSchemaByContent;

return foundSchema
? this.typeNameFormatter.format(foundSchema.name)
: content;
}
}

if (refTypeInfo) {
Expand Down
54 changes: 32 additions & 22 deletions tests/__snapshots__/extended.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ export interface BlockFeed {
id?: string;
}

export interface ChartDataData {
export type ChartDataData = {
/** The names of the columns returned as data. */
columns?: string[];
/** The actual chart data. */
Expand All @@ -228,7 +228,7 @@ export interface ChartDataData {
name?: string;
};
parameters?: object;
}
};

export interface ChartDataParams {
/**
Expand Down Expand Up @@ -678,12 +678,12 @@ export interface GetBlockParams {
username: string;
}

export interface GetCurrentUserThrottleData {
export type GetCurrentUserThrottleData = {
/** Actions taken inside the time window. */
active_data_rate?: number;
/** Max possible actions inside the time window (usually 1 minute). */
data_rate_limit?: number;
}
};

export interface GetCurrentUserThrottleParams {
/** a valid username string */
Expand Down Expand Up @@ -8732,13 +8732,12 @@ export interface SignRequestParams {
test?: number;
}

/** JWT */
export interface SignRetrieveData {
export type SignRetrieveData = {
exp?: number;
field?: string;
/** base64safe encoded public signing key */
sub?: string;
}
};

export type SignRetrieveError = Error;

Expand Down Expand Up @@ -13323,7 +13322,9 @@ export interface ActivityListRepoNotificationsForAuthenticatedUserParams {
since?: string;
}

export type ActivityListReposStarredByAuthenticatedUserData = Repository[];
export type ActivityListReposStarredByAuthenticatedUserData =
| Repository[]
| StarredRepository[];

export interface ActivityListReposStarredByAuthenticatedUserParams {
/**
Expand Down Expand Up @@ -13366,7 +13367,9 @@ export enum ActivityListReposStarredByAuthenticatedUserParams1SortEnum {
Updated = "updated",
}

export type ActivityListReposStarredByUserData = Repository[];
export type ActivityListReposStarredByUserData =
| Repository[]
| StarredRepository[];

export interface ActivityListReposStarredByUserParams {
/**
Expand Down Expand Up @@ -13426,7 +13429,7 @@ export interface ActivityListReposWatchedByUserParams {
username: string;
}

export type ActivityListStargazersForRepoData = SimpleUser[];
export type ActivityListStargazersForRepoData = SimpleUser[] | Stargazer[];

export interface ActivityListStargazersForRepoParams {
owner: string;
Expand Down Expand Up @@ -28162,7 +28165,9 @@ export interface ReposGetCommunityProfileMetricsParams {
repo: string;
}

export type ReposGetContentData = ContentTree;
export type ReposGetContentData =
| ContentTree
| (ContentDirectory | ContentFile | ContentSymlink | ContentSubmodule);

export interface ReposGetContentParams {
owner: string;
Expand Down Expand Up @@ -60092,7 +60097,10 @@ export class Api<
data: ReposCreateForkPayload,
params: RequestParams = {},
) =>
this.request<ReposCreateForkData, BasicError | ValidationError>({
this.request<
ReposCreateForkData,
(BasicError | ScimError) | BasicError | ValidationError
>({
path: \`/repos/\${owner}/\${repo}/forks\`,
method: "POST",
body: data,
Expand Down Expand Up @@ -61604,13 +61612,15 @@ export class Api<
{ owner, repo, ...query }: ReposListCommitsParams,
params: RequestParams = {},
) =>
this.request<ReposListCommitsData, BasicError>({
path: \`/repos/\${owner}/\${repo}/commits\`,
method: "GET",
query: query,
format: "json",
...params,
}),
this.request<ReposListCommitsData, (BasicError | ScimError) | BasicError>(
{
path: \`/repos/\${owner}/\${repo}/commits\`,
method: "GET",
query: query,
format: "json",
...params,
},
),

/**
* @description Users with pull access in a repository can view commit statuses for a given ref. The ref can be a SHA, a branch name, or a tag name. Statuses are returned in reverse chronological order. The first status in the list will be the latest one. This resource is also available via a legacy route: \`GET /repos/:owner/:repo/statuses/:ref\`.
Expand Down Expand Up @@ -61729,7 +61739,7 @@ export class Api<
{ owner, repo, ...query }: ReposListForksParams,
params: RequestParams = {},
) =>
this.request<ReposListForksData, BasicError>({
this.request<ReposListForksData, BasicError | ScimError>({
path: \`/repos/\${owner}/\${repo}/forks\`,
method: "GET",
query: query,
Expand Down Expand Up @@ -62414,7 +62424,7 @@ export class Api<
) =>
this.request<
ReposUpdateInformationAboutPagesSiteData,
BasicError | ValidationError
(BasicError | ScimError) | ValidationError
>({
path: \`/repos/\${owner}/\${repo}/pages\`,
method: "PUT",
Expand Down Expand Up @@ -64787,7 +64797,7 @@ export class Api<
) =>
this.request<
ReposCreateForAuthenticatedUserData,
BasicError | ValidationError
(BasicError | ScimError) | BasicError | ValidationError
>({
path: \`/user/repos\`,
method: "POST",
Expand Down
34 changes: 23 additions & 11 deletions tests/__snapshots__/simple.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -20642,7 +20642,8 @@ export class Api<
documentation_url: string;
message: string;
}
| (ValidationError | ValidationErrorSimple)
| ValidationError
| ValidationErrorSimple
>({
path: \`/orgs/\${org}\`,
method: "PATCH",
Expand Down Expand Up @@ -24462,7 +24463,8 @@ export class Api<
this.request<
ProjectCard,
| BasicError
| (ValidationError | ValidationErrorSimple)
| ValidationError
| ValidationErrorSimple
| {
code?: string;
documentation_url?: string;
Expand Down Expand Up @@ -27970,7 +27972,7 @@ export class Api<
},
params: RequestParams = {},
) =>
this.request<Commit[], BasicError>({
this.request<Commit[], (BasicError | ScimError) | BasicError>({
path: \`/repos/\${owner}/\${repo}/commits\`,
method: "GET",
query: query,
Expand Down Expand Up @@ -28365,7 +28367,11 @@ export class Api<
},
params: RequestParams = {},
) =>
this.request<ContentTree, BasicError>({
this.request<
| ContentTree
| (ContentDirectory | ContentFile | ContentSymlink | ContentSubmodule),
BasicError
>({
path: \`/repos/\${owner}/\${repo}/contents/\${path}\`,
method: "GET",
query: query,
Expand Down Expand Up @@ -28895,7 +28901,7 @@ export class Api<
},
params: RequestParams = {},
) =>
this.request<MinimalRepository[], BasicError>({
this.request<MinimalRepository[], BasicError | ScimError>({
path: \`/repos/\${owner}/\${repo}/forks\`,
method: "GET",
query: query,
Expand All @@ -28920,7 +28926,10 @@ export class Api<
},
params: RequestParams = {},
) =>
this.request<Repository, BasicError | ValidationError>({
this.request<
Repository,
(BasicError | ScimError) | BasicError | ValidationError
>({
path: \`/repos/\${owner}/\${repo}/forks\`,
method: "POST",
body: data,
Expand Down Expand Up @@ -31601,7 +31610,7 @@ export class Api<
},
params: RequestParams = {},
) =>
this.request<void, BasicError | ValidationError>({
this.request<void, (BasicError | ScimError) | ValidationError>({
path: \`/repos/\${owner}/\${repo}/pages\`,
method: "PUT",
body: data,
Expand Down Expand Up @@ -33275,7 +33284,7 @@ export class Api<
},
params: RequestParams = {},
) =>
this.request<SimpleUser[], ValidationError>({
this.request<SimpleUser[] | Stargazer[], ValidationError>({
path: \`/repos/\${owner}/\${repo}/stargazers\`,
method: "GET",
query: query,
Expand Down Expand Up @@ -37318,7 +37327,10 @@ export class Api<
},
params: RequestParams = {},
) =>
this.request<Repository, BasicError | ValidationError>({
this.request<
Repository,
(BasicError | ScimError) | BasicError | ValidationError
>({
path: \`/user/repos\`,
method: "POST",
body: data,
Expand Down Expand Up @@ -37424,7 +37436,7 @@ export class Api<
},
params: RequestParams = {},
) =>
this.request<Repository[], BasicError>({
this.request<Repository[] | StarredRepository[], BasicError>({
path: \`/user/starred\`,
method: "GET",
query: query,
Expand Down Expand Up @@ -38191,7 +38203,7 @@ export class Api<
},
params: RequestParams = {},
) =>
this.request<Repository[], any>({
this.request<Repository[] | StarredRepository[], any>({
path: \`/users/\${username}/starred\`,
method: "GET",
query: query,
Expand Down
Loading