Browse Source

chore: adds EmptyValueDefiner and updates README.md

e22m4u 1 year ago
parent
commit
3a88591490

+ 26 - 6
README.md

@@ -11,6 +11,7 @@
 - [Свойства](#Свойства)
 - [Свойства](#Свойства)
 - [Валидаторы](#Валидаторы)
 - [Валидаторы](#Валидаторы)
 - [Трансформеры](#Трансформеры)
 - [Трансформеры](#Трансформеры)
+- [Пустое значение](#Пустое-значение)
 - [Репозиторий](#Репозиторий)
 - [Репозиторий](#Репозиторий)
 - [Фильтрация](#Фильтрация)
 - [Фильтрация](#Фильтрация)
 - [Связи](#Связи)
 - [Связи](#Связи)
@@ -224,7 +225,7 @@ schema.defineModel({
 - `required: boolean` объявить свойство обязательным
 - `required: boolean` объявить свойство обязательным
 - `default: any` значение по умолчанию
 - `default: any` значение по умолчанию
 - `validate: string | array | object` см. [Валидаторы](#Валидаторы)
 - `validate: string | array | object` см. [Валидаторы](#Валидаторы)
-- `unique: boolean | 'sparse'` допускать только уникальные значения
+- `unique: boolean | 'sparse'` проверять значение на уникальность
 
 
 **unique**
 **unique**
 
 
@@ -233,13 +234,13 @@ schema.defineModel({
 установлен в значение `true`, то будет проверяться каждое входящее
 установлен в значение `true`, то будет проверяться каждое входящее
 значение данного свойства (включая `undefined` и `null`).
 значение данного свойства (включая `undefined` и `null`).
 
 
-Параметр `unique` в режиме `'sparse'` исключает из проверки пустые
-значения, список которых отличается в зависимости от типа свойства.
-Например, для типа `string` пустым значением являются `undefined`,
-`null` и `''` (пустая строка).
+Параметр `unique` в режиме `'sparse'` исключает из проверки
+[пустые значения](#Пустое-значение), список которых отличается
+в зависимости от типа свойства. Например, для типа `string` пустым
+значением являются `undefined`, `null` и `''` (пустая строка).
 
 
 - `unique: true` проверять значение на уникальность
 - `unique: true` проверять значение на уникальность
-- `unique: 'sparse'` исключить из проверки пустые значения
+- `unique: 'sparse'` исключить из проверки [пустые значения](#Пустое-значение)
 - `unique: false` не проверять на уникальность (по умолчанию)
 - `unique: false` не проверять на уникальность (по умолчанию)
 
 
 **Примеры**
 **Примеры**
@@ -362,6 +363,25 @@ schema.defineModel({
 });
 });
 ```
 ```
 
 
+## Пустое значение
+
+Разные типы свойств имеют свои наборы пустых значений. Эти наборы
+используются для определения наличия полезной нагрузки в значении
+свойства. Например, параметр `default` в определении свойства
+устанавливает значение по умолчанию, только если входящее значение
+является пустым. Параметр `required` исключает пустые значения
+выбрасывая ошибку. А параметр `unique` в режиме `sparse` наоборот
+допускает дублирование пустых значений уникального свойства.
+
+| тип         | пустые значения           |
+|-------------|---------------------------|
+| `'any'`     | `undefined`, `null`       |
+| `'string'`  | `undefined`, `null`, `''` |
+| `'number'`  | `undefined`, `null`, `0`  |
+| `'boolean'` | `undefined`, `null`       |
+| `'array'`   | `undefined`, `null`, `[]` |
+| `'object'`  | `undefined`, `null`, `{}` |
+
 ## Репозиторий
 ## Репозиторий
 
 
 Выполняет операции чтения и записи документов определенной модели.
 Выполняет операции чтения и записи документов определенной модели.

File diff suppressed because it is too large
+ 0 - 0
docs/assets/search.js


File diff suppressed because it is too large
+ 0 - 0
docs/classes/ModelDefinitionUtils.html


File diff suppressed because it is too large
+ 46 - 6
docs/index.html


File diff suppressed because it is too large
+ 0 - 0
docs/types/DEFAULT_PRIMARY_KEY_PROPERTY_NAME.html


+ 12 - 2
src/definition/model/model-data-validator.js

@@ -2,6 +2,7 @@ import {Service} from '@e22m4u/js-service';
 import {DataType} from './properties/index.js';
 import {DataType} from './properties/index.js';
 import {getCtorName} from '../../utils/index.js';
 import {getCtorName} from '../../utils/index.js';
 import {isPureObject} from '../../utils/index.js';
 import {isPureObject} from '../../utils/index.js';
+import {EmptyValuesDefiner} from './properties/index.js';
 import {InvalidArgumentError} from '../../errors/index.js';
 import {InvalidArgumentError} from '../../errors/index.js';
 import {PropertyValidatorRegistry} from './properties/index.js';
 import {PropertyValidatorRegistry} from './properties/index.js';
 import {ModelDefinitionUtils} from './model-definition-utils.js';
 import {ModelDefinitionUtils} from './model-definition-utils.js';
@@ -59,8 +60,17 @@ export class ModelDataValidator extends Service {
       propDef,
       propDef,
       propValue,
       propValue,
     );
     );
-    // undefined and null
-    if (propValue == null) {
+    // a required property should
+    // not have an empty property
+    const propType =
+      this.getService(ModelDefinitionUtils).getDataTypeFromPropertyDefinition(
+        propDef,
+      );
+    const isEmpty = this.getService(EmptyValuesDefiner).isEmpty(
+      propType,
+      propValue,
+    );
+    if (isEmpty) {
       const isRequired =
       const isRequired =
         typeof propDef === 'string' ? false : Boolean(propDef.required);
         typeof propDef === 'string' ? false : Boolean(propDef.required);
       if (!isRequired) return;
       if (!isRequired) return;

+ 23 - 0
src/definition/model/model-data-validator.spec.js

@@ -2,6 +2,7 @@ import {expect} from 'chai';
 import {Schema} from '../../schema.js';
 import {Schema} from '../../schema.js';
 import {format} from '@e22m4u/js-format';
 import {format} from '@e22m4u/js-format';
 import {DataType} from './properties/index.js';
 import {DataType} from './properties/index.js';
+import {EmptyValuesDefiner} from './properties/index.js';
 import {ModelDataValidator} from './model-data-validator.js';
 import {ModelDataValidator} from './model-data-validator.js';
 import {DefinitionRegistry} from '../definition-registry.js';
 import {DefinitionRegistry} from '../definition-registry.js';
 import {PropertyValidatorRegistry} from './properties/index.js';
 import {PropertyValidatorRegistry} from './properties/index.js';
@@ -114,6 +115,28 @@ describe('ModelDataValidator', function () {
       );
       );
     });
     });
 
 
+    it('throws an error if a required property has an empty value', function () {
+      const schema = new Schema();
+      schema.defineModel({
+        name: 'model',
+        properties: {
+          foo: {
+            type: DataType.STRING,
+            required: true,
+          },
+        },
+      });
+      schema
+        .getService(EmptyValuesDefiner)
+        .setEmptyValuesOf(DataType.STRING, ['empty']);
+      const throwable = () =>
+        schema.getService(ModelDataValidator).validate('model', {foo: 'empty'});
+      expect(throwable).to.throw(
+        'The property "foo" of the model "model" ' +
+          'is required, but "empty" given.',
+      );
+    });
+
     describe('an option "isPartial" is true', function () {
     describe('an option "isPartial" is true', function () {
       it('does not throw an error if a given data does not have a required property', function () {
       it('does not throw an error if a given data does not have a required property', function () {
         const schema = new Schema();
         const schema = new Schema();

+ 8 - 0
src/definition/model/model-definition-utils.d.ts

@@ -2,6 +2,7 @@ import {ModelData} from '../../types.js';
 import {Service} from '@e22m4u/js-service';
 import {Service} from '@e22m4u/js-service';
 import {DataType} from './properties/index.js';
 import {DataType} from './properties/index.js';
 import {RelationDefinition} from './relations/index.js';
 import {RelationDefinition} from './relations/index.js';
+import {PropertyDefinition} from './properties/index.js';
 import {PropertyDefinitionMap} from './model-definition.js';
 import {PropertyDefinitionMap} from './model-definition.js';
 import {RelationDefinitionMap} from './model-definition.js';
 import {RelationDefinitionMap} from './model-definition.js';
 
 
@@ -94,6 +95,13 @@ export declare class ModelDefinitionUtils extends Service {
    */
    */
   getDataTypeByPropertyName(modelName: string, propertyName: string): DataType;
   getDataTypeByPropertyName(modelName: string, propertyName: string): DataType;
 
 
+  /**
+   * Get data type from property definition.
+   *
+   * @param propDef
+   */
+  getDataTypeFromPropertyDefinition(propDef: PropertyDefinition): DataType;
+
   /**
   /**
    * Get own properties definition of primary keys.
    * Get own properties definition of primary keys.
    *
    *

+ 38 - 2
src/definition/model/model-definition-utils.js

@@ -2,6 +2,7 @@ import {Service} from '@e22m4u/js-service';
 import {DataType} from './properties/index.js';
 import {DataType} from './properties/index.js';
 import {cloneDeep} from '../../utils/index.js';
 import {cloneDeep} from '../../utils/index.js';
 import {excludeObjectKeys} from '../../utils/index.js';
 import {excludeObjectKeys} from '../../utils/index.js';
+import {EmptyValuesDefiner} from './properties/index.js';
 import {InvalidArgumentError} from '../../errors/index.js';
 import {InvalidArgumentError} from '../../errors/index.js';
 import {DefinitionRegistry} from '../definition-registry.js';
 import {DefinitionRegistry} from '../definition-registry.js';
 
 
@@ -139,10 +140,16 @@ export class ModelDefinitionUtils extends Service {
       ? Object.keys(modelData)
       ? Object.keys(modelData)
       : Object.keys(propDefs);
       : Object.keys(propDefs);
     const extendedData = cloneDeep(modelData);
     const extendedData = cloneDeep(modelData);
+    const emptyValueDefiner = this.getService(EmptyValuesDefiner);
     propNames.forEach(propName => {
     propNames.forEach(propName => {
-      const value = extendedData[propName];
-      if (value != null) return;
       const propDef = propDefs[propName];
       const propDef = propDefs[propName];
+      const propValue = extendedData[propName];
+      const propType =
+        propDef != null
+          ? this.getDataTypeFromPropertyDefinition(propDef)
+          : DataType.ANY;
+      const isEmpty = emptyValueDefiner.isEmpty(propType, propValue);
+      if (!isEmpty) return;
       if (
       if (
         propDef &&
         propDef &&
         typeof propDef === 'object' &&
         typeof propDef === 'object' &&
@@ -226,6 +233,35 @@ export class ModelDefinitionUtils extends Service {
     return propDef.type;
     return propDef.type;
   }
   }
 
 
+  /**
+   * Get data type from property definition.
+   *
+   * @param {object} propDef
+   * @returns {string}
+   */
+  getDataTypeFromPropertyDefinition(propDef) {
+    if (
+      (!propDef || typeof propDef !== 'object') &&
+      !Object.values(DataType).includes(propDef)
+    ) {
+      throw new InvalidArgumentError(
+        'The argument "propDef" of the ModelDefinitionUtils.getDataTypeFromPropertyDefinition ' +
+          'should be an Object or the DataType enum, but %v given.',
+        propDef,
+      );
+    }
+    if (typeof propDef === 'string') return propDef;
+    const dataType = propDef.type;
+    if (!Object.values(DataType).includes(dataType))
+      throw new InvalidArgumentError(
+        'The given Object to the ModelDefinitionUtils.getDataTypeFromPropertyDefinition ' +
+          'should have the "type" property with one of values: %l, but %v given.',
+        Object.values(DataType),
+        propDef.type,
+      );
+    return dataType;
+  }
+
   /**
   /**
    * Get own properties definition of primary keys.
    * Get own properties definition of primary keys.
    *
    *

+ 82 - 0
src/definition/model/model-definition-utils.spec.js

@@ -4,6 +4,7 @@ import {Schema} from '../../schema.js';
 import {format} from '@e22m4u/js-format';
 import {format} from '@e22m4u/js-format';
 import {DataType} from './properties/index.js';
 import {DataType} from './properties/index.js';
 import {RelationType} from './relations/index.js';
 import {RelationType} from './relations/index.js';
+import {EmptyValuesDefiner} from './properties/index.js';
 import {InvalidArgumentError} from '../../errors/index.js';
 import {InvalidArgumentError} from '../../errors/index.js';
 import {ModelDefinitionUtils} from './model-definition-utils.js';
 import {ModelDefinitionUtils} from './model-definition-utils.js';
 import {DEFAULT_PRIMARY_KEY_PROPERTY_NAME as DEF_PK} from './model-definition-utils.js';
 import {DEFAULT_PRIMARY_KEY_PROPERTY_NAME as DEF_PK} from './model-definition-utils.js';
@@ -476,6 +477,26 @@ describe('ModelDefinitionUtils', function () {
       expect(result).to.be.eql({foo: 'string'});
       expect(result).to.be.eql({foo: 'string'});
     });
     });
 
 
+    it('sets a default value if a property has an empty value', function () {
+      const schema = new Schema();
+      schema.defineModel({
+        name: 'model',
+        properties: {
+          foo: {
+            type: DataType.STRING,
+            default: 'placeholder',
+          },
+        },
+      });
+      schema
+        .getService(EmptyValuesDefiner)
+        .setEmptyValuesOf(DataType.STRING, ['empty']);
+      const result = schema
+        .getService(ModelDefinitionUtils)
+        .setDefaultValuesToEmptyProperties('model', {foo: 'empty'});
+      expect(result).to.be.eql({foo: 'placeholder'});
+    });
+
     it('sets a value from a factory function', function () {
     it('sets a value from a factory function', function () {
       const schema = new Schema();
       const schema = new Schema();
       schema.defineModel({
       schema.defineModel({
@@ -827,6 +848,67 @@ describe('ModelDefinitionUtils', function () {
     });
     });
   });
   });
 
 
+  describe('getDataTypeFromPropertyDefinition', function () {
+    it('requires the given argument "propDef" must be an Object or DataType', function () {
+      const schema = new Schema();
+      const S = schema.getService(ModelDefinitionUtils);
+      const throwable = v => () => S.getDataTypeFromPropertyDefinition(v);
+      const error = v =>
+        format(
+          'The argument "propDef" of the ModelDefinitionUtils.getDataTypeFromPropertyDefinition ' +
+            'should be an Object or the DataType enum, but %s given.',
+          v,
+        );
+      expect(throwable('str')).to.throw(error('"str"'));
+      expect(throwable('')).to.throw(error('""'));
+      expect(throwable(10)).to.throw(error('10'));
+      expect(throwable(0)).to.throw(error('0'));
+      expect(throwable(true)).to.throw(error('true'));
+      expect(throwable(false)).to.throw(error('false'));
+      expect(throwable(undefined)).to.throw(error('undefined'));
+      expect(throwable(null)).to.throw(error('null'));
+      throwable(DataType.ANY)();
+      throwable({type: DataType.ANY})();
+    });
+
+    it('requires the given Object to have the "type" property with the DataType enum', function () {
+      const schema = new Schema();
+      const S = schema.getService(ModelDefinitionUtils);
+      const throwable = v => () =>
+        S.getDataTypeFromPropertyDefinition({type: v});
+      const error = v =>
+        format(
+          'The given Object to the ModelDefinitionUtils.getDataTypeFromPropertyDefinition ' +
+            'should have the "type" property with one of values: %l, but %s given.',
+          Object.values(DataType),
+          v,
+        );
+      expect(throwable('str')).to.throw(error('"str"'));
+      expect(throwable('')).to.throw(error('""'));
+      expect(throwable(10)).to.throw(error('10'));
+      expect(throwable(0)).to.throw(error('0'));
+      expect(throwable(true)).to.throw(error('true'));
+      expect(throwable(false)).to.throw(error('false'));
+      expect(throwable(undefined)).to.throw(error('undefined'));
+      expect(throwable(null)).to.throw(error('null'));
+      throwable(DataType.ANY)();
+    });
+
+    it('returns the DataType from the given DataType enum', function () {
+      const schema = new Schema();
+      const S = schema.getService(ModelDefinitionUtils);
+      const res = S.getDataTypeFromPropertyDefinition(DataType.STRING);
+      expect(res).to.be.eq(DataType.STRING);
+    });
+
+    it('returns the DataType from the given PropertyDefinition', function () {
+      const schema = new Schema();
+      const S = schema.getService(ModelDefinitionUtils);
+      const res = S.getDataTypeFromPropertyDefinition({type: DataType.STRING});
+      expect(res).to.be.eq(DataType.STRING);
+    });
+  });
+
   describe('getOwnPropertiesDefinitionWithoutPrimaryKeys', function () {
   describe('getOwnPropertiesDefinitionWithoutPrimaryKeys', function () {
     it('returns an empty object if a model does not have properties', function () {
     it('returns an empty object if a model does not have properties', function () {
       const schema = new Schema();
       const schema = new Schema();

Some files were not shown because too many files changed in this diff