All files validation.js

91.21% Statements 83/91
95% Branches 19/20
100% Functions 14/14
91.21% Lines 83/91

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250    1x 1x 1x 1x 1x 1x 1x   1x                 3171x 3171x       3171x 3171x 436x   2735x 2735x 12x   2735x 2735x           2149x 2149x       2149x               2149x 3121x 3121x   2149x       2149x 2149x                         1146x 1146x   1146x   1146x                                                   31x 31x   31x   31x                       13670x 7328x   6342x   6342x 1148x   5194x   6342x 13446x         224x 224x 224x                       112x   112x 112x 3171x 3171x 3171x   3171x 2735x       112x   112x 112x                       112x 112x 112x 112x   112x 3171x   3171x 3171x                       3171x 3171x 436x   2735x   1589x   1146x 1146x         1146x 1146x               112x 112x   112x     6x     6x 2x   4x       112x     1x            
'use strict';
 
const { execute } = require( './execute.js' );
const { Invariants } = require( './Invariants.js' );
const { ZWrapper } = require( './ZWrapper' );
const { containsError, createSchema, createZObjectKey, quoteZObject } = require( './utils.js' );
const { error, normalError } = require( '../function-schemata/javascript/src/error.js' );
const { validatesAsFunctionCall } = require( '../function-schemata/javascript/src/schema.js' );
const { convertZListToItemArray, isString, makeMappedResultEnvelope, getError } = require( '../function-schemata/javascript/src/utils.js' );
 
const validators = new Map();
 
/**
 * Returns a validator schema for the given ZID.
 *
 * @param {Object} Z1 the type ZObject
 * @return {Schema} a fully-initialized Schema or null if unsupported.
 */
async function getSchemaValidator( Z1 ) {
	Z1 = Z1.asJSON();
	const result = {
		typeKey: null,
		schemaValidator: null
	};
	result.typeKey = await createZObjectKey( Z1.Z1K1 );
	if ( result.typeKey.type() === 'GenericTypeKey' ) {
		return result;
	}
	const keyString = result.typeKey.asString();
	if ( !validators.has( keyString ) ) {
		validators.set( keyString, await createSchema( Z1 ) );
	}
	result.schemaValidator = validators.get( keyString );
	return result;
}
 
function createValidatorZ7( Z8, ...Z1s ) {
	// throw new Error('nope');
	// Z8 = Z8.asJSON();
	const argumentDeclarations = convertZListToItemArray( Z8.Z8K1 );
	if ( argumentDeclarations.length !== Z1s.length ) {
		// TODO (T2926668): Call BUILTIN_FUNCTION_CALL_VALIDATOR_ on result to
		// avoid argument mismatches.
	}
	const result = {
		Z1K1: {
			Z1K1: 'Z9',
			Z9K1: 'Z7'
		},
		Z7K1: Z8
	};
	// TBD: Possibly arrange to convert to ZWrapper here instead of below
	for ( const argument of argumentDeclarations ) {
		const nextZ1 = ZWrapper.create( Z1s.shift().asJSON() );
		result[ argument.Z17K2.Z6K1 ] = nextZ1;
	}
	return ZWrapper.create( result );
}
 
async function runValidationFunction( Z8, invariants, scope, ...Z1s ) {
	const validatorZ7 = createValidatorZ7( Z8, ...Z1s );
	return await execute( validatorZ7, invariants, scope, /* doValidate= */ false );
}
 
/**
 * Validates the Z1/Object against its type validator and returns an array of Z5/Error.
 *
 * @param {Object} Z1 the Z1/Object
 * @param {Object} Z4 the type ZObject
 * @param {Invariants} invariants evaluator, resolver: invariants preserved over all function calls
 * @param {Scope} scope current variable bindings
 * @return {Array} an array of Z5/Error
 */
async function runTypeValidator( Z1, Z4, invariants, scope ) {
	await ( Z4.resolveKey( [ 'Z4K3' ], invariants, scope, /* ignoreList= */ null, /* resolveInternals= */ false ) );
	const validationFunction = Z4.Z4K3;
 
	try {
		// TODO (T296681): Catch errors when async functions reject.
		return await runValidationFunction(
			validationFunction, invariants, scope, quoteZObject( Z1 ),
			quoteZObject( Z4 ) );
	} catch ( err ) {
		console.error( err );
		return ZWrapper.create( makeMappedResultEnvelope( null, normalError(
			[ error.zid_not_found ],
			[ `Builtin validator "${validationFunction.Z8K5.Z9K1}" not found for "${Z4.Z4K1.Z9K1}"` ]
		) ) );
	}
}
 
/**
 * Dynamically validates the Z1/Object against its type validator and returns
 * an array of Z5/Error.
 *
 * TODO (T302750): Find a better way to handle this than two separate "runTypeValidator"
 * functions.
 *
 * @param {Object} Z1 the Z1/Object
 * @param {Object} Z4 the type ZObject
 * @param {Invariants} invariants evaluator, resolver: invariants preserved over all function calls
 * @param {Scope} scope current variable bindings
 * @return {Array} an array of Z5/Error
 */
async function runTypeValidatorDynamic( Z1, Z4, invariants, scope ) {
	await ( Z4.resolveKey( [ 'Z4K3' ], invariants, scope, /* ignoreList= */ null, /* resolveInternals= */ false ) );
	const validationFunction = Z4.Z4K3;
 
	try {
		// TODO (T296681): Catch errors when async functions reject.
		return await runValidationFunction(
			validationFunction, invariants, scope, Z1, Z4 );
	} catch ( err ) {
		console.error( err );
		return ZWrapper.create( makeMappedResultEnvelope( null, normalError(
			[ error.zid_not_found ],
			[ `Builtin validator "${validationFunction.Z8K5.Z9K1}" not found for "${Z4.Z4K1.Z9K1}"` ]
		) ) );
	}
}
 
async function traverseInternal( ZObject, callback, promises ) {
	if ( isString( ZObject ) ) {
		return;
	}
	promises.push( callback( ZObject ) );
	let keys;
	if ( ( await validatesAsFunctionCall( ZObject.asJSON() ) ).isValid() ) {
		keys = [ 'Z1K1', 'Z7K1' ];
	} else {
		keys = ZObject.keys();
	}
	for ( const key of keys ) {
		await traverseInternal( ZObject[ key ], callback, promises );
	}
}
 
async function traverseOmittingFunctionCallInputs( ZObject, callback ) {
	const promises = [];
	await traverseInternal( ZObject, callback, promises );
	await Promise.all( promises );
}
 
/**
 * Utility function that traverses the given ZObject to identify all of the types contained in it
 * and return their ZObjects. The ZObjects are fetched from the database.
 *
 * @param {Object} zobject the zobject in normal.
 * @param {Invariants} invariants evaluator, resolver: invariants preserved over all function calls
 * @return {Object} an object mapping the ZID to the ZObject of the type.
 */
async function getContainedTypeZObjects( zobject, invariants ) {
	const containedTypes = new Set();
 
	const promises = [];
	await traverseOmittingFunctionCallInputs( zobject, function ( Z1 ) {
		promises.push( ( async function () {
			const typeKey = await createZObjectKey( Z1.Z1K1 );
			const key = typeKey.asString();
			// TODO (T297717): We should add other types to the set, not just builtins.
			if ( typeKey.type() === 'SimpleTypeKey' ) {
				containedTypes.add( key );
			}
		} )() );
	} );
	await Promise.all( promises );
 
	const result = await invariants.resolver.dereference( containedTypes );
	return result;
}
 
/**
 * Traverses the given zobject and validates each node checking its schema and running its type
 * validator.
 *
 * @param {Object} zobject the zobject in normal form.
 * @param {Invariants} invariants evaluator, resolver: invariants preserved over all function calls
 * @return {Array} an array of validation errors.
 */
async function validate( zobject, invariants ) {
	const errors = [];
	const ZObjectTypes = await getContainedTypeZObjects( zobject, invariants );
	const traversalPromises = [];
	const typeValidatorPromises = [];
 
	await traverseOmittingFunctionCallInputs( zobject, ( Z1 ) => {
		traversalPromises.push( ( async function () {
			let validatorTuple;
			try {
				validatorTuple = await getSchemaValidator( Z1 );
			} catch ( error ) {
				console.error( 'Attempting to validate Z1', Z1, 'produced error', error );
				errors.push(
					ZWrapper.create( normalError(
						[ error.zid_not_found ],
						[ error.message ] ) ) );
				return;
			}
			const {
				typeKey,
				schemaValidator
			} = validatorTuple;
			if ( schemaValidator === null ) {
				return;
			}
			if ( ZObjectTypes[ typeKey.asString() ] === undefined ) {
				// TODO (T297717): We should add other types to the set, not just builtins.
				return;
			}
			const theStatus = await schemaValidator.validateStatus( Z1.asJSON() );
			Iif ( !theStatus.isValid() ) {
				errors.push( ZWrapper.create( theStatus.getZ5() ) );
			} else {
				// TODO (T307244): Use ignoreList instead of setting evaluator
				// to null.
				const noEvaluator = new Invariants( null, invariants.resolver );
				typeValidatorPromises.push(
					runTypeValidator(
						Z1, ZObjectTypes[ typeKey.asString() ].Z2K2, noEvaluator, /* scope= */null )
				);
			}
		} )() );
	} );
 
	await Promise.all( traversalPromises );
	const typeValidatorResults = await Promise.all( typeValidatorPromises );
 
	typeValidatorResults
		.filter( containsError )
		.forEach( ( result ) => {
			const error = getError( result );
 
			// if this is a Z509/Multiple errors it will be flattened
			if ( error.Z5K1.Z9K1 === 'Z509' ) {
				errors.push.apply( errors, convertZListToItemArray( error.Z5K2.Z509K1 ) );
			} else {
				errors.push( error );
			}
		} );
 
	return errors;
}
 
module.exports = {
	runTypeValidator,
	runTypeValidatorDynamic,
	runValidationFunction,
	validate
};