| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648 |
- import {expect} from 'chai';
- import {DataType} from './data-type.js';
- import {format} from '@e22m4u/js-format';
- import {DataParser} from './data-parser.js';
- import {DataSchemaRegistry} from './data-schema-registry.js';
- import {
- arrayTypeParser,
- numberTypeParser,
- objectTypeParser,
- stringTypeParser,
- booleanTypeParser,
- } from './data-parsers/index.js';
- describe('DataParser', function () {
- describe('getParsers', function () {
- it('should return a default parser list', function () {
- const S = new DataParser();
- const res = S.getParsers();
- expect(res).to.be.eql([
- stringTypeParser,
- booleanTypeParser,
- numberTypeParser,
- arrayTypeParser,
- objectTypeParser,
- ]);
- });
- it('should return a modified parser list', function () {
- const S = new DataParser();
- const parser1 = () => undefined;
- const parser2 = () => undefined;
- S.setParsers([parser1, parser2]);
- const res = S.getParsers();
- expect(res).to.be.eql([parser1, parser2]);
- });
- });
- describe('setParsers', function () {
- it('should require a given value to be an array', function () {
- const S = new DataParser();
- const throwable = v => () => S.setParsers(v);
- const error = s =>
- format('Data parsers must be an Array, 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('Object'));
- expect(throwable(null)).to.throw(error('null'));
- expect(throwable(undefined)).to.throw(error('undefined'));
- throwable([])();
- });
- it('should require given parsers to be a function', function () {
- const S = new DataParser();
- const throwable = v => () => S.setParsers([v]);
- const error = s =>
- format('Data parser must be a Function, 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({})).to.throw(error('Object'));
- expect(throwable(null)).to.throw(error('null'));
- expect(throwable(undefined)).to.throw(error('undefined'));
- throwable(() => undefined)();
- });
- it('should set the parsers list', function () {
- const S = new DataParser();
- const parser1 = () => undefined;
- const parser2 = () => undefined;
- S.setParsers([parser1, parser2]);
- const res1 = S.getParsers();
- expect(res1).to.be.eql([parser1, parser2]);
- });
- it('should able to clean the parser list', function () {
- const S = new DataParser();
- const parser1 = () => undefined;
- const parser2 = () => undefined;
- S.setParsers([parser1, parser2]);
- const res1 = S.getParsers();
- expect(res1).to.be.eql([parser1, parser2]);
- S.setParsers([]);
- const res2 = S.getParsers();
- expect(res2).to.be.eql([]);
- });
- });
- describe('defineSchema', function () {
- it('should pass a given schema definition to the registry', function () {
- const S = new DataParser();
- const registry = S.getService(DataSchemaRegistry);
- const schemaDef = {name: 'mySchema', schema: {}};
- S.defineSchema(schemaDef);
- const res = registry.getDefinition(schemaDef.name);
- expect(res).to.be.eql(schemaDef);
- });
- it('should return a current instance', function () {
- const S = new DataParser();
- const schemaDef = {name: 'mySchema', schema: {}};
- const res = S.defineSchema(schemaDef);
- expect(res).to.be.eq(S);
- });
- });
- describe('hasSchema', function () {
- it('should return true if a given name is registered', function () {
- const S = new DataParser();
- const schemaDef = {name: 'mySchema', schema: {}};
- expect(S.hasSchema(schemaDef.name)).to.be.false;
- S.defineSchema(schemaDef);
- expect(S.hasSchema(schemaDef.name)).to.be.true;
- });
- });
- describe('getSchema', function () {
- it('should return a register schema for a given name', function () {
- const S = new DataParser();
- const schemaDef = {name: 'mySchema', schema: {}};
- S.defineSchema(schemaDef);
- const res = S.getSchema(schemaDef.name);
- expect(res).to.be.eql(schemaDef.schema);
- });
- it('should throw an error if a given name is not registered', function () {
- const S = new DataParser();
- const throwable = () => S.getSchema('mySchema');
- expect(throwable).to.throw('Data schema "mySchema" is not found.');
- });
- });
- describe('parse', function () {
- it('should require the "options" argument to be an object', function () {
- const S = new DataParser();
- const throwable = v => () => S.parse(10, {}, v);
- const error = s =>
- format('Parsing options must be an Object, 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('Function'));
- throwable({})();
- throwable(undefined)();
- });
- it('should require the "sourcePath" argument to be a non-empty string', function () {
- const S = new DataParser();
- const throwable = v => () => S.parse(10, {}, {sourcePath: v});
- const error = s =>
- format(
- 'Option "sourcePath" must be 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({})).to.throw(error('Object'));
- expect(throwable(null)).to.throw(error('null'));
- expect(throwable(() => undefined)).to.throw(error('Function'));
- throwable('str')();
- throwable(undefined)();
- });
- it('should require the "shallowMode" argument to be a boolean', function () {
- const S = new DataParser();
- const throwable = v => () => S.parse(10, {}, {shallowMode: v});
- const error = s =>
- format('Option "shallowMode" must be a Boolean, 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([])).to.throw(error('Array'));
- expect(throwable({})).to.throw(error('Object'));
- expect(throwable(null)).to.throw(error('null'));
- expect(throwable(() => undefined)).to.throw(error('Function'));
- throwable(true)();
- throwable(false)();
- throwable(undefined)();
- });
- it('should require the "noParsingErrors" argument to be a boolean', function () {
- const S = new DataParser();
- const throwable = v => () => S.parse(10, {}, {noParsingErrors: v});
- const error = s =>
- format(
- 'Option "noParsingErrors" must be a Boolean, 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([])).to.throw(error('Array'));
- expect(throwable({})).to.throw(error('Object'));
- expect(throwable(null)).to.throw(error('null'));
- expect(throwable(() => undefined)).to.throw(error('Function'));
- throwable(true)();
- throwable(false)();
- throwable(undefined)();
- });
- it('should validate a given schema in the shallow mode', function () {
- const S = new DataParser();
- const throwable = () => S.parse(10, {required: 10});
- expect(throwable).to.throw(
- 'Schema option "required" must be a Boolean, but 10 was given.',
- );
- S.parse([], {type: DataType.ARRAY, items: {type: 10}});
- });
- it('should resolve the data schema from a given factory', function () {
- const S = new DataParser();
- S.setParsers([stringTypeParser]);
- const res = S.parse(10, () => ({type: DataType.STRING}));
- expect(res).to.be.eq('10');
- });
- it('should resolve the data schema from a schema name', function () {
- const S = new DataParser();
- S.setParsers([stringTypeParser]);
- S.getService(DataSchemaRegistry).defineSchema({
- name: 'mySchema',
- schema: {type: DataType.STRING},
- });
- const res = S.parse(10, 'mySchema');
- expect(res).to.be.eq('10');
- });
- it('should resolve a schema name from a given factory', function () {
- const S = new DataParser();
- S.setParsers([stringTypeParser]);
- S.getService(DataSchemaRegistry).defineSchema({
- name: 'mySchema',
- schema: {type: DataType.STRING},
- });
- const res = S.parse(10, () => 'mySchema');
- expect(res).to.be.eq('10');
- });
- it('should resolve a schema factory from a named schema', function () {
- const S = new DataParser();
- S.setParsers([stringTypeParser]);
- S.getService(DataSchemaRegistry).defineSchema({
- name: 'mySchema',
- schema: () => ({type: DataType.STRING}),
- });
- const res = S.parse(10, 'mySchema');
- expect(res).to.be.eq('10');
- });
- it('should pass specific arguments to data parsers', function () {
- const S = new DataParser();
- const value = 10;
- const schema = {type: DataType.NUMBER};
- const options = {sourcePath: 'mySource'};
- let invoked = 0;
- const parser = (...args) => {
- invoked++;
- expect(args).to.be.eql([value, schema, options, S.container]);
- return args[0];
- };
- S.setParsers([parser]);
- const res = S.parse(value, schema, options);
- expect(res).to.be.eq(value);
- expect(invoked).to.be.eq(1);
- });
- it('should propagate an error from the data parser', function () {
- const S = new DataParser();
- const parser = () => {
- throw new Error('Caught!');
- };
- S.setParsers([parser]);
- const throwable = () => S.parse(10, {});
- expect(throwable).to.throw('Caught!');
- });
- it('should apply parsers sequentially to a given value', function () {
- const S = new DataParser();
- const value = 'a';
- S.setParsers([v => v + 'b', v => v + 'c']);
- const res = S.parse(value, {});
- expect(res).to.be.eq('abc');
- });
- it('should not parse array items in the shallow mode even if the items schema is provided', function () {
- const S = new DataParser();
- const value = [1, 2, 3];
- const schema = {
- type: DataType.ARRAY,
- items: {type: DataType.STRING},
- };
- S.setParsers([stringTypeParser]);
- const res = S.parse(value, schema, {shallowMode: true});
- expect(res).to.be.eql(value);
- });
- it('should not parse array items when the items schema is not provided', function () {
- const S = new DataParser();
- const value = [1, 2, 3];
- const schema = {type: DataType.ARRAY};
- let invoked = 0;
- const parser = (...args) => {
- invoked++;
- expect(args[0]).to.be.eql(value);
- return args[0];
- };
- S.setParsers([parser]);
- const res = S.parse(value, schema);
- expect(res).to.be.eql(value);
- expect(invoked).to.be.eq(1);
- });
- it('should parse array items when the array schema is provided', function () {
- const S = new DataParser();
- const value = [1, 2, 3];
- const schema = {
- type: DataType.ARRAY,
- items: {type: DataType.STRING},
- };
- const expectedCalls = [
- [value, schema, undefined, S.container],
- [1, schema.items, {sourcePath: 'array[0]'}, S.container],
- [2, schema.items, {sourcePath: 'array[1]'}, S.container],
- [3, schema.items, {sourcePath: 'array[2]'}, S.container],
- ];
- const calls = [];
- const parser = (...args) => {
- calls.push(args);
- return Array.isArray(args[0]) ? args[0] : String(args[0]);
- };
- S.setParsers([parser]);
- const res = S.parse(value, schema);
- expect(res).to.be.eql(['1', '2', '3']);
- expect(expectedCalls).to.be.eql(calls);
- });
- it('should add an array index to a provided source path', function () {
- const S = new DataParser();
- const value = [1, 2, 3];
- const schema = {
- type: DataType.ARRAY,
- items: {type: DataType.STRING},
- };
- const options = {sourcePath: 'mySource'};
- const expectedCalls = [
- [value, schema, options, S.container],
- [1, schema.items, {sourcePath: 'mySource[0]'}, S.container],
- [2, schema.items, {sourcePath: 'mySource[1]'}, S.container],
- [3, schema.items, {sourcePath: 'mySource[2]'}, S.container],
- ];
- const calls = [];
- const parser = (...args) => {
- calls.push(args);
- return Array.isArray(args[0]) ? args[0] : String(args[0]);
- };
- S.setParsers([parser]);
- const res = S.parse(value, schema, options);
- expect(res).to.be.eql(['1', '2', '3']);
- expect(expectedCalls).to.be.eql(calls);
- });
- it('should resolve a schema factory from the "items" option', function () {
- const S = new DataParser();
- S.setParsers([stringTypeParser]);
- const factory = () => ({type: DataType.STRING});
- const schema = {type: DataType.ARRAY, items: factory};
- const res = S.parse([10], schema);
- expect(res).to.be.eql(['10']);
- });
- it('should resolve a schema name from the "items" option', function () {
- const S = new DataParser();
- S.setParsers([stringTypeParser]);
- const schemaA = {
- type: DataType.ARRAY,
- items: 'schemaB',
- };
- S.getService(DataSchemaRegistry).defineSchema({
- name: 'schemaB',
- schema: {type: DataType.STRING},
- });
- const res = S.parse([10], schemaA);
- expect(res).to.be.eql(['10']);
- });
- it('should not parse object properties in the shallow mode even if the properties schema is provided', function () {
- const S = new DataParser();
- const value = {p1: 1, p2: 2, p3: 3};
- const schema = {
- type: DataType.OBJECT,
- properties: {
- p1: {type: DataType.STRING},
- p2: {type: DataType.STRING},
- p3: {type: DataType.STRING},
- },
- };
- S.setParsers([stringTypeParser]);
- const res = S.parse(value, schema, {shallowMode: true});
- expect(res).to.be.eql(value);
- });
- it('should not parse object properties when the properties schema is not provided', function () {
- const S = new DataParser();
- const value = {p1: 1, p2: 2, p3: 3};
- const schema = {type: DataType.OBJECT};
- let invoked = 0;
- const parser = (...args) => {
- invoked++;
- expect(args).to.be.eql([value, schema, undefined, S.container]);
- return args[0];
- };
- S.setParsers([parser]);
- const res = S.parse(value, schema);
- expect(res).to.be.eql(value);
- expect(invoked).to.be.eq(1);
- });
- it('should parse object properties when the properties schema is provided', function () {
- const S = new DataParser();
- const value = {p1: 1, p2: 2, p3: 3};
- const schema = {
- type: DataType.OBJECT,
- properties: {
- p1: {type: DataType.STRING},
- p2: {type: DataType.STRING},
- p3: {type: DataType.STRING},
- },
- };
- const expectedCalls = [
- [value, schema, undefined, S.container],
- [1, schema.properties.p1, {sourcePath: 'p1'}, S.container],
- [2, schema.properties.p2, {sourcePath: 'p2'}, S.container],
- [3, schema.properties.p3, {sourcePath: 'p3'}, S.container],
- ];
- const calls = [];
- const parser = (...args) => {
- calls.push(args);
- return typeof args[0] === 'object' ? args[0] : String(args[0]);
- };
- S.setParsers([parser]);
- const res = S.parse(value, schema);
- expect(res).to.be.eql({p1: '1', p2: '2', p3: '3'});
- expect(expectedCalls).to.be.eql(calls);
- });
- it('should apply parsers sequentially to a given object and its properties', function () {
- const S = new DataParser();
- const value = {p1: 'a', p2: 'b', p3: 'c'};
- const schema = {
- type: DataType.OBJECT,
- properties: {
- p1: {type: DataType.STRING},
- p2: {type: DataType.STRING},
- p3: {type: DataType.STRING},
- },
- };
- const parser = suffix => value => {
- return typeof value === 'string' ? value + suffix : value;
- };
- S.setParsers([parser('b'), parser('c')]);
- const res = S.parse(value, schema);
- expect(res).to.be.eql({p1: 'abc', p2: 'bbc', p3: 'cbc'});
- });
- it('should not parse object properties without a specified schema', function () {
- const S = new DataParser();
- const value = {p1: 1, p2: 2, p3: 3};
- const schema = {
- type: DataType.OBJECT,
- properties: {
- p1: {type: DataType.NUMBER},
- },
- };
- const expectedCalls = [
- [value, schema, undefined, S.container],
- [value.p1, schema.properties.p1, {sourcePath: 'p1'}, S.container],
- ];
- const calls = [];
- const parser = (...args) => {
- calls.push(args);
- return args[0];
- };
- S.setParsers([parser]);
- const res = S.parse(value, schema);
- expect(res).to.be.eql(value);
- expect(expectedCalls).to.be.eql(calls);
- });
- it('should add property name to a provided source path', function () {
- const S = new DataParser();
- const value = {p1: 1, p2: 2, p3: 3};
- const schema = {
- type: DataType.OBJECT,
- properties: {
- p1: {type: DataType.STRING},
- p2: {type: DataType.STRING},
- p3: {type: DataType.STRING},
- },
- };
- const options = {sourcePath: 'mySource'};
- const expectedCalls = [
- [value, schema, options, S.container],
- [1, schema.properties.p1, {sourcePath: 'mySource.p1'}, S.container],
- [2, schema.properties.p2, {sourcePath: 'mySource.p2'}, S.container],
- [3, schema.properties.p3, {sourcePath: 'mySource.p3'}, S.container],
- ];
- const calls = [];
- const parser = (...args) => {
- calls.push(args);
- return typeof args[0] === 'object' ? args[0] : String(args[0]);
- };
- S.setParsers([parser]);
- const res = S.parse(value, schema, options);
- expect(res).to.be.eql({p1: '1', p2: '2', p3: '3'});
- expect(expectedCalls).to.be.eql(calls);
- });
- it('should resolve a schema factory from the "properties" option', function () {
- const S = new DataParser();
- S.setParsers([stringTypeParser]);
- const factory = () => ({
- type: DataType.OBJECT,
- properties: {
- prop: {type: DataType.STRING},
- },
- });
- const schema = {
- type: DataType.OBJECT,
- properties: factory,
- };
- const res = S.parse({prop: 10}, schema);
- expect(res).to.be.eql({prop: '10'});
- });
- it('should resolve a schema name from the "properties" option', function () {
- const S = new DataParser();
- S.setParsers([stringTypeParser]);
- const schemaA = {
- type: DataType.OBJECT,
- properties: 'schemaB',
- };
- S.getService(DataSchemaRegistry).defineSchema({
- name: 'schemaB',
- schema: {
- type: DataType.OBJECT,
- properties: {
- prop: {type: DataType.STRING},
- },
- },
- });
- const res = S.parse({prop: 10}, schemaA);
- expect(res).to.be.eql({prop: '10'});
- });
- it('should throw an error if a properties schema from the schema factory is a non-array schema', function () {
- const S = new DataParser();
- const factory = () => ({type: DataType.BOOLEAN});
- const schema = {
- type: DataType.OBJECT,
- properties: factory,
- };
- const throwable = () => S.parse({prop: 10}, schema);
- expect(throwable).to.throw(
- 'Unable to get the "properties" option ' +
- 'from the data schema of "boolean" type.',
- );
- });
- it('should throw an error if a properties schema from the schema name is a non-array schema', function () {
- const S = new DataParser();
- const schemaA = {
- type: DataType.OBJECT,
- properties: 'schemaB',
- };
- S.getService(DataSchemaRegistry).defineSchema({
- name: 'schemaB',
- schema: {type: DataType.BOOLEAN},
- });
- const throwable = () => S.parse({prop: 10}, schemaA);
- expect(throwable).to.throw(
- 'Unable to get the "properties" option ' +
- 'from the data schema of "boolean" type.',
- );
- });
- it('should resolve a schema factory from the property schema', function () {
- const S = new DataParser();
- S.setParsers([stringTypeParser]);
- const factory = () => ({type: DataType.STRING});
- const schema = {
- type: DataType.OBJECT,
- properties: {prop: factory},
- };
- const res = S.parse({prop: 10}, schema);
- expect(res).to.be.eql({prop: '10'});
- });
- it('should resolve a schema name from the property schema', function () {
- const S = new DataParser();
- S.setParsers([stringTypeParser]);
- const schemaA = {
- type: DataType.OBJECT,
- properties: {prop: 'schemaB'},
- };
- S.getService(DataSchemaRegistry).defineSchema({
- name: 'schemaB',
- schema: {type: DataType.STRING},
- });
- const res = S.parse({prop: 10}, schemaA);
- expect(res).to.be.eql({prop: '10'});
- });
- it('should not set undefined value to a non existent property', function () {
- const S = new DataParser();
- const parser = value => value;
- S.setParsers([parser]);
- const value = {p1: 'str'};
- const schema = {
- type: DataType.OBJECT,
- properties: {
- p1: {type: DataType.ANY},
- p2: {type: DataType.ANY},
- },
- };
- const res = S.parse(value, schema);
- expect(res).to.be.eql(value);
- });
- });
- });
|