Skip to content

Commit

Permalink
[assert] fix, allow Object.create(null) and disallow stringTagName an…
Browse files Browse the repository at this point in the history
…d iterator symbols (#1214)

* fix(assert): isPlainObject allows Object.create(null)

* fix(assert): isPlainObject allows Object.create(null)

* fix(assert): isPlainObject allows Object.create(null)
  • Loading branch information
belgattitude committed May 17, 2024
1 parent 41da82e commit 226a4b1
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 21 deletions.
5 changes: 5 additions & 0 deletions .changeset/yellow-bottles-smell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@httpx/assert": patch
---

isPlainObject allows Object.create(null) and disallow stringTagName and iterators symbols
7 changes: 5 additions & 2 deletions docs/src/pages/assert/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ assertParsableStrictIsoDateZ('2023-02-29T23:37:31.653z'); // 👉 throws cause n
| isUuidV3 | `string` | `UuidV3` | |
| isUuidV4 | `string` | `UuidV4` | |
| isUuidV5 | `string` | `UuidV5` | |
| isUuidV7 | `string` | `UuidV7` | |
| assertUuid | `string` | `UuidV1 \| UuidV3 \| UuidV4 \| UuidV5 \| UuidV7` | |
| assertUuidV1 | `string` | `UuidV5` | |
| assertUuidV3 | `string` | `UuidV3` | |
Expand Down Expand Up @@ -418,12 +419,14 @@ for the browser.
ESM individual imports are tracked by a
[size-limit configuration](https://github.com/belgattitude/httpx/blob/main/packages/assert/.size-limit.cjs).
| Scenario | Size (compressed) |
|----------------------------------------|------------------:|
| Import `isPlainObject` | ~ 56b |
| Import `isPlainObject` | ~ 100b |
| Import `isUuid` | ~ 175b |
| Import `isEan13` | ~ 117b |
| All typeguards, assertions and helpers | ~ 900b |
| All typeguards, assertions and helpers | ~ 1700b |
> For CJS usage (not recommended) track the size on [bundlephobia](https://bundlephobia.com/package/@httpx/assert@latest).
Expand Down
6 changes: 3 additions & 3 deletions packages/assert/.size-limit.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ module.exports = [
name: 'Only isPlainObject (ESM)',
path: ['dist/index.mjs'],
import: "{ isPlainObject }",
limit: "76B",
limit: "108B",
},
{
name: 'Only isUuid (ESM)',
Expand All @@ -31,12 +31,12 @@ module.exports = [
name: 'Only assertPlainObject (ESM)',
path: ['dist/index.mjs'],
import: "{ assertPlainObject }",
limit: "461B",
limit: "487B",
},
{
name: 'Everything (CJS)',
import: "*",
path: ['dist/index.cjs'],
limit: '2400B',
limit: '2600B',
},
];
7 changes: 4 additions & 3 deletions packages/assert/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -298,11 +298,12 @@ assertParsableStrictIsoDateZ('2023-02-29T23:37:31.653z'); // 👉 throws cause n

| Name | Type | Opaque type | Comment |
|----------------|-------------------------|--------------------------------------------------|--------|
| isUuid | `string` | `UuidV1 \| UuidV3 \| UuidV4 \| UuidV5 \| UuidV7` | |
| isUuid | `string` | `UuidV1 \| UuidV3 \| UuidV4 \| UuidV5 \| UuidV7` | |
| isUuidV1 | `string` | `UuidV1` | |
| isUuidV3 | `string` | `UuidV3` | |
| isUuidV4 | `string` | `UuidV4` | |
| isUuidV5 | `string` | `UuidV5` | |
| isUuidV7 | `string` | `UuidV7` | |
| assertUuid | `string` | `UuidV1 \| UuidV3 \| UuidV4 \| UuidV5 \| UuidV7` | |
| assertUuidV1 | `string` | `UuidV5` | |
| assertUuidV3 | `string` | `UuidV3` | |
Expand Down Expand Up @@ -401,10 +402,10 @@ ESM individual imports are tracked by a

| Scenario | Size (compressed) |
|----------------------------------------|------------------:|
| Import `isPlainObject` | ~ 56b |
| Import `isPlainObject` | ~ 100b |
| Import `isUuid` | ~ 175b |
| Import `isEan13` | ~ 117b |
| All typeguards, assertions and helpers | ~ 900b |
| All typeguards, assertions and helpers | ~ 1700b |

> For CJS usage (not recommended) track the size on [bundlephobia](https://bundlephobia.com/package/@httpx/assert@latest).
Expand Down
51 changes: 42 additions & 9 deletions packages/assert/src/__tests__/object.guards.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,68 @@ import { isPlainObject } from '../object.guards';

describe('Object typeguards tests', () => {
describe('isPlainObject', () => {
const str = 'key';
it.each([
[{}, true],
[Object.create(null), true],
[{ 1: 'cool' }, true],
[{ name: 'seb' }, true],
[{ [str]: 'seb' }, true],
[{ [Symbol('tag')]: 'value' }, true],
[{ children: [{ test: 1 }], name: 'deep-plain' }, true],
[
{ children: [{ test: new Date() }], name: 'deep-with-regular-object' },
true,
],
[{ constructor: { name: 'Object2' } }, true],
[JSON.parse('{}'), true],
// False
// ############ Rejected #############################
['hello', false],
[false, false],
[undefined, false],
[null, false],
[10, false],
[Number.NaN, false],
// functions and objects
[() => 'cool', false],
[new (class Cls {})(), false],
[new (class extends Object {})(), false],
// Symbols
[Symbol('cool'), false],
[
{
[Symbol.iterator]: function* () {
yield 1;
},
},
false,
],
[
{
[Symbol.toStringTag]: 'string-tagged',
},
false,
],
// Static built-in classes
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON
[JSON, false],
[Math, false],
[Atomics, false],
// built-in classes
[new Date(), false],
[new Map(), false],
[new Error(), false],
[new Set(), false],
[new Request('http://localhost'), false],
[Object.create(null), false],
[/(\d+)/, false],
[new Promise(() => {}), false],
['hello', false],
[false, false],
[undefined, false],
[null, false],
[10, false],
[Number.NaN, false],
[Promise.resolve({}), false],
[Object.create({}), false],
[/(\d+)/, false],
// eslint-disable-next-line prefer-regex-literals
[new RegExp('/d+/'), false],
// Template literals
[`cool`, false],
[String.raw`rawtemplate`, false],
])('when "%s" is given, should return %s', (v, expected) => {
expect(isPlainObject(v)).toStrictEqual(expected);
});
Expand Down
14 changes: 10 additions & 4 deletions packages/assert/src/object.guards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,16 @@ export const isPlainObject = <
>(
v: unknown
): v is PlainObject<TValue> => {
if (v === null || typeof v !== 'object') {
return false;
}
const proto = Object.getPrototypeOf(v) as typeof Object.prototype | null;
return (
typeof v === 'object' &&
v !== null &&
(Object.getPrototypeOf(v) as typeof Object.prototype)?.constructor ===
Object.prototype.constructor
(proto === null ||
proto === Object.prototype ||
Object.getPrototypeOf(proto) === null) &&
// https://stackoverflow.com/a/76387885/5490184
!(Symbol.toStringTag in v) &&
!(Symbol.iterator in v)
);
};

0 comments on commit 226a4b1

Please sign in to comment.