| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170 |
- import {InvalidArgumentError} from '@e22m4u/js-format';
- import {validateProjectionSchema} from './validate-projection-schema.js';
- /**
- * Project data.
- *
- * @param {object|Function} schemaOrFactory
- * @param {object} data
- * @param {object|undefined} options
- * @returns {*}
- */
- export function projectData(schemaOrFactory, data, options = undefined) {
- // schemaOrFactory
- if (
- !schemaOrFactory ||
- (typeof schemaOrFactory !== 'object' &&
- typeof schemaOrFactory !== 'function') ||
- Array.isArray(schemaOrFactory)
- ) {
- throw new InvalidArgumentError(
- 'Projection schema must be an Object or a Function ' +
- 'that returns a schema object, but %v was given.',
- schemaOrFactory,
- );
- }
- // options
- if (options !== undefined) {
- if (!options || typeof options !== 'object' || Array.isArray(options)) {
- throw new InvalidArgumentError(
- 'Parameter "options" must be an Object, but %v was given.',
- options,
- );
- }
- // options.strict
- if (options.strict !== undefined && typeof options.strict !== 'boolean') {
- throw new InvalidArgumentError(
- 'Option "strict" must be a Boolean, but %v was given.',
- options.strict,
- );
- }
- // options.scope
- if (
- options.scope !== undefined &&
- (!options.scope || typeof options.scope !== 'string')
- ) {
- throw new InvalidArgumentError(
- 'Option "scope" must be a non-empty String, but %v was given.',
- options.scope,
- );
- }
- }
- const strict = Boolean(options && options.strict);
- const scope = (options && options.scope) || undefined;
- // если вместо схемы передана фабрика,
- // то извлекается фабричное значение
- let schema = schemaOrFactory;
- if (typeof schemaOrFactory === 'function') {
- schema = schemaOrFactory();
- // если не удалось извлечь схему проекции,
- // то выбрасывается ошибка
- if (!schema || typeof schema !== 'object' || Array.isArray(schema)) {
- throw new InvalidArgumentError(
- 'Projection schema factory must return an Object, but %v was given.',
- schema,
- );
- }
- }
- // валидация полученной схемы проекции
- // без проверки вложенных схем (shallowMode)
- validateProjectionSchema(schema, true);
- // если данные не являются объектом (null, undefined, примитив),
- // то значение возвращается без изменений
- if (data === null || typeof data !== 'object') {
- return data;
- }
- // если данные являются массивом, то проекция
- // применяется к каждому элементу
- if (Array.isArray(data)) {
- return data.map(item => projectData(schema, item, options));
- }
- // если данные являются объектом,
- // то создается проекция согласно схеме
- const result = {};
- // в обычном режиме итерация выполняется по ключам исходного
- // объекта, а в строгом режиме по ключам, описанным в схеме
- // (исключая ключи прототипа Object.keys(x))
- const keys = Object.keys(strict ? schema : data);
- for (const key of keys) {
- // если свойство отсутствует в исходных
- // данных, то свойство игнорируется
- if (!(key in data)) continue;
- const propOptionsOrBoolean = schema[key];
- // проверка доступности свойства для данной
- // области проекции (если определена)
- if (_shouldSelect(propOptionsOrBoolean, strict, scope)) {
- const value = data[key];
- // если определена вложенная схема,
- // то проекция применяется рекурсивно
- if (
- propOptionsOrBoolean &&
- typeof propOptionsOrBoolean === 'object' &&
- propOptionsOrBoolean.schema
- ) {
- result[key] = projectData(propOptionsOrBoolean.schema, value, options);
- }
- // иначе значение присваивается
- // свойству без изменений
- else {
- result[key] = value;
- }
- }
- }
- return result;
- }
- /**
- * Should select (internal).
- *
- * Определяет, следует ли включать свойство в результат.
- * Приоритет: правило для области -> общее правило -> по умолчанию true.
- *
- * @param {object|boolean|undefined} propOptionsOrBoolean
- * @param {boolean|undefined} strict
- * @param {string|undefined} scope
- * @returns {boolean}
- */
- function _shouldSelect(propOptionsOrBoolean, strict, scope) {
- // если настройки свойства являются логическим значением,
- // то значение используется как индикатор видимости
- if (typeof propOptionsOrBoolean === 'boolean') {
- return propOptionsOrBoolean;
- }
- // если настройки свойства являются объектом,
- // то проверяется правило области и общее правило
- if (typeof propOptionsOrBoolean === 'object') {
- const propOptions = propOptionsOrBoolean;
- // если определена область проекции,
- // то выполняется проверка правила области
- if (
- scope &&
- propOptions.scopes &&
- typeof propOptions.scopes === 'object' &&
- propOptions.scopes[scope] != null
- ) {
- const scopeOptionsOrBoolean = propOptions.scopes[scope];
- // если настройки области являются логическим значением,
- // то значение используется как индикатор видимости
- if (typeof scopeOptionsOrBoolean === 'boolean') {
- return scopeOptionsOrBoolean;
- }
- // если настройки области являются объектом,
- // то используется опция select
- if (
- scopeOptionsOrBoolean &&
- typeof scopeOptionsOrBoolean === 'object' &&
- typeof scopeOptionsOrBoolean.select === 'boolean'
- ) {
- return scopeOptionsOrBoolean.select;
- }
- }
- // если область проекции не указана,
- // то проверяется общее правило
- if (typeof propOptionsOrBoolean.select === 'boolean') {
- return propOptionsOrBoolean.select;
- }
- }
- // если для свойства нет правил, то свойство
- // по умолчанию доступно (недоступно в режиме strict)
- return !strict;
- }
|