Просмотр исходного кода

feat: allows name resolver return name and factory

e22m4u 1 месяц назад
Родитель
Сommit
96ca394f46

+ 7 - 4
dist/cjs/index.cjs

@@ -175,12 +175,15 @@ function projectData(data, schema, options) {
       );
     }
     schema = options.nameResolver(schema);
-    if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
+    if (!schema || typeof schema !== "object" && typeof schema !== "function" && typeof schema !== "string" || Array.isArray(schema)) {
       throw new import_js_format2.InvalidArgumentError(
-        "Name resolver must return an Object, but %v was given.",
+        "Name resolver must return an Object, a Function or a non-empty String, but %v was given.",
         schema
       );
     }
+    if (typeof schema === "function" || typeof schema === "string") {
+      return projectData(data, schema, options);
+    }
   }
   validateProjectionSchema(schema, true);
   if (data == null || typeof data !== "object") {
@@ -252,9 +255,9 @@ function validateProjectionSchemaDefinition(schemaDef) {
       schemaDef.name
     );
   }
-  if (!schemaDef.schema || typeof schemaDef.schema !== "object" || Array.isArray(schemaDef.schema)) {
+  if (!schemaDef.schema || typeof schemaDef.schema !== "object" && typeof schemaDef.schema !== "function" && typeof schemaDef.schema !== "string" || Array.isArray(schemaDef.schema)) {
     throw new import_js_format3.InvalidArgumentError(
-      'Definition option "schema" must be an Object, but %v was given.',
+      'Definition option "schema" must be an Object, a Function or a non-empty String, but %v was given.',
       schemaDef.schema
     );
   }

+ 16 - 3
src/project-data.js

@@ -89,13 +89,26 @@ export function projectData(data, schema, options) {
     }
     schema = options.nameResolver(schema);
     // если результат разрешающей функции не является
-    // объектом, то выбрасывается ошибка
-    if (!schema || typeof schema !== 'object' || Array.isArray(schema)) {
+    // объектом, функцией или строкой, то выбрасывается
+    // ошибка
+    if (
+      !schema ||
+      (typeof schema !== 'object' &&
+        typeof schema !== 'function' &&
+        typeof schema !== 'string') ||
+      Array.isArray(schema)
+    ) {
       throw new InvalidArgumentError(
-        'Name resolver must return an Object, but %v was given.',
+        'Name resolver must return an Object, a Function ' +
+          'or a non-empty String, but %v was given.',
         schema,
       );
     }
+    // если разрешающая функция вернула фабрику
+    // или строку, то выполняется рекурсия
+    if (typeof schema === 'function' || typeof schema === 'string') {
+      return projectData(data, schema, options);
+    }
   }
   // после нормализации схемы в объект,
   // выполняется поверхностная проверка

+ 101 - 3
src/project-data.spec.js

@@ -184,8 +184,11 @@ describe('projectData', function () {
     const throwable = v => () =>
       projectData({}, 'mySchema', {nameResolver: () => v});
     const error = s =>
-      format('Name resolver must return an Object, but %s was given.', s);
-    expect(throwable('str')).to.throw(error('"str"'));
+      format(
+        'Name resolver must return an Object, a Function ' +
+          'or a non-empty String, but %s was given.',
+        s,
+      );
     expect(throwable('')).to.throw(error('""'));
     expect(throwable(10)).to.throw(error('10'));
     expect(throwable(0)).to.throw(error('0'));
@@ -194,7 +197,6 @@ describe('projectData', function () {
     expect(throwable([])).to.throw(error('Array'));
     expect(throwable(undefined)).to.throw(error('undefined'));
     expect(throwable(null)).to.throw(error('null'));
-    expect(throwable(() => undefined)).to.throw(error('Function'));
     throwable({})();
   });
 
@@ -228,6 +230,102 @@ describe('projectData', function () {
     expect(invoked).to.be.eq(2);
   });
 
+  it('should resolve the schema object through the name chain', function () {
+    let invoked = 0;
+    const nameResolver = name => {
+      invoked++;
+      if (name === 'schema1') {
+        return 'schema2';
+      } else if (name === 'schema2') {
+        return {foo: true, bar: false};
+      }
+      throw new Error('Invalid argument.');
+    };
+    const res = projectData({foo: 10, bar: 20}, 'schema1', {nameResolver});
+    expect(res).to.be.eql({foo: 10});
+    expect(invoked).to.be.eq(2);
+  });
+
+  it('should validate a resolved value from the name chain', function () {
+    const nameResolver = v => name => {
+      if (name === 'schema1') {
+        return 'schema2';
+      } else if (name === 'schema2') {
+        return v;
+      } else if (name === 'schema3') {
+        return {};
+      }
+      throw new Error('Invalid argument.');
+    };
+    const throwable = v => () =>
+      projectData({foo: 10, bar: 20}, 'schema1', {
+        nameResolver: nameResolver(v),
+      });
+    const error = s =>
+      format(
+        'Name resolver must return an Object, a Function ' +
+          'or a non-empty String, but %s was given.',
+        s,
+      );
+    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([])).to.throw(error('Array'));
+    expect(throwable(undefined)).to.throw(error('undefined'));
+    expect(throwable(null)).to.throw(error('null'));
+    throwable('schema3')();
+  });
+
+  it('should resolve schema names through the factory function', function () {
+    let invoked = 0;
+    const nameResolver = name => {
+      invoked++;
+      if (name === 'schema1') {
+        return () => 'schema2';
+      } else if (name === 'schema2') {
+        return {foo: true, bar: false};
+      }
+      throw new Error('Invalid argument.');
+    };
+    const res = projectData({foo: 10, bar: 20}, 'schema1', {nameResolver});
+    expect(res).to.be.eql({foo: 10});
+    expect(invoked).to.be.eq(2);
+  });
+
+  it('should validate a return value of the factory resolved through the name chain', function () {
+    const nameResolver = v => name => {
+      if (name === 'schema1') {
+        return 'schema2';
+      } else if (name === 'schema2') {
+        return () => v;
+      } else if (name === 'schema3') {
+        return {};
+      }
+      throw new Error('Invalid argument.');
+    };
+    const throwable = v => () =>
+      projectData({foo: 10, bar: 20}, 'schema1', {
+        nameResolver: nameResolver(v),
+      });
+    const error = s =>
+      format(
+        'Schema factory must return an Object ' +
+          'or a non-empty String, but %s was given.',
+        s,
+      );
+    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([])).to.throw(error('Array'));
+    expect(throwable(undefined)).to.throw(error('undefined'));
+    expect(throwable(null)).to.throw(error('null'));
+    throwable('schema3')();
+  });
+
   it('should validate the given schema in the shallow mode', function () {
     const schema1 = {foo: '?'};
     const schema2 = {foo: true, bar: {schema: {baz: '?'}}};

+ 5 - 2
src/validate-projection-schema-definition.js

@@ -21,11 +21,14 @@ export function validateProjectionSchemaDefinition(schemaDef) {
   }
   if (
     !schemaDef.schema ||
-    typeof schemaDef.schema !== 'object' ||
+    (typeof schemaDef.schema !== 'object' &&
+      typeof schemaDef.schema !== 'function' &&
+      typeof schemaDef.schema !== 'string') ||
     Array.isArray(schemaDef.schema)
   ) {
     throw new InvalidArgumentError(
-      'Definition option "schema" must be an Object, but %v was given.',
+      'Definition option "schema" must be an Object, a Function ' +
+        'or a non-empty String, but %v was given.',
       schemaDef.schema,
     );
   }

+ 9 - 4
src/validate-projection-schema-definition.spec.js

@@ -32,6 +32,8 @@ describe('validateProjectionSchemaDefinition', function () {
     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([])).to.throw(error('Array'));
     expect(throwable({})).to.throw(error('Object'));
     expect(throwable(null)).to.throw(error('null'));
@@ -40,22 +42,25 @@ describe('validateProjectionSchemaDefinition', function () {
     throwable('str')();
   });
 
-  it('should require the "schema" option to be an object', function () {
+  it('should require the "schema" option to be a valid value', function () {
     const throwable = v => () =>
       validateProjectionSchemaDefinition({name: 'mySchema', schema: v});
     const error = s =>
       format(
-        'Definition option "schema" must be an Object, but %s was given.',
+        'Definition option "schema" must be an Object, a Function ' +
+          'or a non-empty String, but %s was given.',
         s,
       );
-    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([])).to.throw(error('Array'));
     expect(throwable(null)).to.throw(error('null'));
     expect(throwable(undefined)).to.throw(error('undefined'));
-    expect(throwable(() => ({}))).to.throw(error('Function'));
+    throwable('str')();
     throwable({})();
+    throwable(() => ({}))();
   });
 });