builder
- main builder logiccore
core functionality, such as loggingtypes
exports all the built-in type handlers
array-type
boolean-type
date-type
mixed-type
number-type
object-type
string-type
Each of these types inherit from BaseType
in the base-type
package.
conditions
- handles schema conditionals such aswhen
, andif
constraints
- basic constraint logic
validator-bridge
bridges validator
tests
tests
buildSchema(schema: any, config = {})
createBuilder(config = {})
SchemaBuilder
The builder iterates on the properties
of the main/object schema and processes each property.
For each entry createSchemaEntry
is called to with the value of that property.
createSchemaEntry(opts = {})
This creates a SchemaEntry
instance which calls createPropertyValueHandler(opts, config)
to create a property value handler by way of the typeHandlers
registered (either passed in via the config or using a built-in type handler).
A property can either be a single property value (object) or a list of properties (array).
For each property valie, createPropertyValueResolver
is called to create a PropertyValueResolver
instance. This instance then determines how to process the property value based on the type (single value vs array/multi value)
For a single (object) property value, createSinglePropertyValueResolver
is called to create an instance of SinglePropertyValueResolver
.
For an multi (array) property value, createMultiPropertyValueResolver
is called to create an instance of MultiPropertyValueResolver
.
Note: Currently this library does not support resolving a multi (array) property value, but the infrastructure is in place so you can customize/extend it to suit your needs.
The SinglePropertyValueResolver
instance calls resolveTypeHandlers(typeHandlerNames)
with a list of typeHandlerNames
to process for. It iterates on all the typeHandlerNames
and for each name creates a typeHandler
looking it up in the types
object passed in.
As soon as a typeHandler
resolves a result, the iteration is aborted. The resolution is responsible for updating the output instance accordingly.
for (let typeName of typeHandlerNames) {
const typeHandler = types[typeName];
result = resolveTypeHandler(typeHandler, obj, config);
if (result) break;
}
Note that each typehandler is resolved via a call to createTypeHandler
:
typeHandler.createTypeHandler(obj, config);
Any TypeHandler
should export a factory method createTypeHandler
which should be used to create any instance of the TypeHandler
.
The factory method should guard creation by testing if the incoming property value (obj
) is non-empty and creating an instance of a type Guard
.
The guard should determine if the property value object is valid to be used for the particular TypeHandler
. If the value is valid, an instance of the TypeHandler
should be created, taking the property value obj
and the config
as input.
Every TypeHandler
should inherit from BaseType
which implements core type handler functionality.
The BaseType
class uses composition to leverage a number of helpers:
typeErrorHandler
typeModeSelector
typeValueProcessor
mixed
converter
constraintsProcessor
constraintsAdder
A developer can pass in custom versions of any of these helpers, allowing for great flexibility.
The core helper is the converter
which is responsible for converting the property value to a type output that can be added to the builder schema output.
The converter
should implement convert
to do the job, leveraging the mixed
instance (instance of the mixed
type TypeHandler
).
The convert
strategy is as follows:
- convert the enabled constraints for the mixed type
- convert the enabled constraints for the type being processed (such as
string
,array
etc)
this.convertMixed();
this.convertEnabled();
The typeValueProcessor
helper is responsible for pre-processing the property value before being converted. It does this simply through the value
setter.
Note: Currently the typeValueProcessor
is not integrated in the converter.
The constraintBuilder
helper is responsible for builder type constraints via the constraintsAdder
helper.
The constraintsAdder
helper is responsible for adding a type constraint, such as email
for a string
The typeErrorHandler
helper is responsible for handling errors in the TypeHandler
instance or any of its helpers.
The typeModeSelector
helper is responsible for handling runtime modes.
Currently only the required/not-required mode is supported, see Mode.
The BaseTypeConstraintsProcessor
is the base class for the Processor
class of each type, which is responsible for processing all the constraints supported for that type.
For each constraint, a factory method is called to create a Constraint
instance such as MaxItems
for the array
type.
Each Constraint
class should inherit from BaseTypeConstrain
and have a process
method which processes the constraint (building the type handler output for the particular property constraint definition).
A yup
type schema instance is built using method chaining. To facilitate chaining, use the built-in chain
method, such as this.chain((x) => $max && x.max($max));
used in MaxItems
.
The Constraint
class can leverage helpers such as those found in the constraints
package. Conditional constraints (such as when
) can be found in the conditions
package.
The ideal design of a TypeHandler
looks as follows
import { BaseType } from "@schema-to-yup/base-type";
import { guard } from "./guard";
export function processTypeHandler(obj, config = {}) {
return guard(obj, config) && createSchemaEntry(obj, config);
}
export function createSchemaEntry(obj, config = {}) {
return createTypeHandler(obj, config).createSchemaEntry();
}
export function createTypeHandler(obj, config = {}) {
return new TypeHandler(obj, config);
}
export class TypeHandler extends BaseType {
constructor(obj, config) {
super(obj, config);
}
get yupType() {
return "boolean";
}
static create(obj, config) {
return new TypeHandler(obj, config);
}
}
The processTypeHandler
is the main function, to be called by the Converter
to convert a property schema to the output desired. It calls the guard
to validate the property schema and determine if the TypeHandler
is appropriate for the given schema and an instance should be created.
If the schema is invalid for the type handler, processTypeHandler
will return a "falsy" value. If the schema is valid for the type handler, processTypeHandler
will create an instance of TypeHandler
and call createSchemaEntry()
on the instance to generate the new schema entry for the property schema, which is returned.
Each Guard
class must have a guard
method which guards the TypeHandler
by evaluating if the incoming property schema is valid for the given TypeHandler
.
The Guard
may call methods on the config
object, such as isBoolean
in this case, which determines if the property schema is for the boolean type (depending on the input schema variant, such as JSON Schema or GraphQL schema etc).
import { BaseGuard } from "@schema-to-yup/core";
export class Guard extends BaseGuard {
isBoolean() {
return this.config.isBoolean(this.obj);
}
guard() {
return this.isBoolean();
}
}
export const createGuard = (obj, config) => new Guard(obj, config);
export const guard = (obj, config) => obj && createGuard(obj, config).guard();