Skip to content

Commit

Permalink
wip to continue working on another machine
Browse files Browse the repository at this point in the history
  • Loading branch information
hoeck committed Nov 1, 2023
1 parent 218ea74 commit 8f6b386
Show file tree
Hide file tree
Showing 28 changed files with 326 additions and 256 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,3 +308,13 @@ Shortcuts:
- add more combinators: partial, required, get, ...
- separate [`Runtype`](src/runtype.ts#L106) and [`InternalRuntype`](src/runtype.ts#L171) and type runtype internals
(see [this comment](https://github.com/hoeck/simple-runtypes/pull/73#discussion_r948841977))


#### current tasks (metadata) notes

- check that intersection & union tests do properly test the distribution stuff
- make getMetadata public
- maybe make metadata typed and include all options so that you can walk the tree to create testdata orjson-schemas from types
- maybe add a `serialize` function to each runtype too? to use instead of JSON.stringify and to provide a full-service library?
- maybe make `any` a forbidden type of a runtype
- maybe move the `isPure` checks more outward (out of the loops in array and object runtypes)
11 changes: 7 additions & 4 deletions src/any.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { Runtype, internalRuntype } from './runtype'
import { Runtype, setupInternalRuntype } from './runtype'

/**
* A value to check later.
*/
export function any(): Runtype<any> {
return internalRuntype((v) => {
return v as any
}, true)
return setupInternalRuntype(
(v) => {
return v as any
},
{ isPure: true },
)
}
79 changes: 43 additions & 36 deletions src/array.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import {
createFail,
failSymbol,
getInternalRuntype,
InternalRuntype,
internalRuntype,
isFail,
isPureRuntype,
propagateFail,
Runtype,
setupInternalRuntype,
} from './runtype'

export const arrayRuntype = internalRuntype<unknown[]>((v, failOrThrow) => {
export const arrayRuntype: InternalRuntype<unknown[]> = (v, failOrThrow) => {
if (Array.isArray(v)) {
return v
}

return createFail(failOrThrow, `expected an Array`, v)
}, true)
}

/**
* An array of a given type.
Expand All @@ -31,46 +31,53 @@ export function array<A>(
): Runtype<A[]> {
const { maxLength, minLength } = options || {}

const isPure = isPureRuntype(a)
const internalA = getInternalRuntype(a)
const isPure = !!internalA.meta?.isPure

return internalRuntype<any>((v, failOrThrow) => {
const arrayValue = (arrayRuntype as InternalRuntype)(v, failOrThrow)
return setupInternalRuntype<A[]>(
(v, failOrThrow) => {
const arrayValue = arrayRuntype(v, failOrThrow)

if (isFail(arrayValue)) {
return propagateFail(failOrThrow, arrayValue, v)
}
if (isFail(arrayValue)) {
return propagateFail(failOrThrow, arrayValue, v)
}

if (maxLength !== undefined && arrayValue.length > maxLength) {
return createFail(
failOrThrow,
`expected the array to contain at most ${maxLength} elements`,
v,
)
}
if (maxLength !== undefined && arrayValue.length > maxLength) {
return createFail(
failOrThrow,
`expected the array to contain at most ${maxLength} elements`,
v,
)
}

if (minLength !== undefined && arrayValue.length < minLength) {
return createFail(
failOrThrow,
`expected the array to contain at least ${minLength} elements`,
v,
)
}
if (minLength !== undefined && arrayValue.length < minLength) {
return createFail(
failOrThrow,
`expected the array to contain at least ${minLength} elements`,
v,
)
}

// copy the unknown array in case the item runtype is not pure (we do not mutate anything in place)
const res: A[] = isPure ? arrayValue : new Array(arrayValue.length)
// copy the unknown array in case the item runtype is not pure (we do not
// mutate anything in place)
const res: A[] = isPure ? arrayValue : new Array(arrayValue.length)

for (let i = 0; i < arrayValue.length; i++) {
const item = (a as InternalRuntype)(arrayValue[i], failSymbol)
for (let i = 0; i < arrayValue.length; i++) {
const item = internalA(arrayValue[i], failSymbol)

if (isFail(item)) {
return propagateFail(failOrThrow, item, v, i)
}
if (isFail(item)) {
return propagateFail(failOrThrow, item, v, i)
}

if (!isPure) {
res[i] = item
if (!isPure) {
res[i] = item
}
}
}

return res
}, isPure)
return res
},
{
isPure,
},
)
}
17 changes: 10 additions & 7 deletions src/boolean.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { createFail, internalRuntype, Runtype } from './runtype'
import { createFail, Runtype, setupInternalRuntype } from './runtype'

const booleanRuntype = internalRuntype<boolean>((v, failOrThrow) => {
if (v === true || v === false) {
return v
}
const booleanRuntype = setupInternalRuntype<boolean>(
(v, failOrThrow) => {
if (v === true || v === false) {
return v
}

return createFail(failOrThrow, 'expected a boolean', v)
}, true)
return createFail(failOrThrow, 'expected a boolean', v)
},
{ isPure: true },
)

/**
* A boolean.
Expand Down
25 changes: 15 additions & 10 deletions src/custom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import {
createFail,
Fail,
failSymbol,
internalRuntype,
InternalRuntype,
getInternalRuntype,
isFail,
propagateFail,
Runtype,
setupInternalRuntype,
} from './runtype'

/**
Expand All @@ -20,15 +20,20 @@ export function createError(msg: string): Fail {
* Construct a custom runtype from a validation function.
*/
export function runtype<T>(fn: (v: unknown) => T | Fail): Runtype<T> {
return internalRuntype<any>((v, failOrThrow) => {
const res = fn(v)
return setupInternalRuntype<any>(
(v, failOrThrow) => {
const res = fn(v)

if (isFail(res)) {
return propagateFail(failOrThrow, res, v)
}
if (isFail(res)) {
return propagateFail(failOrThrow, res, v)
}

return res
})
return res
},
{
isPure: false,
},
)
}

/**
Expand All @@ -49,7 +54,7 @@ export type ValidationResult<T> =
* exceptions and try-catch for error handling is of concern.
*/
export function use<T>(r: Runtype<T>, v: unknown): ValidationResult<T> {
const result = (r as InternalRuntype)(v, failSymbol)
const result = getInternalRuntype(r)(v, failSymbol)

if (isFail(result)) {
// we don't know who is using the result (passing error up the stack or
Expand Down
101 changes: 52 additions & 49 deletions src/dictionary.ts
Original file line number Diff line number Diff line change
@@ -1,81 +1,84 @@
import { objectRuntype } from './object'
import {
createFail,
internalRuntype,
setupInternalRuntype,
isFail,
isPureRuntype,
propagateFail,
getInternalRuntype,
InternalRuntypeOf,
} from './runtype'
import { debugValue } from './runtypeError'
import type { Runtype, InternalRuntype, Fail } from './runtype'

const internalObjectRuntype: InternalRuntypeOf<typeof objectRuntype> = objectRuntype

function dictionaryRuntype<T extends string, U>(
keyRuntype: Runtype<T>,
valueRuntype: Runtype<U>,
) {
const isPure = isPureRuntype(keyRuntype) && isPureRuntype(valueRuntype)

return internalRuntype<Record<T, U>>((v, failOrThrow) => {
const o: object | Fail = (objectRuntype as InternalRuntype)(v, failOrThrow)
const keyRt: InternalRuntypeOf<typeof keyRuntype> = keyRuntype
const valueRt: InternalRuntypeOf<typeof valueRuntype> = valueRuntype

if (isFail(o)) {
return propagateFail(failOrThrow, o, v)
}
const isPure = !!(keyRt.meta?.isPure && valueRt.meta?.isPure)

if (Object.getOwnPropertySymbols(o).length) {
return createFail(
failOrThrow,
`invalid key in dictionary: ${debugValue(
Object.getOwnPropertySymbols(o),
)}`,
v,
)
}
return setupInternalRuntype<Record<T, U>>(
(v, failOrThrow) => {
const o = internalObjectRuntype(v, failOrThrow)

// optimize allocations: only create a copy if any of the key runtypes
// return a different object - otherwise return value as is
const res = (isPure ? o : {}) as { [key: string]: U }

for (const key in o) {
if (!Object.prototype.hasOwnProperty.call(o, key)) {
continue
if (isFail(o)) {
return propagateFail(failOrThrow, o, v)
}

if (key === '__proto__') {
// e.g. someone tried to sneak __proto__ into this object and that
// will cause havoc when assigning it to a new object (in case its impure)
if (Object.getOwnPropertySymbols(o).length) {
return createFail(
failOrThrow,
`invalid key in dictionary: ${debugValue(key)}`,
`invalid key in dictionary: ${debugValue(
Object.getOwnPropertySymbols(o),
)}`,
v,
)
}
const keyOrFail: T | Fail = (keyRuntype as InternalRuntype)(
key,
failOrThrow,
)

if (isFail(keyOrFail)) {
return propagateFail(failOrThrow, keyOrFail, v)
}
// optimize allocations: only create a copy if any of the key runtypes
// return a different object - otherwise return value as is
const res = (isPure ? o : {}) as { [key: string]: U }

const value = o[key as keyof typeof o]
const valueOrFail: U | Fail = (valueRuntype as InternalRuntype)(
value,
failOrThrow,
)
for (const key in o) {
if (!Object.prototype.hasOwnProperty.call(o, key)) {
continue
}

if (isFail(valueOrFail)) {
return propagateFail(failOrThrow, valueOrFail, v)
}
if (key === '__proto__') {
// e.g. someone tried to sneak __proto__ into this object and that
// will cause havoc when assigning it to a new object (in case its impure)
return createFail(
failOrThrow,
`invalid key in dictionary: ${debugValue(key)}`,
v,
)
}
const keyOrFail = keyRt(key, failOrThrow)

if (isFail(keyOrFail)) {
return propagateFail(failOrThrow, keyOrFail, v)
}

const value = o[key as keyof typeof o]
const valueOrFail = valueRt(value, failOrThrow)

if (isFail(valueOrFail)) {
return propagateFail(failOrThrow, valueOrFail, v)
}

if (!isPure) {
res[keyOrFail] = valueOrFail
if (!isPure) {
res[keyOrFail] = valueOrFail
}
}
}

return res
}, isPure)
return res
},
{ isPure },
)
}

/**
Expand Down
6 changes: 3 additions & 3 deletions src/enum.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createFail, internalRuntype, Runtype } from './runtype'
import { createFail, setupInternalRuntype, Runtype } from './runtype'
import { debugValue } from './runtypeError'

type EnumObject = { [key: string]: string | number }
Expand All @@ -9,7 +9,7 @@ type EnumObject = { [key: string]: string | number }
function enumRuntype<T extends EnumObject, S extends keyof T>(
enumObject: T,
): Runtype<T[S]> {
return internalRuntype((v, failOrThrow) => {
return setupInternalRuntype((v, failOrThrow) => {
// use the fast reverse lookup of number enums to check whether v is a
// value of the enum
if (typeof v === 'number' && (enumObject as any)[v as any] !== undefined) {
Expand All @@ -25,7 +25,7 @@ function enumRuntype<T extends EnumObject, S extends keyof T>(
`expected a value that belongs to the enum ${debugValue(enumObject)}`,
v,
)
}, true)
})
}

export { enumRuntype as enum }
6 changes: 3 additions & 3 deletions src/guardedBy.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { createFail, internalRuntype, Runtype } from './runtype'
import { createFail, setupInternalRuntype, Runtype } from './runtype'

/**
* A runtype based on a type guard
*/
export function guardedBy<F>(typeGuard: (v: unknown) => v is F): Runtype<F> {
return internalRuntype((v, failOrThrow) => {
return setupInternalRuntype((v, failOrThrow) => {
if (!typeGuard(v)) {
return createFail(failOrThrow, 'expected typeguard to return true', v)
}

return v
}, true)
})
}
Loading

0 comments on commit 8f6b386

Please sign in to comment.