mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[REF] reorganize file structure
in order to separate compiler/ and runtime/ code
This commit is contained in:
committed by
Sam Degueldre
parent
e4b810c027
commit
22a79cdedd
@@ -0,0 +1,161 @@
|
||||
type BaseType =
|
||||
| typeof String
|
||||
| typeof Boolean
|
||||
| typeof Number
|
||||
| typeof Date
|
||||
| typeof Object
|
||||
| typeof Array
|
||||
| true
|
||||
| "*";
|
||||
|
||||
type UnionType = (TypeInfo | BaseType)[];
|
||||
type TypeDescription = BaseType | UnionType | TypeInfo;
|
||||
|
||||
interface TypeInfo {
|
||||
type?: TypeDescription;
|
||||
optional?: boolean;
|
||||
validate?: Function;
|
||||
shape?: Schema;
|
||||
element?: TypeDescription;
|
||||
}
|
||||
|
||||
type SimplifiedSchema = string[];
|
||||
type NormalizedSchema = { [key: string]: TypeDescription };
|
||||
export type Schema = SimplifiedSchema | NormalizedSchema;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// helpers
|
||||
// -----------------------------------------------------------------------------
|
||||
const isUnionType = (t: TypeDescription): t is UnionType => Array.isArray(t);
|
||||
const isBaseType = (t: TypeDescription): t is BaseType => typeof t !== "object";
|
||||
|
||||
export function isOptional(t: TypeDescription): Boolean {
|
||||
return typeof t === "object" && "optional" in t ? t.optional || false : false;
|
||||
}
|
||||
|
||||
function describeType(type: BaseType): string {
|
||||
return type === "*" || type === true ? "value" : type.name.toLowerCase();
|
||||
}
|
||||
|
||||
function describe(info: TypeDescription): string {
|
||||
if (isBaseType(info)) {
|
||||
return describeType(info);
|
||||
} else if (isUnionType(info)) {
|
||||
return info.map(describe).join(" or ");
|
||||
}
|
||||
if ("element" in info) {
|
||||
return `list of ${describe({ type: info.element, optional: false })}s`;
|
||||
}
|
||||
if ("shape" in (info as TypeInfo)) {
|
||||
return `object`;
|
||||
}
|
||||
return describe(info.type || "*");
|
||||
}
|
||||
|
||||
function toSchema(spec: SimplifiedSchema): NormalizedSchema {
|
||||
return Object.fromEntries(
|
||||
spec.map((e) =>
|
||||
e.endsWith("?") ? [e.slice(0, -1), { optional: true }] : [e, { type: "*", optional: false }]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main validate function
|
||||
*/
|
||||
export function validate(obj: { [key: string]: any }, spec: Schema) {
|
||||
let errors = validateSchema(obj, spec);
|
||||
if (errors.length) {
|
||||
throw new Error("Invalid object: " + errors.join(", "));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper validate function, to get the list of errors. useful if one want to
|
||||
* manipulate the errors without parsing an error object
|
||||
*/
|
||||
export function validateSchema(obj: { [key: string]: any }, schema: Schema): string[] {
|
||||
if (Array.isArray(schema)) {
|
||||
schema = toSchema(schema);
|
||||
}
|
||||
let errors = [];
|
||||
// check if each value in obj has correct shape
|
||||
for (let key in obj) {
|
||||
if (key in schema) {
|
||||
let result = validateType(key, obj[key], schema[key]);
|
||||
if (result) {
|
||||
errors.push(result);
|
||||
}
|
||||
} else if (!("*" in schema)) {
|
||||
errors.push(`unknown key '${key}'`);
|
||||
}
|
||||
}
|
||||
// check that all specified keys are defined in obj
|
||||
for (let key in schema) {
|
||||
const spec = schema[key];
|
||||
if (key !== "*" && !isOptional(spec) && !(key in obj)) {
|
||||
const isObj = typeof spec === "object" && !Array.isArray(spec);
|
||||
const isAny = spec === "*" || (isObj && "type" in spec ? spec.type === "*" : isObj);
|
||||
let detail = isAny ? "" : ` (should be a ${describe(spec)})`;
|
||||
errors.push(`'${key}' is missing${detail}`);
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
function validateBaseType(key: string, value: any, type: BaseType): string | null {
|
||||
if (typeof type === "function") {
|
||||
if (typeof value === "object") {
|
||||
if (!(value instanceof type)) {
|
||||
return `'${key}' is not a ${describeType(type)}`;
|
||||
}
|
||||
} else if (typeof value !== type.name.toLowerCase()) {
|
||||
return `'${key}' is not a ${describeType(type)}`;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function validateArrayType(key: string, value: any, descr: TypeDescription): string | null {
|
||||
if (!Array.isArray(value)) {
|
||||
return `'${key}' is not a list of ${describe(descr)}s`;
|
||||
}
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
const error = validateType(`${key}[${i}]`, value[i], descr);
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function validateType(key: string, value: any, descr: TypeDescription): string | null {
|
||||
if (value === undefined) {
|
||||
return isOptional(descr) ? null : `'${key}' is undefined (should be a ${describe(descr)})`;
|
||||
} else if (isBaseType(descr)) {
|
||||
return validateBaseType(key, value, descr);
|
||||
} else if (isUnionType(descr)) {
|
||||
let validDescr = descr.find((p) => !validateType(key, value, p));
|
||||
return validDescr ? null : `'${key}' is not a ${describe(descr)}`;
|
||||
}
|
||||
let result: string | null = null;
|
||||
if ("element" in descr) {
|
||||
result = validateArrayType(key, value, descr.element!);
|
||||
} else if ("shape" in descr && !result) {
|
||||
if (typeof value !== "object" || Array.isArray(value)) {
|
||||
result = `'${key}' is not an object`;
|
||||
} else {
|
||||
const errors = validateSchema(value, descr.shape!);
|
||||
if (errors.length) {
|
||||
result = `'${key}' has not the correct shape (${errors.join(", ")})`;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ("type" in descr && !result) {
|
||||
result = validateType(key, value, descr.type!);
|
||||
}
|
||||
if ("validate" in descr && !result) {
|
||||
result = !descr.validate!(value) ? `'${key}' is not valid` : null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user