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/fix-union-content-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"swagger-typescript-api": patch
---

Fix `ContentType` in http-client not respecting `enumStyle: "union"`. It now generates a plain type alias instead of an enum, and all call sites emit string literals instead of `ContentType.Json` etc.
18 changes: 18 additions & 0 deletions templates/base/content-type-accessors.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<%
const { config } = it;
const isUnion = config.enumStyle === "union";

// camelCase keys (Json, FormData, …) are used by http-client templates as CT.Json, CT.FormData, …
// They mirror the ContentType property names so the access pattern is identical regardless of enumStyle.
const v = {
Json: isUnion ? '"application/json"' : "ContentType.Json",
JsonApi: isUnion ? '"application/vnd.api+json"' : "ContentType.JsonApi",
FormData: isUnion ? '"multipart/form-data"' : "ContentType.FormData",
UrlEncoded: isUnion ? '"application/x-www-form-urlencoded"' : "ContentType.UrlEncoded",
Text: isUnion ? '"text/plain"' : "ContentType.Text",
};

// UPPER_SNAKE aliases (JSON, FORM_DATA, …) are used by procedure-call templates as
// requestContentKind["JSON"], matching the CONTENT_KIND constants produced by schema-routes.ts.
return { ...v, JSON: v.Json, JSON_API: v.JsonApi, FORM_DATA: v.FormData, URL_ENCODED: v.UrlEncoded, TEXT: v.Text };
%>
7 changes: 5 additions & 2 deletions templates/base/http-clients/axios-http-client.ejs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<%
const { apiConfig, generateResponses, config } = it;
const CT = includeFile("@base/content-type-accessors", { config });
%>

import type { AxiosInstance, AxiosRequestConfig, HeadersDefaults, ResponseType, AxiosResponse } from "axios";
Expand Down Expand Up @@ -39,6 +40,8 @@ export const ContentType = {
Text: "text/plain",
} as const;
export type ContentType = (typeof ContentType)[keyof typeof ContentType];
<% } else if (config.enumStyle === "union") { %>
export type ContentType = "application/json" | "application/vnd.api+json" | "multipart/form-data" | "application/x-www-form-urlencoded" | "text/plain";
<% } else { %>
export enum ContentType {
Json = "application/json",
Expand Down Expand Up @@ -127,11 +130,11 @@ export class HttpClient<SecurityDataType = unknown> {
const requestParams = this.mergeRequestParams(params, secureParams);
const responseFormat = (format || this.format) || undefined;

if (type === ContentType.FormData && body && body !== null && typeof body === "object") {
if (type === <%~ CT.FormData %> && body && body !== null && typeof body === "object") {
body = this.createFormData(body as Record<string, unknown>);
}

if (type === ContentType.Text && body && body !== null && typeof body !== "string") {
if (type === <%~ CT.Text %> && body && body !== null && typeof body !== "string") {
body = JSON.stringify(body);
}

Expand Down
17 changes: 10 additions & 7 deletions templates/base/http-clients/fetch-http-client.ejs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<%
const { apiConfig, generateResponses, config } = it;
const CT = includeFile("@base/content-type-accessors", { config });
%>

export type QueryParamsType = Record<string | number, any>;
Expand Down Expand Up @@ -50,6 +51,8 @@ export const ContentType = {
Text: "text/plain",
} as const;
export type ContentType = (typeof ContentType)[keyof typeof ContentType];
<% } else if (config.enumStyle === "union") { %>
export type ContentType = "application/json" | "application/vnd.api+json" | "multipart/form-data" | "application/x-www-form-urlencoded" | "text/plain";
<% } else { %>
export enum ContentType {
Json = "application/json",
Expand Down Expand Up @@ -114,10 +117,10 @@ export class HttpClient<SecurityDataType = unknown> {
}

private contentFormatters: Record<ContentType, (input: any) => any> = {
[ContentType.Json]: (input:any) => input !== null && (typeof input === "object" || typeof input === "string") ? JSON.stringify(input) : input,
[ContentType.JsonApi]: (input:any) => input !== null && (typeof input === "object" || typeof input === "string") ? JSON.stringify(input) : input,
[ContentType.Text]: (input:any) => input !== null && typeof input !== "string" ? JSON.stringify(input) : input,
[ContentType.FormData]: (input: any) => {
[<%~ CT.Json %>]: (input:any) => input !== null && (typeof input === "object" || typeof input === "string") ? JSON.stringify(input) : input,
[<%~ CT.JsonApi %>]: (input:any) => input !== null && (typeof input === "object" || typeof input === "string") ? JSON.stringify(input) : input,
[<%~ CT.Text %>]: (input:any) => input !== null && typeof input !== "string" ? JSON.stringify(input) : input,
[<%~ CT.FormData %>]: (input: any) => {
if (input instanceof FormData) {
return input;
}
Expand All @@ -135,7 +138,7 @@ export class HttpClient<SecurityDataType = unknown> {
return formData;
}, new FormData());
},
[ContentType.UrlEncoded]: (input: any) => this.toQueryString(input),
[<%~ CT.UrlEncoded %>]: (input: any) => this.toQueryString(input),
}

protected mergeRequestParams(params1: RequestParams, params2?: RequestParams): RequestParams {
Expand Down Expand Up @@ -192,7 +195,7 @@ export class HttpClient<SecurityDataType = unknown> {
const secureParams = ((typeof secure === 'boolean' ? secure : this.baseApiParams.secure) && this.securityWorker && await this.securityWorker(this.securityData)) || {};
const requestParams = this.mergeRequestParams(params, secureParams);
const queryString = query && this.toQueryString(query);
const payloadFormatter = this.contentFormatters[type || ContentType.Json];
const payloadFormatter = this.contentFormatters[type || <%~ CT.Json %>];
const responseFormat = format || requestParams.format;

return this.customFetch(
Expand All @@ -201,7 +204,7 @@ export class HttpClient<SecurityDataType = unknown> {
...requestParams,
headers: {
...(requestParams.headers || {}),
...(type && type !== ContentType.FormData ? { "Content-Type": type } : {}),
...(type && type !== <%~ CT.FormData %> ? { "Content-Type": type } : {}),
},
signal: (cancelToken ? this.createAbortSignal(cancelToken) : requestParams.signal) || null,
body: typeof body === "undefined" || body === null ? null : payloadFormatter(body),
Expand Down
8 changes: 1 addition & 7 deletions templates/default/procedure-call.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,7 @@ const wrapperArgs = _
.join(', ')

// RequestParams["type"]
const requestContentKind = {
"JSON": "ContentType.Json",
"JSON_API": "ContentType.JsonApi",
"URL_ENCODED": "ContentType.UrlEncoded",
"FORM_DATA": "ContentType.FormData",
"TEXT": "ContentType.Text",
}
const requestContentKind = includeFile("@base/content-type-accessors", { config });
// RequestParams["format"]
const responseContentKind = {
"JSON": '"json"',
Expand Down
4 changes: 4 additions & 0 deletions templates/modular/api.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ const dataContracts = _.map(modelTypes, "name");

<% if (config.httpClientType === config.constants.HTTP_CLIENT.AXIOS) { %> import type { AxiosRequestConfig, AxiosResponse } from "axios"; <% } %>

<% if (config.enumStyle === "union") { %>
import { HttpClient, RequestParams, type ContentType, HttpResponse } from "./<%~ config.fileNames.httpClient %>";
<% } else { %>
import { HttpClient, RequestParams, ContentType, HttpResponse } from "./<%~ config.fileNames.httpClient %>";
<% } %>
<% if (dataContracts.length) { %>
import { <%~ dataContracts.join(", ") %> } from "./<%~ config.fileNames.dataContracts %>"
<% } %>
Expand Down
8 changes: 1 addition & 7 deletions templates/modular/procedure-call.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,7 @@ const wrapperArgs = _
.join(', ')

// RequestParams["type"]
const requestContentKind = {
"JSON": "ContentType.Json",
"JSON_API": "ContentType.JsonApi",
"URL_ENCODED": "ContentType.UrlEncoded",
"FORM_DATA": "ContentType.FormData",
"TEXT": "ContentType.Text",
}
const requestContentKind = includeFile("@base/content-type-accessors", { config });
// RequestParams["format"]
const responseContentKind = {
"JSON": '"json"',
Expand Down
Loading