union.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. 'use strict';
  2. /*!
  3. * ignore
  4. */
  5. const SchemaUnionOptions = require('../options/schemaUnionOptions');
  6. const SchemaType = require('../schemaType');
  7. const firstValueSymbol = Symbol('firstValue');
  8. /*!
  9. * ignore
  10. */
  11. class Union extends SchemaType {
  12. /**
  13. * Create a Union schema type.
  14. *
  15. * @param {String} key the path in the schema for this schema type
  16. * @param {Object} options SchemaType-specific options (must have 'of' as array)
  17. * @param {Object} schemaOptions additional options from the schema this schematype belongs to
  18. * @param {Schema} parentSchema the schema this schematype belongs to
  19. */
  20. constructor(key, options, schemaOptions, parentSchema) {
  21. super(key, options, 'Union', parentSchema);
  22. if (!Array.isArray(options?.of) || options.of.length === 0) {
  23. throw new Error('Union schema type requires an array of types');
  24. }
  25. this.schemaTypes = options.of.map(obj => parentSchema.interpretAsType(key, obj, schemaOptions));
  26. }
  27. cast(val, doc, init, prev, options) {
  28. let firstValue = firstValueSymbol;
  29. let lastError;
  30. // Loop through each schema type in the union. If one of the schematypes returns a value that is `=== val`, then
  31. // use `val`. Otherwise, if one of the schematypes casted successfully, use the first successfully casted value.
  32. // Finally, if none of the schematypes casted successfully, throw the error from the last schema type in the union.
  33. // The `=== val` check is a workaround to ensure that the original value is returned if it matches one of the schema types,
  34. // avoiding cases like where numbers are casted to strings or dates even if the schema type is a number.
  35. for (let i = 0; i < this.schemaTypes.length; ++i) {
  36. try {
  37. const casted = this.schemaTypes[i].cast(val, doc, init, prev, options);
  38. if (casted === val) {
  39. return casted;
  40. }
  41. if (firstValue === firstValueSymbol) {
  42. firstValue = casted;
  43. }
  44. } catch (error) {
  45. lastError = error;
  46. }
  47. }
  48. if (firstValue !== firstValueSymbol) {
  49. return firstValue;
  50. }
  51. throw lastError;
  52. }
  53. // Setters also need to be aware of casting - we need to apply the setters of the entry in the union we choose.
  54. applySetters(val, doc, init, prev, options) {
  55. let firstValue = firstValueSymbol;
  56. let lastError;
  57. // Loop through each schema type in the union. If one of the schematypes returns a value that is `=== val`, then
  58. // use `val`. Otherwise, if one of the schematypes casted successfully, use the first successfully casted value.
  59. // Finally, if none of the schematypes casted successfully, throw the error from the last schema type in the union.
  60. // The `=== val` check is a workaround to ensure that the original value is returned if it matches one of the schema types,
  61. // avoiding cases like where numbers are casted to strings or dates even if the schema type is a number.
  62. for (let i = 0; i < this.schemaTypes.length; ++i) {
  63. try {
  64. let castedVal = this.schemaTypes[i]._applySetters(val, doc, init, prev, options);
  65. if (castedVal == null) {
  66. castedVal = this.schemaTypes[i]._castNullish(castedVal);
  67. } else {
  68. castedVal = this.schemaTypes[i].cast(castedVal, doc, init, prev, options);
  69. }
  70. if (castedVal === val) {
  71. return castedVal;
  72. }
  73. if (firstValue === firstValueSymbol) {
  74. firstValue = castedVal;
  75. }
  76. } catch (error) {
  77. lastError = error;
  78. }
  79. }
  80. if (firstValue !== firstValueSymbol) {
  81. return firstValue;
  82. }
  83. throw lastError;
  84. }
  85. clone() {
  86. const schematype = super.clone();
  87. schematype.schemaTypes = this.schemaTypes.map(schemaType => schemaType.clone());
  88. return schematype;
  89. }
  90. }
  91. /**
  92. * This schema type's name, to defend against minifiers that mangle
  93. * function names.
  94. *
  95. * @api public
  96. */
  97. Union.schemaName = 'Union';
  98. Union.defaultOptions = {};
  99. Union.prototype.OptionsConstructor = SchemaUnionOptions;
  100. module.exports = Union;