Skip to content

Commit

Permalink
metadata for runtypes
Browse files Browse the repository at this point in the history
  • Loading branch information
hoeck committed Dec 4, 2023
1 parent 218ea74 commit 6988517
Show file tree
Hide file tree
Showing 33 changed files with 731 additions and 623 deletions.
44 changes: 26 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@ That's how runtypes work.
- [Why?](#why)
- [Benchmarks](#benchmarks)
- [Documentation](#documentation)
* [Intro](#intro)
* [Usage Examples](#usage-examples)
+ [Nesting](#nesting)
+ [Strict Property Checks](#strict-property-checks)
+ [Ignore Individual Properties](#ignore-individual-properties)
+ [Optional Properties](#optional-properties)
+ [Non-strict Property Checks](#non-strict-property-checks)
+ [Discriminating Unions](#discriminating-unions)
+ [Custom Runtypes](#custom-runtypes)
* [Reference](#reference)
* [Roadmap / Todos](#roadmap--todos)
- [Intro](#intro)
- [Usage Examples](#usage-examples)
- [Nesting](#nesting)
- [Strict Property Checks](#strict-property-checks)
- [Ignore Individual Properties](#ignore-individual-properties)
- [Optional Properties](#optional-properties)
- [Non-strict Property Checks](#non-strict-property-checks)
- [Discriminating Unions](#discriminating-unions)
- [Custom Runtypes](#custom-runtypes)
- [Reference](#reference)
- [Roadmap / Todos](#roadmap--todos)

<!-- tocstop -->

Expand Down Expand Up @@ -212,7 +212,7 @@ const networkSuccessState = st.record({
title: st.string(),
duration: st.number(),
summary: st.string(),
})
}),
})

const networdStateRuntype = st.union(
Expand All @@ -237,14 +237,14 @@ const bigIntRuntype = st.runtype((v) => {
const stringCheck = st.use(bigIntStringRuntype, v)

if (!stringCheck.ok) {
return stringCheck.error
return stringCheck.error
}

return BigInt(stringCheck.result.slice(0, -1))
})

bigIntRuntype("123n") // => 123n
bigIntRuntype("2.2") // => error: "expected string to match ..."
bigIntRuntype('123n') // => 123n
bigIntRuntype('2.2') // => error: "expected string to match ..."
```

### Reference
Expand Down Expand Up @@ -298,13 +298,21 @@ Shortcuts:
on all types that [`literal`](src/literal.ts#L10) accepts
- rename record to object: [#69](https://github.com/hoeck/simple-runtypes/issues/69)
- improve docs:
- *preface*: what is a runtype and why is it useful
- *why*: explain or link to example that shows "strict by default"
- _preface_: what is a runtype and why is it useful
- _why_: explain or link to example that shows "strict by default"
- show that `simple-runtypes` is feature complete because it can
1. express all TypeScript types
2. is extendable with custom runtypes (add documentation)
- add small frontend and backend example projects that show how to use `simple-runtypes` in production
- test *all* types with [tsd](https://github.com/SamVerschueren/tsd)
- test _all_ types with [tsd](https://github.com/SamVerschueren/tsd)
- 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
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
Loading

0 comments on commit 6988517

Please sign in to comment.