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

fix(core): keep default validateStatus when request passes undefined (#10899)

Co-authored-by: Jason Saayman <jasonsaayman@gmail.com>
This commit is contained in:
Eyüp Can Akman
2026-06-03 21:09:05 +03:00
committed by GitHub
parent 614f4552a1
commit a8e4f13aee
9 changed files with 101 additions and 1 deletions
+5
View File
@@ -9,3 +9,8 @@
## Bug Fixes
- **Types:** Add the missing readonly `name: 'CanceledError'` declaration to CommonJS `CanceledError` typings to match the ESM declarations. (**#10922**)
- **Config Merge:** Added `transitional.validateStatusUndefinedResolves` (default `true`) so applications can opt into treating explicit `validateStatus: undefined` like an omitted option by setting it to `false`. `validateStatus: null` still accepts every response status. (**#10899**, closes **#6688**)
## Release Tracking
- ESM/CJS typings are updated for `transitional.validateStatusUndefinedResolves`; README/docs updates are tracked in `PRE_RELEASE_DOCS.md` for release preparation.
+20
View File
@@ -37,3 +37,23 @@ axios.get('https://api.example.com/users', {
```
- **Notes:** Add a security page row linking to the request-config section and add a `sensitiveHeaders` request-config entry marked Node.js only.
### validateStatus undefined transitional option
- **Change:** Document `transitional.validateStatusUndefinedResolves` for the `validateStatus: undefined` merge behavior.
- **Source:** `PRE_RELEASE_CHANGELOG.md` Bug Fixes, #10899, closes #6688.
- **Status:** Pending.
- **Docs targets:** README request config section; `docs/pages/advanced/request-config.md` `validateStatus` section and request config example; translated request-config docs after English docs are finalized.
- **Required content:** Explain that `validateStatus: undefined` keeps legacy behavior by default and resolves every response status because `transitional.validateStatusUndefinedResolves` defaults to `true`. Explain that setting `transitional.validateStatusUndefinedResolves` to `false` makes explicit `validateStatus: undefined` behave like the option was omitted, so axios uses the configured/default validator and rejects non-2xx responses by default. Mention that `validateStatus: null` still accepts every response status, and users who disable the transitional behavior should use `null` or `() => true` when they intentionally want all statuses to resolve.
- **Examples:** Include a short opt-in example.
```js
axios.get('/user/12345', {
validateStatus: undefined,
transitional: {
validateStatusUndefinedResolves: false
}
});
```
- **Notes:** This is release-prep documentation only; do not update README or docs pages in the feature/fix PR.
+1
View File
@@ -397,6 +397,7 @@ declare namespace axios {
clarifyTimeoutError?: boolean;
legacyInterceptorReqResOrdering?: boolean;
advertiseZstdAcceptEncoding?: boolean;
validateStatusUndefinedResolves?: boolean;
}
interface GenericAbortSignal {
Vendored
+1
View File
@@ -284,6 +284,7 @@ export interface TransitionalOptions {
clarifyTimeoutError?: boolean;
legacyInterceptorReqResOrdering?: boolean;
advertiseZstdAcceptEncoding?: boolean;
validateStatusUndefinedResolves?: boolean;
}
export interface GenericAbortSignal {
+1
View File
@@ -102,6 +102,7 @@ class Axios {
clarifyTimeoutError: validators.transitional(validators.boolean),
legacyInterceptorReqResOrdering: validators.transitional(validators.boolean),
advertiseZstdAcceptEncoding: validators.transitional(validators.boolean),
validateStatusUndefinedResolves: validators.transitional(validators.boolean),
},
false
);
+34
View File
@@ -68,6 +68,28 @@ export default function mergeConfig(config1, config2) {
}
}
function getMergedTransitionalOption(prop) {
const transitional2 = utils.hasOwnProp(config2, 'transitional') ? config2.transitional : undefined;
if (!utils.isUndefined(transitional2)) {
if (utils.isPlainObject(transitional2)) {
if (utils.hasOwnProp(transitional2, prop)) {
return transitional2[prop];
}
} else {
return undefined;
}
}
const transitional1 = utils.hasOwnProp(config1, 'transitional') ? config1.transitional : undefined;
if (utils.isPlainObject(transitional1) && utils.hasOwnProp(transitional1, prop)) {
return transitional1[prop];
}
return undefined;
}
// eslint-disable-next-line consistent-return
function mergeDirectKeys(a, b, prop) {
if (utils.hasOwnProp(config2, prop)) {
@@ -120,5 +142,17 @@ export default function mergeConfig(config1, config2) {
(utils.isUndefined(configValue) && merge !== mergeDirectKeys) || (config[prop] = configValue);
});
if (
utils.hasOwnProp(config2, 'validateStatus') &&
utils.isUndefined(config2.validateStatus) &&
getMergedTransitionalOption('validateStatusUndefinedResolves') === false
) {
if (utils.hasOwnProp(config1, 'validateStatus')) {
config.validateStatus = getMergedValue(undefined, config1.validateStatus);
} else {
delete config.validateStatus;
}
}
return config;
}
+1
View File
@@ -6,4 +6,5 @@ export default {
clarifyTimeoutError: false,
legacyInterceptorReqResOrdering: true,
advertiseZstdAcceptEncoding: false,
validateStatusUndefinedResolves: true,
};
+18 -1
View File
@@ -283,7 +283,7 @@ describe('requests (vitest browser)', () => {
await expect(promise).resolves.toBeDefined();
});
it('should resolve when validateStatus is undefined', async () => {
it('should resolve when validateStatus is undefined by default', async () => {
const { request, promise } = startRequest('/foo', {
validateStatus: undefined,
});
@@ -292,6 +292,23 @@ describe('requests (vitest browser)', () => {
await expect(promise).resolves.toBeDefined();
});
// https://github.com/axios/axios/issues/6688
it('should reject when validateStatus is undefined and the transitional option is disabled', async () => {
const { request, promise } = startRequest('/foo', {
validateStatus: undefined,
transitional: { validateStatusUndefinedResolves: false },
});
request.respondWith({ status: 500 });
const reason = await promise.catch((error) => error);
expect(reason).toBeInstanceOf(Error);
expect(reason.message).toBe('Request failed with status code 500');
expect(reason.config.method).toBe('get');
expect(reason.config.url).toBe('/foo');
expect(reason.response.status).toBe(500);
});
// https://github.com/axios/axios/issues/378
it('should return JSON when rejecting', async () => {
const { request, promise } = startRequest(
+20
View File
@@ -353,5 +353,25 @@ describe('core::mergeConfig', () => {
expect(mergeConfig({ validateStatus: obj }, {}).validateStatus).toBe(obj);
expect(mergeConfig({ validateStatus: null }, {}).validateStatus).toBe(null);
});
it('keeps legacy undefined behavior by default', () => {
expect(mergeConfig(defaults, { validateStatus: undefined }).validateStatus).toBeUndefined();
});
it('keeps config1 value when the transitional option is disabled (issue #6688)', () => {
const validateStatus = () => false;
expect(
mergeConfig(
{ validateStatus },
{
validateStatus: undefined,
transitional: { validateStatusUndefinedResolves: false },
}
).validateStatus
).toBe(validateStatus);
expect(mergeConfig(defaults, { validateStatus: null }).validateStatus).toBe(null);
});
});
});