property-uniqueness-validator.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. import {Service} from '@e22m4u/js-service';
  2. import {isPlainObject} from '../../../utils/index.js';
  3. import {PropertyUniqueness} from './property-uniqueness.js';
  4. import {InvalidArgumentError} from '../../../errors/index.js';
  5. import {ModelDefinitionUtils} from '../model-definition-utils.js';
  6. /**
  7. * Property uniqueness validator.
  8. */
  9. export class PropertyUniquenessValidator extends Service {
  10. /**
  11. * Validate.
  12. *
  13. * @param {Function} countMethod
  14. * @param {string} methodName
  15. * @param {string} modelName
  16. * @param {object} modelData
  17. * @param {*} modelId
  18. * @returns {Promise<undefined>}
  19. */
  20. async validate(
  21. countMethod,
  22. methodName,
  23. modelName,
  24. modelData,
  25. modelId = undefined,
  26. ) {
  27. if (typeof countMethod !== 'function')
  28. throw new InvalidArgumentError(
  29. 'The parameter "countMethod" of the PropertyUniquenessValidator ' +
  30. 'must be a Function, but %v was given.',
  31. countMethod,
  32. );
  33. if (!methodName || typeof methodName !== 'string')
  34. throw new InvalidArgumentError(
  35. 'The parameter "methodName" of the PropertyUniquenessValidator ' +
  36. 'must be a non-empty String, but %v was given.',
  37. methodName,
  38. );
  39. if (!modelName || typeof modelName !== 'string')
  40. throw new InvalidArgumentError(
  41. 'The parameter "modelName" of the PropertyUniquenessValidator ' +
  42. 'must be a non-empty String, but %v was given.',
  43. modelName,
  44. );
  45. if (!isPlainObject(modelData))
  46. throw new InvalidArgumentError(
  47. 'The data of the model %v should be an Object, but %v was given.',
  48. modelName,
  49. modelData,
  50. );
  51. const propDefs =
  52. this.getService(
  53. ModelDefinitionUtils,
  54. ).getPropertiesDefinitionInBaseModelHierarchy(modelName);
  55. const isPartial = methodName === 'patch' || methodName === 'patchById';
  56. const propNames = Object.keys(isPartial ? modelData : propDefs);
  57. const idProp =
  58. this.getService(ModelDefinitionUtils).getPrimaryKeyAsPropertyName(
  59. modelName,
  60. );
  61. const createError = (propName, propValue) =>
  62. new InvalidArgumentError(
  63. 'An existing document of the model %v already has ' +
  64. 'the property %v with the value %v and should be unique.',
  65. modelName,
  66. propName,
  67. propValue,
  68. );
  69. let willBeReplaced = undefined;
  70. for (const propName of propNames) {
  71. const propDef = propDefs[propName];
  72. if (
  73. !propDef ||
  74. typeof propDef === 'string' ||
  75. !propDef.unique ||
  76. propDef.unique === PropertyUniqueness.NON_UNIQUE
  77. ) {
  78. continue;
  79. }
  80. // в режиме "sparse" проверка на уникальность
  81. // должна игнорировать ложные значения
  82. const propValue = modelData[propName];
  83. if (propDef.unique === PropertyUniqueness.SPARSE && !propValue) {
  84. continue;
  85. }
  86. // create
  87. if (methodName === 'create') {
  88. const count = await countMethod({[propName]: propValue});
  89. if (count > 0) throw createError(propName, propValue);
  90. }
  91. // replaceById
  92. else if (methodName === 'replaceById') {
  93. const count = await countMethod({
  94. [idProp]: {neq: modelId},
  95. [propName]: propValue,
  96. });
  97. if (count > 0) throw createError(propName, propValue);
  98. }
  99. // replaceOrCreate
  100. else if (methodName === 'replaceOrCreate') {
  101. const idFromData = modelData[idProp];
  102. if (willBeReplaced == null && idFromData != null) {
  103. const count = await countMethod({[idProp]: idFromData});
  104. willBeReplaced = count > 0;
  105. }
  106. // replaceById by replaceOrCreate
  107. if (willBeReplaced) {
  108. const count = await countMethod({
  109. [idProp]: {neq: idFromData},
  110. [propName]: propValue,
  111. });
  112. if (count > 0) throw createError(propName, propValue);
  113. }
  114. // create by replaceOrCreate
  115. else {
  116. const count = await countMethod({[propName]: propValue});
  117. if (count > 0) throw createError(propName, propValue);
  118. }
  119. }
  120. // patch
  121. else if (methodName === 'patch') {
  122. const count = await countMethod({[propName]: propValue});
  123. if (count > 0) throw createError(propName, propValue);
  124. }
  125. // patchById
  126. else if (methodName === 'patchById') {
  127. const count = await countMethod({
  128. [idProp]: {neq: modelId},
  129. [propName]: propValue,
  130. });
  131. if (count > 0) throw createError(propName, propValue);
  132. }
  133. // unsupported method
  134. else {
  135. throw new InvalidArgumentError(
  136. 'The PropertyUniquenessValidator does not ' +
  137. 'support the adapter method %v.',
  138. methodName,
  139. );
  140. }
  141. }
  142. }
  143. }