Skip to content

Commit

Permalink
fix impure records with optional fields
Browse files Browse the repository at this point in the history
fixes #100
  • Loading branch information
hoeck committed Dec 6, 2023
1 parent 5e5b52c commit 1744f62
Show file tree
Hide file tree
Showing 7 changed files with 62 additions and 3 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
- replaced by `nonStrict`: `sloppyRecord(X)` -> `nonStrict(record(X))`
- fixes [#23](https://github.com/hoeck/simple-runtypes/issues/23)
- fix: allow literal booleans as type-discriminators in discriminated unions, see [#98](https://github.com/hoeck/simple-runtypes/issues/98)
- fix: `string` trim & maxlength checks: https://github.com/hoeck/simple-runtypes/issues/100
- fix: `record` optionals: https://github.com/hoeck/simple-runtypes/issues/100

### 7.1.3

Expand Down
2 changes: 1 addition & 1 deletion src/optional.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ export function optional<A>(t: Runtype<A>): OptionalRuntype<A> {

return ti(v, failOrThrow)
},
{ isPure: ti.meta?.isPure },
{ isPure: ti.meta?.isPure, optional: true },
) as OptionalRuntype<A>
}
8 changes: 8 additions & 0 deletions src/record.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ export function internalRecord(
const k = typemapKeys[i]
const t = typemapValues[i] as InternalRuntype<any>

// optional fields not present in the given object do not need to be
// checked at all
// this is vital to preserve the object shape of an impure record
// with optional fields
if (t.meta?.optional && !o.hasOwnProperty(k)) {

Check failure on line 61 in src/record.ts

View workflow job for this annotation

GitHub Actions / ubuntu-latest Node 12 cache

Do not access Object.prototype method 'hasOwnProperty' from target object

Check failure on line 61 in src/record.ts

View workflow job for this annotation

GitHub Actions / ubuntu-latest Node 14 cache

Do not access Object.prototype method 'hasOwnProperty' from target object

Check failure on line 61 in src/record.ts

View workflow job for this annotation

GitHub Actions / ubuntu-latest Node 16 cache

Do not access Object.prototype method 'hasOwnProperty' from target object
break
}

// nested types should always fail with explicit `Fail` so we can add additional data
const value = t(o[k], failSymbol)

Expand Down
3 changes: 3 additions & 0 deletions src/runtype.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ export interface RuntypeMetadata {

// literal value used to identify tagged union members
literal?: string | boolean | number

// when true, this runtype is an optional in a record
optional?: boolean
}

/**
Expand Down
5 changes: 3 additions & 2 deletions test/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ export function expectAcceptValuesImpure<T>(
const [vIn, vOut] = valuesAreTuples ? (v as [unknown, unknown]) : [v, v]
const result = st.use(rt, vIn)

expect(result).toEqual({ ok: true, result: vOut })
expect(result).toStrictEqual({ ok: true, result: vOut })

// this identity check does not account for unmodified primitive values,
// which are always identical to themselves; these cases are handled
// directly in the relevant tests
Expand All @@ -39,7 +40,7 @@ export function expectAcceptValuesPure<T>(
values.forEach((v) => {
const result = st.use(rt, v)

expect(result).toEqual({ ok: true, result: v })
expect(result).toStrictEqual({ ok: true, result: v })
expect(result.ok && result.result).toBe(v)

expect(() => rt(v)).not.toThrow()
Expand Down
30 changes: 30 additions & 0 deletions test/issues.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { st } from './helpers'

describe('github issues', () => {
it('#100 (1)', () => {
expect(
st.use(
st.partial(
st.record({ name: st.string({ trim: false }), other: st.string() }),
),
{},
),
).toEqual({ ok: true, result: {} })

expect(
st.use(
st.partial(
st.record({ name: st.string({ trim: true }), other: st.string() }),
),
{},
),
).toEqual({ ok: true, result: {} })
})

it('#100 (2)', () => {
const s = st.string({ trim: true, minLength: 1, maxLength: 3 })

expect(() => s(' ')).toThrow()
expect(s('abc ')).toEqual('abc')
})
})
15 changes: 15 additions & 0 deletions test/record.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,21 @@ describe('record', () => {
])
})

it('keeps optional attributes of impure records', () => {
const runtype = st.record({ a: st.optional(st.string({ trim: true })) })

expectAcceptValuesImpure(
runtype,
[
[{}, {}],
[{ a: '' }, { a: '' }],
[{ a: ' a ' }, { a: 'a' }],
[{ a: undefined }, { a: undefined }],
],
true,
)
})

it('accepts nested records', () => {
const runtype = st.record({
a: st.record({
Expand Down

0 comments on commit 1744f62

Please sign in to comment.