You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					206 lines
				
				6.4 KiB
			
		
		
			
		
	
	
					206 lines
				
				6.4 KiB
			| 
											3 years ago
										 | 'use strict'; | ||
|  | 
 | ||
|  | const Mixed = require('../../schema/mixed'); | ||
|  | const defineKey = require('../document/compile').defineKey; | ||
|  | const get = require('../get'); | ||
|  | const utils = require('../../utils'); | ||
|  | 
 | ||
|  | const CUSTOMIZABLE_DISCRIMINATOR_OPTIONS = { | ||
|  |   toJSON: true, | ||
|  |   toObject: true, | ||
|  |   _id: true, | ||
|  |   id: true | ||
|  | }; | ||
|  | 
 | ||
|  | /*! | ||
|  |  * ignore | ||
|  |  */ | ||
|  | 
 | ||
|  | module.exports = function discriminator(model, name, schema, tiedValue, applyPlugins) { | ||
|  |   if (!(schema && schema.instanceOfSchema)) { | ||
|  |     throw new Error('You must pass a valid discriminator Schema'); | ||
|  |   } | ||
|  | 
 | ||
|  |   if (model.schema.discriminatorMapping && | ||
|  |       !model.schema.discriminatorMapping.isRoot) { | ||
|  |     throw new Error('Discriminator "' + name + | ||
|  |         '" can only be a discriminator of the root model'); | ||
|  |   } | ||
|  | 
 | ||
|  |   if (applyPlugins) { | ||
|  |     const applyPluginsToDiscriminators = get(model.base, | ||
|  |       'options.applyPluginsToDiscriminators', false); | ||
|  |     // Even if `applyPluginsToDiscriminators` isn't set, we should still apply
 | ||
|  |     // global plugins to schemas embedded in the discriminator schema (gh-7370)
 | ||
|  |     model.base._applyPlugins(schema, { | ||
|  |       skipTopLevel: !applyPluginsToDiscriminators | ||
|  |     }); | ||
|  |   } | ||
|  | 
 | ||
|  |   const key = model.schema.options.discriminatorKey; | ||
|  | 
 | ||
|  |   const existingPath = model.schema.path(key); | ||
|  |   if (existingPath != null) { | ||
|  |     if (!utils.hasUserDefinedProperty(existingPath.options, 'select')) { | ||
|  |       existingPath.options.select = true; | ||
|  |     } | ||
|  |     existingPath.options.$skipDiscriminatorCheck = true; | ||
|  |   } else { | ||
|  |     const baseSchemaAddition = {}; | ||
|  |     baseSchemaAddition[key] = { | ||
|  |       default: void 0, | ||
|  |       select: true, | ||
|  |       $skipDiscriminatorCheck: true | ||
|  |     }; | ||
|  |     baseSchemaAddition[key][model.schema.options.typeKey] = String; | ||
|  |     model.schema.add(baseSchemaAddition); | ||
|  |     defineKey(key, null, model.prototype, null, [key], model.schema.options); | ||
|  |   } | ||
|  | 
 | ||
|  |   if (schema.path(key) && schema.path(key).options.$skipDiscriminatorCheck !== true) { | ||
|  |     throw new Error('Discriminator "' + name + | ||
|  |         '" cannot have field with name "' + key + '"'); | ||
|  |   } | ||
|  | 
 | ||
|  |   let value = name; | ||
|  |   if ((typeof tiedValue === 'string' && tiedValue.length) || tiedValue != null) { | ||
|  |     value = tiedValue; | ||
|  |   } | ||
|  | 
 | ||
|  |   function merge(schema, baseSchema) { | ||
|  |     // Retain original schema before merging base schema
 | ||
|  |     schema._baseSchema = baseSchema; | ||
|  |     if (baseSchema.paths._id && | ||
|  |         baseSchema.paths._id.options && | ||
|  |         !baseSchema.paths._id.options.auto) { | ||
|  |       schema.remove('_id'); | ||
|  |     } | ||
|  | 
 | ||
|  |     // Find conflicting paths: if something is a path in the base schema
 | ||
|  |     // and a nested path in the child schema, overwrite the base schema path.
 | ||
|  |     // See gh-6076
 | ||
|  |     const baseSchemaPaths = Object.keys(baseSchema.paths); | ||
|  |     const conflictingPaths = []; | ||
|  | 
 | ||
|  |     for (const path of baseSchemaPaths) { | ||
|  |       if (schema.nested[path]) { | ||
|  |         conflictingPaths.push(path); | ||
|  |         continue; | ||
|  |       } | ||
|  | 
 | ||
|  |       if (path.indexOf('.') === -1) { | ||
|  |         continue; | ||
|  |       } | ||
|  |       const sp = path.split('.').slice(0, -1); | ||
|  |       let cur = ''; | ||
|  |       for (const piece of sp) { | ||
|  |         cur += (cur.length ? '.' : '') + piece; | ||
|  |         if (schema.paths[cur] instanceof Mixed || | ||
|  |             schema.singleNestedPaths[cur] instanceof Mixed) { | ||
|  |           conflictingPaths.push(path); | ||
|  |         } | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     utils.merge(schema, baseSchema, { | ||
|  |       isDiscriminatorSchemaMerge: true, | ||
|  |       omit: { discriminators: true, base: true }, | ||
|  |       omitNested: conflictingPaths.reduce((cur, path) => { | ||
|  |         cur['tree.' + path] = true; | ||
|  |         return cur; | ||
|  |       }, {}) | ||
|  |     }); | ||
|  | 
 | ||
|  |     // Clean up conflicting paths _after_ merging re: gh-6076
 | ||
|  |     for (const conflictingPath of conflictingPaths) { | ||
|  |       delete schema.paths[conflictingPath]; | ||
|  |     } | ||
|  | 
 | ||
|  |     // Rebuild schema models because schemas may have been merged re: #7884
 | ||
|  |     schema.childSchemas.forEach(obj => { | ||
|  |       obj.model.prototype.$__setSchema(obj.schema); | ||
|  |     }); | ||
|  | 
 | ||
|  |     const obj = {}; | ||
|  |     obj[key] = { | ||
|  |       default: value, | ||
|  |       select: true, | ||
|  |       set: function(newName) { | ||
|  |         if (newName === value || (Array.isArray(value) && utils.deepEqual(newName, value))) { | ||
|  |           return value; | ||
|  |         } | ||
|  |         throw new Error('Can\'t set discriminator key "' + key + '"'); | ||
|  |       }, | ||
|  |       $skipDiscriminatorCheck: true | ||
|  |     }; | ||
|  |     obj[key][schema.options.typeKey] = existingPath ? existingPath.options[schema.options.typeKey] : String; | ||
|  |     schema.add(obj); | ||
|  | 
 | ||
|  | 
 | ||
|  |     schema.discriminatorMapping = { key: key, value: value, isRoot: false }; | ||
|  | 
 | ||
|  |     if (baseSchema.options.collection) { | ||
|  |       schema.options.collection = baseSchema.options.collection; | ||
|  |     } | ||
|  | 
 | ||
|  |     const toJSON = schema.options.toJSON; | ||
|  |     const toObject = schema.options.toObject; | ||
|  |     const _id = schema.options._id; | ||
|  |     const id = schema.options.id; | ||
|  | 
 | ||
|  |     const keys = Object.keys(schema.options); | ||
|  |     schema.options.discriminatorKey = baseSchema.options.discriminatorKey; | ||
|  | 
 | ||
|  |     for (const _key of keys) { | ||
|  |       if (!CUSTOMIZABLE_DISCRIMINATOR_OPTIONS[_key]) { | ||
|  |         // Special case: compiling a model sets `pluralization = true` by default. Avoid throwing an error
 | ||
|  |         // for that case. See gh-9238
 | ||
|  |         if (_key === 'pluralization' && schema.options[_key] == true && baseSchema.options[_key] == null) { | ||
|  |           continue; | ||
|  |         } | ||
|  | 
 | ||
|  |         if (!utils.deepEqual(schema.options[_key], baseSchema.options[_key])) { | ||
|  |           throw new Error('Can\'t customize discriminator option ' + _key + | ||
|  |             ' (can only modify ' + | ||
|  |             Object.keys(CUSTOMIZABLE_DISCRIMINATOR_OPTIONS).join(', ') + | ||
|  |             ')'); | ||
|  |         } | ||
|  |       } | ||
|  |     } | ||
|  |     schema.options = utils.clone(baseSchema.options); | ||
|  |     if (toJSON) schema.options.toJSON = toJSON; | ||
|  |     if (toObject) schema.options.toObject = toObject; | ||
|  |     if (typeof _id !== 'undefined') { | ||
|  |       schema.options._id = _id; | ||
|  |     } | ||
|  |     schema.options.id = id; | ||
|  |     schema.s.hooks = model.schema.s.hooks.merge(schema.s.hooks); | ||
|  | 
 | ||
|  |     schema.plugins = Array.prototype.slice.call(baseSchema.plugins); | ||
|  |     schema.callQueue = baseSchema.callQueue.concat(schema.callQueue); | ||
|  |     delete schema._requiredpaths; // reset just in case Schema#requiredPaths() was called on either schema
 | ||
|  |   } | ||
|  | 
 | ||
|  |   // merges base schema into new discriminator schema and sets new type field.
 | ||
|  |   merge(schema, model.schema); | ||
|  | 
 | ||
|  |   if (!model.discriminators) { | ||
|  |     model.discriminators = {}; | ||
|  |   } | ||
|  | 
 | ||
|  |   if (!model.schema.discriminatorMapping) { | ||
|  |     model.schema.discriminatorMapping = { key: key, value: null, isRoot: true }; | ||
|  |   } | ||
|  |   if (!model.schema.discriminators) { | ||
|  |     model.schema.discriminators = {}; | ||
|  |   } | ||
|  | 
 | ||
|  |   model.schema.discriminators[name] = schema; | ||
|  | 
 | ||
|  |   if (model.discriminators[name]) { | ||
|  |     throw new Error('Discriminator with name "' + name + '" already exists'); | ||
|  |   } | ||
|  | 
 | ||
|  |   return schema; | ||
|  | }; |