2
0
mirror of https://github.com/tenrok/axios.git synced 2026-06-17 19:21:29 +03:00

fix: transformRequest input typing (#10745)

* fix(types): type transformRequest input data

* chore: imrpove testing and overall posture

* fix:f failing tests

---------

Co-authored-by: Jay <jasonsaayman@gmail.com>
This commit is contained in:
Atharva Singh
2026-04-26 13:33:20 +05:30
committed by GitHub
parent bd3816bf9e
commit 694f1eca8c
11 changed files with 192 additions and 20 deletions
+5 -5
View File
@@ -320,14 +320,14 @@ declare namespace axios {
type AxiosResponseHeaders = RawAxiosResponseHeaders & AxiosHeaders;
interface AxiosRequestTransformer {
(this: InternalAxiosRequestConfig, data: any, headers: AxiosRequestHeaders): any;
interface AxiosRequestTransformer<D = any> {
(this: InternalAxiosRequestConfig<D>, data: D, headers: AxiosRequestHeaders): any;
}
interface AxiosResponseTransformer {
interface AxiosResponseTransformer<T = any> {
(
this: InternalAxiosRequestConfig,
data: any,
data: T,
headers: AxiosResponseHeaders,
status?: number
): any;
@@ -471,7 +471,7 @@ declare namespace axios {
method?: Method | string;
baseURL?: string;
allowAbsoluteUrls?: boolean;
transformRequest?: AxiosRequestTransformer | AxiosRequestTransformer[];
transformRequest?: AxiosRequestTransformer<D> | AxiosRequestTransformer<D>[];
transformResponse?: AxiosResponseTransformer | AxiosResponseTransformer[];
headers?: (RawAxiosRequestHeaders & MethodsHeaders) | AxiosHeaders;
params?: any;
Vendored
+5 -5
View File
@@ -138,14 +138,14 @@ export type RawAxiosResponseHeaders = Partial<RawAxiosHeaders & RawCommonRespons
export type AxiosResponseHeaders = RawAxiosResponseHeaders & AxiosHeaders;
export interface AxiosRequestTransformer {
(this: InternalAxiosRequestConfig, data: any, headers: AxiosRequestHeaders): any;
export interface AxiosRequestTransformer<D = any> {
(this: InternalAxiosRequestConfig<D>, data: D, headers: AxiosRequestHeaders): any;
}
export interface AxiosResponseTransformer {
export interface AxiosResponseTransformer<T = any> {
(
this: InternalAxiosRequestConfig,
data: any,
data: T,
headers: AxiosResponseHeaders,
status?: number
): any;
@@ -367,7 +367,7 @@ export interface AxiosRequestConfig<D = any> {
method?: StringLiteralsOrString<Method>;
baseURL?: string;
allowAbsoluteUrls?: boolean;
transformRequest?: AxiosRequestTransformer | AxiosRequestTransformer[];
transformRequest?: AxiosRequestTransformer<D> | AxiosRequestTransformer<D>[];
transformResponse?: AxiosResponseTransformer | AxiosResponseTransformer[];
headers?: (RawAxiosRequestHeaders & MethodsHeaders) | AxiosHeaders;
params?: any;
+7 -1
View File
@@ -1,13 +1,19 @@
const fs = require('fs');
const path = require('path');
const createTempFixture = (suiteRoot, name, sourcePath, tsconfig, packageJson) => {
const createTempFixture = (suiteRoot, repoRoot, name, sourcePath, tsconfig, packageJson) => {
const tempRoot = fs.mkdtempSync(path.join(suiteRoot, `.tmp-module-${name}-`));
const source = fs.readFileSync(sourcePath, 'utf8');
fs.writeFileSync(path.join(tempRoot, 'index.ts'), source);
fs.writeFileSync(path.join(tempRoot, 'tsconfig.json'), JSON.stringify(tsconfig, null, 2));
if (repoRoot) {
const axiosFixturePath = path.join(tempRoot, 'node_modules', 'axios');
fs.mkdirSync(path.dirname(axiosFixturePath), { recursive: true });
fs.symlinkSync(repoRoot, axiosFixturePath, 'junction');
}
if (packageJson) {
fs.writeFileSync(path.join(tempRoot, 'package.json'), JSON.stringify(packageJson, null, 2));
}
@@ -0,0 +1,52 @@
import axios = require('axios');
type Payload = {
foo: string;
count: number;
};
type OtherPayload = {
other: number;
};
const transformer: axios.AxiosRequestTransformer<Payload> = function (data, headers) {
headers.setContentType('application/json');
data.foo.toUpperCase();
data.count.toFixed();
// `this` is narrowed to InternalAxiosRequestConfig<Payload>, so this.data is Payload
this.data?.foo.toUpperCase();
this.data?.count.toFixed();
// @ts-expect-error this.data is Payload, not OtherPayload
this.data?.other;
// @ts-expect-error property does not exist on Payload
data.bar;
return JSON.stringify(data);
};
const wrongTransformer: axios.AxiosRequestTransformer<OtherPayload> = (data) => JSON.stringify(data);
const config: axios.AxiosRequestConfig<Payload> = {
data: {
foo: 'hello',
count: 1,
},
transformRequest: [
transformer,
(data) => {
data.foo.toUpperCase();
data.count.toFixed();
// @ts-expect-error property does not exist on Payload
data.bar;
return JSON.stringify(data);
},
// @ts-expect-error transformer payload type does not match config D
wrongTransformer,
],
};
void axios.request(config);
@@ -21,7 +21,13 @@ const tsconfig = {
describe('module ts-require-default compatibility', () => {
it('compiles and executes require("axios").default imports', () => {
const sourcePath = path.join(repoRoot, 'tests/module/cjs/tests/helpers/ts-require-default.ts');
const fixturePath = createTempFixture(suiteRoot, 'ts-require-default', sourcePath, tsconfig);
const fixturePath = createTempFixture(
suiteRoot,
null,
'ts-require-default',
sourcePath,
tsconfig
);
try {
runCommand('node', [tscBin, '-p', 'tsconfig.json'], { cwd: fixturePath });
@@ -21,7 +21,7 @@ const tsconfig = {
describe('module ts-require compatibility', () => {
it('compiles and executes require("axios") imports', () => {
const sourcePath = path.join(repoRoot, 'tests/module/cjs/tests/helpers/ts-require.ts');
const fixturePath = createTempFixture(suiteRoot, 'ts-require', sourcePath, tsconfig);
const fixturePath = createTempFixture(suiteRoot, null, 'ts-require', sourcePath, tsconfig);
try {
runCommand('node', [tscBin, '-p', 'tsconfig.json'], { cwd: fixturePath });
+21 -1
View File
@@ -17,7 +17,27 @@ const tsconfig = {
describe('module cjs typings compatibility', () => {
it('type-checks commonjs axios typings', () => {
const sourcePath = path.join(repoRoot, 'tests/module/cjs/tests/helpers/cjs-typing.ts');
const fixturePath = createTempFixture(suiteRoot, 'typings-cjs', sourcePath, tsconfig);
const fixturePath = createTempFixture(suiteRoot, repoRoot, 'typings-cjs', sourcePath, tsconfig);
try {
runCommand('node', [tscBin, '--noEmit', '-p', 'tsconfig.json'], { cwd: fixturePath });
} finally {
cleanupTempFixture(fixturePath);
}
});
it('type-checks generic commonjs request transformer typings', () => {
const sourcePath = path.join(
repoRoot,
'tests/module/cjs/tests/helpers/transform-request-typing.ts'
);
const fixturePath = createTempFixture(
suiteRoot,
repoRoot,
'typings-cjs-transform-request',
sourcePath,
tsconfig
);
try {
runCommand('node', [tscBin, '--noEmit', '-p', 'tsconfig.json'], { cwd: fixturePath });
+7 -1
View File
@@ -1,13 +1,19 @@
import fs from 'node:fs';
import path from 'node:path';
export const createTempFixture = (suiteRoot, name, sourcePath, tsconfig, packageJson) => {
export const createTempFixture = (suiteRoot, repoRoot, name, sourcePath, tsconfig, packageJson) => {
const tempRoot = fs.mkdtempSync(path.join(suiteRoot, `.tmp-module-${name}-`));
const source = fs.readFileSync(sourcePath, 'utf8');
fs.writeFileSync(path.join(tempRoot, 'index.ts'), source);
fs.writeFileSync(path.join(tempRoot, 'tsconfig.json'), JSON.stringify(tsconfig, null, 2));
if (repoRoot) {
const axiosFixturePath = path.join(tempRoot, 'node_modules', 'axios');
fs.mkdirSync(path.dirname(axiosFixturePath), { recursive: true });
fs.symlinkSync(repoRoot, axiosFixturePath, 'junction');
}
if (packageJson) {
fs.writeFileSync(path.join(tempRoot, 'package.json'), JSON.stringify(packageJson, null, 2));
}
@@ -0,0 +1,52 @@
import axios, { AxiosRequestConfig, AxiosRequestTransformer } from 'axios';
type Payload = {
foo: string;
count: number;
};
type OtherPayload = {
other: number;
};
const transformer: AxiosRequestTransformer<Payload> = function (data, headers) {
headers.setContentType('application/json');
data.foo.toUpperCase();
data.count.toFixed();
// `this` is narrowed to InternalAxiosRequestConfig<Payload>, so this.data is Payload
this.data?.foo.toUpperCase();
this.data?.count.toFixed();
// @ts-expect-error this.data is Payload, not OtherPayload
this.data?.other;
// @ts-expect-error property does not exist on Payload
data.bar;
return JSON.stringify(data);
};
const wrongTransformer: AxiosRequestTransformer<OtherPayload> = (data) => JSON.stringify(data);
const config: AxiosRequestConfig<Payload> = {
data: {
foo: 'hello',
count: 1,
},
transformRequest: [
transformer,
(data) => {
data.foo.toUpperCase();
data.count.toFixed();
// @ts-expect-error property does not exist on Payload
data.bar;
return JSON.stringify(data);
},
// @ts-expect-error transformer payload type does not match config D
wrongTransformer,
],
};
void axios.request(config);
+1 -1
View File
@@ -22,7 +22,7 @@ const tsconfig = {
describe('module ts compatibility', () => {
it('compiles and executes import axios syntax', () => {
const sourcePath = path.join(repoRoot, 'tests/module/esm/tests/helpers/esm-functions.ts');
const fixturePath = createTempFixture(suiteRoot, 'ts', sourcePath, tsconfig, {
const fixturePath = createTempFixture(suiteRoot, null, 'ts', sourcePath, tsconfig, {
type: 'commonjs',
});
+34 -4
View File
@@ -16,11 +16,41 @@ const tsconfig = {
};
describe('module esm typings compatibility', () => {
it('type-checks esm axios typings', () => {
it('type-checks esm axios typings', { timeout: 20000 }, () => {
const sourcePath = path.join(repoRoot, 'tests/module/esm/tests/helpers/esm-index.ts');
const fixturePath = createTempFixture(suiteRoot, 'typings-esm', sourcePath, tsconfig, {
type: 'module',
});
const fixturePath = createTempFixture(
suiteRoot,
repoRoot,
'typings-esm',
sourcePath,
tsconfig,
{
type: 'module',
}
);
try {
runCommand('node', [tscBin, '--noEmit', '-p', 'tsconfig.json'], { cwd: fixturePath });
} finally {
cleanupTempFixture(fixturePath);
}
});
it('type-checks generic request transformer typings', { timeout: 20000 }, () => {
const sourcePath = path.join(
repoRoot,
'tests/module/esm/tests/helpers/transform-request-typing.ts'
);
const fixturePath = createTempFixture(
suiteRoot,
repoRoot,
'typings-esm-transform-request',
sourcePath,
tsconfig,
{
type: 'module',
}
);
try {
runCommand('node', [tscBin, '--noEmit', '-p', 'tsconfig.json'], { cwd: fixturePath });