mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[IMP] props_validation: have clearer error messages
With this commit, props validation error messages will include a more developer-friendly error message, avoiding the need to investigate in the Developer Tools why a complex props structure is invalid.
This commit is contained in:
committed by
Géry Debongnie
parent
a83731007a
commit
32d8b23b9d
@@ -72,17 +72,20 @@ export function validateProps<P>(name: string | ComponentConstructor<P>, props:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let isValid;
|
||||
let whyInvalid;
|
||||
try {
|
||||
isValid = isValidProp((props as any)[propName], propDef);
|
||||
whyInvalid = whyInvalidProp((props as any)[propName], propDef);
|
||||
} catch (e) {
|
||||
(e as Error).message = `Invalid prop '${propName}' in component ${ComponentClass.name} (${
|
||||
(e as Error).message
|
||||
})`;
|
||||
throw e;
|
||||
}
|
||||
if (!isValid) {
|
||||
throw new Error(`Invalid Prop '${propName}' in component '${ComponentClass.name}'`);
|
||||
if (whyInvalid !== null) {
|
||||
whyInvalid = whyInvalid.replace(/\${propName}/g, propName);
|
||||
throw new Error(
|
||||
`Invalid Prop '${propName}' in component '${ComponentClass.name}': ${whyInvalid}`
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!allowAdditionalProps) {
|
||||
@@ -95,11 +98,11 @@ export function validateProps<P>(name: string | ComponentConstructor<P>, props:
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an invidual prop value matches its (static) prop definition
|
||||
* Check why an invidual prop value doesn't match its (static) prop definition
|
||||
*/
|
||||
function isValidProp(prop: any, propDef: any): boolean {
|
||||
function whyInvalidProp(prop: any, propDef: any): string | null {
|
||||
if (propDef === true) {
|
||||
return true;
|
||||
return null;
|
||||
}
|
||||
if (typeof propDef === "function") {
|
||||
// Check if a value is constructed by some Constructor. Note that there is a
|
||||
@@ -108,43 +111,66 @@ function isValidProp(prop: any, propDef: any): boolean {
|
||||
// So, even though 1 is not an instance of Number, we want to consider that
|
||||
// it is valid.
|
||||
if (typeof prop === "object") {
|
||||
return prop instanceof propDef;
|
||||
if (prop instanceof propDef) {
|
||||
return null;
|
||||
}
|
||||
return `\${propName} is not an instance of ${propDef.name}`;
|
||||
}
|
||||
return typeof prop === propDef.name.toLowerCase();
|
||||
if (typeof prop === propDef.name.toLowerCase()) {
|
||||
return null;
|
||||
}
|
||||
return `type of \${propName} is not ${propDef.name}`;
|
||||
} else if (propDef instanceof Array) {
|
||||
// If this code is executed, this means that we want to check if a prop
|
||||
// matches at least one of its descriptor.
|
||||
let result = false;
|
||||
let reasons: string[] = [];
|
||||
for (let i = 0, iLen = propDef.length; i < iLen; i++) {
|
||||
result = result || isValidProp(prop, propDef[i]);
|
||||
const why = whyInvalidProp(prop, propDef[i]);
|
||||
if (why === null) {
|
||||
return null;
|
||||
}
|
||||
reasons.push(why);
|
||||
}
|
||||
if (reasons.length > 1) {
|
||||
return reasons.slice(0, -1).join(", ") + " and " + reasons[reasons.length - 1];
|
||||
} else {
|
||||
return reasons[0];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
// propsDef is an object
|
||||
if (propDef.optional && prop === undefined) {
|
||||
return true;
|
||||
return null;
|
||||
}
|
||||
let result = propDef.type ? isValidProp(prop, propDef.type) : true;
|
||||
if (propDef.validate) {
|
||||
result = result && propDef.validate(prop);
|
||||
if (propDef.type) {
|
||||
const why = whyInvalidProp(prop, propDef.type);
|
||||
if (why !== null) {
|
||||
return why;
|
||||
}
|
||||
}
|
||||
if (propDef.validate && !propDef.validate(prop)) {
|
||||
return "${propName} could not be validated by `validate` function";
|
||||
}
|
||||
if (propDef.type === Array && propDef.element) {
|
||||
for (let i = 0, iLen = prop.length; i < iLen; i++) {
|
||||
result = result && isValidProp(prop[i], propDef.element);
|
||||
const why = whyInvalidProp(prop[i], propDef.element);
|
||||
if (why !== null) {
|
||||
return why.replace(/\${propName}/g, `\${propName}[${i}]`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (propDef.type === Object && propDef.shape) {
|
||||
const shape = propDef.shape;
|
||||
for (let key in shape) {
|
||||
result = result && isValidProp(prop[key], shape[key]);
|
||||
const why = whyInvalidProp(prop[key], shape[key]);
|
||||
if (why !== null) {
|
||||
return why.replace(/\${propName}/g, `\${propName}['${key}']`);
|
||||
}
|
||||
}
|
||||
if (result) {
|
||||
for (let propName in prop) {
|
||||
if (!(propName in shape)) {
|
||||
throw new Error(`unknown prop '${propName}'`);
|
||||
}
|
||||
for (let propName in prop) {
|
||||
if (!(propName in shape)) {
|
||||
return `unknown prop \${propName}['${propName}']`;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user