Simple Example
This is an example using schemata-ts derivation on a simple schema.
Imports
This example uses the following imports:
import * as fc from 'fast-check'
import * as B from 'fp-ts/boolean'
import { pipe } from 'fp-ts/function'
import * as RA from 'fp-ts/ReadonlyArray'
import { strict as assert } from 'node:assert'
import * as S from 'schemata-ts'
import { deriveArbitrary } from 'schemata-ts/Arbitrary'
import { deriveEq } from 'schemata-ts/Eq'
import { type Float } from 'schemata-ts/float'
import { deriveGuard } from 'schemata-ts/Guard'
import { deriveJsonSchema } from 'schemata-ts/JsonSchema'
import { deriveMergeSemigroup } from 'schemata-ts/MergeSemigroup'
import * as TC from 'schemata-ts/Transcoder'
import { deriveTypeString } from 'schemata-ts/TypeString'
Schema
Use the
Simport to access all schemas and schema combinators.
This schema uses the Struct combinator, String schema, Number schema, and Optional combinator.
const Person = S.Struct({
name: S.String(),
age: S.Number,
favoriteColor: S.Optional(S.String()),
})
TypeScript Types
Use
S.InputOfto extract the underlying input type, andS.OutputOfto extract the underlying output type.
The Person schema has nearly identical input/output types, with one exception. The favoriteColor key may be present or not in the input type, but will always be present in the output type.
/**
* This type wull be:
*
* { name: string, age: Float, favoriteColor?: string | undefined }
*/
export type PersonInput = S.InputOf<typeof Person>
/**
* This type will be:
*
* { name: string, age: Float, favoriteColor: string | undefined }
*/
export type PersonOutput = S.OutputOf<typeof Person>
Validation
Use
deriveTranscoderto interpret the schema as aTranscoder.
Transcoders can decode an unknown value of an expected input type and transform it to the expected output type. They can also encode a value of the output type and transform it to the expected input type.
const transcoderPerson = TC.deriveTranscoder(Person)
// failed decoding
const decodeResult = transcoderPerson.decode({})
assert.deepStrictEqual(
decodeResult,
TC.failure(
TC.transcodeErrors(
TC.errorAtKey('age', TC.typeMismatch('Float', undefined)),
TC.errorAtKey('name', TC.typeMismatch('string', undefined)),
),
),
)
// successful decoding
const decodeResult2 = transcoderPerson.decode({
name: 'John',
age: 42,
})
assert.deepStrictEqual(
decodeResult2,
TC.success({ name: 'John', age: 42, favoriteColor: undefined }),
)
Type Guards
Use
deriveGuardto interpret the schema as aGuard
Guards are used to determine if the type of an unknown value matches the output type, and also serve as TypeScript type guards.
const guardPerson = deriveGuard(Person)
const testPerson1: unknown = {
name: 'John',
age: NaN,
}
// failed type guard
const isPerson = guardPerson.is(testPerson1)
if (isPerson) {
// $ExpectType PersonOutput
testPerson1
}
assert.strictEqual(isPerson, false)
const testPerson2: unknown = {
name: 'John',
age: 42,
}
// successful type guard
const isPerson2 = guardPerson.is(testPerson2)
assert.strictEqual(isPerson2, true)
Json Schema
Use
deriveJsonSchemato interpret the schema as aJsonSchema.
Json Schemas are used to generate a JSON Schema Draft 7, 2019-09, or 2020-12 representation of the schema.
/** Derives Json-Schema version 2019-09 */
const jsonSchemaPerson = deriveJsonSchema(Person)
assert.deepStrictEqual(jsonSchemaPerson, {
type: 'object',
properties: {
name: { type: 'string' },
age: { type: 'number' },
favoriteColor: { type: 'string' },
},
required: ['age', 'name'],
})
Arbitrary
Use
deriveArbitraryto interpret the schema as anArbitrary
Arbitraries are used to generate random values of the expected output type.
/** Note: fast-check start imports needs to be supplied in the `arbitrary` method */
const arbitraryPerson = deriveArbitrary(Person).arbitrary(fc)
// generate 5 random people
const people = fc.sample(arbitraryPerson, 5)
// all five should be valid according to `guard`
assert.deepStrictEqual(
pipe(
people,
// check that all people are valid
RA.foldMap(B.MonoidAll)(guardPerson.is),
),
true,
)
Merge Semigroup
Use
deriveMergeSemigroupto interpret the schema as aMergeSemigroup
Merge Semigroups are used to merge two values of the output type, and can be configured as first, or last semigroups; or to concat two primitives based on supplied semigroups and a fallback semigroup. MergeSemigroup will merge mergable data types such as arrays and records.
/** Derives a semigroup that configurably merges two people */
const mergeSemigroupPerson = deriveMergeSemigroup(Person)
const mergeSemigroupPersonFirst = mergeSemigroupPerson.semigroup('first')
assert.deepStrictEqual(
mergeSemigroupPersonFirst.concat(
{ name: 'John', age: 42 as Float, favoriteColor: 'brown' },
{ name: 'Tim', age: 12 as Float, favoriteColor: 'blue' },
),
{ name: 'John', age: 42 as Float, favoriteColor: 'brown' },
)
const mergeSemigroupPersonLast = mergeSemigroupPerson.semigroup('last')
assert.deepStrictEqual(
mergeSemigroupPersonLast.concat(
{ name: 'John', age: 42 as Float, favoriteColor: 'brown' },
{ name: 'Tim', age: 12 as Float, favoriteColor: 'blue' },
),
{ name: 'Tim', age: 12 as Float, favoriteColor: 'blue' },
)
Eq
Use
deriveEqto interpret the schema as anEq
Eq instances are used to determine if two values of the output type are equal. Which is in essence a schema-tailored equality check.
/** An `Eq` instance for person, essentially schema-tailored deep-equality */
const eqPerson = deriveEq(Person)
assert.deepStrictEqual(
eqPerson.equals(
{ name: 'John', age: 42 as Float, favoriteColor: undefined },
{ name: 'John', age: 42 as Float, favoriteColor: undefined },
),
true,
)
assert.deepStrictEqual(
eqPerson.equals(
{ name: 'John', age: 42 as Float, favoriteColor: undefined },
{ name: 'John', age: 42 as Float, favoriteColor: 'blue' },
),
false,
)
Type String
Use
deriveTypeStringto interpret the schema as aTypeString
TypeStrings are used to generate a user-descriptive string that describes the underlying input and output types.
const [inputString, outputString] = deriveTypeString(Person)
assert.deepStrictEqual(
inputString,
'{ age: Float, favoriteColor?: string?, name: string }',
)
assert.deepStrictEqual(
outputString,
'{ age: Float, favoriteColor: string?, name: string }',
)
Displaying Errors
Use
drawErrorTreeto display errors in a friendly way
There are many ways to format errors in schemata, but the easiest is drawErrorTree from the Transcoder module.
const testError = TC.transcodeErrors(
TC.errorAtKey('age', TC.typeMismatch('Float', undefined)),
TC.errorAtKey('name', TC.typeMismatch('string', undefined)),
)
assert.strictEqual(
TC.drawErrorTree(testError),
`Encountered 2 transcode errors:
┌ at key age:
└── Expected Float but got \`undefined\`
┌ at key name:
└── Expected string but got \`undefined\``,
)