Skip to content

Commit ec4fe52

Browse files
authored
fix(Codec): fix crash on prototype-less objects (#756)
- Replace `input.hasOwnProperty(key)` with `Object.prototype.hasOwnProperty.call(input, key)` in `Codec.ts`. This prevents runtime errors when decoding objects created via `Object.create(null)`. - Add regression tests for prototype-less objects. - Apply formatting and linting fixes to `Maybe.ts` and `Maybe.test.ts`.
1 parent c3f84e2 commit ec4fe52

File tree

4 files changed

+56
-14
lines changed

4 files changed

+56
-14
lines changed

src/Codec.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,16 @@ describe('Codec', () => {
6666
expect(mockCodec.decode({ a: 0, b: '', c: '' })).toEqual(
6767
Right({ a: 0, b: '', c: '' })
6868
)
69+
expect(
70+
mockCodec.decode(
71+
(() => {
72+
const res: any = Object.create(null)
73+
res.a = 0
74+
res.b = 'woo'
75+
return res
76+
})()
77+
)
78+
).toEqual(Right({ a: 0, b: 'woo' }))
6979
})
7080

7181
test('unsafeDecode', () => {
@@ -295,6 +305,16 @@ describe('Codec', () => {
295305
expect(numberRecord.decode({})).toEqual(Right({}))
296306
expect(numberRecord.decode({ a: 0 })).toEqual(Right({ a: 0 }))
297307
expect(numberRecord.decode({ a: 0, b: 1 })).toEqual(Right({ a: 0, b: 1 }))
308+
expect(
309+
numberRecord.decode(
310+
(() => {
311+
const res: any = Object.create(null)
312+
res.a = 0
313+
res.b = -50
314+
return res
315+
})()
316+
)
317+
).toEqual(Right({ a: 0, b: -50 }))
298318
})
299319

300320
test('decode with number key', () => {
@@ -323,6 +343,15 @@ describe('Codec', () => {
323343
expect(record(mockKeyCodec, mockValueCodec).encode({ a: 0 })).toEqual({
324344
haha: 1
325345
})
346+
expect(
347+
record(string, string).encode(
348+
(() => {
349+
const res: Record<string, string> = Object.create(null)
350+
res.hi = 'bye'
351+
return res
352+
})()
353+
)
354+
).toEqual({ hi: 'bye' })
326355
})
327356
})
328357

src/Codec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ const reportError = (expectedType: string, input: unknown): string => {
4747
input === null
4848
? 'null'
4949
: Array.isArray(input)
50-
? 'an array with value ' + JSON.stringify(input, serializeValue)
51-
: 'an object with value ' + JSON.stringify(input, serializeValue)
50+
? 'an array with value ' + JSON.stringify(input, serializeValue)
51+
: 'an object with value ' + JSON.stringify(input, serializeValue)
5252
break
5353

5454
case 'boolean':
@@ -129,7 +129,7 @@ export const Codec = {
129129

130130
for (const key of keys) {
131131
if (
132-
!input.hasOwnProperty(key) &&
132+
!Object.prototype.hasOwnProperty.call(input, key) &&
133133
!(properties[key] as any)._isOptional
134134
) {
135135
return Left(
@@ -254,7 +254,7 @@ export const optional = <T>(codec: Codec<T>): Codec<T | undefined> =>
254254
...oneOf([codec, undefinedType]),
255255
schema: codec.schema,
256256
_isOptional: true
257-
} as any)
257+
}) as any
258258

259259
/** A codec for a value T or null. Keep in mind if you use `nullable` inside `Codec.interface` the property will still be required */
260260
export const nullable = <T>(codec: Codec<T>): Codec<T | null> =>
@@ -397,7 +397,7 @@ export const record = <K extends keyof any, V>(
397397
}
398398

399399
for (const key of Object.keys(input)) {
400-
if (input.hasOwnProperty(key)) {
400+
if (Object.prototype.hasOwnProperty.call(input, key)) {
401401
const decodedKey = keyCodecOverride.decode(key)
402402
const decodedValue = valueCodec.decode((input as any)[key])
403403

@@ -421,7 +421,7 @@ export const record = <K extends keyof any, V>(
421421
const result = {} as Record<K, V>
422422

423423
for (const key in input) {
424-
if (input.hasOwnProperty(key)) {
424+
if (Object.prototype.hasOwnProperty.call(input, key)) {
425425
result[keyCodec.encode(key) as K] = valueCodec.encode(input[key]) as V
426426
}
427427
}

src/Maybe.test.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,21 @@ describe('Maybe', () => {
4646
})
4747

4848
test('fromPredicate with guards', () => {
49-
const isNotEmpty = <T>(value: T[] | null): value is [T, ...T[]] => Array.isArray(value) && value.length > 0
50-
expect(Maybe.fromPredicate(isNotEmpty, null as number[] | null)).toEqual(Nothing)
51-
expect(Maybe.fromPredicate(isNotEmpty, Array<number>()).map(([x]) => x)).toEqual(Nothing)
52-
expect(Maybe.fromPredicate(isNotEmpty, [1]).map(([x]) => x)).toEqual(Just(1))
53-
const isString = (value: string|number): value is string => typeof value === 'string'
49+
const isNotEmpty = <T>(value: T[] | null): value is [T, ...T[]] =>
50+
Array.isArray(value) && value.length > 0
51+
expect(Maybe.fromPredicate(isNotEmpty, null as number[] | null)).toEqual(
52+
Nothing
53+
)
54+
expect(
55+
Maybe.fromPredicate(isNotEmpty, Array<number>()).map(([x]) => x)
56+
).toEqual(Nothing)
57+
expect(Maybe.fromPredicate(isNotEmpty, [1]).map(([x]) => x)).toEqual(
58+
Just(1)
59+
)
60+
const isString = (value: string | number): value is string =>
61+
typeof value === 'string'
5462
expect(Maybe.fromPredicate(isString, 2)).toEqual(Nothing)
55-
expect(Maybe.fromPredicate(isString, 'a')).toEqual(Just("a"))
63+
expect(Maybe.fromPredicate(isString, 'a')).toEqual(Just('a'))
5664
})
5765

5866
test('empty', () => {

src/Maybe.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,13 @@ interface MaybeTypeRef {
9494
/** Takes a predicate and a value, passes the value to the predicate and returns a Just if it returns true, otherwise a Nothing is returned */
9595
fromPredicate<T>(pred: (value: T) => boolean): (value: T) => Maybe<T>
9696
fromPredicate<T>(pred: (value: T) => boolean, value: T): Maybe<T>
97-
fromPredicate<T, P extends T>(pred: (value: T) => value is P, value: T): Maybe<P>
98-
fromPredicate<T, P extends T>(pred: (value: T) => value is P): (value: T) => Maybe<P>
97+
fromPredicate<T, P extends T>(
98+
pred: (value: T) => value is P,
99+
value: T
100+
): Maybe<P>
101+
fromPredicate<T, P extends T>(
102+
pred: (value: T) => value is P
103+
): (value: T) => Maybe<P>
99104
/** Returns only the `Just` values in a list */
100105
catMaybes<T>(list: readonly Maybe<T>[]): T[]
101106
/** Maps over a list of values and returns a list of all resulting `Just` values */

0 commit comments

Comments
 (0)