Compare commits
7 Commits
9a46e52f93
...
v1.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
adf8c400bc | ||
|
|
ff891cd303 | ||
|
|
1ddb84234e | ||
|
|
741b96ee50 | ||
|
|
69a350c4da | ||
|
|
24810302b2 | ||
|
|
5fc32d7414 |
88
README.md
Normal file
88
README.md
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# valideno
|
||||||
|
|
||||||
|
Validate your values and schemas with **valideno**. Convert values to your desired type if needed. All you need is a single function.
|
||||||
|
|
||||||
|
`validate(data, validators, options = {})`;
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import {
|
||||||
|
ValidationError, validate, ArraySymbol,
|
||||||
|
isString, isEmail, fulfillsRegex,
|
||||||
|
isNumber, isInRange,
|
||||||
|
} from "./mod.ts";
|
||||||
|
|
||||||
|
const errorsA: ValidationError[] = await validate(3.14, isNumber());
|
||||||
|
console.log(errorsA);
|
||||||
|
// []
|
||||||
|
|
||||||
|
const errorsB: ValidationError[] = await validate(3.14, isString());
|
||||||
|
console.log(errorsB);
|
||||||
|
// [ { type: "isString", args: {}, message: "This value has to be a string." } ]
|
||||||
|
|
||||||
|
const errorsC: ValidationError[] = await validate(
|
||||||
|
"john.doe@example.com",
|
||||||
|
[isString(), isEmail()],
|
||||||
|
);
|
||||||
|
console.log(errorsC);
|
||||||
|
// []
|
||||||
|
|
||||||
|
const entity = {
|
||||||
|
name: "John Doe",
|
||||||
|
age: 25,
|
||||||
|
emails: [
|
||||||
|
{ type: "work", address: "john.doe@example.com" },
|
||||||
|
{ type: "home", address: "fancy_joe69@rainbow.com" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const scheme = {
|
||||||
|
name: isString(),
|
||||||
|
age: isInRange({ greaterThanOrEqualTo: 18 }),
|
||||||
|
emails: {
|
||||||
|
[ArraySymbol]: {
|
||||||
|
type: fulfillsRegex({ regex: /^[a-z]+$/ }),
|
||||||
|
address: isEmail(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const errorsD: ValidationError[] = await validate(entity, scheme);
|
||||||
|
console.log(errorsD);
|
||||||
|
// []
|
||||||
|
```
|
||||||
|
|
||||||
|
## Conversion
|
||||||
|
|
||||||
|
To allow automatic conversion to the desired data type add the `doConversion: true` option. To retreive the converted values, you need to pass a reference to an object to `converted`. If the conversion is not possible (e.g. when passing a `string` to `isBoolean()`) no conversion will take place and the value stays the same. In this case errors are thrown, because the non-converted `string` is not a Boolean.
|
||||||
|
|
||||||
|
The converted data is not in the reference passed to `converted` in the property `output`. This also works for schemes.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import {
|
||||||
|
ValidationError, validate,
|
||||||
|
isNumber
|
||||||
|
} from "./mod.ts";
|
||||||
|
|
||||||
|
const converted: any = {};
|
||||||
|
const errors: ValidationError[] = await validate("420", isNumber(), { doConversion: true, converted });
|
||||||
|
console.log(errors);
|
||||||
|
// []
|
||||||
|
console.log(converted);
|
||||||
|
// { output: 420 }
|
||||||
|
```
|
||||||
|
|
||||||
|
To disable validation and do "best effort" conversion only, disable validation with `doValidation: false`.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import {
|
||||||
|
ValidationError, validate,
|
||||||
|
isBoolean
|
||||||
|
} from "./mod.ts";
|
||||||
|
|
||||||
|
const converted: any = {};
|
||||||
|
const errors: ValidationError[] = await validate("foobar", isBoolean(), { doConversion: true, converted });
|
||||||
|
console.log(errors);
|
||||||
|
// [ { type: "isBoolean", args: {}, message: "This value has to be a boolean." } ]
|
||||||
|
console.log(converted);
|
||||||
|
// { output: "foobar" }
|
||||||
|
```
|
||||||
@@ -52,7 +52,10 @@ Deno.test("validate schema (match)", async () => {
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
for (const [value, constraints] of values) {
|
for (const [value, constraints] of values) {
|
||||||
assertEquals([], await validate(value, constraints));
|
const valueBefore = JSON.stringify(value);
|
||||||
|
assertEquals(await validate(value, constraints), [], String(value));
|
||||||
|
const valueAfter = JSON.stringify(value);
|
||||||
|
assertEquals(valueAfter, valueBefore);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -92,6 +95,73 @@ Deno.test("validate schema (no match)", async () => {
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
for (const [value, constraints] of values) {
|
for (const [value, constraints] of values) {
|
||||||
assertNotEquals([], await validate(value, constraints));
|
const valueBefore = JSON.stringify(value);
|
||||||
|
assertNotEquals(await validate(value, constraints), [], String(value));
|
||||||
|
const valueAfter = JSON.stringify(value);
|
||||||
|
assertEquals(valueAfter, valueBefore);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("validate doConversion (match)", async () => {
|
||||||
|
const values: [any, Validatable, any][] = [
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
isNumber(),
|
||||||
|
1,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"2",
|
||||||
|
isNumber(),
|
||||||
|
2,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"03",
|
||||||
|
isNumber(),
|
||||||
|
3,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ foo: 3, bar: { baz: "4", other: "val" } },
|
||||||
|
{ foo: isNumber(), bar: { baz: isNumber() } },
|
||||||
|
{ foo: 3, bar: { baz: 4 } },
|
||||||
|
],
|
||||||
|
];
|
||||||
|
for (const [value, constraints, conv] of values) {
|
||||||
|
const valueBefore = JSON.stringify(value);
|
||||||
|
const converted: any = {};
|
||||||
|
assertEquals(
|
||||||
|
await validate(value, constraints, { doConversion: true, converted }),
|
||||||
|
[],
|
||||||
|
String(value),
|
||||||
|
);
|
||||||
|
const valueAfter = JSON.stringify(value);
|
||||||
|
assertEquals(valueAfter, valueBefore);
|
||||||
|
assertEquals(converted.hasOwnProperty("output"), true);
|
||||||
|
assertEquals(converted?.output, conv);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("validate doConversion (no match)", async () => {
|
||||||
|
const values: [any, Validatable][] = [
|
||||||
|
[
|
||||||
|
"1x",
|
||||||
|
isNumber(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Symbol(),
|
||||||
|
isNumber(),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
for (const [value, constraints] of values) {
|
||||||
|
const valueBefore = JSON.stringify(value);
|
||||||
|
const converted: any = {};
|
||||||
|
assertNotEquals(
|
||||||
|
await validate(value, constraints, { doConversion: true, converted }),
|
||||||
|
[],
|
||||||
|
String(value),
|
||||||
|
);
|
||||||
|
const valueAfter = JSON.stringify(value);
|
||||||
|
assertEquals(valueAfter, valueBefore);
|
||||||
|
assertEquals(converted.hasOwnProperty("output"), true);
|
||||||
|
assertEquals(converted?.output, value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
82
Validator.ts
82
Validator.ts
@@ -3,6 +3,7 @@ export type Args = { [_: string]: any };
|
|||||||
export interface Validator {
|
export interface Validator {
|
||||||
type: string;
|
type: string;
|
||||||
extends?: Validator[];
|
extends?: Validator[];
|
||||||
|
convert?: (value: any) => Promise<any> | any;
|
||||||
check: (value: any) => Promise<Args | undefined> | Args | undefined;
|
check: (value: any) => Promise<Args | undefined> | Args | undefined;
|
||||||
message: (
|
message: (
|
||||||
value: any,
|
value: any,
|
||||||
@@ -14,7 +15,7 @@ export interface ValidationError {
|
|||||||
type: string;
|
type: string;
|
||||||
param?: string[];
|
param?: string[];
|
||||||
message?: string | null;
|
message?: string | null;
|
||||||
args?: Args;
|
args: Args;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Validatable = Schema | Validator | Validator[];
|
export type Validatable = Schema | Validator | Validator[];
|
||||||
@@ -27,42 +28,60 @@ export type Schema = { [key: string]: Validatable } | {
|
|||||||
export async function validate(
|
export async function validate(
|
||||||
value: any,
|
value: any,
|
||||||
validators: Validatable,
|
validators: Validatable,
|
||||||
|
{ doConversion = false, converted = undefined, doValidation = true }: {
|
||||||
|
doConversion?: boolean;
|
||||||
|
converted?: any;
|
||||||
|
doValidation?: boolean;
|
||||||
|
} = {},
|
||||||
): Promise<ValidationError[]> {
|
): Promise<ValidationError[]> {
|
||||||
if (instanceofValidatorArray(validators) || instanceofValidator(validators)) {
|
const options = { doConversion, converted, doValidation };
|
||||||
return validateValue(value, validators);
|
if (Array.isArray(validators) || instanceofValidator(validators)) {
|
||||||
|
return validateValue(value, validators, options);
|
||||||
} else {
|
} else {
|
||||||
return validateSchema(value, validators);
|
return validateSchema(value, validators, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function instanceofValidator(value: any): value is Validator {
|
export function instanceofValidator(value: any): value is Validator {
|
||||||
return value &&
|
return value &&
|
||||||
value.hasOwnProperty("type") && typeof value.type === "string" &&
|
value.hasOwnProperty("type") && typeof value.type === "string" &&
|
||||||
value.hasOwnProperty("check") && typeof value.check === "function" &&
|
value.hasOwnProperty("check") && typeof value.check === "function" &&
|
||||||
value.hasOwnProperty("message") && typeof value.message === "function";
|
value.hasOwnProperty("message") && typeof value.message === "function";
|
||||||
}
|
}
|
||||||
|
|
||||||
function instanceofValidatorArray(value: any): value is Validator[] {
|
export async function validateValue(
|
||||||
return Array.isArray(value) && value.every((x) => instanceofValidator(x));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function validateValue(
|
|
||||||
value: any,
|
value: any,
|
||||||
validators: Validator | Validator[],
|
validators: Validator | Validator[],
|
||||||
|
{ doConversion = false, converted = undefined, doValidation = true }: {
|
||||||
|
doConversion?: boolean;
|
||||||
|
converted?: any;
|
||||||
|
doValidation?: boolean;
|
||||||
|
} = {},
|
||||||
): Promise<ValidationError[]> {
|
): Promise<ValidationError[]> {
|
||||||
|
const options = { doConversion, converted, doValidation };
|
||||||
if (!Array.isArray(validators)) {
|
if (!Array.isArray(validators)) {
|
||||||
validators = [validators];
|
validators = [validators];
|
||||||
}
|
}
|
||||||
const result: ValidationError[] = [];
|
const result: ValidationError[] = [];
|
||||||
for (const validator of validators) {
|
for (const validator of validators) {
|
||||||
|
// 1. convert
|
||||||
|
if (doConversion && validator.convert) {
|
||||||
|
value = await validator.convert(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. extends
|
||||||
if (validator.extends) {
|
if (validator.extends) {
|
||||||
const prerequisites = await validate(value, validator.extends);
|
const prerequisites = await validate(value, validator.extends, options);
|
||||||
if (prerequisites.length > 0) {
|
if (prerequisites.length > 0) {
|
||||||
result.push(...prerequisites);
|
result.push(...prerequisites);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const args = await validator.check(value);
|
|
||||||
|
// 3. check
|
||||||
|
const args = doValidation ? await validator.check(value) : undefined;
|
||||||
|
|
||||||
|
// 4. message
|
||||||
if (args !== undefined) {
|
if (args !== undefined) {
|
||||||
const message = await validator.message(value, args);
|
const message = await validator.message(value, args);
|
||||||
result.push({
|
result.push({
|
||||||
@@ -72,19 +91,39 @@ async function validateValue(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (converted) {
|
||||||
|
converted.output = value;
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function validateSchema(
|
export async function validateSchema(
|
||||||
value: any,
|
value: any,
|
||||||
validators: Schema,
|
validators: Schema,
|
||||||
|
{ doConversion = false, converted = undefined, doValidation = true }: {
|
||||||
|
doConversion?: boolean;
|
||||||
|
converted?: any;
|
||||||
|
doValidation?: boolean;
|
||||||
|
} = {},
|
||||||
): Promise<ValidationError[]> {
|
): Promise<ValidationError[]> {
|
||||||
|
const conv: any = {};
|
||||||
|
const options = { doConversion, converted: conv, doValidation };
|
||||||
|
|
||||||
|
// Validate array
|
||||||
if (validators.hasOwnProperty(ArraySymbol)) {
|
if (validators.hasOwnProperty(ArraySymbol)) {
|
||||||
const v = validators as { [ArraySymbol]: Validatable };
|
const v = validators as { [ArraySymbol]: Validatable };
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
const arr = await Promise.all(
|
if (converted) {
|
||||||
value.map((val) => validate(val, v[ArraySymbol])),
|
converted.output = [];
|
||||||
);
|
}
|
||||||
|
const arr: ValidationError[][] = [];
|
||||||
|
for (const val of value) {
|
||||||
|
const errors = await validate(val, v[ArraySymbol], options);
|
||||||
|
arr.push(errors);
|
||||||
|
if (converted) {
|
||||||
|
converted.output.push(conv.output);
|
||||||
|
}
|
||||||
|
}
|
||||||
const errors = arr.flatMap((val, idx) =>
|
const errors = arr.flatMap((val, idx) =>
|
||||||
(val ?? []).map((error) => ({
|
(val ?? []).map((error) => ({
|
||||||
...error,
|
...error,
|
||||||
@@ -101,14 +140,23 @@ async function validateSchema(
|
|||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate object
|
||||||
const v = validators as { [key: string]: Validatable };
|
const v = validators as { [key: string]: Validatable };
|
||||||
const valErrors: ValidationError[] = [];
|
const valErrors: ValidationError[] = [];
|
||||||
|
|
||||||
|
if (converted) {
|
||||||
|
converted.output = {};
|
||||||
|
}
|
||||||
for (const prop in validators) {
|
for (const prop in validators) {
|
||||||
if (!validators.hasOwnProperty(prop)) {
|
if (!validators.hasOwnProperty(prop)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const errors = await validate(value && value[prop], v[prop]);
|
const errors = await validate(value && value[prop], v[prop], options);
|
||||||
valErrors.push(...(errors ?? []));
|
valErrors.push(...(errors ?? []));
|
||||||
|
if (converted) {
|
||||||
|
converted.output[prop] = conv.output;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return valErrors;
|
return valErrors;
|
||||||
}
|
}
|
||||||
|
|||||||
14
mod.ts
14
mod.ts
@@ -6,8 +6,18 @@ export {
|
|||||||
Validatable,
|
Validatable,
|
||||||
ArraySymbol,
|
ArraySymbol,
|
||||||
} from "./Validator.ts";
|
} from "./Validator.ts";
|
||||||
export { isString, isUrl, isEmail } from "./validators/string.ts";
|
export {
|
||||||
export { isNumber, isInteger } from "./validators/number.ts";
|
isString,
|
||||||
|
isUrl,
|
||||||
|
isEmail,
|
||||||
|
fulfillsRegex,
|
||||||
|
} from "./validators/string.ts";
|
||||||
|
export {
|
||||||
|
isNumber,
|
||||||
|
isInteger,
|
||||||
|
isInRange,
|
||||||
|
hasParity,
|
||||||
|
} from "./validators/number.ts";
|
||||||
export { isArray } from "./validators/array.ts";
|
export { isArray } from "./validators/array.ts";
|
||||||
export { or } from "./validators/logic.ts";
|
export { or } from "./validators/logic.ts";
|
||||||
export { isRequired, isDefined, isNotEmpty } from "./validators/empty.ts";
|
export { isRequired, isDefined, isNotEmpty } from "./validators/empty.ts";
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ Deno.test("isArray (match)", async () => {
|
|||||||
["foo"],
|
["foo"],
|
||||||
];
|
];
|
||||||
for (const value of values) {
|
for (const value of values) {
|
||||||
assertEquals(await validate(value, isArray()), []);
|
assertEquals(await validate(value, isArray()), [], String(value));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -33,6 +33,6 @@ Deno.test("isArray (no match)", async () => {
|
|||||||
Symbol(),
|
Symbol(),
|
||||||
];
|
];
|
||||||
for (const value of values) {
|
for (const value of values) {
|
||||||
assertNotEquals(await validate(value, isArray()), []);
|
assertNotEquals(await validate(value, isArray()), [], String(value));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ Deno.test("isBoolean (match)", async () => {
|
|||||||
false,
|
false,
|
||||||
];
|
];
|
||||||
for (const value of values) {
|
for (const value of values) {
|
||||||
assertEquals(await validate(value, isBoolean()), []);
|
assertEquals(await validate(value, isBoolean()), [], String(value));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -31,6 +31,6 @@ Deno.test("isBoolean (no match)", async () => {
|
|||||||
Symbol(),
|
Symbol(),
|
||||||
];
|
];
|
||||||
for (const value of values) {
|
for (const value of values) {
|
||||||
assertNotEquals(await validate(value, isBoolean()), []);
|
assertNotEquals(await validate(value, isBoolean()), [], String(value));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ Deno.test("isNotEmpty (match)", async () => {
|
|||||||
Symbol("foo"),
|
Symbol("foo"),
|
||||||
];
|
];
|
||||||
for (const value of values) {
|
for (const value of values) {
|
||||||
assertEquals(await validate(value, isNotEmpty()), []);
|
assertEquals(await validate(value, isNotEmpty()), [], String(value));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -94,6 +94,6 @@ Deno.test("isNotEmpty (no match)", async () => {
|
|||||||
[],
|
[],
|
||||||
];
|
];
|
||||||
for (const value of values) {
|
for (const value of values) {
|
||||||
assertNotEquals(await validate(value, isNotEmpty()), []);
|
assertNotEquals(await validate(value, isNotEmpty()), [], String(value));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ Deno.test("or (match)", async () => {
|
|||||||
[1, [isString(), isInteger()]],
|
[1, [isString(), isInteger()]],
|
||||||
];
|
];
|
||||||
for (const [value, validators] of values) {
|
for (const [value, validators] of values) {
|
||||||
assertEquals(await validate(value, or(...validators)), []);
|
assertEquals(await validate(value, or(...validators)), [], String(value));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -22,6 +22,10 @@ Deno.test("or (no match)", async () => {
|
|||||||
[1, [isString()]],
|
[1, [isString()]],
|
||||||
];
|
];
|
||||||
for (const [value, validators] of values) {
|
for (const [value, validators] of values) {
|
||||||
assertNotEquals(await validate(value, or(...validators)), []);
|
assertNotEquals(
|
||||||
|
await validate(value, or(...validators)),
|
||||||
|
[],
|
||||||
|
String(value),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { isNumber, isInteger } from "./number.ts";
|
import { isNumber, isInteger, isInRange, hasParity } from "./number.ts";
|
||||||
import { validate } from "../mod.ts";
|
import { validate } from "../mod.ts";
|
||||||
import {
|
import {
|
||||||
assertEquals,
|
assertEquals,
|
||||||
@@ -49,9 +49,13 @@ Deno.test("isInteger (match)", async () => {
|
|||||||
-1,
|
-1,
|
||||||
];
|
];
|
||||||
for (const value of values) {
|
for (const value of values) {
|
||||||
assertEquals(await validate(value, isInteger()), []);
|
assertEquals(await validate(value, isInteger()), [], String(value));
|
||||||
}
|
}
|
||||||
assertEquals(await validate(NaN, isInteger({ allowNaN: true })), []);
|
assertEquals(
|
||||||
|
await validate(NaN, isInteger({ allowNaN: true })),
|
||||||
|
[],
|
||||||
|
String(NaN),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Deno.test("isInteger (no match)", async () => {
|
Deno.test("isInteger (no match)", async () => {
|
||||||
@@ -67,7 +71,102 @@ Deno.test("isInteger (no match)", async () => {
|
|||||||
0.1,
|
0.1,
|
||||||
];
|
];
|
||||||
for (const value of values) {
|
for (const value of values) {
|
||||||
assertNotEquals(await validate(value, isInteger()), []);
|
assertNotEquals(await validate(value, isInteger()), [], String(value));
|
||||||
|
}
|
||||||
|
assertNotEquals(
|
||||||
|
await validate(NaN, isInteger({ allowNaN: false })),
|
||||||
|
[],
|
||||||
|
String(NaN),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("isInRange (match)", async () => {
|
||||||
|
const values: [number[], object][] = [
|
||||||
|
[[10.5, 11], { greaterThan: 10 }],
|
||||||
|
[[10, 10.5, 11], { greaterThanOrEqualTo: 10 }],
|
||||||
|
[[10], { equalTo: 10 }],
|
||||||
|
[[9.5, 9], { lessThan: 10 }],
|
||||||
|
[[10, 9.5, 9], { lessThanOrEqualTo: 10 }],
|
||||||
|
[[11, 11.5], { greaterThan: 10, lessThan: 12 }],
|
||||||
|
[[10, 11, 11.5], { greaterThanOrEqualTo: 10, lessThan: 12 }],
|
||||||
|
[[NaN], { allowNaN: true }],
|
||||||
|
];
|
||||||
|
for (const [vals, options] of values) {
|
||||||
|
for (const value of vals) {
|
||||||
|
assertEquals(
|
||||||
|
await validate(value, isInRange(options)),
|
||||||
|
[],
|
||||||
|
`${String(value)} - ${JSON.stringify(options)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("isInRange (no match)", async () => {
|
||||||
|
const values: [number[], any][] = [
|
||||||
|
[[8.5, 9, 10], { greaterThan: 10 }],
|
||||||
|
[[8.5, 9], { greaterThanOrEqualTo: 10 }],
|
||||||
|
[[9, 9.5, 11], { equalTo: 10 }],
|
||||||
|
[[10, 10.5, 11], { lessThan: 10 }],
|
||||||
|
[[10.5, 11], { lessThanOrEqualTo: 10 }],
|
||||||
|
[[9.5, 10, 12, 13], { greaterThan: 10, lessThan: 12 }],
|
||||||
|
[[9.5, 12, 13], { greaterThanOrEqualTo: 10, lessThan: 12 }],
|
||||||
|
[[NaN], { allowNaN: false }],
|
||||||
|
[[NaN], {}],
|
||||||
|
];
|
||||||
|
for (const [vals, options] of values) {
|
||||||
|
for (const value of vals) {
|
||||||
|
assertNotEquals(
|
||||||
|
await validate(value, isInRange(options)),
|
||||||
|
[],
|
||||||
|
`${String(value)} - ${JSON.stringify(options)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("hasParity (match)", async () => {
|
||||||
|
const values: [number[], object][] = [
|
||||||
|
[[-2, -1, 0, 1, 2, 3, 4, 5, 6], {}],
|
||||||
|
[[-2, 0, 2, 4, 6], { odd: false }],
|
||||||
|
[[-1, 1, 3, 5], { even: false }],
|
||||||
|
[[0, 3, 6], { divisibleBy: 3 }],
|
||||||
|
[[], { odd: false, even: false }],
|
||||||
|
[[0, 6], { odd: false, divisibleBy: 3 }],
|
||||||
|
[[3], { even: false, divisibleBy: 3 }],
|
||||||
|
[[NaN], { allowNaN: true }],
|
||||||
|
];
|
||||||
|
for (const [vals, options] of values) {
|
||||||
|
for (const value of vals) {
|
||||||
|
assertEquals(
|
||||||
|
await validate(value, hasParity(options)),
|
||||||
|
[],
|
||||||
|
`${String(value)} - ${JSON.stringify(options)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("hasParity (no match)", async () => {
|
||||||
|
const values: [number[], any][] = [
|
||||||
|
[[], {}],
|
||||||
|
[[-1, 1, 3, 5], { odd: false }],
|
||||||
|
[[-2, 0, 2, 4, 6], { even: false }],
|
||||||
|
[[-2, -1, 1, 2, 4, 5], { divisibleBy: 3 }],
|
||||||
|
[[-2, -1, 0, 1, 2, 3, 4, 5, 6], { odd: false, even: false }],
|
||||||
|
[[-2, -1, 1, 2, 3, 4, 5], { odd: false, divisibleBy: 3 }],
|
||||||
|
[[-2, -1, 0, 1, 2, 4, 5, 6], { even: false, divisibleBy: 3 }],
|
||||||
|
[[NaN], { allowNaN: false }],
|
||||||
|
[[NaN], {}],
|
||||||
|
[[0.5], {}],
|
||||||
|
];
|
||||||
|
for (const [vals, options] of values) {
|
||||||
|
for (const value of vals) {
|
||||||
|
assertNotEquals(
|
||||||
|
await validate(value, hasParity(options)),
|
||||||
|
[],
|
||||||
|
`${String(value)} - ${JSON.stringify(options)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
assertNotEquals(await validate(NaN, isInteger({ allowNaN: false })), []);
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,6 +5,13 @@ export function isNumber(
|
|||||||
): Validator {
|
): Validator {
|
||||||
return {
|
return {
|
||||||
type: "isNumber",
|
type: "isNumber",
|
||||||
|
convert: (value: any) => {
|
||||||
|
if (typeof value === "string" || value instanceof String) {
|
||||||
|
const num = Number(value);
|
||||||
|
return Number.isNaN(num) ? value : num;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
},
|
||||||
check: (value: any) => {
|
check: (value: any) => {
|
||||||
if (value === null || value === undefined) return;
|
if (value === null || value === undefined) return;
|
||||||
if (allowNaN && Number.isNaN(value)) return;
|
if (allowNaN && Number.isNaN(value)) return;
|
||||||
@@ -35,3 +42,87 @@ export function isInteger(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isInRange(
|
||||||
|
{
|
||||||
|
allowNaN = false,
|
||||||
|
greaterThan = undefined,
|
||||||
|
greaterThanOrEqualTo = undefined,
|
||||||
|
equalTo = undefined,
|
||||||
|
lessThan = undefined,
|
||||||
|
lessThanOrEqualTo = undefined,
|
||||||
|
}: {
|
||||||
|
allowNaN?: boolean;
|
||||||
|
greaterThan?: number;
|
||||||
|
greaterThanOrEqualTo?: number;
|
||||||
|
equalTo?: number;
|
||||||
|
lessThan?: number;
|
||||||
|
lessThanOrEqualTo?: number;
|
||||||
|
} = {},
|
||||||
|
): Validator {
|
||||||
|
return {
|
||||||
|
type: "isInRange",
|
||||||
|
extends: [isNumber({ allowNaN })],
|
||||||
|
check: (value: any) => {
|
||||||
|
if (value === null || value === undefined) return;
|
||||||
|
if (allowNaN && Number.isNaN(value)) return;
|
||||||
|
if (greaterThan !== undefined && value <= greaterThan) {
|
||||||
|
return { range: "greaterThan" };
|
||||||
|
}
|
||||||
|
if (greaterThanOrEqualTo !== undefined && value < greaterThanOrEqualTo) {
|
||||||
|
return { range: "greaterThanOrEqualTo" };
|
||||||
|
}
|
||||||
|
if (equalTo !== undefined && value !== equalTo) {
|
||||||
|
return { range: "equalTo" };
|
||||||
|
}
|
||||||
|
if (lessThan !== undefined && value >= lessThan) {
|
||||||
|
return { range: "lessThan" };
|
||||||
|
}
|
||||||
|
if (lessThanOrEqualTo !== undefined && value > lessThanOrEqualTo) {
|
||||||
|
return { range: "lessThanOrEqualTo" };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
message: (value: any, args?: Args) => {
|
||||||
|
return `This value is not in the given range.`;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasParity(
|
||||||
|
{
|
||||||
|
allowNaN = false,
|
||||||
|
odd = true,
|
||||||
|
even = true,
|
||||||
|
divisibleBy = undefined,
|
||||||
|
}: {
|
||||||
|
allowNaN?: boolean;
|
||||||
|
odd?: boolean;
|
||||||
|
even?: boolean;
|
||||||
|
divisibleBy?: number;
|
||||||
|
} = {},
|
||||||
|
): Validator {
|
||||||
|
return {
|
||||||
|
type: "hasParity",
|
||||||
|
extends: [isInteger({ allowNaN })],
|
||||||
|
check: (value: any) => {
|
||||||
|
if (value === null || value === undefined) return;
|
||||||
|
if (allowNaN && Number.isNaN(value)) return;
|
||||||
|
if (!odd && Math.abs(value % 2) == 1) {
|
||||||
|
return { partity: "odd" };
|
||||||
|
}
|
||||||
|
if (!even && value % 2 == 0) {
|
||||||
|
return { partity: "even" };
|
||||||
|
}
|
||||||
|
if (divisibleBy !== undefined && (value % divisibleBy !== 0)) {
|
||||||
|
return { divisibleBy };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
message: (value: any, args?: Args) => {
|
||||||
|
if (args?.hasOwnProperty("divisibleBy")) {
|
||||||
|
return `This value is not divisible by ${divisibleBy}.`;
|
||||||
|
} else {
|
||||||
|
return `This value has a wrong partiy.`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { isString, isUrl, isEmail } from "./string.ts";
|
import { isString, isUrl, isEmail, fulfillsRegex } from "./string.ts";
|
||||||
import { validate } from "../mod.ts";
|
import { validate } from "../mod.ts";
|
||||||
import {
|
import {
|
||||||
assertEquals,
|
assertEquals,
|
||||||
@@ -15,7 +15,7 @@ Deno.test("isString (match)", async () => {
|
|||||||
new String("bar"),
|
new String("bar"),
|
||||||
];
|
];
|
||||||
for (const value of values) {
|
for (const value of values) {
|
||||||
assertEquals(await validate(value, isString()), []);
|
assertEquals(await validate(value, isString()), [], String(value));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -31,12 +31,14 @@ Deno.test("isString (no match)", async () => {
|
|||||||
Symbol(),
|
Symbol(),
|
||||||
];
|
];
|
||||||
for (const value of values) {
|
for (const value of values) {
|
||||||
assertNotEquals(await validate(value, isString()), []);
|
assertNotEquals(await validate(value, isString()), [], String(value));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Deno.test("isUrl (match)", async () => {
|
Deno.test("isUrl (match)", async () => {
|
||||||
const values = [
|
const values = [
|
||||||
|
undefined,
|
||||||
|
null,
|
||||||
"http://google.com",
|
"http://google.com",
|
||||||
"http://10.1.1.1",
|
"http://10.1.1.1",
|
||||||
"http://10.1.1.254",
|
"http://10.1.1.254",
|
||||||
@@ -47,7 +49,7 @@ Deno.test("isUrl (match)", async () => {
|
|||||||
assertEquals(
|
assertEquals(
|
||||||
await validate(value, isUrl({ allowLocal: true, allowDataUrl: true })),
|
await validate(value, isUrl({ allowLocal: true, allowDataUrl: true })),
|
||||||
[],
|
[],
|
||||||
"" + value,
|
String(value),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -66,12 +68,15 @@ Deno.test("isUrl (no match)", async () => {
|
|||||||
assertNotEquals(
|
assertNotEquals(
|
||||||
await validate(value, isUrl({ allowLocal: true })),
|
await validate(value, isUrl({ allowLocal: true })),
|
||||||
[],
|
[],
|
||||||
"" + value,
|
String(value),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Deno.test("isEmail (match)", async () => {
|
Deno.test("isEmail (match)", async () => {
|
||||||
const values = [
|
const values = [
|
||||||
|
undefined,
|
||||||
|
null,
|
||||||
"email@example.com",
|
"email@example.com",
|
||||||
"firstname.lastname@example.com",
|
"firstname.lastname@example.com",
|
||||||
"email@subdomain.example.com",
|
"email@subdomain.example.com",
|
||||||
@@ -88,11 +93,7 @@ Deno.test("isEmail (match)", async () => {
|
|||||||
"firstname-lastname@example.com",
|
"firstname-lastname@example.com",
|
||||||
];
|
];
|
||||||
for (const value of values) {
|
for (const value of values) {
|
||||||
assertEquals(
|
assertEquals(await validate(value, isEmail()), [], String(value));
|
||||||
await validate(value, isEmail()),
|
|
||||||
[],
|
|
||||||
"" + value,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -117,10 +118,40 @@ Deno.test("isEmail (no match)", async () => {
|
|||||||
"Abc..123@example.com",
|
"Abc..123@example.com",
|
||||||
];
|
];
|
||||||
for (const value of values) {
|
for (const value of values) {
|
||||||
assertNotEquals(
|
assertNotEquals(await validate(value, isEmail()), [], String(value));
|
||||||
await validate(value, isEmail()),
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("fulfillsRegex (match)", async () => {
|
||||||
|
const values: [any, RegExp][] = [
|
||||||
|
["abc", /[a-z]+/],
|
||||||
|
[null, /[a-z]+/],
|
||||||
|
[undefined, /[a-z]+/],
|
||||||
|
["123abc", /[a-z]+/],
|
||||||
|
["abc", /^[a-z]+$/],
|
||||||
|
["^ab$", /^\^(ab)|(cd)\$$/],
|
||||||
|
];
|
||||||
|
for (const [value, regex] of values) {
|
||||||
|
assertEquals(
|
||||||
|
await validate(value, fulfillsRegex({ regex })),
|
||||||
[],
|
[],
|
||||||
"" + value,
|
`${String(value)} - ${regex}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("fulfillsRegex (no match)", async () => {
|
||||||
|
const values: [any, RegExp][] = [
|
||||||
|
[Symbol(), /[a-z]+/],
|
||||||
|
["", /[a-z]+/],
|
||||||
|
["abc123", /^[a-z]+$/],
|
||||||
|
["^abcd$", /^\^(ab|cd)\$$/],
|
||||||
|
];
|
||||||
|
for (const [value, regex] of values) {
|
||||||
|
assertNotEquals(
|
||||||
|
await validate(value, fulfillsRegex({ regex })),
|
||||||
|
[],
|
||||||
|
`${String(value)} - ${regex}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -126,6 +126,15 @@ export function isEmail(): Validator {
|
|||||||
return {
|
return {
|
||||||
type: "isEmail",
|
type: "isEmail",
|
||||||
extends: [isString()],
|
extends: [isString()],
|
||||||
|
convert: (value: any) => {
|
||||||
|
if (value === true) {
|
||||||
|
return "true";
|
||||||
|
} else if (value === false) {
|
||||||
|
return "false";
|
||||||
|
} else if (typeof value === "number") {
|
||||||
|
return value.toString();
|
||||||
|
}
|
||||||
|
},
|
||||||
check: (value: any) => {
|
check: (value: any) => {
|
||||||
if (value === null || value === undefined) return;
|
if (value === null || value === undefined) return;
|
||||||
const regex =
|
const regex =
|
||||||
@@ -140,3 +149,19 @@ export function isEmail(): Validator {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function fulfillsRegex({ regex }: { regex: RegExp }): Validator {
|
||||||
|
return {
|
||||||
|
type: "fulfillsRegex",
|
||||||
|
extends: [isString()],
|
||||||
|
check: (value: any) => {
|
||||||
|
if (value === null || value === undefined) return;
|
||||||
|
if (!value.match(regex)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
message: (value: any, args?: Args) => {
|
||||||
|
return `This value has to fulfill the regex ${regex}.`;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ Deno.test("isSymbol (match)", async () => {
|
|||||||
Symbol(),
|
Symbol(),
|
||||||
];
|
];
|
||||||
for (const value of values) {
|
for (const value of values) {
|
||||||
assertEquals(await validate(value, isSymbol()), []);
|
assertEquals(await validate(value, isSymbol()), [], String(value));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -31,6 +31,6 @@ Deno.test("isSymbol (no match)", async () => {
|
|||||||
new Object(),
|
new Object(),
|
||||||
];
|
];
|
||||||
for (const value of values) {
|
for (const value of values) {
|
||||||
assertNotEquals(await validate(value, isSymbol()), []);
|
assertNotEquals(await validate(value, isSymbol()), [], String(value));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user