| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233 |
- import {DataType} from './data-type.js';
- import {Service} from '@e22m4u/js-service';
- import {DataValidator} from './data-validator.js';
- import {InvalidArgumentError} from '@e22m4u/js-format';
- import {validateDataSchema} from './validate-data-schema.js';
- import {DataSchemaResolver} from './data-schema-resolver.js';
- import {DataSchemaRegistry} from './data-schema-registry.js';
- import {
- arrayTypeParser,
- stringTypeParser,
- numberTypeParser,
- objectTypeParser,
- booleanTypeParser,
- } from './data-parsers/index.js';
- /**
- * Data parser.
- */
- export class DataParser extends Service {
- /**
- * Parsers.
- *
- * @type {Function[]}
- */
- _parsers = [
- stringTypeParser,
- booleanTypeParser,
- numberTypeParser,
- arrayTypeParser,
- objectTypeParser,
- ];
- /**
- * Get parsers.
- *
- * @returns {Function[]}
- */
- getParsers() {
- return [...this._parsers];
- }
- /**
- * Set parsers.
- *
- * @param {Function[]} list
- * @returns {this}
- */
- setParsers(list) {
- if (!Array.isArray(list)) {
- throw new InvalidArgumentError(
- 'Data parsers must be an Array, but %v was given.',
- list,
- );
- }
- list.forEach(parser => {
- if (typeof parser !== 'function') {
- throw new InvalidArgumentError(
- 'Data parser must be a Function, but %v was given.',
- parser,
- );
- }
- });
- this._parsers = [...list];
- return this;
- }
- /**
- * Define schema.
- *
- * @param {object} schemaDef
- * @returns {this}
- */
- defineSchema(schemaDef) {
- this.getService(DataSchemaRegistry).defineSchema(schemaDef);
- return this;
- }
- /**
- * Has schema.
- *
- * @param {string} schemaName
- * @returns {boolean}
- */
- hasSchema(schemaName) {
- return this.getService(DataSchemaRegistry).hasSchema(schemaName);
- }
- /**
- * Get schema.
- *
- * @param {string} schemaName
- * @returns {object}
- */
- getSchema(schemaName) {
- return this.getService(DataSchemaRegistry).getSchema(schemaName);
- }
- /**
- * Parse.
- *
- * @param {*} value
- * @param {object|Function|string} schema
- * @param {object} [options]
- * @returns {*}
- */
- parse(value, schema, options) {
- if (options !== undefined) {
- if (
- options === null ||
- typeof options !== 'object' ||
- Array.isArray(options)
- ) {
- throw new InvalidArgumentError(
- 'Parsing options must be an Object, but %v was given.',
- options,
- );
- }
- if (options.sourcePath !== undefined) {
- if (!options.sourcePath || typeof options.sourcePath !== 'string') {
- throw new InvalidArgumentError(
- 'Option "sourcePath" must be a non-empty String, but %v was given.',
- options.sourcePath,
- );
- }
- }
- if (options.shallowMode !== undefined) {
- if (typeof options.shallowMode !== 'boolean') {
- throw new InvalidArgumentError(
- 'Option "shallowMode" must be a Boolean, but %v was given.',
- options.shallowMode,
- );
- }
- }
- if (options.noParsingErrors !== undefined) {
- if (typeof options.noParsingErrors !== 'boolean') {
- throw new InvalidArgumentError(
- 'Option "noParsingErrors" must be a Boolean, but %v was given.',
- options.noParsingErrors,
- );
- }
- }
- }
- const sourcePath = (options && options.sourcePath) || undefined;
- const shallowMode = Boolean(options && options.shallowMode);
- const noParsingErrors = Boolean(options && options.noParsingErrors);
- // поверхностная проверка схемы
- // (режим shallowMode)
- validateDataSchema(schema, true);
- // если схема данных не является объектом,
- // то выполняется извлечение схемы данных
- const schemaResolver = this.getService(DataSchemaResolver);
- if (typeof schema !== 'object') {
- schema = schemaResolver.resolve(schema);
- }
- // передача значения через каждую функцию
- // преобразования в соответствующем порядке
- value = this._parsers.reduce((input, parser) => {
- return parser(input, schema, options, this.container);
- }, value);
- // если значение является массивом или объектом,
- // то выполняется обработка вложенных элементов
- if (!shallowMode) {
- // если значение является массивом, то выполняется
- // преобразование каждого элемента по схеме, указанной
- // в опции items
- if (Array.isArray(value) && schema.items !== undefined) {
- // чтобы избежать изменения оригинального массива,
- // выполняется его поверхностное копирование
- value = [...value];
- value.forEach((item, index) => {
- const itemPath = (sourcePath || 'array') + `[${index}]`;
- const itemOptions = {...options, sourcePath: itemPath};
- value[index] = this.parse(item, schema.items, itemOptions);
- });
- }
- // если значение является объектом, то выполняется
- // рекурсивный обход каждого свойства
- else if (
- value !== null &&
- typeof value === 'object' &&
- schema.properties !== undefined
- ) {
- let propsSchema = schema.properties;
- // чтобы избежать изменения оригинального объекта,
- // выполняется его поверхностное копирование
- value = {...value};
- // если схема свойств не является объектом,
- // то выполняется извлечение схемы данных
- if (typeof propsSchema !== 'object') {
- const resolvedSchema = schemaResolver.resolve(propsSchema);
- // если извлеченная схема не является
- // схемой объекта, то выбрасывается ошибка
- if (resolvedSchema.type !== DataType.OBJECT) {
- throw new InvalidArgumentError(
- 'Unable to get the "properties" option ' +
- 'from the data schema of %v type.',
- resolvedSchema.type || DataType.ANY,
- );
- }
- propsSchema = resolvedSchema.properties || {};
- }
- Object.keys(propsSchema).forEach(propName => {
- const propSchema = propsSchema[propName];
- // если схема свойства не определена,
- // то преобразование пропускается
- if (propSchema === undefined) {
- return;
- }
- const propValue = value[propName];
- const propPath = sourcePath ? sourcePath + `.${propName}` : propName;
- const propOptions = {...options, sourcePath: propPath};
- const newPropValue = this.parse(propValue, propSchema, propOptions);
- // исходный объект может не иметь ключа данного свойства,
- // и чтобы избежать его добавления, выполняется проверка
- // на отличие старого и нового значения, таким образом,
- // значение undefined не будет присвоено свойству,
- // которого нет (новый ключ не будет добавлен)
- if (value[propName] !== newPropValue) {
- value[propName] = newPropValue;
- }
- });
- }
- }
- // если допускается выброс ошибок, то результирующее
- // значение проверяется согласно схеме данных
- if (!noParsingErrors) {
- const validator = this.getService(DataValidator);
- validator.validate(value, schema, {shallowMode: true});
- }
- return value;
- }
- }
|