swagger-typescript-api
swagger-typescript-api copied to clipboard
Axios client does not respect `type` param for `Content-Type` header
Problem
When generating an Axios client, the Content-Type
headers are not being set correctly if using the default type
param to specify the content type (note: if you specify specifically the content type header in the param, that will work).
Background
The generated code results in something like this:
this.request<ExecuteScriptData, any>({
path: `/execute`,
method: "POST",
body: data,
secure: true,
type: ContentType.Json,
...params,
});
Although you can see it is setting type: ContentType.Json
. This is not having the desired effect of setting the Content-Type
in the header of the request.
From what I can tell, this was a bit of an oversight in how we merge params and the difference in "defaults" between fetch and axios.
Axios defaults include header values such as 'Content-Type' whereas fetch defaults do not.
Defaults
Axios defaults
{
transitional: {
silentJSONParsing: true,
forcedJSONParsing: true,
clarifyTimeoutError: false
},
adapter: [Function: httpAdapter],
transformRequest: [ [Function: transformRequest] ],
transformResponse: [ [Function: transformResponse] ],
timeout: 0,
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
maxBodyLength: -1,
env: {
FormData: [Function: FormData] {
LINE_BREAK: '\r\n',
DEFAULT_CONTENT_TYPE: 'application/octet-stream'
}
},
validateStatus: [Function: validateStatus],
headers: {
common: { Accept: 'application/json, text/plain, */*' },
delete: {},
get: {},
head: {},
post: { 'Content-Type': 'application/x-www-form-urlencoded' },
put: { 'Content-Type': 'application/x-www-form-urlencoded' },
patch: { 'Content-Type': 'application/x-www-form-urlencoded' }
},
baseURL: 'https://api.scriptable.run/v1'
}
Fetch defaults
{
credentials: 'same-origin',
headers: {},
redirect: 'follow',
referrerPolicy: 'no-referrer',
}
Generated request
function
public request = async <T = any, _E = any>({
secure,
path,
type,
query,
format,
body,
...params
}: FullRequestParams): Promise<AxiosResponse<T>> => {
const secureParams =
((typeof secure === "boolean" ? secure : this.secure) &&
this.securityWorker &&
(await this.securityWorker(this.securityData))) ||
{};
const requestParams = this.mergeRequestParams(params, secureParams);
const responseFormat = format || this.format || undefined;
if (type === ContentType.FormData && body && body !== null && typeof body === "object") {
body = this.createFormData(body as Record<string, unknown>);
}
return this.instance.request({
...requestParams,
headers: {
...(type && type !== ContentType.FormData ? { "Content-Type": type } : {}),
...(requestParams.headers || {}),
},
params: query,
responseType: responseFormat,
data: body,
url: path,
});
};
Generated mergeRequestParams
protected mergeRequestParams(params1: AxiosRequestConfig, params2?: AxiosRequestConfig): AxiosRequestConfig {
const method = params1.method || (params2 && params2.method);
return {
...this.instance.defaults,
...params1,
...(params2 || {}),
headers: {
...((method && this.instance.defaults.headers[method.toLowerCase() as keyof HeadersDefaults]) || {}),
...(params1.headers || {}),
...((params2 && params2.headers) || {}),
},
};
}
Possible Solution
Remove the following line from the headers field in mergeRequestParams
return value.
...((method && this.instance.defaults.headers[method.toLowerCase() as keyof HeadersDefaults]) || {}),
Move that line to the request function so that it ends up building headers like this:
headers: {
...((requestParams.method && this.instance.defaults.headers[requestParams.method.toLowerCase() as keyof HeadersDefaults]) || {}),
...(type && type !== ContentType.FormData ? { "Content-Type": type } : {}),
...(requestParams.headers || {}),
},
The other solution that came to mind was to just swap the order of the lines in headers
to be:
headers: {
...(requestParams.headers || {}),
...(type && type !== ContentType.FormData ? { "Content-Type": type } : {}),
},
But this would likely be very confusing for anyone that was explicitly setting the Content-Type
header via params, since the type would override that.
mergeRequestParams
should not work with this.instance.defaults
(or this.instance.defaults.headers
) at all (as I already mentioned in #331) - there is no reason for that. Axios defaults are it's own - Axios merges them into request config automatically.
I'm using forked version of the template with following code and it works as expected:
protected mergeRequestParams(params1: AxiosRequestConfig, params2?: AxiosRequestConfig): AxiosRequestConfig {
const method = params1.method || (params2 && params2.method)
return {
...params1,
...(params2 || {}),
headers: {
...(params1.headers || {}),
...((params2 && params2.headers) || {}),
},
};
}
@Jackman3005 @Liwoj this problem should be fixed in 11.* releases, thanks @Liwoj for this request changes