Skip to content

Commit

Permalink
feat: add optional
Browse files Browse the repository at this point in the history
  • Loading branch information
LuciNyan committed Mar 1, 2023
1 parent f934e3a commit 669b61b
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 12 deletions.
30 changes: 28 additions & 2 deletions src/operator.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest'

import { isBoolean, isNumber, isObject, isString } from './base'
import { and, arrayOf, objectOf, or } from './operator'
import { and, arrayOf, objectOf, optional, or } from './operator'
import { SELF } from './property'

type Elem = {
Expand Down Expand Up @@ -119,9 +119,35 @@ describe('and', () => {
it('should return false if value does not match all of the guards', () => {
expect(guard(undefined)).toBe(false)
expect(guard(null)).toBe(false)
expect(guard({ name: 123 })).toBe(false)
expect(guard({ name: 'Luci' })).toBe(false)
expect(guard({ age: 30 })).toBe(false)
expect(guard({ name: 123 })).toBe(false)
expect(guard({ name: 'LuciNyan', age: '17' })).toBe(false)
})
})

describe('optional', () => {
const guard = and(
isObject,
objectOf({
name: optional(isString),
}),
objectOf({
age: optional(isNumber),
})
)

it('should return true if value matches all of the guards', () => {
expect(guard({ name: 'LuciNyan', age: 17 })).toBe(true)
expect(guard({ name: 'LuciNyan' })).toBe(true)
expect(guard({ age: 17 })).toBe(true)
expect(guard({})).toBe(true)
})

it('should return false if value does not match all of the guards', () => {
expect(guard(undefined)).toBe(false)
expect(guard(null)).toBe(false)
expect(guard({ name: 'LuciNyan', age: '17' })).toBe(false)
expect(guard({ name: 17 })).toBe(false)
})
})
12 changes: 12 additions & 0 deletions src/operator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@ export function intersection<T extends any[]>(
}
}

export function optional<T>(guard: Guard<T>): Guard<T> & {
optional: true
} {
const _guard = (...args: Parameters<Guard<T>>) => guard(...args)

_guard.optional = true

return _guard as Guard<T> & {
optional: true
}
}

export const or = union
export const and = intersection
export const make = objectOf
14 changes: 5 additions & 9 deletions src/property.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
import { isObject } from './base'
import { Guard } from './utils'

export const SELF = (x: unknown): x is any => true

export function hasUnknownProperty<K extends string>(x: unknown, name: K): x is { [Key in K]: unknown } {
return isObject(x) && name in x
}

export const SELF = (x: unknown): x is any => true

export function hasProperty<K extends string, V>(
x: unknown,
name: K,
guard: (value: unknown) => value is V
): x is { [Key in K]: V } {
export function hasProperty<K extends string, V>(x: unknown, name: K, guard: Guard<V>): x is { [Key in K]: V } {
if (!hasUnknownProperty(x, name)) {
return false
return !!guard.optional
}

const memo = new Set([x])
Expand Down Expand Up @@ -48,7 +44,7 @@ export function _hasProperty<K extends string, V, R extends Record<PropertyKey,
} = {}
): x is { [Key in K]: V } {
if (!hasUnknownProperty(x, name)) {
return false
return !!guard.optional
}

memo.add(x)
Expand Down
4 changes: 3 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never

export type Guard<T> = (x: unknown) => x is T
export type Guard<T> = ((x: unknown) => x is T) & {
optional?: boolean
}

export function toString(x: unknown): string {
return Object.prototype.toString.call(x)
Expand Down

0 comments on commit 669b61b

Please sign in to comment.