mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
7786077921
This commit makes all errors thrown in owl use a custom error class. The main point of this is to always wrap user-code errors that happen during the owl lifecycle so that they can be treated uniformly in onError by checking the cause property, and also allows user code to differenciate owl errors from non-owl errors reliably at runtime.
170 lines
5.3 KiB
TypeScript
170 lines
5.3 KiB
TypeScript
import { OwlError } from "./error_handling";
|
|
|
|
type BaseType =
|
|
| typeof String
|
|
| typeof Boolean
|
|
| typeof Number
|
|
| typeof Date
|
|
| typeof Object
|
|
| typeof Array
|
|
| true
|
|
| "*";
|
|
|
|
interface TypeInfo {
|
|
type?: TypeDescription;
|
|
optional?: boolean;
|
|
validate?: Function;
|
|
shape?: Schema;
|
|
element?: TypeDescription;
|
|
}
|
|
|
|
type ValueType = { value: any };
|
|
|
|
type TypeDescription = BaseType | TypeInfo | ValueType | TypeDescription[];
|
|
type SimplifiedSchema = string[];
|
|
type NormalizedSchema = { [key: string]: TypeDescription };
|
|
export type Schema = SimplifiedSchema | NormalizedSchema;
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// helpers
|
|
// -----------------------------------------------------------------------------
|
|
const isUnionType = (t: TypeDescription): t is TypeDescription[] => Array.isArray(t);
|
|
const isBaseType = (t: TypeDescription): t is BaseType => typeof t !== "object";
|
|
const isValueType = (t: TypeDescription): t is ValueType =>
|
|
typeof t === "object" && t && "value" in t;
|
|
|
|
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 ");
|
|
} else if (isValueType(info)) {
|
|
return String(info.value);
|
|
}
|
|
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 OwlError("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 (isValueType(descr)) {
|
|
return value === descr.value ? null : `'${key}' is not equal to '${descr.value}'`;
|
|
} 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;
|
|
}
|