oak-domain/lib/store/checker.js

482 lines
21 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createCreateCheckers = exports.createRemoveCheckers = exports.translateCheckerInSyncContext = exports.translateCheckerInAsyncContext = void 0;
const tslib_1 = require("tslib");
const assert_1 = tslib_1.__importDefault(require("assert"));
const filter_1 = require("../store/filter");
const Exception_1 = require("../types/Exception");
const types_1 = require("../types");
const lodash_1 = require("../utils/lodash");
const action_1 = require("../actions/action");
function getFullProjection(entity, schema) {
const { attributes } = schema[entity];
const projection = {
id: 1,
$$createAt$$: 1,
$$updateAt$$: 1,
$$deleteAt$$: 1,
};
Object.keys(attributes).forEach((k) => Object.assign(projection, {
[k]: 1,
}));
return projection;
}
/**
*
* @param checker 要翻译的checker
* @param silent 如果silent则row和relation类型的checker只会把限制条件加到查询上而不报错除掉create动作
* @returns
*/
function translateCheckerInAsyncContext(checker, schema) {
const { entity, type } = checker;
const when = 'before'; // 现在create的relation改成提前的expression检查了原先是先插入再后检查性能不行而且select也需要实现前检查
switch (type) {
case 'data': {
const { checker: checkerFn } = checker;
const fn = (async ({ operation }, context) => {
const { data } = operation;
await checkerFn(data, context);
return 0;
});
return {
fn,
when,
};
}
case 'row': {
const { filter, errMsg, err, inconsistentRows } = checker;
const fn = (async ({ operation }, context, option) => {
const { filter: operationFilter, data, action, bornAt } = operation;
const filter2 = typeof filter === 'function' ? await filter(operation, context, option) : filter;
if (!filter2) {
return 0;
}
if (['select', 'count', 'stat'].includes(action)) {
operation.filter = operationFilter ? (0, filter_1.combineFilters)(entity, context.getSchema(), [operationFilter, filter2]) : filter2;
return 0;
}
else {
const checkSingle = async (f) => {
if (await (0, filter_1.checkFilterContains)(entity, context, filter2, f, true)) {
return;
}
if (inconsistentRows) {
const { entity: entity2, selection: selection2 } = inconsistentRows;
const rows2 = await context.select(entity2, selection2(operationFilter), {
dontCollect: true,
blockTrigger: true,
});
const e = new (err || (Exception_1.OakRowInconsistencyException))(errMsg);
e.addData(entity2, rows2, context.getSchema());
throw e;
}
else {
// 可能会暴露隐私信息暂时不能这样做。by Xc
/* const rows2 = await context.select(entity, {
data: getFullProjection(entity, context.getSchema()),
filter: Object.assign({}, operationFilter, {
$not: filter2,
})
}, {
dontCollect: true,
blockTrigger: true,
}); */
const e = new (err || (Exception_1.OakRowInconsistencyException))(errMsg);
// e.addData(entity, rows2);
throw e;
}
};
let operationFilter2 = operationFilter;
if (action === 'create') {
// 后台进行创建检查时以传入的data为准
(0, assert_1.default)(data);
if (data instanceof Array) {
for (const d of data) {
await checkSingle((0, filter_1.translateCreateDataToFilter)(schema, entity, d, !!bornAt));
}
}
else {
await checkSingle((0, filter_1.translateCreateDataToFilter)(schema, entity, data, !!bornAt));
}
return;
}
(0, assert_1.default)(operationFilter2, 'row类型的checker遇到了操作的filter未定义');
await checkSingle(operationFilter2);
return 0;
}
});
return {
fn,
when,
};
}
case 'logical':
case 'logicalData': {
const { checker: checkerFn } = checker;
const fn = (async ({ operation }, context, option) => {
await checkerFn(operation, context, option);
return 0;
});
return {
fn,
when,
};
}
default: {
(0, assert_1.default)(false);
}
}
}
exports.translateCheckerInAsyncContext = translateCheckerInAsyncContext;
function translateCheckerInSyncContext(checker, schema) {
const { entity, type } = checker;
const when = 'before'; // 现在create的relation改成提前的expression检查了原先是先插入再后检查性能不行而且select也需要实现前检查
switch (type) {
case 'data': {
const { checker: checkerFn } = checker;
const fn = (operation, context) => checkerFn(operation.data, context);
return {
fn,
when,
};
}
case 'row': {
const { filter, errMsg, entity } = checker;
const fn = (operation, context, option) => {
const { filter: operationFilter, data, action, bornAt } = operation;
const filter2 = typeof filter === 'function' ? filter(operation, context, option) : filter;
if (!filter2) {
return;
}
let operationFilter2 = operationFilter;
if (action === 'create') {
if (data) {
// 前端的策略是有data用data无data用filter
// 目前前端应该不可能制造出来createMultiple
(0, assert_1.default)(!(data instanceof Array));
operationFilter2 = (0, filter_1.translateCreateDataToFilter)(schema, entity, data, !!bornAt);
}
}
(0, assert_1.default)(!(filter2 instanceof Promise));
(0, assert_1.default)(operationFilter2, '定义了row类型的checker但却进行了无filter操作');
if ((0, filter_1.checkFilterContains)(entity, context, filter2, operationFilter2, true)) {
return;
}
const e = new Exception_1.OakRowInconsistencyException(errMsg || 'row checker condition illegal');
throw e;
};
return {
fn,
when,
};
}
case 'logical':
case 'logicalData': {
const { checker: checkerFn } = checker;
const fn = (operation, context, option) => {
checkerFn(operation, context, option);
};
return {
fn,
when,
};
}
default: {
(0, assert_1.default)(false);
}
}
}
exports.translateCheckerInSyncContext = translateCheckerInSyncContext;
/**
* 对对象的删除,检查其是否会产生其他行上的空指针,不允许这种情况的出现
* @param schema
* @returns
* 如果有的对象允许删除需要使用trigger来处理其相关联的外键对象这些trigger写作before则会在checker之前执行仍然可以删除成功
*/
function createRemoveCheckers(schema) {
const checkers = [];
// 先建立所有的一对多的关系
const OneToManyMatrix = {};
const OneToManyOnEntityMatrix = {};
const addToMto = (e, f, attr) => {
if (OneToManyMatrix[f]) {
OneToManyMatrix[f]?.push([e, attr]);
}
else {
OneToManyMatrix[f] = [[e, attr]];
}
};
const addToMtoEntity = (e, fs) => {
for (const f of fs) {
if (!OneToManyOnEntityMatrix[f]) {
OneToManyOnEntityMatrix[f] = [e];
}
else {
OneToManyOnEntityMatrix[f]?.push(e);
}
}
};
for (const entity in schema) {
if (['operEntity', 'modiEntity', 'userEntityGrant'].includes(entity)) {
continue; // 系统功能性数据,不用处理
}
const { attributes } = schema[entity];
for (const attr in attributes) {
if (attributes[attr].type === 'ref') {
addToMto(entity, attributes[attr].ref, attr);
}
else if (attr === 'entity') {
if (attributes[attr].ref) {
addToMtoEntity(entity, attributes[attr].ref);
}
else if (process.env.NODE_ENV === 'development') {
console.warn(`${entity}的entity反指指针找不到有效的对象`);
}
}
}
}
// 当删除一时,要确认多上面没有指向一的数据
const entities = (0, lodash_1.union)(Object.keys(OneToManyMatrix), Object.keys(OneToManyOnEntityMatrix));
for (const entity of entities) {
checkers.push({
entity: entity,
action: 'remove',
type: 'logical',
priority: types_1.CHECKER_MAX_PRIORITY,
checker: (operation, context, option) => {
const promises = [];
if (OneToManyMatrix[entity]) {
for (const otm of OneToManyMatrix[entity]) {
const [e, attr] = otm;
const proj = {
id: 1,
[attr]: 1,
};
const filter = operation.filter && {
[attr.slice(0, attr.length - 2)]: operation.filter
};
const result = context.select(e, {
data: proj,
filter,
indexFrom: 0,
count: 1
}, { dontCollect: true, ignoreAttrMiss: true });
if (result instanceof Promise) {
promises.push(result.then(([row]) => {
if (row) {
const err = new Exception_1.OakRowInconsistencyException(`您无法删除存在有效数据「${e}」关联的行`);
err.addData(e, [row], context.getSchema());
throw err;
}
}));
}
else {
const [row] = result;
if (row) {
const err = new Exception_1.OakRowInconsistencyException(`您无法删除存在有效数据「${e}」关联的行`);
err.addData(e, [row], context.getSchema());
throw err;
}
}
}
}
if (OneToManyOnEntityMatrix[entity]) {
for (const otm of OneToManyOnEntityMatrix[entity]) {
const proj = {
id: 1,
entity: 1,
entityId: 1,
};
const filter = operation.filter && {
[entity]: operation.filter
};
const result = context.select(otm, {
data: proj,
filter,
indexFrom: 0,
count: 1
}, { dontCollect: true, ignoreAttrMiss: true });
if (result instanceof Promise) {
promises.push(result.then(([row]) => {
if (row) {
const e = new Exception_1.OakRowInconsistencyException(`您无法删除存在有效数据「${otm}」关联的行`);
e.addData(otm, [row], context.getSchema());
throw e;
}
}));
}
else {
const [row] = result;
if (row) {
const record = {
a: 's',
d: {
[otm]: {
[row.id]: row,
}
}
};
const e = new Exception_1.OakRowInconsistencyException(`您无法删除存在有效数据「${otm}」关联的行`);
e.addData(otm, [row], context.getSchema());
throw e;
}
}
}
}
if (promises.length > 0) {
return Promise.all(promises).then(() => undefined);
}
}
});
}
return checkers;
}
exports.createRemoveCheckers = createRemoveCheckers;
function checkAttributeLegal(schema, entity, data) {
const { attributes } = schema[entity];
for (const attr in data) {
if (attributes[attr]) {
const { type, params, default: defaultValue, enumeration, notNull } = attributes[attr];
if (data[attr] === null || data[attr] === undefined) {
if (notNull && defaultValue === undefined) {
throw new Exception_1.OakAttrNotNullException(entity, [attr]);
}
if (defaultValue !== undefined) {
Object.assign(data, {
[attr]: defaultValue,
});
}
continue;
}
switch (type) {
case 'char':
case 'varchar': {
if (typeof data[attr] !== 'string') {
throw new Exception_1.OakInputIllegalException(entity, [attr], 'error::attributesFormatError', 'oak-domain', {
attribtues: attr,
format: 'string',
});
}
const { length } = params;
if (length && data[attr].length > length) {
throw new Exception_1.OakInputIllegalException(entity, [attr], 'error::attributesTooLong', 'oak-domain', {
attributes: attr,
length,
});
}
break;
}
case 'int':
case 'smallint':
case 'tinyint':
case 'bigint':
case 'decimal':
case 'money': {
if (typeof data[attr] !== 'number') {
throw new Exception_1.OakInputIllegalException(entity, [attr], 'error::attributesFormatError', 'oak-domain', {
attribtues: attr,
format: 'number',
});
}
const { min, max } = params || {};
if (typeof min === 'number' && data[attr] < min) {
throw new Exception_1.OakInputIllegalException(entity, [attr], 'error::attributesUnderMin', 'oak-domain', {
attributes: attr,
threshold: `${min}`,
});
}
if (typeof max === 'number' && data[attr] > max) {
throw new Exception_1.OakInputIllegalException(entity, [attr], 'error::attributesOverMax', 'oak-domain', {
attributes: attr,
threshold: `${max}`,
});
}
break;
}
case 'enum': {
(0, assert_1.default)(enumeration);
if (!enumeration.includes(data[attr])) {
throw new Exception_1.OakInputIllegalException(entity, [attr], 'error::attributesNotInEnumeration');
}
break;
}
}
}
else {
// 这里似乎还有一种update中带cascade remove的case等遇到再说貌似cascadeUpdate没有处理完整这种情况 by Xc
if (typeof data[attr] === 'object' && data[attr]?.action === 'remove') {
console.warn('cascade remove可能是未处理的边界请注意');
}
}
}
}
function createCreateCheckers(schema) {
const checkers = [];
for (const entity in schema) {
const { attributes, actions } = schema[entity];
const notNullAttrs = Object.keys(attributes).filter(ele => attributes[ele].notNull);
const updateActions = (0, lodash_1.difference)(actions, action_1.excludeUpdateActions);
checkers.push({
entity,
type: 'data',
action: 'create',
checker: (data) => {
const checkData = (data2) => {
const illegalNullAttrs = (0, lodash_1.difference)(notNullAttrs, Object.keys(data2).filter(ele => data2[ele] !== null));
if (illegalNullAttrs.length > 0) {
const emtpyAttrs = [];
// 要处理多对一的cascade create
for (const attr of illegalNullAttrs) {
if (attr === 'entityId') {
if (illegalNullAttrs.includes('entity')) {
continue;
}
}
else if (attr === 'entity' && attributes[attr].ref) {
let hasCascadeCreate = false;
for (const ref of attributes[attr].ref) {
if (data2[ref] && data2[ref].action === 'create') {
hasCascadeCreate = true;
break;
}
}
if (hasCascadeCreate) {
continue;
}
}
else if (attributes[attr].type === 'ref') {
const ref = attributes[attr].ref;
const attr2 = attr.slice(0, attr.length - 2);
if (data2[attr2] && data2[attr2].action === 'create') {
continue;
}
}
// 到这里说明确实是有not null的属性没有赋值
emtpyAttrs.push(attr);
}
if (emtpyAttrs.length > 0) {
throw new Exception_1.OakAttrNotNullException(entity, emtpyAttrs);
}
}
checkAttributeLegal(schema, entity, data2);
};
if (data instanceof Array) {
data.forEach(ele => checkData(ele));
}
else {
checkData(data);
}
}
}, {
entity,
type: 'data',
action: updateActions,
checker: (data) => {
checkAttributeLegal(schema, entity, data);
}
});
}
return checkers;
}
exports.createCreateCheckers = createCreateCheckers;