diff --git a/README.md b/README.md index de49a6c..f83607b 100644 --- a/README.md +++ b/README.md @@ -486,6 +486,19 @@ check({ roles: ["user", "admin", "user"] }); // Fail check({ roles: [1, 2, 1] }); // Fail ``` +**Example for `convert`:** + +```js +const schema = { + roles: { type: "array", items: 'string', convert: true } +} +const check = v.compile(schema); + +check({ roles: ["user"] }); // Valid +check({ roles: "user" }); // Valid +// After both validation: roles = ["user"] +``` + ### Properties Property | Default | Description -------- | -------- | ----------- @@ -497,6 +510,7 @@ Property | Default | Description `unique` | `null` | The array must be unique (array of objects is always unique). `enum` | `null` | Every element must be an element of the `enum` array. `items` | `null` | Schema for array items. +`convert`| `null` | Wrap value into array if different type provided ## `boolean` This is a `Boolean` validator. diff --git a/lib/rules/array.js b/lib/rules/array.js index fdf43c3..bf64945 100644 --- a/lib/rules/array.js +++ b/lib/rules/array.js @@ -5,6 +5,17 @@ module.exports = function ({ schema, messages }, path, context) { const src = []; + let sanitized = false; + if (schema.convert === true) { + sanitized = true; + // Convert to array if not and the value is not null or undefined + src.push(` + if (!Array.isArray(value) && value != null) { + value = [value]; + } + `); + } + src.push(` if (!Array.isArray(value)) { ${this.makeError({ type: "array", actual: "value", messages })} @@ -99,6 +110,7 @@ module.exports = function ({ schema, messages }, path, context) { } return { + sanitized, source: src.join("\n") }; }; diff --git a/test/rules/array.spec.js b/test/rules/array.spec.js index 10ed7e2..93be05d 100644 --- a/test/rules/array.spec.js +++ b/test/rules/array.spec.js @@ -191,5 +191,45 @@ describe("Test rule: array", () => { expect(errors[0].type).toBe("evenNumber"); expect(o.a).toEqual([2, 4, 8]); }); + + describe("conversion behavior", () => { + // !! Don't use a $$root schema because the value to check will not be passed as reference but as value + const check = v.compile({ data: { type: "array", items: "string", convert: true } }); + // Single value check + it ("should wrap single value into array", () => { + const value = { data: "John" }; + expect(check(value)).toEqual(true); + expect(value.data).toEqual(["John"]); + }); + // Already array, one element + it ("should not change array with one element", () => { + const value = { data: ["John"] }; + expect(check(value)).toEqual(true); + expect(value.data).toEqual(["John"]); + }); + // Already array, multiple elements + it ("should not change array with multiple elements", () => { + const value = { data: ["John", "Jane"] }; + expect(check(value)).toEqual(true); + expect(value.data).toEqual(["John", "Jane"]); + }); + // Empty array + it ("should not change empty array", () => { + const value = { data: [] }; + expect(check(value)).toEqual(true); + expect(value.data).toEqual([]); + }); + // Null/undefined + it ("should not convert into array if null or undefined", () => { + // Null check + const value = { data: null }; + expect(check(value)).toEqual([{ type: "required", field: "data", actual: null, message: "The 'data' field is required." }]); + expect(value.data).toEqual(null); + // Undefined check + const value2 = { data: undefined }; + expect(check(value2)).toEqual([{ type: "required", field: "data", actual: undefined, message: "The 'data' field is required." }]); + expect(value2.data).toEqual(undefined); + }); + }); }); }); diff --git a/test/typescript/rules/array.spec.ts b/test/typescript/rules/array.spec.ts index 8a4287a..71725ab 100644 --- a/test/typescript/rules/array.spec.ts +++ b/test/typescript/rules/array.spec.ts @@ -129,5 +129,44 @@ describe('TypeScript Definitions', () => { }); }); + describe("conversion behavior", () => { + // !! Don't use a $$root schema because the value to check will not be passed as reference but as value + const check = v.compile({ data: { type: "array", items: "string", convert: true } }); + // Single value check + it ("should wrap single value into array", () => { + const value = { data: "John" }; + expect(check(value)).toEqual(true); + expect(value.data).toEqual(["John"]); + }); + // Already array, one element + it ("should not change array with one element", () => { + const value = { data: ["John"] }; + expect(check(value)).toEqual(true); + expect(value.data).toEqual(["John"]); + }); + // Already array, multiple elements + it ("should not change array with multiple elements", () => { + const value = { data: ["John", "Jane"] }; + expect(check(value)).toEqual(true); + expect(value.data).toEqual(["John", "Jane"]); + }); + // Empty array + it ("should not change empty array", () => { + const value = { data: [] }; + expect(check(value)).toEqual(true); + expect(value.data).toEqual([]); + }); + // Null/undefined + it ("should not convert into array if null or undefined", () => { + // Null check + const value = { data: null }; + expect(check(value)).toEqual([{ type: "required", field: "data", actual: null, message: "The 'data' field is required." }]); + expect(value.data).toEqual(null); + // Undefined check + const value2 = { data: undefined }; + expect(check(value2)).toEqual([{ type: "required", field: "data", actual: undefined, message: "The 'data' field is required." }]); + expect(value2.data).toEqual(undefined); + }); + }); }); });