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:
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -397,6 +397,7 @@ declare namespace axios {
|
||||
clarifyTimeoutError?: boolean;
|
||||
legacyInterceptorReqResOrdering?: boolean;
|
||||
advertiseZstdAcceptEncoding?: boolean;
|
||||
validateStatusUndefinedResolves?: boolean;
|
||||
}
|
||||
|
||||
interface GenericAbortSignal {
|
||||
|
||||
Vendored
+1
@@ -284,6 +284,7 @@ export interface TransitionalOptions {
|
||||
clarifyTimeoutError?: boolean;
|
||||
legacyInterceptorReqResOrdering?: boolean;
|
||||
advertiseZstdAcceptEncoding?: boolean;
|
||||
validateStatusUndefinedResolves?: boolean;
|
||||
}
|
||||
|
||||
export interface GenericAbortSignal {
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -6,4 +6,5 @@ export default {
|
||||
clarifyTimeoutError: false,
|
||||
legacyInterceptorReqResOrdering: true,
|
||||
advertiseZstdAcceptEncoding: false,
|
||||
validateStatusUndefinedResolves: true,
|
||||
};
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user