diff --git a/README.md b/README.md index 4539c13..ad1214d 100644 --- a/README.md +++ b/README.md @@ -217,7 +217,7 @@ export const auditor = new Auditor({ Options: - `items` _(required)_: `BaseType`; - > With this option you can specify the structure of every item stored in the array, using the same options described in the past types described. __You can declare nested arrays, or object arrays too.__ + > With this option you can specify the structure of every item stored in the array, using the same options described in the past types described. __You can declare nested arrays, object arrays, or nested dictionaries too.__ - `min` _(optional)_: `number`; > If the incoming array has a length __lower__ than the value setted, the `Auditor` instance will throws an `WrongLengthError` instance. - `max` _(optional)_: `number`; @@ -343,6 +343,34 @@ export const auditor = new Auditor({ }); ``` +### Type `record` +Options: +- `items` _(required)_: `BaseType`; + > With this option you can specify the structure of every item stored for each key inside of the, using the same options described in the past types described. __You can declare nested arrays, object arrays, or nested dictionaries too.__ + + ```ts + import { Auditor } from 'audit-var'; + + // The incoming data + const target = { + joder: { id: 666, nick: 'ghostlug' }, + shavo: { id: 666, nick: 'dir en grey' }, + }; + + // The auditor + const auditor = new Auditor({ + type: 'record', + items: { + type: 'object', + keys: { + id: { type: 'number' }, + nick: { type: 'string' } + } + } + }); + ``` + + ## Utilities ### `this.structure` Gets the actual structure of the current instance. Whith this you attach them to another more complex instance. diff --git a/package.json b/package.json index 3155daf..2841c14 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "audit-var", - "version": "2.1.0", + "version": "2.2.0", "type": "module", "description": "Inspects variables according to a defined structure", "main": "./dist/cjs/index.js", diff --git a/src/converters/record-conv.ts b/src/converters/record-conv.ts new file mode 100644 index 0000000..3a299e4 --- /dev/null +++ b/src/converters/record-conv.ts @@ -0,0 +1,24 @@ +import { InvalidTypeError, NotOptionalError } from '../errors/index.js'; +import { converterFunct, RecordType } from '../interfaces/index.js'; +import { recursiveConv } from './recursive-conv.js'; + +export const recordConv: converterFunct = (d, t, p) => { + if (t == null) { + if (!d.optional) { + throw new NotOptionalError(p); + } else { + return undefined; + } + } else if (typeof t !== 'object') { + throw new InvalidTypeError(d.type, p); + } else { + const resp: any = {}; + for (const key of Object.keys(t)) { + const path = [...p, key]; + const item = recursiveConv(d.items, t[key], path); + resp[key] = item; + } + + return resp; + } +}; \ No newline at end of file diff --git a/src/converters/recursive-conv.ts b/src/converters/recursive-conv.ts index a48eed9..0bdca07 100644 --- a/src/converters/recursive-conv.ts +++ b/src/converters/recursive-conv.ts @@ -3,6 +3,7 @@ import { booleanConv } from './boolean-conv.js'; import { numberConv } from './number-conv.js'; import { objectConv } from './object-conv.js'; import { stringConv } from './string-conv.js'; +import { recordConv } from './record-conv.js'; import { arrayConv } from './array-conv.js'; import { dateConv } from './date-conv.js'; @@ -26,6 +27,9 @@ export const recursiveConv: converterFunct = (d, t, p) => { case 'object': return objectConv(d, t, p); + case 'record': + return recordConv(d, t, p); + default: throw new Error('Unsupported type'); } diff --git a/src/interfaces/array-type.ts b/src/interfaces/array-type.ts index fcb4bde..b2ec457 100644 --- a/src/interfaces/array-type.ts +++ b/src/interfaces/array-type.ts @@ -1,8 +1,9 @@ import { BaseType } from './base-type.js'; import { NumberType } from './number-type.js'; import { StringType } from './string-type.js'; -import { BooleanType } from './boolean-type.js'; import { ObjectType } from './object-type.js'; +import { RecordType } from './record-type.js'; +import { BooleanType } from './boolean-type.js'; export interface ArrayType extends BaseType<'array'> { /** @@ -30,5 +31,6 @@ export interface ArrayType extends BaseType<'array'> { NumberType | StringType | ObjectType | + RecordType | BooleanType; } \ No newline at end of file diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index 6e33afb..3e40f09 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -6,6 +6,7 @@ export { converterFunct } from './converter-funct.js'; export { BaseType } from './base-type.js'; export { ArrayType } from './array-type.js'; export { ObjectType } from './object-type.js'; +export { RecordType } from './record-type.js'; export { DateType } from './date-type.js'; export { NumberType } from './number-type.js'; diff --git a/src/interfaces/object-type.ts b/src/interfaces/object-type.ts index fbb96d2..386f157 100644 --- a/src/interfaces/object-type.ts +++ b/src/interfaces/object-type.ts @@ -4,6 +4,7 @@ import { ArrayType } from './array-type.js'; import { DateType } from './date-type.js'; import { NumberType } from './number-type.js'; import { StringType } from './string-type.js'; +import { RecordType } from './record-type.js'; import { BooleanType } from './boolean-type.js'; export interface ObjectType extends BaseType<'object'> { @@ -14,7 +15,7 @@ export interface ObjectType extends BaseType<'object'> { string, ArrayType | ObjectType | - + RecordType | DateType | NumberType | StringType | diff --git a/src/interfaces/record-type.ts b/src/interfaces/record-type.ts new file mode 100644 index 0000000..c7f33fa --- /dev/null +++ b/src/interfaces/record-type.ts @@ -0,0 +1,22 @@ +import { BaseType } from './base-type.js'; +import { NumberType } from './number-type.js'; +import { StringType } from './string-type.js'; +import { BooleanType } from './boolean-type.js'; +import { ObjectType } from './object-type.js'; +import { ArrayType } from './array-type.js'; + +export interface RecordType extends BaseType<'record'> { + /** + * With this option you can specify the structure of every + * item stored in the record, using the same options described + * in the past types described. __You can declare nested arrays, + * or object arrays too.__ + */ + items: + ArrayType | + RecordType | + NumberType | + StringType | + ObjectType | + BooleanType; +} \ No newline at end of file diff --git a/src/interfaces/response-type.ts b/src/interfaces/response-type.ts index a66f4cd..6030f8b 100644 --- a/src/interfaces/response-type.ts +++ b/src/interfaces/response-type.ts @@ -7,6 +7,7 @@ import { DateType } from './date-type.js'; import { ArrayType } from './array-type.js'; import { StringType } from './string-type.js'; import { ObjectType } from './object-type.js'; +import { RecordType } from './record-type.js'; export type ResponseType = T extends DateType @@ -34,6 +35,11 @@ export type ResponseType = ? ResponseType[] | undefined : ResponseType[] + : T extends RecordType + ? T['optional'] extends true + ? Record> | undefined + : Record> + : T extends ObjectType ? T['optional'] extends true ? { [K in keyof T['keys']]: ResponseType } | undefined diff --git a/src/interfaces/types.ts b/src/interfaces/types.ts index 7ba44ca..a3ab9db 100644 --- a/src/interfaces/types.ts +++ b/src/interfaces/types.ts @@ -3,11 +3,13 @@ import { ArrayType } from './array-type.js'; import { ObjectType } from './object-type.js'; import { NumberType } from './number-type.js'; import { StringType } from './string-type.js'; +import { RecordType } from './record-type.js'; import { BooleanType } from './boolean-type.js'; export type Types = DateType | ArrayType | + RecordType | ObjectType | NumberType | StringType | diff --git a/src/tests/record.test.ts b/src/tests/record.test.ts new file mode 100644 index 0000000..3297431 --- /dev/null +++ b/src/tests/record.test.ts @@ -0,0 +1,58 @@ +import test from 'ava'; + +import { Auditor } from '../auditor.js'; +import { InvalidTypeError } from '../errors/index.js'; + +test('Test Record', t => { + const targetOk = { + foo: 'bar', + goo: 'baz' + }; + + const targetEr = { + foo: 'bar', + goo: 777 + }; + + const auditor = new Auditor({ + type: 'record', + items: { type: 'string' } + }); + + const result = auditor.audit(targetOk); + t.deepEqual(result, targetOk); + t.throws( + () => { auditor.audit(targetEr) }, + { instanceOf: InvalidTypeError } + ); +}); + +test('Test Record', t => { + const targetOk = { + joder: { id: 666, nick: 'ghostlug' }, + shavo: { id: 666, nick: 'dir en grey' }, + }; + + const targetEr = { + joder: { id: 'lol', nick: 'ghostlug' }, + shavo: { id: 666, nick: 'dir en grey' }, + }; + + const auditor = new Auditor({ + type: 'record', + items: { + type: 'object', + keys: { + id: { type: 'number' }, + nick: { type: 'string' } + } + } + }); + + const resp = auditor.audit(targetOk); + t.deepEqual(resp, targetOk); + t.throws( + () => { auditor.audit(targetEr) }, + { instanceOf: InvalidTypeError } + ); +}); \ No newline at end of file