class ValidationError extends Error {
constructor(message: string) {
super(message);
this.name = 'ValidationError';
}
}
type PrimitiveType = 'string' | 'boolean' | 'number' | 'any';
type Validator<T> = (value: T) => boolean | string;
interface PrimitiveSchema {
type: PrimitiveType;
required?: boolean;
custom?: Validator<any>;
}
interface ArraySchema {
type: 'array';
required?: boolean;
itemRequired?: boolean;
items: Schema;
custom?: Validator<any[]>;
}
interface ObjectSchema {
type: 'object';
required?: boolean;
properties: Record<string, Schema>;
custom?: Validator<object>;
}
export type Schema = PrimitiveSchema | ArraySchema | ObjectSchema;
export const Validation = new (class {
/**
* useful for validating primitive value data type
*/
public primitive(value: unknown, schema: PrimitiveSchema, path: string = 'value') {
if (value === undefined || value === null) {
if (schema.required) {
throw new ValidationError(`${path} is required`);
}
return;
}
if (schema.type !== 'any' && typeof value !== schema.type) {
throw new ValidationError(`${path} is not a ${schema.type}`);
}
if (schema.custom) {
const custom = schema.custom(value);
if (typeof custom === 'string') {
throw new ValidationError(custom);
} else if (!custom) {
throw new ValidationError(`${path} is invalid`);
}
}
}
/**
* useful for validating array and the items inside
*/
public array(value: unknown[], schema: ArraySchema, path: string = 'value') {
if (!value) {
if (schema.required) {
throw new ValidationError(`${path} is required`);
}
return;
}
if (!Array.isArray(value)) {
throw new ValidationError(`${path} is not an array`);
}
if (schema.itemRequired && !value.length) {
throw new ValidationError(`${path} cannot be empty`);
}
value.forEach((item, index) => {
const newPath = `${path}[${index}]`;
const itemSchema = schema.items;
if (itemSchema.type === 'object') {
this.object(item, itemSchema, newPath);
} else if (itemSchema.type === 'array') {
this.array(item as any[], itemSchema, newPath);
} else {
this.primitive(item, itemSchema, newPath);
}
});
if (schema.custom) {
const custom = schema.custom(value);
if (typeof custom === 'string') {
throw new ValidationError(custom);
} else if (!custom) {
throw new ValidationError(`${path} is invalid array`);
}
}
}
/**
* useful for validating object and the properties inside
*/
public object(value: any, schema: ObjectSchema, path: string = 'value') {
if (!value) {
if (schema.required && Object.keys(value).length) {
throw new ValidationError(`${path} is required`);
}
return;
}
for (const key in value) {
if (!Object.hasOwn(schema.properties, key)) {
throw new ValidationError(`${path}.${key} is not allowed`);
}
}
for (const key in schema.properties) {
const newPath = `${path === 'value' ? 'root' : path}.${key}`;
const propertySchema = schema.properties[key];
const propertyValue = value[key];
if (propertySchema.required && !Object.hasOwn(value, key)) {
throw new ValidationError(`${newPath} is required`);
}
if (propertySchema.type === 'array') {
this.array(propertyValue, propertySchema, newPath);
} else if (propertySchema.type === 'object') {
this.object(propertyValue, propertySchema, newPath);
} else {
this.primitive(propertyValue, propertySchema, newPath);
}
}
if (schema.custom) {
const custom = schema.custom(value);
if (typeof custom === 'string') {
throw new ValidationError(custom);
} else if (!custom) {
throw new ValidationError(`${path} is invalid object`);
}
}
}
})();
Validation.object({
index: [undefined]
}, {
type: 'object',
properties: {
archived: { type: 'boolean' },
index: { type: 'array', items: { type: 'string' }, itemRequired: true },
labels: { type: 'array', items: { type: 'string' } },
},
})
To embed this project on your website, copy the following code and paste it into your website's HTML: