map.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. 'use strict';
  2. /*!
  3. * ignore
  4. */
  5. const MongooseMap = require('../types/map');
  6. const SchemaMapOptions = require('../options/schemaMapOptions');
  7. const SchemaType = require('../schemaType');
  8. const createJSONSchemaTypeDefinition = require('../helpers/createJSONSchemaTypeDefinition');
  9. const MongooseError = require('../error/mongooseError');
  10. const Schema = require('../schema');
  11. const utils = require('../utils');
  12. class SchemaMap extends SchemaType {
  13. /**
  14. * Map SchemaType constructor.
  15. *
  16. * @param {String} path
  17. * @param {Object} options
  18. * @param {Object} schemaOptions
  19. * @param {Schema} parentSchema
  20. * @inherits SchemaType
  21. * @api public
  22. */
  23. constructor(key, options, schemaOptions, parentSchema) {
  24. super(key, options, 'Map', parentSchema);
  25. this.$isSchemaMap = true;
  26. // Create the nested schema type for the map values
  27. this._createNestedSchemaType(parentSchema, key, options, schemaOptions);
  28. }
  29. /**
  30. * Sets a default option for all Map instances.
  31. *
  32. * @param {String} option The option you'd like to set the value for
  33. * @param {Any} value value for option
  34. * @return {undefined}
  35. * @function set
  36. * @api public
  37. */
  38. set(option, value) {
  39. return SchemaType.set(option, value);
  40. }
  41. /**
  42. * Casts to Map
  43. *
  44. * @param {Object} value
  45. * @param {Object} model this value is optional
  46. * @api private
  47. */
  48. cast(val, doc, init, prev, options) {
  49. if (val instanceof MongooseMap) {
  50. return val;
  51. }
  52. const path = this.path;
  53. if (init) {
  54. const map = new MongooseMap({}, path, doc, this.$__schemaType, options);
  55. // Use the map's path for passing to nested casts.
  56. // If map's parent is a subdocument, use the relative path so nested casts get relative paths.
  57. const mapPath = map.$__pathRelativeToParent != null ? map.$__pathRelativeToParent : map.$__path;
  58. if (val instanceof global.Map) {
  59. for (const key of val.keys()) {
  60. let _val = val.get(key);
  61. if (_val == null) {
  62. _val = map.$__schemaType._castNullish(_val);
  63. } else {
  64. _val = map.$__schemaType.cast(_val, doc, true, null, { ...options, path: mapPath + '.' + key });
  65. }
  66. map.$init(key, _val);
  67. }
  68. } else {
  69. for (const key of Object.keys(val)) {
  70. let _val = val[key];
  71. if (_val == null) {
  72. _val = map.$__schemaType._castNullish(_val);
  73. } else {
  74. _val = map.$__schemaType.cast(_val, doc, true, null, { ...options, path: mapPath + '.' + key });
  75. }
  76. map.$init(key, _val);
  77. }
  78. }
  79. return map;
  80. }
  81. return new MongooseMap(val, path, doc, this.$__schemaType, options);
  82. }
  83. /**
  84. * Creates a copy of this map schema type.
  85. *
  86. * @api private
  87. */
  88. clone() {
  89. const schematype = super.clone();
  90. if (this.$__schemaType != null) {
  91. schematype.$__schemaType = this.$__schemaType.clone();
  92. }
  93. return schematype;
  94. }
  95. /**
  96. * Returns the embedded schema type (i.e. the `.$*` path)
  97. *
  98. * @api public
  99. */
  100. getEmbeddedSchemaType() {
  101. return this.$__schemaType;
  102. }
  103. /**
  104. * Returns this schema type's representation in a JSON schema.
  105. *
  106. * @param [options]
  107. * @param [options.useBsonType=false] If true, return a representation with `bsonType` for use with MongoDB's `$jsonSchema`.
  108. * @returns {Object} JSON schema properties
  109. */
  110. toJSONSchema(options) {
  111. const useBsonType = options?.useBsonType;
  112. const embeddedSchemaType = this.getEmbeddedSchemaType();
  113. const isRequired = this.options.required && typeof this.options.required !== 'function';
  114. const result = createJSONSchemaTypeDefinition('object', 'object', useBsonType, isRequired);
  115. result.additionalProperties = embeddedSchemaType.toJSONSchema(options);
  116. return result;
  117. }
  118. /**
  119. * Returns the auto encryption type for this schema type.
  120. *
  121. * @api public
  122. */
  123. autoEncryptionType() {
  124. return 'object';
  125. }
  126. }
  127. /**
  128. * This schema type's name, to defend against minifiers that mangle
  129. * function names.
  130. *
  131. * @api public
  132. */
  133. SchemaMap.schemaName = 'Map';
  134. SchemaMap.prototype.OptionsConstructor = SchemaMapOptions;
  135. SchemaMap.defaultOptions = {};
  136. /*!
  137. * ignore
  138. */
  139. SchemaMap.prototype._createNestedSchemaType = function _createNestedSchemaType(schema, path, obj, options) {
  140. const mapPath = path + '.$*';
  141. let _mapType = { type: {} };
  142. if (utils.hasUserDefinedProperty(obj, 'of')) {
  143. const isInlineSchema = utils.isPOJO(obj.of) &&
  144. utils.hasOwnKeys(obj.of) &&
  145. !utils.hasUserDefinedProperty(obj.of, schema.options.typeKey);
  146. if (isInlineSchema) {
  147. _mapType = { [schema.options.typeKey]: new Schema(obj.of) };
  148. } else if (utils.isPOJO(obj.of)) {
  149. _mapType = Object.assign({}, obj.of);
  150. } else {
  151. _mapType = { [schema.options.typeKey]: obj.of };
  152. }
  153. if (_mapType[schema.options.typeKey] && _mapType[schema.options.typeKey].instanceOfSchema) {
  154. const subdocumentSchema = _mapType[schema.options.typeKey];
  155. subdocumentSchema.eachPath((subpath, type) => {
  156. if (type.options.select === true || type.options.select === false) {
  157. throw new MongooseError('Cannot use schema-level projections (`select: true` or `select: false`) within maps at path "' + path + '.' + subpath + '"');
  158. }
  159. });
  160. }
  161. if (utils.hasUserDefinedProperty(obj, 'ref')) {
  162. _mapType.ref = obj.ref;
  163. }
  164. }
  165. this.$__schemaType = schema.interpretAsType(mapPath, _mapType, options);
  166. };
  167. module.exports = SchemaMap;