UNPKG

@deepkit/bson

Version:
1,023 lines (876 loc) 42.3 kB
import { binaryBigIntAnnotation, BinaryBigIntType, buildFunction, callExtractedFunctionIfAvailable, collapsePath, ContainerAccessor, createTypeGuardFunction, embeddedAnnotation, EmbeddedOptions, excludedAnnotation, executeTemplates, extendTemplateLiteral, extractStateToFunctionAndCallIt, getIndexCheck, getNameExpression, getStaticDefaultCodeForProperty, hasDefaultValue, isNullable, isOptional, mongoIdAnnotation, ReflectionClass, ReflectionKind, resolveTypeMembers, RuntimeCode, sortSignatures, TemplateState, Type, TypeClass, TypeGuardRegistry, TypeIndexSignature, TypeLiteral, TypeObjectLiteral, TypeProperty, TypePropertySignature, TypeTemplateLiteral, TypeTuple, TypeUnion, uuidAnnotation } from '@deepkit/type'; import { seekElementSize } from './continuation'; import { BSONType, digitByteSize, isSerializable } from './utils'; function getNameComparator(name: string): string { //todo: support utf8 names const bufferCompare: string[] = []; for (let i = 0; i < name.length; i++) { bufferCompare.push(`state.parser.buffer[state.parser.offset + ${i}] === ${name.charCodeAt(i)}`); } bufferCompare.push(`state.parser.buffer[state.parser.offset + ${name.length}] === 0`); return bufferCompare.join(' && '); } function throwInvalidBsonType(type: Type, state: TemplateState) { state.setContext({ BSONType }); return state.throwCode(type, JSON.stringify('invalid BSON type'), `'bson type ' + BSONType[state.elementType]`); } export function deserializeBinary(type: Type, state: TemplateState) { const typeVar = state.setVariable('type', type); state.addCode(` if (state.elementType === ${BSONType.BINARY}) { ${state.setter} = state.parser.parseBinary(${typeVar}); } else { ${throwInvalidBsonType(type, state)} } `); } export function deserializeAny(type: Type, state: TemplateState) { state.addCode(` ${state.setter} = state.parser.parse(state.elementType); `); } export function deserializeNumber(type: Type, state: TemplateState) { const readBigInt = type.kind === ReflectionKind.bigint ? `state.parser.parseBinaryBigInt()` : `Number(state.parser.parseBinaryBigInt())`; state.addCode(` if (state.elementType === ${BSONType.INT}) { ${state.setter} = state.parser.parseInt(); } else if (state.elementType === ${BSONType.NULL} || state.elementType === ${BSONType.UNDEFINED}) { ${state.setter} = 0; } else if (state.elementType === ${BSONType.NUMBER}) { ${state.setter} = state.parser.parseNumber(); } else if (state.elementType === ${BSONType.LONG} || state.elementType === ${BSONType.TIMESTAMP}) { ${state.setter} = state.parser.parseLong(); } else if (state.elementType === ${BSONType.BOOLEAN}) { ${state.setter} = state.parser.parseBoolean() ? 1 : 0; } else if (state.elementType === ${BSONType.BINARY}) { ${state.setter} = ${readBigInt}; } else if (state.elementType === ${BSONType.STRING}) { ${state.setter} = Number(state.parser.parseString()); if (isNaN(${state.setter})) { ${throwInvalidBsonType(type, state)} } } else { ${throwInvalidBsonType(type, state)} } `); } export function deserializeBigInt(type: Type, state: TemplateState) { const binaryBigInt = binaryBigIntAnnotation.getFirst(type); const parseBigInt = binaryBigInt === BinaryBigIntType.signed ? 'parseSignedBinaryBigInt' : 'parseBinaryBigInt'; state.addCode(` if (state.elementType === ${BSONType.INT}) { ${state.setter} = BigInt(state.parser.parseInt()); } else if (state.elementType === ${BSONType.NULL} || state.elementType === ${BSONType.UNDEFINED}) { ${state.setter} = 0n; } else if (state.elementType === ${BSONType.NUMBER}) { ${state.setter} = BigInt(state.parser.parseNumber()); } else if (state.elementType === ${BSONType.LONG} || state.elementType === ${BSONType.TIMESTAMP}) { ${state.setter} = BigInt(state.parser.parseLong()); } else if (state.elementType === ${BSONType.BOOLEAN}) { ${state.setter} = BigInt(state.parser.parseBoolean() ? 1 : 0); } else if (state.elementType === ${BSONType.BINARY} && ${binaryBigInt} !== undefined) { ${state.setter} = state.parser.${parseBigInt}(); } else if (state.elementType === ${BSONType.STRING}) { ${state.setter} = BigInt(state.parser.parseString()); } else { ${throwInvalidBsonType(type, state)} } `); } export function deserializeString(type: Type, state: TemplateState) { const branches: string[] = []; if (uuidAnnotation.getFirst(type)) { branches.push(` } else if (state.elementType === ${BSONType.BINARY}) { ${state.setter} = state.parser.parseBinary(); `); } else if (mongoIdAnnotation.getFirst(type)) { branches.push(` } else if (state.elementType === ${BSONType.OID}) { ${state.setter} = state.parser.parseOid(); `); } state.addCode(` if (state.elementType === ${BSONType.STRING}) { ${state.setter} = state.parser.parseString(); } else if (state.elementType === ${BSONType.NULL} || state.elementType === ${BSONType.UNDEFINED}) { ${state.setter} = ''; } else if (state.elementType === ${BSONType.INT}) { ${state.setter} = '' + state.parser.parseInt(); } else if (state.elementType === ${BSONType.BOOLEAN}) { ${state.setter} = '' + state.parser.parseBoolean(); } else if (state.elementType === ${BSONType.NUMBER}) { ${state.setter} = '' + state.parser.parseNumber(); } else if (state.elementType === ${BSONType.LONG} || state.elementType === ${BSONType.TIMESTAMP}) { ${state.setter} = '' + state.parser.parseLong(); ${branches.join('\n')} } else { ${throwInvalidBsonType(type, state)} } ${executeTemplates(state.fork(state.setter, state.setter).forRegistry(state.registry.serializer.deserializeRegistry), type)} `); } export function deserializeLiteral(type: TypeLiteral, state: TemplateState) { state.addCode(` ${state.setter} = ${state.setVariable('literal', type.literal)}; seekElementSize(elementType, state.parser); `); } export function deserializeTemplateLiteral(type: TypeTemplateLiteral, state: TemplateState) { state.setContext({ extendTemplateLiteral: extendTemplateLiteral }); const typeVar = state.setVariable('type', type); state.addCode(` if (state.elementType === ${BSONType.STRING}) { ${state.setter} = state.parser.parseString(); if (!extendTemplateLiteral({kind: ${ReflectionKind.literal}, literal: ${state.setter}}, ${typeVar})) { ${state.throwCode(type, '', state.setter)} } } else { ${throwInvalidBsonType(type, state)} } `); } export function deserializeNull(type: Type, state: TemplateState) { state.addCode(` if (state.elementType === ${BSONType.NULL} || state.elementType === ${BSONType.UNDEFINED}) { ${state.setter} = null; } else { ${throwInvalidBsonType(type, state)} } `); } export function deserializeUndefined(type: Type, state: TemplateState) { state.addCode(` if (state.elementType === ${BSONType.NULL} || state.elementType === ${BSONType.UNDEFINED}) { ${state.setter} = undefined; } else { ${throwInvalidBsonType(type, state)} } `); } export function deserializeBoolean(type: Type, state: TemplateState) { state.addCode(` if (state.elementType === ${BSONType.BOOLEAN}) { ${state.setter} = state.parser.parseBoolean(); } else if (state.elementType === ${BSONType.NULL} || state.elementType === ${BSONType.UNDEFINED}) { ${state.setter} = false; } else if (state.elementType === ${BSONType.INT}) { ${state.setter} = state.parser.parseInt() ? true : false; } else if (state.elementType === ${BSONType.NUMBER}) { ${state.setter} = state.parser.parseNumber() ? true : false; } else if (state.elementType === ${BSONType.LONG} || state.elementType === ${BSONType.TIMESTAMP}) { ${state.setter} = state.parser.parseLong() ? true : false; } else { ${throwInvalidBsonType(type, state)} } `); } export function deserializeDate(type: Type, state: TemplateState) { state.addCode(` if (state.elementType === ${BSONType.DATE}) { ${state.setter} = state.parser.parseDate(); } else if (state.elementType === ${BSONType.INT}) { ${state.setter} = new Date(state.parser.parseInt()); } else if (state.elementType === ${BSONType.NUMBER}) { ${state.setter} = new Date(state.parser.parseNumber()); } else if (state.elementType === ${BSONType.LONG} || state.elementType === ${BSONType.TIMESTAMP}) { ${state.setter} = new Date(state.parser.parseLong()); } else if (state.elementType === ${BSONType.STRING}) { ${state.setter} = new Date(state.parser.parseString()); } else { ${throwInvalidBsonType(type, state)} } `); } export function deserializeRegExp(type: Type, state: TemplateState) { state.addCode(` if (state.elementType === ${BSONType.REGEXP}) { ${state.setter} = state.parser.parseRegExp(); } else { ${throwInvalidBsonType(type, state)} } `); } export function deserializeUnion(bsonTypeGuards: TypeGuardRegistry, type: TypeUnion, state: TemplateState) { const lines: string[] = []; //see handleUnion from deepkit/type for more information const typeGuards = bsonTypeGuards.getSortedTemplateRegistries(); for (const [specificality, typeGuard] of typeGuards) { for (const t of type.types) { const fn = createTypeGuardFunction(t, state.fork().forRegistry(typeGuard)); if (!fn) continue; const guard = state.setVariable('guard' + t.kind, fn); const looseCheck = specificality <= 0 ? `state.loosely && ` : ''; lines.push(`else if (${looseCheck}${guard}(${state.accessor || 'undefined'}, state, ${collapsePath(state.path)})) { //type = ${t.kind}, specificality=${specificality} ${executeTemplates(state.fork(state.setter, state.accessor).forPropertyName(state.propertyName), t)} }`); } } state.addCodeForSetter(` { if (!state.elementType) state.elementType = ${BSONType.OBJECT}; const oldElementType = state.elementType; if (false) { } ${lines.join(' ')} else { ${throwInvalidBsonType(type, state)} } state.elementType = oldElementType; } `); } export function bsonTypeGuardUnion(bsonTypeGuards: TypeGuardRegistry, type: TypeUnion, state: TemplateState) { const lines: string[] = []; //see serializeTypeUnion from deepkit/type for more information const typeGuards = bsonTypeGuards.getSortedTemplateRegistries(); for (const [specificality, typeGuard] of typeGuards) { for (const t of type.types) { const fn = createTypeGuardFunction(t, state.fork().forRegistry(typeGuard)); if (!fn) continue; const guard = state.setVariable('guard' + t.kind, fn); const looseCheck = specificality <= 0 ? `state.loosely && ` : ''; lines.push(`else if (${looseCheck}${guard}(${state.accessor || 'undefined'}, state)) { //type = ${t.kind}, specificality=${specificality} ${state.setter} = true; }`); } } state.addCodeForSetter(` { if (!state.elementType) state.elementType = ${BSONType.OBJECT}; const oldElementType = state.elementType; if (false) { } ${lines.join(' ')} state.elementType = oldElementType; } `); } export function deserializeTuple(type: TypeTuple, state: TemplateState) { const result = state.compilerContext.reserveName('result'); const v = state.compilerContext.reserveName('v'); const i = state.compilerContext.reserveName('i'); const length = state.compilerContext.reserveName('length'); state.setContext({ digitByteSize }); const lines: string[] = []; let restEndOffset = 0; let restStart = 0; let hasRest = false; //when there are types behind a rest (e.g. [string, ...number[], boolean]), then we have to iterate overall items first to determine when to use the boolean code. let hasTypedBehindRest = false; for (let i = 0; i < type.types.length; i++) { if (type.types[i].type.kind === ReflectionKind.rest) { hasRest = true; restStart = i; restEndOffset = type.types.length - (i + 1); } else if (hasRest) { hasTypedBehindRest = true; } } let j = 0; for (const member of type.types) { if (member.type.kind === ReflectionKind.rest) { const check = hasTypedBehindRest ? `${i} >= ${j} && ${i} < ${length} - ${restEndOffset}` : `${i} >= ${j}`; lines.push(` //tuple rest item ${j} else if (${check}) { ${executeTemplates(state.fork(v).extendPath(member.name || new RuntimeCode(i)), member.type.type)} if (${v} !== undefined || ${isOptional(member)}) { ${result}.push(${v}); } ${i}++; continue; } `); } else { const offsetRight = type.types.length - j; const check = hasRest && j >= restStart ? `${length} - ${i} === ${offsetRight}` : `${i} === ${j}`; lines.push(` //tuple item ${j} else if (${check}) { ${executeTemplates(state.fork(v).extendPath(member.name || new RuntimeCode(i)), member.type)} if (${v} !== undefined || ${isOptional(member)}) { ${result}.push(${v}); } ${i}++; continue; } `); } j++; } let readLength = ''; if (hasTypedBehindRest) { readLength = ` const lengthStart = state.parser.offset; while (state.parser.offset < end) { const elementType = state.elementType = state.parser.eatByte(); if (elementType === 0) break; //arrays are represented as objects, so we skip the key name state.parser.seek(digitByteSize(${length})); seekElementSize(elementType, state.parser); ${length}++; } state.parser.offset = lengthStart; `; } state.addCode(` if (state.elementType && state.elementType !== ${BSONType.ARRAY}) ${throwInvalidBsonType({ kind: ReflectionKind.array, type: type }, state)} { var ${result} = []; let ${length} = 0; const end = state.parser.eatUInt32() + state.parser.offset; const oldElementType = state.elementType; ${readLength} let ${i} = 0; while (state.parser.offset < end) { const elementType = state.elementType = state.parser.eatByte(); if (elementType === 0) break; //arrays are represented as objects, so we skip the key name state.parser.seek(digitByteSize(${i})); let ${v}; if (false) {} ${lines.join('\n')} ${i}++; //if no type match, seek seekElementSize(elementType, state.parser); } ${state.setter} = ${result}; state.elementType = oldElementType; } `); } export function bsonTypeGuardTuple(type: TypeTuple, state: TemplateState) { const valid = state.compilerContext.reserveName('valid'); const i = state.compilerContext.reserveName('i'); const length = state.compilerContext.reserveName('length'); state.setContext({ digitByteSize, seekElementSize }); const lines: string[] = []; let restEndOffset = 0; let restStart = 0; let hasRest = false; //when there are types behind a rest (e.g. [string, ...number[], boolean]), then we have to iterate overall items first to determine when to use the boolean code. let hasTypedBehindRest = false; for (let i = 0; i < type.types.length; i++) { if (type.types[i].type.kind === ReflectionKind.rest) { hasRest = true; restStart = i; restEndOffset = type.types.length - (i + 1); } else if (hasRest) { hasTypedBehindRest = true; } } let j = 0; for (const member of type.types) { if (member.type.kind === ReflectionKind.rest) { const check = hasTypedBehindRest ? `${i} >= ${j} && ${i} < ${length} - ${restEndOffset}` : `${i} >= ${j}`; lines.push(` //tuple rest item ${j} else if (${check}) { ${executeTemplates(state.fork(valid).extendPath(new RuntimeCode(i)), member.type.type)} if (!${valid}) { break; } ${i}++; seekElementSize(elementType, state.parser); continue; } `); } else { const offsetRight = type.types.length - j; const check = hasRest && j >= restStart ? `${length} - ${i} === ${offsetRight}` : `${i} === ${j}`; lines.push(` //tuple item ${j} else if (${check}) { ${executeTemplates(state.fork(valid).extendPath(new RuntimeCode(i)), member.type)} if (!${valid}) { break; } ${i}++; seekElementSize(elementType, state.parser); continue; } `); } j++; } let readLength = ''; if (hasTypedBehindRest) { readLength = ` const lengthStart = state.parser.offset; while (state.parser.offset < end) { const elementType = state.elementType = state.parser.eatByte(); if (elementType === 0) break; //arrays are represented as objects, so we skip the key name state.parser.seek(digitByteSize(${length})); seekElementSize(elementType, state.parser); ${length}++; } state.parser.offset = lengthStart; `; } state.addCode(` let ${valid} = state.elementType && state.elementType === ${BSONType.ARRAY}; if (${valid}){ let ${length} = 0; const start = state.parser.offset; const end = state.parser.eatUInt32() + state.parser.offset; const oldElementType = state.elementType; ${readLength} let ${i} = 0; while (state.parser.offset < end) { const elementType = state.elementType = state.parser.eatByte(); if (elementType === 0) break; //arrays are represented as objects, so we skip the key name state.parser.seek(digitByteSize(${i})); if (false) {} ${lines.join('\n')} //if no type match, seek ${i}++; seekElementSize(elementType, state.parser); } state.elementType = oldElementType; state.parser.offset = start; } ${state.setter} = ${valid}; `); } export function deserializeArray(elementType: Type, state: TemplateState) { const result = state.compilerContext.reserveName('result'); const v = state.compilerContext.reserveName('v'); const i = state.compilerContext.reserveName('i'); state.setContext({ digitByteSize }); state.addCode(` if (state.elementType && state.elementType !== ${BSONType.ARRAY}) ${throwInvalidBsonType({ kind: ReflectionKind.array, type: elementType }, state)} { var ${result} = []; const end = state.parser.eatUInt32() + state.parser.offset; const oldElementType = state.elementType; let ${i} = 0; while (state.parser.offset < end) { const elementType = state.elementType = state.parser.eatByte(); if (elementType === 0) break; //arrays are represented as objects, so we skip the key name state.parser.seek(digitByteSize(${i})); let ${v}; ${executeTemplates(state.fork(v, '').extendPath(new RuntimeCode(i)), elementType)} ${result}.push(${v}); ${i}++; } ${state.setter} = ${result}; state.elementType = oldElementType; } `); } /** * This array type guard goes through all array elements in order to determine the correct type. * This is only necessary when a union has at least 2 array members, otherwise a simple array check is enough. */ export function bsonTypeGuardArray(elementType: Type, state: TemplateState) { const v = state.compilerContext.reserveName('v'); const i = state.compilerContext.reserveName('i'); state.setContext({ digitByteSize, seekElementSize }); const typeGuardCode = executeTemplates(state.fork(v, '').extendPath(new RuntimeCode(i)), elementType); state.addCode(` ${state.setter} = state.elementType && state.elementType === ${BSONType.ARRAY}; if (${state.setter}) { const start = state.parser.offset; const oldElementType = state.elementType; const end = state.parser.eatUInt32() + state.parser.offset; let ${i} = 0; while (state.parser.offset < end) { const elementType = state.elementType = state.parser.eatByte(); if (elementType === 0) break; //arrays are represented as objects, so we skip the key name state.parser.seek(digitByteSize(${i})); let ${v}; ${typeGuardCode} if (!${v}) { ${state.setter} = false; break; } //since type guards don't eat, we seek automatically forward seekElementSize(elementType, state.parser); ${i}++; } state.parser.offset = start; state.elementType = oldElementType; } `); } export function getEmbeddedClassesForProperty(type: Type): { type: TypeClass, options: EmbeddedOptions }[] { if (type.kind === ReflectionKind.propertySignature || type.kind === ReflectionKind.property) type = type.type; const res: { type: TypeClass, options: EmbeddedOptions }[] = []; if (type.kind === ReflectionKind.union) { for (const t of type.types) { if (t.kind === ReflectionKind.class) { const embedded = embeddedAnnotation.getFirst(t); if (embedded) res.push({ options: embedded, type: t }); } } } else if (type.kind === ReflectionKind.class) { const embedded = embeddedAnnotation.getFirst(type); if (embedded) res.push({ options: embedded, type: type }); } return res; } export function deserializeObjectLiteral(type: TypeClass | TypeObjectLiteral, state: TemplateState) { //emdedded for the moment disabled. treat it as normal property. // const embedded = embeddedAnnotation.getFirst(type); // if (type.kind === ReflectionKind.class && embedded) { // const container = state.compilerContext.reserveName('container'); // // const embedded = deserializeEmbedded(type, state.fork(undefined, container).forRegistry(state.registry.serializer.deserializeRegistry)); // if (embedded) { // state.addCode(` // const ${container} = state.parser.parse(state.elementType); // console.log('container data', '${state.setter}', ${container}); // ${embedded} // `); // return; // } // } if (callExtractedFunctionIfAvailable(state, type)) return; const extract = extractStateToFunctionAndCallIt(state, type); state = extract.state; const lines: string[] = []; const signatures: TypeIndexSignature[] = []; const object = state.compilerContext.reserveName('object'); const resetDefaultSets: string[] = []; //run code for properties that had no value in the BSON. either set static values (literal, or null), or throw an error. const setDefaults: string[] = []; interface HandleEmbedded { type: TypeClass; valueSetVar: string; property: TypeProperty | TypePropertySignature; containerVar: string; } const handleEmbeddedClasses: HandleEmbedded[] = []; for (const member of resolveTypeMembers(type)) { if (member.kind === ReflectionKind.indexSignature) { if (excludedAnnotation.isExcluded(member.type, state.registry.serializer.name)) continue; signatures.push(member); } if (member.kind !== ReflectionKind.property && member.kind !== ReflectionKind.propertySignature) continue; if (!isSerializable(member.type)) continue; if (excludedAnnotation.isExcluded(member.type, state.registry.serializer.name)) continue; const nameInBson = String(state.namingStrategy.getPropertyName(member, state.registry.serializer.name)); const valueSetVar = state.compilerContext.reserveName('valueSetVar'); // //since Embedded<T> can have arbitrary prefix, we have to collect all fields first, and then after the loop, build everything together. // const embeddedClasses = getEmbeddedClassesForProperty(member); // //todo: // // 1. Embedded in a union need for each entry in the union (there can be multiple Embedded in one union) to collect all possible values. We collect them into own container // // then run the typeGuardObjectLiteral on it at the end of the loop, if the member was not already set. this works the same for non-union members as well, right? // // 2. we have to delay detecting the union, right? Otherwise `Embedded<T, {prefix: 'p'}> | string` will throw an error that it can't convert undefined to string, when // // ${member.name} is not provided. // // 3. we could also collect all values in a loop earlier? // if (embeddedClasses.length) { // for (const embedded of embeddedClasses) { // const constructorProperties = getConstructorProperties(embedded.type); // if (!constructorProperties.properties.length) throw new BSONError(`Can not embed class ${getClassName(embedded.type.classType)} since it has no constructor properties`); // // const containerVar = state.compilerContext.reserveName('container'); // const handleEmbedded: HandleEmbedded = { // type: embedded.type, containerVar, property: member, valueSetVar // }; // handleEmbeddedClasses.push(handleEmbedded); // // for (const property of constructorProperties.properties) { // const setter = getEmbeddedPropertyName(state.namingStrategy, property, embedded.options); // const accessor = getEmbeddedAccessor(embedded.type, constructorProperties.properties.length !== 1, nameInBson, state.namingStrategy, property, embedded.options); // //todo: handle explicit undefined and non-existing // lines.push(` // if (${getNameComparator(accessor)}) { // state.parser.offset += ${accessor.length} + 1; // ${executeTemplates(state.fork(new ContainerAccessor(containerVar, JSON.stringify(setter)), ''), property.type)}; // continue; // } // `); // } // } // } resetDefaultSets.push(`var ${valueSetVar} = false;`); const setter = new ContainerAccessor(object, JSON.stringify(member.name)); const staticDefault = getStaticDefaultCodeForProperty(member, setter, state); let throwInvalidTypeError = ''; if (!isOptional(member) && !hasDefaultValue(member)) { throwInvalidTypeError = state.fork().extendPath(member.name).throwCode(member.type as Type, '', `'undefined value'`); } setDefaults.push(`if (!${valueSetVar}) { ${staticDefault || throwInvalidTypeError} } `); let seekOnExplicitUndefined = ''; //handle explicitly set `undefined`, by jumping over the registered deserializers. if `null` is given and the property has no null type, we treat it as undefined. if (isOptional(member) || hasDefaultValue(member)) { const setUndefined = isOptional(member) ? `${setter} = undefined;` : hasDefaultValue(member) ? `` : `${setter} = undefined;`; const check = isNullable(member) ? `elementType === ${BSONType.UNDEFINED}` : `elementType === ${BSONType.UNDEFINED} || elementType === ${BSONType.NULL}`; seekOnExplicitUndefined = ` if (${check}) { ${setUndefined} seekElementSize(elementType, state.parser); continue; }`; } const setterTemplate = executeTemplates(state.fork(setter, '').extendPath(member.name), member.type); lines.push(` //property ${String(member.name)} (${member.type.kind}) else if (!${valueSetVar} && ${getNameComparator(nameInBson)}) { state.parser.offset += ${nameInBson.length} + 1; ${valueSetVar} = true; ${seekOnExplicitUndefined} ${setterTemplate} continue; } `); } if (signatures.length) { const i = state.compilerContext.reserveName('i'); const signatureLines: string[] = []; sortSignatures(signatures); for (const signature of signatures) { const check = isOptional(signature.type) ? `` : `elementType !== ${BSONType.UNDEFINED} &&`; signatureLines.push(`else if (${check} ${getIndexCheck(state, i, signature.index)}) { ${executeTemplates(state.fork(`${object}[${i}]`).extendPath(new RuntimeCode(i)).forPropertyName(new RuntimeCode(i)), signature.type)} continue; }`); } //the index signature type could be: string, number, symbol. //or a literal when it was constructed by a mapped type. lines.push(`else { let ${i} = state.parser.eatObjectPropertyName(); if (false) {} ${signatureLines.join(' ')} //if no index signature matches, we skip over it seekElementSize(elementType, state.parser); continue; }`); } state.setContext({ seekElementSize }); let initializeObject = `{}`; let createClassInstance = ``; if (type.kind === ReflectionKind.class && type.classType !== Object) { const reflection = ReflectionClass.from(type.classType); const constructor = reflection.getConstructorOrUndefined(); if (constructor && constructor.parameters.length) { const constructorArguments: string[] = []; for (const parameter of constructor.getParameters()) { const name = getNameExpression(parameter.getName(), state); constructorArguments.push(parameter.getVisibility() === undefined ? 'undefined' : `${object}[${name}]`); } createClassInstance = ` ${state.setter} = new ${state.compilerContext.reserveConst(type.classType, 'classType')}(${constructorArguments.join(', ')}); Object.assign(${state.setter}, ${object}); `; } else { initializeObject = `new ${state.compilerContext.reserveConst(type.classType, 'classType')}()`; } } const handleEmbeddedClassesLines: string[] = []; // for (const handle of handleEmbeddedClasses) { // // const constructorProperties = getConstructorProperties(handle.type); // const setter = new ContainerAccessor(object, JSON.stringify(handle.property.name)); // handleEmbeddedClassesLines.push(` // ${deserializeEmbedded(handle.type, state.fork(setter, handle.containerVar).forRegistry(state.registry.serializer.deserializeRegistry), handle.containerVar)} // if (${inAccessor(setter)}) { // ${handle.valueSetVar} = true; // } // `); // } state.addCode(` if (state.elementType && state.elementType !== ${BSONType.OBJECT}) ${throwInvalidBsonType(type, state)} var ${object} = ${initializeObject}; ${handleEmbeddedClasses.map(v => `const ${v.containerVar} = {};`).join('\n')} { const end = state.parser.eatUInt32() + state.parser.offset; ${resetDefaultSets.join('\n')} const oldElementType = state.elementType; while (state.parser.offset < end) { const elementType = state.elementType = state.parser.eatByte(); if (elementType === 0) break; if (false) {} ${lines.join('\n')} //jump over this property when not registered in schema while (state.parser.offset < end && state.parser.buffer[state.parser.offset++] != 0); //seek property value if (state.parser.offset >= end) break; seekElementSize(elementType, state.parser); } ${handleEmbeddedClassesLines.join('\n')} ${setDefaults.join('\n')} ${state.setter} = ${object}; ${createClassInstance} state.elementType = oldElementType; } `); extract.setFunction(buildFunction(state, type)); } export function bsonTypeGuardObjectLiteral(type: TypeClass | TypeObjectLiteral, state: TemplateState) { //emdedded for the moment disabled. treat it as normal property. // const embedded = embeddedAnnotation.getFirst(type); // if (type.kind === ReflectionKind.class && embedded && state.target === 'deserialize') { // const container = state.compilerContext.reserveName('container'); // const sub = state.fork(undefined, container).forRegistry(state.registry.serializer.typeGuards.getRegistry(1)); // typeGuardEmbedded(type, sub, embedded); // state.addCode(` // const ${container} = state.parser.read(state.elementType); // console.log('container data', '${state.setter}', ${container}); // ${sub.template} // `); // return; // } if (callExtractedFunctionIfAvailable(state, type)) return; const extract = extractStateToFunctionAndCallIt(state, type); state = extract.state; const lines: string[] = []; const signatures: TypeIndexSignature[] = []; const valid = state.compilerContext.reserveName('valid'); const resetDefaultSets: string[] = []; //run code for properties that had no value in the BSON. either set static values (literal, or null), or throw an error. const setDefaults: string[] = []; for (const member of resolveTypeMembers(type)) { if (member.kind === ReflectionKind.indexSignature) { if (excludedAnnotation.isExcluded(member.type, state.registry.serializer.name)) continue; signatures.push(member); } if (member.kind !== ReflectionKind.property && member.kind !== ReflectionKind.propertySignature) continue; if (!isSerializable(member.type)) continue; if (excludedAnnotation.isExcluded(member.type, state.registry.serializer.name)) continue; const nameInBson = String(state.namingStrategy.getPropertyName(member, state.registry.serializer.name)); const valueSetVar = state.compilerContext.reserveName('valueSetVar'); resetDefaultSets.push(`var ${valueSetVar} = false;`); let invalidType = ''; if (!isOptional(member) && !hasDefaultValue(member)) { invalidType = `${valid} = false`; } setDefaults.push(`if (!${valueSetVar}) { ${invalidType} } `); let seekOnExplicitUndefined = ''; //handle explicitly set `undefined`, by jumping over the registered deserializers. if `null` is given and the property has no null type, we treat it as undefined. if (isOptional(member) || hasDefaultValue(member)) { const check = isNullable(member) ? `elementType === ${BSONType.UNDEFINED}` : `elementType === ${BSONType.UNDEFINED} || elementType === ${BSONType.NULL}`; seekOnExplicitUndefined = ` if (${check}) { seekElementSize(elementType, state.parser); continue; }`; } const template = executeTemplates(state.fork(valid).extendPath(member.name), member.type); lines.push(` //property ${String(member.name)} (${member.type.kind}) else if (!${valueSetVar} && ${getNameComparator(nameInBson)}) { state.parser.offset += ${nameInBson.length} + 1; ${valueSetVar} = true; ${seekOnExplicitUndefined} ${template} if (!${valid}) break; //guards never eat/parse parser, so we jump over the value automatically seekElementSize(elementType, state.parser); continue; } `); } if (signatures.length) { const i = state.compilerContext.reserveName('i'); const signatureLines: string[] = []; sortSignatures(signatures); for (const signature of signatures) { signatureLines.push(`else if (${getIndexCheck(state, i, signature.index)}) { ${executeTemplates(state.fork(valid).extendPath(new RuntimeCode(i)).forPropertyName(new RuntimeCode(i)), signature.type)} if (!${valid}) break; //guards never eat/parse parser, so we jump over the value automatically seekElementSize(elementType, state.parser); continue; }`); } //the index signature type could be: string, number, symbol. //or a literal when it was constructed by a mapped type. lines.push(`else { let ${i} = state.parser.eatObjectPropertyName(); if (false) {} ${signatureLines.join(' ')} //if no index signature matches, we skip over it seekElementSize(elementType, state.parser); }`); } state.setContext({ seekElementSize }); state.addCode(` ${state.setter} = state.elementType && state.elementType === ${BSONType.OBJECT}; if (${state.setter}) { let ${valid} = true; const start = state.parser.offset; const end = state.parser.eatUInt32() + state.parser.offset; ${resetDefaultSets.join('\n')} const oldElementType = state.elementType; while (state.parser.offset < end) { const elementType = state.elementType = state.parser.eatByte(); if (elementType === 0) break; if (false) {} ${lines.join('\n')} //jump over this property when not registered in schema while (state.parser.offset < end && state.parser.buffer[state.parser.offset++] != 0); //seek property value if (state.parser.offset >= end) break; seekElementSize(elementType, state.parser); } ${setDefaults.join('\n')} state.elementType = oldElementType; state.parser.offset = start; ${state.setter} = ${valid}; } `); extract.setFunction(buildFunction(state, type)); } export function bsonTypeGuardForBsonTypes(types: BSONType[]): (type: Type, state: TemplateState) => void { return (type, state) => { state.addSetter(types.map(v => `state.elementType === ${v}`).join(' || ')); const decoratorTemplates = state.registry.getDecorator(type).slice(); decoratorTemplates.push(...state.registry.serializer.typeGuards.getRegistry(1).getDecorator(type)); if (decoratorTemplates.length) { const value = state.compilerContext.reserveName('value'); const decoratorState = state.fork(undefined, value); for (const template of decoratorTemplates) template(type, decoratorState); if (!decoratorState.template) return; state.addCode(` let ${value} = state.parser.read(state.elementType, ${decoratorState.compilerContext.reserveConst(type, 'type')}); ${decoratorState.template} `); } }; } export function bsonTypeGuardLiteral(type: TypeLiteral, state: TemplateState) { const literal = state.setVariable('literal', type.literal); state.addSetter(`state.parser.read(state.elementType) === ${literal}`); } export function bsonTypeGuardTemplateLiteral(type: TypeTemplateLiteral, state: TemplateState) { state.setContext({ extendTemplateLiteral: extendTemplateLiteral }); const typeVar = state.setVariable('type', type); state.addSetterAndReportErrorIfInvalid('type', 'Invalid literal', `state.elementType === ${BSONType.STRING} && extendTemplateLiteral({kind: ${ReflectionKind.literal}, literal: state.parser.read(state.elementType)}, ${typeVar})`); }