"use strict" ;
const util = require ( "util" ) ;
const createStringValidator = require ( "./validator-functions/string" ) ;
const createNumberValidator = require ( "./validator-functions/number" ) ;
const createBooleanValidator = require ( "./validator-functions/boolean" ) ;
const createFunctionValidator = require ( "./validator-functions/function" ) ;
const createNothingValidator = require ( "./validator-functions/nothing" ) ;
const createNullValidator = require ( "./validator-functions/null" ) ;
const createUndefinedValidator = require ( "./validator-functions/undefined" ) ;
const getValueType = require ( "./util/get-value-type" ) ;
const errors = require ( "./errors" ) ;
module . exports = function generateValidator ( rule , name = "<unknown>" ) {
let baseRule ;
if ( rule . _baseType != null ) {
let validatorFactories = {
string : createStringValidator ,
boolean : createBooleanValidator ,
number : createNumberValidator ,
guardedFunction : createFunctionValidator ,
nothing : createNothingValidator ,
null : createNullValidator ,
undefined : createUndefinedValidator
} ;
let factory = validatorFactories [ rule . _baseType ] ;
if ( factory != null ) {
baseRule = factory ( rule . _options , name ) ;
} else {
throw new Error ( ` Unrecognized base type: ${ rule . _baseType } ` ) ;
}
} else if ( rule . _collectionType != null ) {
baseRule = ( ) => true ; /* FIXME */
} else if ( rule . _modifierType != null ) {
if ( rule . _modifierType === "either" ) {
let validators = rule . _types . map ( ( type ) => generateValidator ( type ) ) ;
baseRule = function ( value ) {
let matched = false ;
for ( let validator of validators ) {
if ( validator . call ( this , value ) === true ) {
matched = true ;
break ;
}
}
if ( matched === true ) {
return true ;
} else {
let acceptableTypes = rule . _types . map ( ( type ) => getValueType ( type , false ) ) . join ( ", " ) ;
return new errors . ValidationError ( ` Expected one of ( ${ acceptableTypes } ), got ${ getValueType ( value ) } instead ` ) ;
}
} ;
} else {
throw new Error ( ` Unrecognized modifier: ${ rule . _modifierType } ` ) ;
}
} else if ( rule . _isTypeAlias === true ) {
baseRule = generateValidator ( rule . _alias , name ) ;
} else if ( rule . _isCustomType === true ) {
let validator = rule . _validator ;
baseRule = function ( value ) {
if ( value . _isDeserializationPlaceholder === true ) {
return true ;
} else if ( validator . call ( this , value ) === true ) {
return true ;
} else {
return new errors . ValidationError ( ` Expected ${ getValueType ( rule ) } , got ${ getValueType ( value ) } instead ` ) ;
}
} ;
} else if ( rule . _isCustomRegistryType ) {
/* HACK: We performantly memoize the validator for a registry-stored alias, by storing it in the validator generation scope. */
let aliasValidator ;
baseRule = function ( value ) {
/* FIXME: Better error when the type is unknown */
if ( value . _isDeserializationPlaceholder === true ) {
return true ;
} else {
let actualType = rule . _registry . _getType ( rule . _name ) ;
if ( actualType . _isCustomType ) {
if ( actualType . _validator . call ( this , value ) === true ) {
return true ;
} else {
return new errors . ValidationError ( ` Expected ${ getValueType ( rule ) } , got ${ getValueType ( value ) } instead ` ) ;
}
} else {
if ( aliasValidator == null ) {
aliasValidator = generateValidator ( actualType . _alias , name ) ;
}
let validatorResult = aliasValidator . call ( this , value ) ;
if ( validatorResult === true ) {
return true ;
} else {
// return new errors.ValidationError(`Expected ${getValueType(actualType._alias)}, got ${getValueType(value)} instead`);
/* FIXME: Possible this is done wrong elsewhere too, swallowing the actual validation error? */
return validatorResult ;
}
}
}
} ;
} else if ( rule . _isTrait === true ) {
baseRule = function ( value ) {
if ( value . _isDeserializationPlaceholder === true ) {
return true ;
} else if ( value . _type != null && value . _type . _implementedTraits != null && value . _type . _implementedTraits . has ( rule ) ) {
return true ;
} else {
return new errors . ValidationError ( ` Expected object of a type with the ${ rule . _name } trait, got ${ getValueType ( value ) } instead ` ) ;
}
} ;
} else if ( rule . _isRegistryTrait === true ) {
baseRule = function ( value ) {
if ( value . _isDeserializationPlaceholder === true ) {
return true ;
} else {
/* TODO: The below approach requires that traits be defined before the types that use them, and disallows trait registry references in `.implements` calls, due to _implementedTraits always needing to contain actual trait definitions; in the future, a better approach needs to be found for this such that trait registry references can be used everywhere. */
let actualRule = rule . _registry . _getTrait ( rule . _name ) ;
/* FIXME: Better error when the trait is unknown */
if ( value . _type != null && value . _type . _implementedTraits != null && value . _type . _implementedTraits . has ( actualRule ) ) {
return true ;
} else {
return new errors . ValidationError ( ` Expected object of a type with the ${ rule . _name } trait, got ${ getValueType ( value ) } instead ` ) ;
}
}
} ;
} else if ( rule . _isSelfRule === true ) {
baseRule = function ( value ) {
if ( value instanceof this . _type ) {
return true ;
} else {
return new errors . ValidationError ( ` Expected ${ getValueType ( this . _type ) } , got ${ getValueType ( value ) } instead ` ) ;
}
} ;
} else if ( rule . _constructorType != null ) {
/* This is for handling third-party constructors and their instances. */
baseRule = function ( value ) {
if ( value instanceof rule . _constructorType ) {
return true ;
} else {
return new errors . ValidationError ( ` Expected ${ getValueType ( rule ) } , got ${ getValueType ( value ) } instead ` ) ;
}
} ;
} else {
throw new Error ( ` Unrecognized rule type for rule ${ util . inspect ( rule ) } ` ) ; /* FIXME? */
}
let compositeFunction ;
if ( rule . _constraints . length === 0 ) {
compositeFunction = function baseRuleWrapper ( value ) {
if ( value === undefined && rule . _allowUndefined === true ) {
return true ;
} else if ( value == null ) {
return new errors . ValidationError ( ` Value is required for property ' ${ name } ' ` , {
property : name
} ) ;
} else {
return baseRule . call ( this , value ) ;
}
} ;
} else {
let hasDefaultValue = false , defaultValue ;
let filteredConstraints = rule . _constraints . filter ( ( constraint ) => {
if ( constraint . type === "default" ) {
hasDefaultValue = true ;
defaultValue = constraint . value ;
return false ;
} else {
return true ;
}
} ) ;
let rules = [ baseRule ] . concat ( filteredConstraints . map ( ( constraint ) => {
if ( constraint . type === "validate" ) {
return constraint . validator ;
} else {
throw new Error ( ` Encountered unrecognized constraint type: ${ constraint . type } ` ) ;
}
} ) ) ;
compositeFunction = function complexRuleWrapper ( value ) {
if ( value == null ) {
if ( hasDefaultValue ) {
return {
_default : defaultValue
} ;
} else if ( value === undefined && rule . _allowUndefined === true ) {
return true ;
} else {
return new errors . ValidationError ( ` Value is required for property ' ${ name } ' ` , {
property : name
} ) ;
}
} else {
/* TODO: Possibly special-case (for better performance) if the only extra rule is a 'default value' rule? This would avoid a `for` loop in the case where a value is explicitly specified. */
for ( let rule of rules ) {
let result = rule . call ( this , value ) ;
if ( result === true ) {
continue ;
} else {
return result ;
}
}
return true ;
}
} ;
}
compositeFunction . _rule = rule ;
return compositeFunction ;
} ;