skema
Version:
Skema provides a handy & composable way to validate / transform / purify the input data.
210 lines (173 loc) • 4.59 kB
JavaScript
// The Factory of Skema according to user preset
///////////////////////////////////////////////////////////
import {Skema} from './skema'
import {Types} from './future'
import {Options} from './options'
import makeArray from 'make-array'
import {error} from './error'
import {
isString, isArray, isObject, isDefined
} from './util'
import {isSkema} from './future'
import {TypeDefinition} from './type'
import {
ObjectShape,
ArrayShape,
ObjectOfShape,
ArrayOfShape,
set
} from './shape'
const METHODS = [
'type',
'shape',
'objectOf',
'arrayOf',
'any',
'declare'
]
const REGEX_ENDS_QUESTION_MARK = /\?$/
// Trim and validate type name
const parseTypeName = name => {
if (!isString(name)
|| REGEX_ENDS_QUESTION_MARK.test(name = name.trim())
) {
throw error('INVALID_TYPE_NAME', name)
}
return name
}
const validateTypeName = name => isObject(name)
? name
: parseTypeName(name)
// @decorator
const memoize = (target, key, descriptor) => {
const original = descriptor.value
descriptor.value = function (arg) {
const value = this._types.get(arg)
if (isDefined(value)) {
return value
}
if (Object.keys(arg).length === 0) {
throw error('EMPTY_TYPE')
}
const created = original.call(this, arg)
this._types.set(arg, created)
return created
}
return descriptor
}
class SkemaFactory {
constructor (options) {
METHODS.forEach(method => {
this[method] = this[method].bind(this)
})
const {
types = [],
...others
} = options
this._options = new Options(others)
this._types = new Types
if (!isArray(types)) {
throw error('NON_ARRAY_TYPES')
}
types.forEach(({name, definition}) => {
this.declare(name, definition)
})
}
set (...args) {
return set(...args)
}
// IPTypeDefinition: {set () {}, type: Skema | StringType} -> Skema
// SkemaAlias: string | object
// TypeDef: SkemaAlias | IPTypeDefinition | Skema
// ShapeDef: {[string]: TypeDef} -> Skema
// - ArrayShape: TypeDef[TypeDef] -> Skema
// - special: objectOf(TypeDef) -> Skema
// - special: arrayOf(TypeDef) -> Skema
// Create a single type
type (def: TypeDef): Skema {
if (isSkema(def)) {
return def
}
if (isString(def)) {
return this._stringType(def)
}
if (isObject(def)) {
return this._type(def)
}
throw error('INVALID_TYPE')
}
_type (def: TypeDef): Skema {
const definition = new TypeDefinition(def)
if (definition._type) {
definition._type = this.type(definition._type)
}
return this._create(definition)
}
shape (shape: ShapeDef, clean: boolean): Skema {
return this._create({
_shape: isArray(shape)
? new ArrayShape(this._arrayShape(shape), clean)
: new ObjectShape(this._objectShape(shape), clean)
})
}
// `objectOf` and `arrayOf` are two special kinds of shapes.
// An object with property values of a certain type
objectOf (type: TypeDef): Skema {
return this._create({
_shape: new ObjectOfShape(this.type(type))
})
}
// An array of a certain type
arrayOf (type: TypeDef): Skema {
return this._create({
_shape: new ArrayOfShape(this.type(type))
})
}
// Anything that is ok
any (): Skema {
return this._create({
_any: true
})
}
// Declare a basic type
declare (name, definition) {
const names = makeArray(name).map(validateTypeName)
const skema = this.type(definition)
names.forEach(name => this._types.set(name, skema))
}
// 'number' -> Skema
_stringType (string): Skema {
string = string.trim()
const hasOptionalMark = REGEX_ENDS_QUESTION_MARK.test(string)
if (hasOptionalMark) {
// 'number?' -> 'number'
// 'number ?' -> 'number'
string = string.slice(0, string.length - 1).trimRight()
}
const skema = this._types.get(string, true)
return hasOptionalMark
? skema.isOptional()
? skema
: this.type({
type: skema,
optional: true
})
: skema
}
_arrayShape (array: Array): Skema {
return array.map(type => this.type(type))
}
_objectShape (shape: Object): Skema {
const skemaMap = {}
Object.keys(shape).forEach(key => {
skemaMap[key] = this.type(shape[key])
})
return skemaMap
}
_create (definition: TypeDefinition): Skema {
definition._options = this._options
return new Skema(definition)
}
}
export const factory = (options = {}) => new SkemaFactory(options)