Browse Source

wip: validateAgainstType

pull/2090/head
Wing-Kam Wong 3 years ago
parent
commit
bf7451ef5f
  1. 108
      packages/nc-gui/components/project/spreadsheet/components/editColumn/FormulaOptions.vue

108
packages/nc-gui/components/project/spreadsheet/components/editColumn/FormulaOptions.vue

@ -226,65 +226,68 @@ export default {
parseAndValidateFormula(formula) { parseAndValidateFormula(formula) {
try { try {
const pt = jsep(formula) const pt = jsep(formula)
const err = this.validateAgainstMeta(pt) const metaErrors = this.validateAgainstMeta(pt)
if (err.size) { if (metaErrors.size) {
return [...err].join(', ') return [...metaErrors].join(', ')
} }
return true return true
} catch (e) { } catch (e) {
return e.message return e.message
} }
}, },
validateAgainstMeta(pt, arr = new Set()) { validateAgainstMeta(pt, errors = new Set(), typeErrors = new Set()) {
if (pt.type === jsep.CALL_EXP) { if (pt.type === jsep.CALL_EXP) {
// validate function name
if (!this.availableFunctions.includes(pt.callee.name)) { if (!this.availableFunctions.includes(pt.callee.name)) {
arr.add(`'${pt.callee.name}' function is not available`) errors.add(`'${pt.callee.name}' function is not available`)
} }
const validation = formulas[pt.callee.name] && formulas[pt.callee.name].validation
// validate arguments // validate arguments
const validation = formulas[pt.callee.name] && formulas[pt.callee.name].validation
if (validation && validation.args) { if (validation && validation.args) {
if (validation.args.rqd !== undefined && validation.args.rqd !== pt.arguments.length) { if (validation.args.rqd !== undefined && validation.args.rqd !== pt.arguments.length) {
arr.add(`'${pt.callee.name}' required ${validation.args.rqd} arguments`) errors.add(`'${pt.callee.name}' required ${validation.args.rqd} arguments`)
} else if (validation.args.min !== undefined && validation.args.min > pt.arguments.length) { } else if (validation.args.min !== undefined && validation.args.min > pt.arguments.length) {
arr.add(`'${pt.callee.name}' required minimum ${validation.args.min} arguments`) errors.add(`'${pt.callee.name}' required minimum ${validation.args.min} arguments`)
} else if (validation.args.max !== undefined && validation.args.max < pt.arguments.length) { } else if (validation.args.max !== undefined && validation.args.max < pt.arguments.length) {
arr.add(`'${pt.callee.name}' required maximum ${validation.args.max} arguments`) errors.add(`'${pt.callee.name}' required maximum ${validation.args.max} arguments`)
} }
} }
pt.arguments.map(arg => this.validateAgainstMeta(arg, errors))
// validate data type // validate data type
const type = formulas[pt.callee.name].type const type = formulas[pt.callee.name].type
if (type === formulaTypes.NUMERIC) { if (
for (const arg of pt.arguments) { type === formulaTypes.NUMERIC ||
if (arg.value && typeof arg.value !== 'number') { type === formulaTypes.STRING
arr.add(`Value '${arg.value}' should have a numeric type`) ) {
} pt.arguments.map(arg => this.validateAgainstType(arg, type, func, typeErrors))
if (arg.name) {
// TODO: handle jsep.IDENTIFIER case
// arr.add(`Column '${arg.name}' should have a numeric type`)
}
}
} else if (type === formulaTypes.STRING) {
for (const arg of pt.arguments) {
if (arg.value && typeof arg.value !== 'string') {
arr.add(`Value '${arg.value}' should have a string type`)
}
if (arg.name) {
// TODO: handle jsep.IDENTIFIER case
// arr.add(`Column '${arg.name}' should have a string type`)
}
}
} else if (type === formulaTypes.DATE) { } else if (type === formulaTypes.DATE) {
if (pt.callee.name === 'DATEADD') { if (pt.callee.name === 'DATEADD') {
// pt.arguments[0] = date type // pt.arguments[0] = date type
this.validateAgainstType(pt.arguments[0], formulaTypes.DATE, (v) => {
if (!(v instanceof Date)) {
typeErrors.add('The first parameter of DATEADD() should have date value')
}
})
// pt.arguments[1] = numeric // pt.arguments[1] = numeric
this.validateAgainstType(pt.arguments[1], formulaTypes.NUMERIC, (v) => {
if (typeof v !== 'number') {
typeErrors.add('The second parameter of DATEADD() should have numeric value')
}
})
// pt.arguments[2] = ["day" | "week" | "month" | "year"] // pt.arguments[2] = ["day" | "week" | "month" | "year"]
// TODO: write a dry-run function to validate each segment this.validateAgainstType(pt.arguments[2], formulaTypes.STRING, (v) => {
if (!['day', 'week', 'month', 'year'].includes(v)) {
typeErrors.add('The third parameter of DATEADD() should have the value either "day", "week", "month" or "year"')
}
})
} }
// NOW()?
} }
pt.arguments.map(arg => this.validateAgainstMeta(arg, arr)) errors = new Set([...errors, ...typeErrors])
} else if (pt.type === jsep.IDENTIFIER) { } else if (pt.type === jsep.IDENTIFIER) {
if (this.meta.columns.filter(c => !this.column || this.column.id !== c.id).every(c => c.title !== pt.name)) { if (this.meta.columns.filter(c => !this.column || this.column.id !== c.id).every(c => c.title !== pt.name)) {
arr.add(`Column '${pt.name}' is not available`) errors.add(`Column '${pt.name}' is not available`)
} }
// check circular reference // check circular reference
@ -351,25 +354,52 @@ export default {
} }
// vertices not same as visited = cycle found // vertices not same as visited = cycle found
if (vertices !== visited) { if (vertices !== visited) {
arr.add('Can’t save field because it causes a circular reference') errors.add('Can’t save field because it causes a circular reference')
} }
} }
} else if (pt.type === jsep.BINARY_EXP) { } else if (pt.type === jsep.BINARY_EXP) {
if (!this.availableBinOps.includes(pt.operator)) { if (!this.availableBinOps.includes(pt.operator)) {
arr.add(`'${pt.operator}' operation is not available`) errors.add(`'${pt.operator}' operation is not available`)
} }
this.validateAgainstMeta(pt.left, arr) this.validateAgainstMeta(pt.left, errors)
this.validateAgainstMeta(pt.right, arr) this.validateAgainstMeta(pt.right, errors)
} else if (pt.type === jsep.LITERAL) { } else if (pt.type === jsep.LITERAL) {
// do nothing // do nothing
} else if (pt.type === jsep.COMPOUND) { } else if (pt.type === jsep.COMPOUND) {
if (pt.body.length) { if (pt.body.length) {
arr.add('Can’t save field because the formula is invalid') errors.add('Can’t save field because the formula is invalid')
} }
} else { } else {
arr.add('Can’t save field because the formula is invalid') errors.add('Can’t save field because the formula is invalid')
}
return errors
},
validateAgainstType(pt, type, func, typeErrors = new Set()) {
if (pt === false || typeof pt === 'undefined') { return typeErrors }
if (pt.type === jsep.LITERAL) {
if (typeof func === 'function') {
func(pt.value)
} else if (type === formulaTypes.NUMERIC) {
if (typeof pt.value !== 'number') {
typeErrors.add('Numeric type is expected')
}
} else if (type === formulaTypes.STRING) {
if (typeof pt.value !== 'string') {
typeErrors.add('string type is expected')
}
}
} else if (pt.type == jsep.IDENTIFIER) {
// TODO:
} else if (pt.type === jsep.UNARY_EXP || pt.type === jsep.BINARY_EXP) {
if (type !== formulaTypes.NUMERIC) {
typeErrors.add('Numeric type is expected')
}
} else if (pt.type === jsep.CALL_EXP) {
if (type !== formulas[pt.callee.name].type) {
typeErrors.add(`${type} not matched with ${formulas[pt.callee.name].type}`)
}
} }
return arr return typeErrors
}, },
appendText(it) { appendText(it) {
const text = it.text const text = it.text

Loading…
Cancel
Save