mirror of
https://github.com/tenrok/axios.git
synced 2026-06-17 19:21:29 +03:00
fix: preserve symbol keys in merged request data (#10812)
* fix: preserve symbol keys in merged request data * fix: address symbol merge review feedback * fix: align symbol merge with v1.x own-prop guard * fix: avoid merge conflict on target key handling * feat: collapsed the duplicated typeof key === symbol branch in assignValue * chore: added should honor skipUndefined for symbol keys to lock in skipUndefined semantics * chore: added should pass symbol keys to transformRequest through axios.create covering the instance * fix(utils): skip symbol scan for arrays --------- Co-authored-by: laplace young <yangqk12@whu.edu.cn> Co-authored-by: Jay <jasonsaayman@gmail.com>
This commit is contained in:
@@ -6,6 +6,10 @@
|
||||
|
||||
- **HTTP Adapter - Zstandard:** Added automatic zstd decompression on Node.js versions that support it. `zstd` is only advertised in the default `Accept-Encoding` header when `transitional.advertiseZstdAcceptEncoding: true` is set. (**#6792**)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **Request Data:** Preserve enumerable symbol keys when merging plain request data before `transformRequest`. (**#6392**)
|
||||
|
||||
## Release Documentation TODO
|
||||
|
||||
- Update `README.md` request config docs for `transitional.advertiseZstdAcceptEncoding` and zstd decompression support.
|
||||
|
||||
+23
-2
@@ -412,7 +412,9 @@ function merge(...objs) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetKey = (caseless && findKey(result, key)) || key;
|
||||
// findKey lowercases the key, so caseless lookup only applies to strings —
|
||||
// symbol keys are identity-matched.
|
||||
const targetKey = (caseless && typeof key === 'string' && findKey(result, key)) || key;
|
||||
// Read via own-prop only — a bare `result[targetKey]` walks the prototype
|
||||
// chain, so a polluted Object.prototype value could surface here and get
|
||||
// copied into the merged result.
|
||||
@@ -429,7 +431,24 @@ function merge(...objs) {
|
||||
};
|
||||
|
||||
for (let i = 0, l = objs.length; i < l; i++) {
|
||||
objs[i] && forEach(objs[i], assignValue);
|
||||
const source = objs[i];
|
||||
if (!source || isBuffer(source)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
forEach(source, assignValue);
|
||||
|
||||
if (typeof source !== 'object' || isArray(source)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const symbols = Object.getOwnPropertySymbols(source);
|
||||
for (let j = 0; j < symbols.length; j++) {
|
||||
const symbol = symbols[j];
|
||||
if (propertyIsEnumerable.call(source, symbol)) {
|
||||
assignValue(source[symbol], symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -658,6 +677,8 @@ const hasOwnProperty = (
|
||||
hasOwnProperty.call(obj, prop)
|
||||
)(Object.prototype);
|
||||
|
||||
const { propertyIsEnumerable } = Object.prototype;
|
||||
|
||||
/**
|
||||
* Determine if a value is a RegExp object
|
||||
*
|
||||
|
||||
@@ -80,6 +80,36 @@ describe('static api', () => {
|
||||
it('should have getAdapter properties', () => {
|
||||
assert.strictEqual(typeof axios.getAdapter, 'function');
|
||||
});
|
||||
|
||||
it('should pass symbol keys to transformRequest', async () => {
|
||||
const symbolKey = Symbol('example');
|
||||
let transformedData;
|
||||
|
||||
await axios.post(
|
||||
'/test',
|
||||
{
|
||||
[symbolKey]: 'value',
|
||||
stringKey: 'value',
|
||||
},
|
||||
{
|
||||
transformRequest(data) {
|
||||
transformedData = data;
|
||||
return '';
|
||||
},
|
||||
adapter: (config) =>
|
||||
Promise.resolve({
|
||||
data: null,
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {},
|
||||
config,
|
||||
request: {},
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
assert.strictEqual(transformedData[symbolKey], 'value');
|
||||
});
|
||||
});
|
||||
|
||||
describe('instance api', () => {
|
||||
@@ -101,4 +131,35 @@ describe('instance api', () => {
|
||||
assert.strictEqual(typeof instance.interceptors.request, 'object');
|
||||
assert.strictEqual(typeof instance.interceptors.response, 'object');
|
||||
});
|
||||
|
||||
it('should pass symbol keys to transformRequest through axios.create', async () => {
|
||||
const symbolKey = Symbol('example');
|
||||
let transformedData;
|
||||
|
||||
const client = axios.create({
|
||||
transformRequest: [
|
||||
(data) => {
|
||||
transformedData = data;
|
||||
return '';
|
||||
},
|
||||
],
|
||||
adapter: (config) =>
|
||||
Promise.resolve({
|
||||
data: null,
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {},
|
||||
config,
|
||||
request: {},
|
||||
}),
|
||||
});
|
||||
|
||||
await client.post('/test', {
|
||||
[symbolKey]: 'value',
|
||||
stringKey: 'value',
|
||||
});
|
||||
|
||||
assert.strictEqual(transformedData[symbolKey], 'value');
|
||||
assert.strictEqual(transformedData.stringKey, 'value');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -94,4 +94,78 @@ describe('utils::merge', () => {
|
||||
x: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it('should merge enumerable symbol keys', () => {
|
||||
const key = Symbol('key');
|
||||
const nestedKey = Symbol('nested');
|
||||
const first = { [key]: { first: true } };
|
||||
const second = {
|
||||
[key]: { second: true },
|
||||
nested: {
|
||||
[nestedKey]: 'value',
|
||||
},
|
||||
};
|
||||
const merged = merge(first, second);
|
||||
|
||||
expect(merged[key]).toEqual({ first: true, second: true });
|
||||
expect(merged[key]).not.toBe(first[key]);
|
||||
expect(merged.nested[nestedKey]).toBe('value');
|
||||
expect(merged.nested).not.toBe(second.nested);
|
||||
});
|
||||
|
||||
it('should skip non-enumerable symbol keys', () => {
|
||||
const key = Symbol('key');
|
||||
const source = {};
|
||||
|
||||
Object.defineProperty(source, key, {
|
||||
value: 'hidden',
|
||||
enumerable: false,
|
||||
});
|
||||
|
||||
const merged = merge(source);
|
||||
|
||||
expect(merged[key]).toBeUndefined();
|
||||
expect(Object.getOwnPropertySymbols(merged)).toEqual([]);
|
||||
});
|
||||
|
||||
it('should support caseless string keys with symbol keys', () => {
|
||||
const key = Symbol('key');
|
||||
const merged = merge.call(
|
||||
{ caseless: true },
|
||||
{ x: 1, [key]: 'first' },
|
||||
{ X: 2, [key]: 'second' }
|
||||
);
|
||||
|
||||
expect(merged.x).toBe(2);
|
||||
expect(merged.X).toBeUndefined();
|
||||
expect(merged[key]).toBe('second');
|
||||
});
|
||||
|
||||
it('should ignore symbol keys on buffers', () => {
|
||||
const key = Symbol('key');
|
||||
const buffer = Buffer.from('value');
|
||||
buffer[key] = 'symbol value';
|
||||
|
||||
const merged = merge({ x: 1 }, buffer);
|
||||
|
||||
expect(merged).toEqual({ x: 1 });
|
||||
});
|
||||
|
||||
it('should ignore symbol keys on arrays', () => {
|
||||
const key = Symbol('key');
|
||||
const array = ['value'];
|
||||
array[key] = 'symbol value';
|
||||
|
||||
const merged = merge({ x: 1 }, array);
|
||||
|
||||
expect(merged).toEqual({ 0: 'value', x: 1 });
|
||||
expect(merged[key]).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should honor skipUndefined for symbol keys', () => {
|
||||
const key = Symbol('key');
|
||||
const merged = merge.call({ skipUndefined: true }, { [key]: 'first' }, { [key]: undefined });
|
||||
|
||||
expect(merged[key]).toBe('first');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user