diff --git a/lib/base-app-domain/Modi/Storage.js b/lib/base-app-domain/Modi/Storage.js index 2058baf..72680e2 100644 --- a/lib/base-app-domain/Modi/Storage.js +++ b/lib/base-app-domain/Modi/Storage.js @@ -5,30 +5,35 @@ var Action_1 = require("./Action"); exports.desc = { attributes: { targetEntity: { + notNull: true, type: "varchar", params: { length: 32 } }, entity: { + notNull: true, type: "varchar", params: { length: 32 } }, entityId: { + notNull: true, type: "varchar", params: { length: 64 } }, action: { + notNull: true, type: "varchar", params: { length: 16 } }, data: { + notNull: true, type: "object" }, filter: { diff --git a/lib/base-app-domain/ModiEntity/Storage.js b/lib/base-app-domain/ModiEntity/Storage.js index 7b8a078..eee9ae2 100644 --- a/lib/base-app-domain/ModiEntity/Storage.js +++ b/lib/base-app-domain/ModiEntity/Storage.js @@ -5,10 +5,12 @@ var action_1 = require("../../actions/action"); exports.desc = { attributes: { modiId: { + notNull: true, type: "ref", ref: "modi" }, entity: { + notNull: true, type: "varchar", params: { length: 32 @@ -16,6 +18,7 @@ exports.desc = { ref: ["user", "userEntityGrant"] }, entityId: { + notNull: true, type: "varchar", params: { length: 64 diff --git a/lib/base-app-domain/Oper/Storage.js b/lib/base-app-domain/Oper/Storage.js index a440ae5..344d8d2 100644 --- a/lib/base-app-domain/Oper/Storage.js +++ b/lib/base-app-domain/Oper/Storage.js @@ -5,12 +5,14 @@ var action_1 = require("../../actions/action"); exports.desc = { attributes: { action: { + notNull: true, type: "varchar", params: { length: 16 } }, data: { + notNull: true, type: "object" }, filter: { @@ -24,6 +26,7 @@ exports.desc = { ref: "user" }, targetEntity: { + notNull: true, type: "varchar", params: { length: 32 diff --git a/lib/base-app-domain/OperEntity/Storage.js b/lib/base-app-domain/OperEntity/Storage.js index 2ee6a26..3421012 100644 --- a/lib/base-app-domain/OperEntity/Storage.js +++ b/lib/base-app-domain/OperEntity/Storage.js @@ -5,10 +5,12 @@ var action_1 = require("../../actions/action"); exports.desc = { attributes: { operId: { + notNull: true, type: "ref", ref: "oper" }, entity: { + notNull: true, type: "varchar", params: { length: 32 @@ -16,6 +18,7 @@ exports.desc = { ref: ["user", "userEntityGrant"] }, entityId: { + notNull: true, type: "varchar", params: { length: 64 diff --git a/lib/base-app-domain/UserEntityGrant/Storage.js b/lib/base-app-domain/UserEntityGrant/Storage.js index 952b3bd..68fbcf7 100644 --- a/lib/base-app-domain/UserEntityGrant/Storage.js +++ b/lib/base-app-domain/UserEntityGrant/Storage.js @@ -5,18 +5,21 @@ var action_1 = require("../../actions/action"); exports.desc = { attributes: { entity: { + notNull: true, type: "varchar", params: { length: 32 } }, entityId: { + notNull: true, type: "varchar", params: { length: 64 } }, relation: { + notNull: true, type: "varchar", params: { length: 32 diff --git a/lib/checkers/index.js b/lib/checkers/index.js index 4ef4b19..b679393 100644 --- a/lib/checkers/index.js +++ b/lib/checkers/index.js @@ -8,6 +8,7 @@ function createDynamicCheckers(schema, authDict) { var checkers = []; checkers.push.apply(checkers, tslib_1.__spreadArray([], tslib_1.__read((0, modi_1.createModiRelatedCheckers)(schema)), false)); checkers.push.apply(checkers, tslib_1.__spreadArray([], tslib_1.__read((0, checker_1.createRemoveCheckers)(schema, authDict)), false)); + checkers.push.apply(checkers, tslib_1.__spreadArray([], tslib_1.__read((0, checker_1.createCreateCheckers)(schema)), false)); if (authDict) { checkers.push.apply(checkers, tslib_1.__spreadArray([], tslib_1.__read((0, checker_1.createAuthCheckers)(schema, authDict)), false)); } diff --git a/lib/compiler/schemalBuilder.js b/lib/compiler/schemalBuilder.js index 2ce4d2d..e33febd 100644 --- a/lib/compiler/schemalBuilder.js +++ b/lib/compiler/schemalBuilder.js @@ -2917,7 +2917,10 @@ function constructAttributes(entity) { var result = []; schemaAttrs.forEach(function (attr) { var attrAssignments = []; - var name = attr.name, type = attr.type; + var name = attr.name, type = attr.type, allowNull = attr.questionToken; + if (!allowNull) { + attrAssignments.push(factory.createPropertyAssignment(factory.createIdentifier("notNull"), factory.createTrue())); + } var name2 = name; if (ts.isTypeReferenceNode(type)) { var typeName = type.typeName, typeArguments = type.typeArguments; diff --git a/lib/store/checker.d.ts b/lib/store/checker.d.ts index 7aa7e0c..0c7772a 100644 --- a/lib/store/checker.d.ts +++ b/lib/store/checker.d.ts @@ -30,3 +30,4 @@ export declare function createAuthCheckers | SyncContext>(schema: StorageSchema, authDict?: AuthDefDict): Checker[]; +export declare function createCreateCheckers | SyncContext>(schema: StorageSchema): Checker[]; diff --git a/lib/store/checker.js b/lib/store/checker.js index 550fe04..5c95ce9 100644 --- a/lib/store/checker.js +++ b/lib/store/checker.js @@ -1,6 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.createRemoveCheckers = exports.createAuthCheckers = exports.translateCheckerInSyncContext = exports.translateCheckerInAsyncContext = void 0; +exports.createCreateCheckers = exports.createRemoveCheckers = exports.createAuthCheckers = exports.translateCheckerInSyncContext = exports.translateCheckerInAsyncContext = void 0; var tslib_1 = require("tslib"); var assert_1 = tslib_1.__importDefault(require("assert")); var filter_1 = require("../store/filter"); @@ -11,6 +11,7 @@ var string_1 = require("../utils/string"); var lodash_1 = require("../utils/lodash"); var relation_1 = require("./relation"); var uuid_1 = require("../utils/uuid"); +var action_1 = require("../actions/action"); /** * * @param checker 要翻译的checker @@ -1216,3 +1217,155 @@ function createRemoveCheckers(schema, authDict) { return checkers; } exports.createRemoveCheckers = createRemoveCheckers; +function checkAttributeLegal(schema, entity, data) { + var _a; + var attributes = schema[entity].attributes; + for (var attr in data) { + if (attributes[attr]) { + var _b = attributes[attr], type = _b.type, params = _b.params, defaultValue = _b.default, enumeration = _b.enumeration, notNull = _b.notNull; + if (data[attr] === null || data[attr] === undefined) { + if (notNull && defaultValue === undefined) { + throw new Exception_1.OakAttrNotNullException(entity, [attr]); + } + if (defaultValue !== undefined) { + Object.assign(data, (_a = {}, + _a[attr] = defaultValue, + _a)); + } + continue; + } + switch (type) { + case 'char': + case 'varchar': { + if (typeof data[attr] !== 'string') { + throw new Exception_1.OakInputIllegalException(entity, [attr], 'not a string'); + } + var length_1 = params.length; + if (length_1 && data[attr].length > length_1) { + throw new Exception_1.OakInputIllegalException(entity, [attr], 'too long'); + } + 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], 'not a number'); + } + var _c = params || {}, min = _c.min, max = _c.max; + if (typeof min === 'number' && data[attr] < min) { + throw new Exception_1.OakInputIllegalException(entity, [attr], 'too small'); + } + if (typeof max === 'number' && data[attr] > max) { + throw new Exception_1.OakInputIllegalException(entity, [attr], 'too big'); + } + break; + } + case 'enum': { + (0, assert_1.default)(enumeration); + if (!enumeration.includes(data[attr])) { + throw new Exception_1.OakInputIllegalException(entity, [attr], 'not in enumberation'); + } + 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) { + var checkers = []; + var _loop_9 = function (entity) { + var _a = schema[entity], attributes = _a.attributes, actions = _a.actions; + var notNullAttrs = Object.keys(attributes).filter(function (ele) { return attributes[ele].notNull; }); + var updateActions = (0, lodash_1.difference)(actions, action_1.excludeUpdateActions); + checkers.push({ + entity: entity, + type: 'data', + action: 'create', + checker: function (data) { + var checkData = function (data2) { + var e_9, _a, e_10, _b; + var illegalNullAttrs = (0, lodash_1.difference)(notNullAttrs, Object.keys(data2)); + if (illegalNullAttrs.length > 0) { + try { + // 要处理多对一的cascade create + for (var illegalNullAttrs_1 = (e_9 = void 0, tslib_1.__values(illegalNullAttrs)), illegalNullAttrs_1_1 = illegalNullAttrs_1.next(); !illegalNullAttrs_1_1.done; illegalNullAttrs_1_1 = illegalNullAttrs_1.next()) { + var attr = illegalNullAttrs_1_1.value; + if (attr === 'entityId') { + if (illegalNullAttrs.includes('entity')) { + continue; + } + } + else if (attr === 'entity' && attributes[attr].type === 'ref') { + var hasCascadeCreate = false; + try { + for (var _c = (e_10 = void 0, tslib_1.__values(attributes[attr].ref)), _d = _c.next(); !_d.done; _d = _c.next()) { + var ref = _d.value; + if (data2[ref] && data2[ref].action === 'create') { + hasCascadeCreate = true; + break; + } + } + } + catch (e_10_1) { e_10 = { error: e_10_1 }; } + finally { + try { + if (_d && !_d.done && (_b = _c.return)) _b.call(_c); + } + finally { if (e_10) throw e_10.error; } + } + if (hasCascadeCreate) { + continue; + } + } + else if (attributes[attr].type === 'ref') { + var ref = attributes[attr].ref; + if (data2[ref] && data2[ref].action === 'create') { + continue; + } + } + // 到这里说明确实是有not null的属性没有赋值 + throw new Exception_1.OakAttrNotNullException(entity, illegalNullAttrs); + } + } + catch (e_9_1) { e_9 = { error: e_9_1 }; } + finally { + try { + if (illegalNullAttrs_1_1 && !illegalNullAttrs_1_1.done && (_a = illegalNullAttrs_1.return)) _a.call(illegalNullAttrs_1); + } + finally { if (e_9) throw e_9.error; } + } + } + checkAttributeLegal(schema, entity, data2); + }; + if (data instanceof Array) { + data.forEach(function (ele) { return checkData(ele); }); + } + else { + checkData(data); + } + } + }, { + entity: entity, + type: 'data', + action: updateActions, + checker: function (data) { + checkAttributeLegal(schema, entity, data); + } + }); + }; + for (var entity in schema) { + _loop_9(entity); + } + return checkers; +} +exports.createCreateCheckers = createCreateCheckers; diff --git a/lib/store/modi.js b/lib/store/modi.js index c7e53db..381824b 100644 --- a/lib/store/modi.js +++ b/lib/store/modi.js @@ -151,8 +151,8 @@ function createModiRelatedTriggers(schema) { var _this = this; var triggers = []; var _loop_2 = function (entity) { - var inModi = schema[entity].inModi; - if (inModi) { + var toModi = schema[entity].toModi; + if (toModi) { // 当关联modi的对象被删除时,对应的modi也删除。这里似乎只需要删除掉活跃对象?因为oper不能删除,所以oper和modi是必须要支持对deleted对象的容错? // 这里没有想清楚,by Xc 20230209 triggers.push({ diff --git a/lib/store/relation.js b/lib/store/relation.js index 9338ae4..765dc26 100644 --- a/lib/store/relation.js +++ b/lib/store/relation.js @@ -55,6 +55,7 @@ function judgeRelation(schema, entity, attr) { else if (attributes.hasOwnProperty('entity') && attributes.hasOwnProperty('entityId') && schema.hasOwnProperty(attr)) { + (0, assert_1.default)(attributes.entity.ref.includes(attr), '不应当出现的case'); // 反向指针的外键 return 2; } diff --git a/lib/timers/vaccum.js b/lib/timers/vaccum.js index 8d68731..fcbf533 100644 --- a/lib/timers/vaccum.js +++ b/lib/timers/vaccum.js @@ -5,7 +5,6 @@ var tslib_1 = require("tslib"); var dayjs_1 = tslib_1.__importDefault(require("dayjs")); var fs_1 = require("fs"); var filter_1 = require("../store/filter"); -var node_zlib_1 = require("node:zlib"); var stream_1 = require("stream"); var uuid_1 = require("../utils/uuid"); /** @@ -23,7 +22,7 @@ function vaccumEntities(option, context) { case 0: entities = option.entities, backupDir = option.backupDir; _loop_1 = function (ele) { - var entity, filter, aliveLine, filter2, zip, now, backFile, fd_1, attributes_1, projection_1, attr, count_1, appendData_1, gzip_1, source_1, destination_1, _c, _d, _e; + var entity, filter, aliveLine, filter2, zip, now, backFile, fd_1, attributes_1, projection_1, attr, count_1, appendData_1, createGzip, gzip_1, source_1, destination_1, _c, _d, _e; var _f, _g; return tslib_1.__generator(this, function (_h) { switch (_h.label) { @@ -37,7 +36,7 @@ function vaccumEntities(option, context) { if (filter) { filter2 = (0, filter_1.combineFilters)([filter2, filter]); } - if (!backupDir) return [3 /*break*/, 4]; + if (!(backupDir && process.env.OAK_PLATFORM === 'server')) return [3 /*break*/, 4]; zip = option.zip; now = (0, dayjs_1.default)(); backFile = "".concat(backupDir, "/").concat(entity, "-").concat(now.format('YYYY-MM-DD HH:mm:ss'), ".csv"); @@ -107,7 +106,8 @@ function vaccumEntities(option, context) { return [3 /*break*/, 4]; case 2: if (!zip) return [3 /*break*/, 4]; - gzip_1 = (0, node_zlib_1.createGzip)(); + createGzip = require('zlib').createGzip; + gzip_1 = createGzip(); source_1 = (0, fs_1.createReadStream)(backFile); destination_1 = (0, fs_1.createWriteStream)("".concat(backFile, ".zip")); return [4 /*yield*/, new Promise(function (resolve, reject) { diff --git a/lib/types/Entity.d.ts b/lib/types/Entity.d.ts index 99255df..bb3642f 100644 --- a/lib/types/Entity.d.ts +++ b/lib/types/Entity.d.ts @@ -65,7 +65,7 @@ export interface EntityShape { $$updateAt$$: number | Date; $$deleteAt$$?: number | Date | null; } -interface GeneralEntityShape extends EntityShape { +export interface GeneralEntityShape extends EntityShape { [K: string]: any; } export declare type MakeAction = A; diff --git a/lib/types/Exception.d.ts b/lib/types/Exception.d.ts index a0c875a..4907711 100644 --- a/lib/types/Exception.d.ts +++ b/lib/types/Exception.d.ts @@ -55,12 +55,15 @@ export declare class OakRowInconsistencyException extends export declare class OakInputIllegalException extends OakUserException { private attributes; private entity; - constructor(entity: string, attributes: string[], message?: string); - getEntity(): string; + constructor(entity: keyof ED, attributes: string[], message?: string); + getEntity(): keyof ED; getAttributes(): string[]; addAttributesPrefix(prefix: string): void; toString(): string; } +export declare class OakAttrNotNullException extends OakInputIllegalException { + constructor(entity: keyof ED, attributes: string[], message?: string); +} /** * 用户权限不够时抛的异常 */ diff --git a/lib/types/Exception.js b/lib/types/Exception.js index ecfeaee..83a3dd8 100644 --- a/lib/types/Exception.js +++ b/lib/types/Exception.js @@ -1,6 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.makeException = exports.OakPreConditionUnsetException = exports.OakDeadlock = exports.OakCongruentRowExists = exports.OakRowLockedException = exports.OakUnloggedInException = exports.OakUserUnpermittedException = exports.OakInputIllegalException = exports.OakRowInconsistencyException = exports.OakUserException = exports.OakExternalException = exports.OakRowUnexistedException = exports.OakOperExistedException = exports.OakImportDataParseException = exports.OakUniqueViolationException = exports.OakDataException = exports.OakException = void 0; +exports.makeException = exports.OakPreConditionUnsetException = exports.OakDeadlock = exports.OakCongruentRowExists = exports.OakRowLockedException = exports.OakUnloggedInException = exports.OakUserUnpermittedException = exports.OakAttrNotNullException = exports.OakInputIllegalException = exports.OakRowInconsistencyException = exports.OakUserException = exports.OakExternalException = exports.OakRowUnexistedException = exports.OakOperExistedException = exports.OakImportDataParseException = exports.OakUniqueViolationException = exports.OakDataException = exports.OakException = void 0; var tslib_1 = require("tslib"); var assert_1 = tslib_1.__importDefault(require("assert")); var OakException = /** @class */ (function (_super) { @@ -179,6 +179,15 @@ var OakInputIllegalException = /** @class */ (function (_super) { }(OakUserException)); exports.OakInputIllegalException = OakInputIllegalException; ; +// 属性为空 +var OakAttrNotNullException = /** @class */ (function (_super) { + tslib_1.__extends(OakAttrNotNullException, _super); + function OakAttrNotNullException(entity, attributes, message) { + return _super.call(this, entity, attributes, message || '属性不允许为空') || this; + } + return OakAttrNotNullException; +}(OakInputIllegalException)); +exports.OakAttrNotNullException = OakAttrNotNullException; /** * 用户权限不够时抛的异常 */ @@ -344,6 +353,11 @@ function makeException(data) { e.setOpRecords(data.opRecords); return e; } + case 'OakAttrNotNullException': { + var e = new OakAttrNotNullException(data.entity, data.attributes, data.message); + e.setOpRecords(data.opRecords); + return e; + } default: return; } diff --git a/lib/utils/validator.js b/lib/utils/validator.js index 746ac13..25cfe1c 100644 --- a/lib/utils/validator.js +++ b/lib/utils/validator.js @@ -114,7 +114,7 @@ function checkAttributesNotNull(entity, data, attributes, allowEmpty) { } }); if (attrs.length > 0) { - throw new types_1.OakInputIllegalException(entity, attrs, '属性不能为空'); + throw new types_1.OakAttrNotNullException(entity, attrs, '属性不能为空'); } } exports.checkAttributesNotNull = checkAttributesNotNull; diff --git a/package.json b/package.json index 2ca4be7..ca4cd34 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oak-domain", - "version": "2.6.7", + "version": "2.6.8", "author": { "name": "XuChang" }, diff --git a/src/checkers/index.ts b/src/checkers/index.ts index 840ce8b..10acf66 100644 --- a/src/checkers/index.ts +++ b/src/checkers/index.ts @@ -1,6 +1,6 @@ import { EntityDict } from '../base-app-domain'; import { AsyncContext } from '../store/AsyncRowStore'; -import { createAuthCheckers, createRemoveCheckers } from '../store/checker'; +import { createAuthCheckers, createRemoveCheckers, createCreateCheckers } from '../store/checker'; import { createModiRelatedCheckers } from '../store/modi'; import { SyncContext } from '../store/SyncRowStore'; import { StorageSchema, EntityDict as BaseEntityDict, Checker, AuthDef, AuthDefDict } from '../types'; @@ -9,6 +9,7 @@ export function createDynamicCheckers[] = []; checkers.push(...createModiRelatedCheckers(schema)); checkers.push(...createRemoveCheckers(schema, authDict)); + checkers.push(...createCreateCheckers(schema)); if (authDict) { checkers.push(...createAuthCheckers(schema, authDict)); } diff --git a/src/compiler/schemalBuilder.ts b/src/compiler/schemalBuilder.ts index 94db06f..060f53e 100644 --- a/src/compiler/schemalBuilder.ts +++ b/src/compiler/schemalBuilder.ts @@ -5268,7 +5268,15 @@ function constructAttributes(entity: string): ts.PropertyAssignment[] { schemaAttrs.forEach( (attr) => { const attrAssignments: ts.PropertyAssignment[] = []; - const { name, type } = attr; + const { name, type, questionToken: allowNull } = attr; + if (!allowNull) { + attrAssignments.push( + factory.createPropertyAssignment( + factory.createIdentifier("notNull"), + factory.createTrue(), + ), + ); + } let name2 = name; if (ts.isTypeReferenceNode(type!)) { diff --git a/src/store/TriggerExecutor.ts b/src/store/TriggerExecutor.ts index 3b7c726..ce092ca 100644 --- a/src/store/TriggerExecutor.ts +++ b/src/store/TriggerExecutor.ts @@ -241,7 +241,7 @@ export class TriggerExecutor { } } const number = (trigger as CreateTrigger).fn({ operation: operation as ED[T]['Create'] }, context, option as OperateOption); - if (number > 0) { + if (number as number > 0) { this.logger.info(`触发器「${trigger.name}」成功触发了「${number}」行数据更改`); } } @@ -371,7 +371,7 @@ export class TriggerExecutor { operation: operation as ED[T]['Selection'], result: result!, }, context, option as SelectOption); - if (number > 0) { + if (number as number > 0) { this.logger.info(`触发器「${trigger.name}」成功触发了「${number}」行数据更改`); } } diff --git a/src/store/checker.ts b/src/store/checker.ts index 2efede7..486f8fd 100644 --- a/src/store/checker.ts +++ b/src/store/checker.ts @@ -1,6 +1,6 @@ import assert from 'assert'; import { addFilterSegment, checkFilterContains, combineFilters } from "../store/filter"; -import { OakRowInconsistencyException, OakUserUnpermittedException } from '../types/Exception'; +import { OakAttrNotNullException, OakInputIllegalException, OakRowInconsistencyException, OakUserUnpermittedException } from '../types/Exception'; import { AuthDefDict, CascadeRelationItem, Checker, CreateTriggerInTxn, EntityDict, OperateOption, SelectOption, StorageSchema, Trigger, UpdateTriggerInTxn, RelationHierarchy, SelectOpResult, REMOVE_CASCADE_PRIORITY, RefOrExpression, SyncOrAsync @@ -13,6 +13,7 @@ import { firstLetterUpperCase } from '../utils/string'; import { union, uniq, difference } from '../utils/lodash'; import { judgeRelation } from './relation'; import { generateNewId } from '../utils/uuid'; +import { excludeUpdateActions } from '../actions/action'; /** * @@ -777,10 +778,10 @@ export function createAuthCheckers( + schema: StorageSchema, + entity: keyof ED, + data: ED[keyof ED]['Update']['data'] | ED[keyof ED]['CreateSingle']['data']) { + const { attributes } = schema[entity]; + for (const attr in data) { + if (attributes[attr as string]) { + const { type, params, default: defaultValue, enumeration, notNull } = attributes[attr as string]; + if (data[attr] === null || data[attr] === undefined) { + if (notNull && defaultValue === undefined) { + throw new OakAttrNotNullException(entity, [attr]); + } + if (defaultValue !== undefined) { + Object.assign(data, { + [attr]: defaultValue, + }); + } + continue; + } + switch (type) { + case 'char': + case 'varchar': { + if (typeof (data as ED[keyof ED]['CreateSingle']['data'])[attr] !== 'string') { + throw new OakInputIllegalException(entity, [attr], 'not a string'); + } + const { length } = params!; + if (length && (data as ED[keyof ED]['CreateSingle']['data'])[attr]!.length > length) { + throw new OakInputIllegalException(entity, [attr], 'too long'); + } + break; + } + case 'int': + case 'smallint': + case 'tinyint': + case 'bigint': + case 'decimal': + case 'money': { + if (typeof (data as ED[keyof ED]['CreateSingle']['data'])[attr] !== 'number') { + throw new OakInputIllegalException(entity, [attr], 'not a number'); + } + const { min, max } = params || {}; + if (typeof min === 'number' && (data as ED[keyof ED]['CreateSingle']['data'])[attr] < min) { + throw new OakInputIllegalException(entity, [attr], 'too small'); + } + if (typeof max === 'number' && (data as ED[keyof ED]['CreateSingle']['data'])[attr] > max) { + throw new OakInputIllegalException(entity, [attr], 'too big'); + } + break; + } + case 'enum': { + assert(enumeration); + if (!enumeration.includes((data as ED[keyof ED]['CreateSingle']['data'])[attr])) { + throw new OakInputIllegalException(entity, [attr], 'not in enumberation'); + } + break; + } + } + } + else { + // 这里似乎还有一种update中带cascade remove的case,等遇到再说(貌似cascadeUpdate没有处理完整这种情况) by Xc + if (typeof data[attr] === 'object' && data[attr].action === 'remove') { + console.warn('cascade remove可能是未处理的边界,请注意'); + } + } + } +} + +export function createCreateCheckers | SyncContext>(schema: StorageSchema) { + const checkers: Checker[] = []; + + for (const entity in schema) { + const { attributes, actions } = schema[entity]; + const notNullAttrs = Object.keys(attributes).filter( + ele => attributes[ele].notNull + ); + + const updateActions = difference(actions, excludeUpdateActions); + + checkers.push({ + entity, + type: 'data', + action: 'create' as ED[keyof ED]['Action'], + checker: (data) => { + const checkData = (data2: ED[keyof ED]['CreateSingle']['data']) => { + const illegalNullAttrs = difference(notNullAttrs, Object.keys(data2)); + if (illegalNullAttrs.length > 0) { + // 要处理多对一的cascade create + for (const attr of illegalNullAttrs) { + if (attr === 'entityId') { + if (illegalNullAttrs.includes('entity')) { + continue; + } + } + else if (attr === 'entity' && attributes[attr].type === 'ref') { + let hasCascadeCreate = false; + for (const ref of attributes[attr].ref as string[]) { + if (data2[ref] && data2[ref].action === 'create') { + hasCascadeCreate = true; + break; + } + } + if (hasCascadeCreate) { + continue; + } + } + else if (attributes[attr].type === 'ref') { + const ref = attributes[attr].ref as string; + if (data2[ref] && data2[ref].action === 'create') { + continue; + } + } + // 到这里说明确实是有not null的属性没有赋值 + throw new OakAttrNotNullException(entity, illegalNullAttrs); + } + } + checkAttributeLegal(schema, entity, data2); + }; + if (data instanceof Array) { + data.forEach( + ele => checkData(ele) + ); + } + else { + checkData(data as ED[keyof ED]['CreateSingle']['data']); + } + } + }, { + entity, + type: 'data', + action: updateActions as ED[keyof ED]['Action'][], + checker: (data) => { + checkAttributeLegal(schema, entity, data); + } + }) + } + return checkers; } \ No newline at end of file diff --git a/src/store/modi.ts b/src/store/modi.ts index 2fe1233..338c5cc 100644 --- a/src/store/modi.ts +++ b/src/store/modi.ts @@ -149,8 +149,8 @@ export function createModiRelatedTriggers[] = []; for (const entity in schema) { - const { inModi } = schema[entity]; - if (inModi) { + const { toModi } = schema[entity]; + if (toModi) { // 当关联modi的对象被删除时,对应的modi也删除。这里似乎只需要删除掉活跃对象?因为oper不能删除,所以oper和modi是必须要支持对deleted对象的容错? // 这里没有想清楚,by Xc 20230209 triggers.push({ diff --git a/src/store/relation.ts b/src/store/relation.ts index 7a65389..3723cdd 100644 --- a/src/store/relation.ts +++ b/src/store/relation.ts @@ -59,6 +59,7 @@ export function judgeRelation extends OakUser */ export class OakInputIllegalException extends OakUserException { private attributes: string[]; - private entity: string; - constructor(entity: string, attributes: string[], message?: string) { + private entity: keyof ED; + constructor(entity: keyof ED, attributes: string[], message?: string) { super(message); this.entity = entity; this.attributes = attributes; @@ -169,6 +169,13 @@ export class OakInputIllegalException extends OakUserExce } }; +// 属性为空 +export class OakAttrNotNullException extends OakInputIllegalException { + constructor(entity: keyof ED, attributes: string[], message?: string) { + super(entity, attributes, message || '属性不允许为空'); + } +} + /** * 用户权限不够时抛的异常 */ @@ -329,6 +336,15 @@ export function makeException(data: { e.setOpRecords(data.opRecords); return e; } + case 'OakAttrNotNullException': { + const e = new OakAttrNotNullException( + data.entity, + data.attributes, + data.message + ); + e.setOpRecords(data.opRecords); + return e; + } default: return; } diff --git a/src/utils/validator.ts b/src/utils/validator.ts index 34bd97f..5ce1ce5 100644 --- a/src/utils/validator.ts +++ b/src/utils/validator.ts @@ -3,7 +3,7 @@ */ 'use strict'; -import { EntityDict, OakInputIllegalException } from "../types"; +import { EntityDict, OakAttrNotNullException, OakInputIllegalException } from "../types"; type ValidatorFunction = (text: string, size?:number) => string|boolean; type ValidatorMoneyFunction = (text: string, zero?:boolean) => string|boolean; @@ -129,7 +129,7 @@ export function checkAttributesNotNull 0) { - throw new OakInputIllegalException(entity as string, attrs, '属性不能为空'); + throw new OakAttrNotNullException(entity as string, attrs, '属性不能为空'); } };