New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@contember/schema-utils

Package Overview
Dependencies
Maintainers
5
Versions
272
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@contember/schema-utils - npm Package Compare versions

Comparing version 1.1.0-alpha.5 to 1.1.0-alpha.6

dist/src/type-schema/acl.d.ts

6

dist/src/acl/index.js
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {

@@ -6,0 +10,0 @@ if (k2 === undefined) k2 = k;

4

dist/src/dataUtils.d.ts

@@ -7,3 +7,3 @@ import { Input, Model, Value } from '@contember/schema';

export declare const resolveDefaultValue: (column: Model.AnyColumn, providers: Pick<Providers, 'now'>) => string | number | boolean | null;
export declare const resolvePrimaryGenerator: (column: Model.AnyColumn, providers: Providers) => (() => Input.PrimaryValue);
export declare const resolvePrimaryGenerator: (column: Model.AnyColumn, providers: Providers) => (() => Input.PrimaryValue | undefined);
export declare const resolveColumnValue: ({ entity, column, input, }: {

@@ -13,5 +13,5 @@ entity: Model.Entity;

input: Input.ColumnValue | undefined;
}, providers: Providers) => Value.AtomicValue;
}, providers: Providers) => Value.AtomicValue | undefined;
export declare class NoDataError extends Error {
}
//# sourceMappingURL=dataUtils.d.ts.map

@@ -39,3 +39,3 @@ "use strict";

}
throw new Error('not implemented');
return () => undefined;
};

@@ -42,0 +42,0 @@ exports.resolvePrimaryGenerator = resolvePrimaryGenerator;

import { Schema } from '@contember/schema';
import * as Typesafe from '@contember/typesafe';
export * from './model';

@@ -10,2 +11,3 @@ export * from './acl';

export declare const emptySchema: Schema;
export declare const schemaType: Typesafe.Type<Schema>;
//# sourceMappingURL=index.d.ts.map
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {

@@ -9,2 +13,14 @@ if (k2 === undefined) k2 = k;

}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __exportStar = (this && this.__exportStar) || function(m, exports) {

@@ -14,4 +30,6 @@ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);

Object.defineProperty(exports, "__esModule", { value: true });
exports.emptySchema = exports.deepCompare = void 0;
exports.schemaType = exports.emptySchema = exports.deepCompare = void 0;
const model_1 = require("./model");
const Typesafe = __importStar(require("@contember/typesafe"));
const type_schema_1 = require("./type-schema");
__exportStar(require("./model"), exports);

@@ -30,2 +48,7 @@ __exportStar(require("./acl"), exports);

};
exports.schemaType = Typesafe.object({
model: type_schema_1.modelSchema,
acl: type_schema_1.aclSchema,
validation: type_schema_1.validationSchema,
});
//# sourceMappingURL=index.js.map
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {

@@ -6,0 +10,0 @@ if (k2 === undefined) k2 = k;

export declare class NamingHelper {
static createForeignKeyName(fromTable: string, fromColumn: string, toTable: string, toColumn: string): string;
static createUniqueConstraintName: (entityName: string, fields: string[]) => string;
static createUniqueConstraintName: (entityName: string, fields: readonly string[]) => string;
}
//# sourceMappingURL=NamingHelper.d.ts.map
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {

@@ -6,0 +10,0 @@ if (k2 === undefined) k2 = k;

@@ -6,3 +6,3 @@ import { Acl, Model } from '@contember/schema';

constructor(model: Model.Schema);
validate(schema: unknown): [Acl.Schema, ValidationError[]];
validate(schema: Acl.Schema): ValidationError[];
private validateRoles;

@@ -13,3 +13,2 @@ private validateRolePermissions;

private validateVariable;
private validateStagesDefinition;
private validatePermissions;

@@ -16,0 +15,0 @@ private validateEntityPermissions;

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AclValidator = void 0;
const schema_1 = require("@contember/schema");
const utils_1 = require("./utils");
const acl_1 = require("../acl");

@@ -15,22 +13,6 @@ const model_1 = require("../model");

const errorBuilder = new errors_1.ErrorBuilder([], ['acl']);
let validSchema;
if (!(0, utils_1.isObject)(schema)) {
errorBuilder.add('Must be an object');
validSchema = { roles: {} };
}
else if (!schema.roles) {
errorBuilder.for('roles').add('Property is missing');
validSchema = { roles: {} };
}
else {
validSchema = { roles: this.validateRoles(schema.roles, errorBuilder) };
}
return [validSchema, errorBuilder.errors];
this.validateRoles(schema.roles, errorBuilder);
return errorBuilder.errors;
}
validateRoles(roles, errorBuilder) {
if (!(0, utils_1.isObject)(roles)) {
errorBuilder.add('Must be an object');
return {};
}
const validRoles = {};
for (const role in roles) {

@@ -40,36 +22,10 @@ if (!roles.hasOwnProperty(role)) {

}
// if (role === 'admin') {
// errorBuilder.add('Role "admin" is reserved.')
// continue
// }
const rolePermissions = this.validateRolePermissions(roles[role], Object.keys(roles), errorBuilder.for(role));
if (rolePermissions !== undefined) {
validRoles[role] = rolePermissions;
}
this.validateRolePermissions(roles[role], Object.keys(roles), errorBuilder.for(role));
}
return validRoles;
}
validateRolePermissions(permissions, roles, errorBuilder) {
if (!(0, utils_1.isObject)(permissions)) {
errorBuilder.add('Must be an object');
return;
}
const { inherits: inheritsIn, variables: variablesIn, stages: stagesIn, entities: entitiesIn, implicit: implicitIn, ...plugins } = permissions;
const inherits = this.validateInherits(inheritsIn, roles, errorBuilder.for('inherits'));
const variables = this.validateVariables(variablesIn, errorBuilder.for('variables'));
const stages = this.validateStagesDefinition(stagesIn, errorBuilder.for('stages'));
const entities = this.validatePermissions(entitiesIn, permissions.variables || {}, errorBuilder.for('entities'));
this.validateInherits(permissions.inherits, roles, errorBuilder.for('inherits'));
this.validateVariables(permissions.variables, errorBuilder.for('variables'));
this.validatePermissions(permissions.entities, permissions.variables || {}, errorBuilder.for('entities'));
// todo: plugins validation
const result = { variables, stages, entities };
if (inherits !== undefined) {
result.inherits = inherits;
}
if (implicitIn !== undefined) {
if (typeof implicitIn !== 'boolean') {
errorBuilder.for('implicit').add('Must be boolean');
return;
}
result.implicit = implicitIn;
}
return { ...plugins, ...result };
}

@@ -80,26 +36,9 @@ validateInherits(inherits, roles, errorBuilder) {

}
if (!Array.isArray(inherits) || !(0, utils_1.everyIs)(inherits, (it) => typeof it === 'string')) {
errorBuilder.add('Must be an array of strings');
return [];
}
else {
const validRoles = [];
for (const inheritsFrom of inherits) {
if (!roles.includes(inheritsFrom)) {
// todo: check recursion
errorBuilder.for(inheritsFrom).add('Referenced role not exists.');
}
else {
validRoles.push(inheritsFrom);
}
for (const inheritsFrom of inherits) {
if (!roles.includes(inheritsFrom)) {
errorBuilder.for(inheritsFrom).add('Referenced role not exists.');
}
return validRoles;
}
}
validateVariables(variables, errorBuilder) {
let validVariables = {};
if (!(0, utils_1.isObject)(variables)) {
errorBuilder.add('Must be an object');
return {};
}
for (const varName in variables) {

@@ -109,24 +48,8 @@ if (!variables.hasOwnProperty(varName)) {

}
const validVariable = this.validateVariable(variables[varName], errorBuilder.for(varName));
if (validVariable) {
validVariables[varName] = validVariable;
}
this.validateVariable(variables[varName], errorBuilder.for(varName));
}
return validVariables;
}
validateVariable(variable, errorBuilder) {
if (!(0, utils_1.isObject)(variable)) {
errorBuilder.add('Must be an object');
return;
}
if (!(0, utils_1.hasStringProperty)(variable, 'type')) {
errorBuilder.add('Variable type is not defined');
return;
}
switch (variable.type) {
case schema_1.Acl.VariableType.entity:
if (!(0, utils_1.hasStringProperty)(variable, 'entityName')) {
errorBuilder.add('Entity name must be specified for this type of variable');
return;
}
case 'entity':
if (!this.model.entities[variable.entityName]) {

@@ -136,43 +59,6 @@ errorBuilder.add(`Entity "${variable.entityName}" not found`);

}
const extra = (0, utils_1.checkExtraProperties)(variable, ['entityName', 'type']);
if (extra.length) {
errorBuilder.add('Unsupported properties found: ' + extra.join(', '));
}
return { type: schema_1.Acl.VariableType.entity, entityName: variable.entityName };
case schema_1.Acl.VariableType.predefined:
if (!(0, utils_1.hasStringProperty)(variable, 'value')) {
errorBuilder.add('Variable value type must be specified for this type of variable');
return;
}
if (variable.value !== 'identityID' && variable.value !== 'personID') {
errorBuilder.add('Unknown predefined variable');
return;
}
const extra2 = (0, utils_1.checkExtraProperties)(variable, ['type', 'value']);
if (extra2.length) {
errorBuilder.add('Unsupported properties found: ' + extra2.join(', '));
}
return { type: variable.type, value: variable.value };
default:
errorBuilder.add(`Variable type "${variable.type}" is not supported.`);
}
return;
}
validateStagesDefinition(stages, errorBuilder) {
if (stages === '*') {
return '*';
}
if (!Array.isArray(stages) || !(0, utils_1.everyIs)(stages, (it) => typeof it === 'string')) {
errorBuilder.add('Stages must be either "*" or array of stage names');
return [];
}
// todo validate stages ??
return stages;
}
validatePermissions(permissions, variables, errorBuilder) {
if (!(0, utils_1.isObject)(permissions)) {
errorBuilder.add('Must be an object');
return {};
}
const validPermissions = {};
for (const entityName in permissions) {

@@ -187,28 +73,10 @@ if (!permissions.hasOwnProperty(entityName)) {

const entity = (0, model_1.getEntity)(this.model, entityName);
const entityPermissions = this.validateEntityPermissions(permissions[entityName], entity, variables, errorBuilder.for(entityName));
if (entityPermissions) {
validPermissions[entityName] = entityPermissions;
}
this.validateEntityPermissions(permissions[entityName], entity, variables, errorBuilder.for(entityName));
}
return validPermissions;
}
validateEntityPermissions(entityPermissions, entity, variables, errorBuilder) {
if (!(0, utils_1.isObject)(entityPermissions)) {
errorBuilder.add('Must be an object');
return;
}
const extra = (0, utils_1.checkExtraProperties)(entityPermissions, ['predicates', 'operations']);
if (extra.length) {
errorBuilder.add('Unsupported properties found: ' + extra.join(', '));
}
const predicates = this.validatePredicates(entityPermissions.predicates, entity, variables, errorBuilder.for('predicates'));
const operations = this.validateOperations(entityPermissions.operations, entity, entityPermissions.predicates, errorBuilder.for('operations'));
return { operations, predicates };
this.validatePredicates(entityPermissions.predicates, entity, variables, errorBuilder.for('predicates'));
this.validateOperations(entityPermissions.operations, entity, entityPermissions.predicates, errorBuilder.for('operations'));
}
validatePredicates(predicates, entity, variables, errorBuilder) {
if (!(0, utils_1.isObject)(predicates)) {
errorBuilder.add('Must be an object');
return {};
}
const validPredicates = {};
for (const predicateName in predicates) {

@@ -218,20 +86,10 @@ if (!predicates.hasOwnProperty(predicateName)) {

}
const predicate = this.validatePredicateDefinition(predicates[predicateName], entity, variables, errorBuilder.for(predicateName));
if (predicate) {
validPredicates[predicateName] = predicate;
}
this.validatePredicateDefinition(predicates[predicateName], entity, variables, errorBuilder.for(predicateName));
}
return validPredicates;
}
validatePredicateDefinition(predicate, entity, variables, errorBuilder) {
if (!(0, utils_1.isObject)(predicate)) {
errorBuilder.add('Must be an object');
return;
}
const processor = new acl_1.PredicateDefinitionProcessor(this.model);
let valid = true;
const result = processor.process(entity, predicate, {
processor.process(entity, predicate, {
handleColumn: ctx => {
if (typeof ctx.value === 'string' && !variables[ctx.value]) {
valid = false;
errorBuilder.for(...ctx.path).add(`Undefined variable ${ctx.value}`);

@@ -243,3 +101,2 @@ }

if (typeof ctx.value === 'string' && !variables[ctx.value]) {
valid = false;
errorBuilder.for(...ctx.path).add(`Undefined variable ${ctx.value}`);

@@ -251,43 +108,18 @@ }

errorBuilder.for(...ctx.path).add(`Undefined field ${ctx.name} on entity ${ctx.entity.name}`);
valid = false;
return ctx.value;
},
});
if (!valid) {
return;
}
return result;
}
validateOperations(operations, entity, predicates, errorBuilder) {
if (!(0, utils_1.isObject)(operations)) {
errorBuilder.add('Must be an object');
return {};
}
const extra = (0, utils_1.checkExtraProperties)(operations, ['read', 'create', 'update', 'delete', 'customPrimary']);
if (extra.length) {
errorBuilder.add('Unsupported properties found: ' + extra.join(', '));
}
const validOperations = {};
if (operations.delete !== undefined) {
validOperations.delete = this.validatePredicate(operations.delete, predicates, errorBuilder.for('delete'));
this.validatePredicate(operations.delete, predicates, errorBuilder.for('delete'));
}
for (const op of ['read', 'create', 'update']) {
if (operations[op]) {
validOperations[op] = this.validateFieldPermissions(operations[op], entity, predicates, errorBuilder.for(op));
const operation = operations[op];
if (operation !== undefined) {
this.validateFieldPermissions(operation, entity, predicates, errorBuilder.for(op));
}
}
if (operations.customPrimary !== undefined) {
if (typeof operations.customPrimary !== 'boolean') {
errorBuilder.for('customPrimary').add('Must be boolean');
}
validOperations.customPrimary = operations.customPrimary;
}
return validOperations;
}
validateFieldPermissions(permissions, entity, predicates, errorBuilder) {
if (!(0, utils_1.isObject)(permissions)) {
errorBuilder.add('Must be an object');
return {};
}
const valid = {};
for (const field in permissions) {

@@ -300,19 +132,12 @@ if (!permissions.hasOwnProperty(field)) {

}
valid[field] = this.validatePredicate(permissions[field], predicates, errorBuilder.for(field));
this.validatePredicate(permissions[field], predicates, errorBuilder.for(field));
}
return valid;
}
validatePredicate(predicate, predicates, errorBuilder) {
if (predicate === false || predicate === true) {
return predicate;
return;
}
if (typeof predicate !== 'string') {
errorBuilder.add('Predicate must be either boolean or predicate reference');
return false;
}
if (!predicates[predicate]) {
errorBuilder.add(`Predicate ${predicate} not found`);
return false;
}
return predicate;
}

@@ -319,0 +144,0 @@ }

export * from './AclValidator';
export * from './ModelValidator';
export * from './ValidationValidator';
export * from './errors';
export * from './SchemaValidator';
//# sourceMappingURL=index.d.ts.map
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {

@@ -14,4 +18,6 @@ if (k2 === undefined) k2 = k;

__exportStar(require("./AclValidator"), exports);
__exportStar(require("./ModelValidator"), exports);
__exportStar(require("./ValidationValidator"), exports);
__exportStar(require("./errors"), exports);
__exportStar(require("./SchemaValidator"), exports);
//# sourceMappingURL=index.js.map

@@ -6,3 +6,3 @@ import { Model } from '@contember/schema';

constructor(model: Model.Schema);
validate(): [Model.Schema, ValidationError[]];
validate(): ValidationError[];
private validateEnums;

@@ -9,0 +9,0 @@ private validateEntities;

@@ -6,3 +6,2 @@ "use strict";

const errors_1 = require("./errors");
const utils_1 = require("./utils");
const model_1 = require("../model");

@@ -16,159 +15,39 @@ const IDENTIFIER_PATTERN = /^[_a-zA-Z][_a-zA-Z0-9]*$/;

validate() {
const model = this.model;
const errorBuilder = new errors_1.ErrorBuilder([], []);
let validModel;
if (!(0, utils_1.isObject)(model)) {
errorBuilder.add('Must be an object');
validModel = { entities: {}, enums: {} };
}
else {
const enums = this.validateEnums(model.enums, errorBuilder.for('enums'));
const entities = this.validateEntities(model.entities, errorBuilder.for('entities'));
validModel = { enums, entities };
}
this.validateCollisions(Object.values(validModel.entities), errorBuilder.for('entities'));
return [validModel, errorBuilder.errors];
this.validateEnums(this.model.enums, errorBuilder.for('enums'));
this.validateEntities(this.model.entities, errorBuilder.for('entities'));
this.validateCollisions(Object.values(this.model.entities), errorBuilder.for('entities'));
return errorBuilder.errors;
}
validateEnums(enums, errors) {
if (!enums) {
errors.add('Enums definitions are missing');
return {};
}
if (!(0, utils_1.isObject)(enums)) {
errors.add('Enums must be an object');
return {};
}
const validEnums = {};
enums: for (const [enumName, enumValues] of Object.entries(enums)) {
if (!Array.isArray(enumValues) || !(0, utils_1.everyIs)(enumValues, (it) => typeof it === 'string')) {
errors.for(enumName).add('Enum values must be an array of strings');
continue;
}
if (enumValues.length === 0) {
errors.for(enumName).add('Enum must have at least one value');
}
for (const [enumName, enumValues] of Object.entries(enums)) {
for (const value of enumValues) {
const valueErrors = errors.for(value);
if (!this.validateIdentifier(value, valueErrors)) {
continue enums;
}
const valueErrors = errors.for(enumName, value);
this.validateIdentifier(value, valueErrors);
}
validEnums[enumName] = enumValues;
}
return validEnums;
}
validateEntities(entities, errors) {
if (!entities) {
errors.add('Entities definitions are missing');
return {};
}
if (!(0, utils_1.isObject)(entities)) {
errors.add('Entities must be an object');
return {};
}
const validEntities = {};
for (const [entityName, entity] of Object.entries(entities)) {
const entityErrors = errors.for(entityName);
const validEntity = this.validateEntity(entity, entityErrors);
if (!validEntity) {
continue;
if (entity.name !== entityName) {
entityErrors.add(`Entity name "${entity.name}" does not match the name in a map "${entityName}"`);
}
if (validEntity.name !== entityName) {
entityErrors.add(`Entity name "${validEntity.name}" does not match the name in a map "${entityName}"`);
continue;
}
validEntities[entityName] = validEntity;
this.validateEntity(entity, entityErrors);
}
return validEntities;
}
validateEntity(entity, errors) {
if (!(0, utils_1.isObject)(entity)) {
errors.add('Entity must be an object');
return undefined;
}
if (typeof entity.name !== 'string') {
errors.for('name').add('Entity name must be a string');
return undefined;
}
if (!this.validateIdentifier(entity.name, errors)) {
return undefined;
}
if (typeof entity.primary !== 'string') {
errors.for('primary').add('Primary must be a string');
return undefined;
}
if (typeof entity.primaryColumn !== 'string') {
errors.for('primaryColumn').add('Primary column must be a string');
return undefined;
}
if (typeof entity.tableName !== 'string') {
errors.for('tableName').add('Table name must be a string');
return undefined;
}
const fields = entity.fields;
if (!(0, utils_1.isObject)(fields)) {
errors.add('Fields must be an object');
return undefined;
}
const partialEntity = {
name: entity.name,
primary: entity.primary,
primaryColumn: entity.primaryColumn,
tableName: entity.tableName,
fields: {},
unique: {},
};
if (entity.view) {
const viewTmp = entity.view;
if (!(0, utils_1.isObject)(viewTmp)) {
errors.for('view').add('View must be an object');
return undefined;
}
if (typeof viewTmp.sql !== 'string') {
errors.for('view', 'sql').add('View SQL must be a string');
return undefined;
}
partialEntity.view = entity.view;
}
const validFields = {};
for (const [fieldName, field] of Object.entries(fields)) {
this.validateIdentifier(entity.name, errors);
for (const [fieldName, field] of Object.entries(entity.fields)) {
const fieldErrors = errors.for(fieldName);
const validField = this.validateField(partialEntity, field, fieldErrors);
if (!validField) {
continue;
this.validateField(entity, field, fieldErrors);
if (field.name !== fieldName) {
fieldErrors.add(`Field name "${field.name}" does not match the name in a map "${fieldName}"`);
}
if (validField.name !== fieldName) {
fieldErrors.add(`Field name "${validField.name}" does not match the name in a map "${fieldName}"`);
continue;
}
validFields[fieldName] = validField;
}
const uniqueConstraints = entity.unique;
if (!(0, utils_1.isObject)(uniqueConstraints)) {
errors.add('Unique constraints must be an object');
return undefined;
}
const validUniqueConstraints = this.validateUniqueConstraints(uniqueConstraints, new Set(Object.keys(validFields)), errors.for('unique'));
return {
...partialEntity,
fields: validFields,
unique: validUniqueConstraints,
};
this.validateUniqueConstraints(entity.unique, new Set(Object.keys(entity.fields)), errors.for('unique'));
}
validateUniqueConstraints(uniqueConstraints, fields, errors) {
if (!(0, utils_1.isObject)(uniqueConstraints)) {
errors.add('Unique constraints must be an object');
return {};
}
const validUniqueConstraints = {};
constraint: for (const [constraintName, constraint] of Object.entries(uniqueConstraints)) {
for (const [constraintName, constraint] of Object.entries(uniqueConstraints)) {
const uniqueErrors = errors.for(constraintName);
if (!(0, utils_1.isObject)(constraint)) {
uniqueErrors.add('Unique constraint must be an object');
continue;
}
if (typeof constraint.name !== 'string') {
uniqueErrors.add('Constraint name is not defined');
continue;
}
if (constraint.name !== constraintName) {

@@ -178,37 +57,14 @@ uniqueErrors.add(`Constraint name ${constraint.name} does not match the name in a map "${constraintName}"`);

}
if (!Array.isArray(constraint.fields) ||
!(0, utils_1.everyIs)(constraint.fields, (it) => typeof it === 'string')) {
uniqueErrors.add('Every field must be a string');
continue;
}
for (const field of constraint.fields) {
if (!fields.has(field)) {
uniqueErrors.add(`Referenced field ${field} in a constraint does not exists`);
continue constraint;
}
}
validUniqueConstraints[constraintName] = { name: constraint.name, fields: constraint.fields };
}
return validUniqueConstraints;
}
validateField(partialEntity, field, errors) {
if (!(0, utils_1.isObject)(field)) {
errors.add('Field must be an object');
return undefined;
}
if (typeof field.type !== 'string') {
errors.add('Field type must be a string');
return undefined;
}
if (typeof field.name !== 'string') {
errors.for('name').add('Field name must be a string');
return undefined;
}
if (!this.validateIdentifier(field.name, errors)) {
return undefined;
}
this.validateIdentifier(field.name, errors);
if (isRelation(field)) {
return this.validateRelation(partialEntity, field, errors);
this.validateRelation(partialEntity, field, errors);
}
return field;
}

@@ -218,7 +74,6 @@ validateRelation(partialEntity, field, errors) {

const entityName = partialEntity.name;
const targetEntityName = field.target; // todo
const targetEntityName = field.target;
const targetEntity = this.model.entities[targetEntityName] || undefined;
if (!targetEntity) {
errors.add(`Target entity ${targetEntityName} not found`);
return undefined;
return errors.add(`Target entity ${targetEntityName} not found`);
}

@@ -247,60 +102,46 @@ if (((it) => 'orderBy' in it)(field)) {

if (partialEntity.view) {
const type = field.type;
if (type === schema_1.Model.RelationType.ManyHasMany ||
type === schema_1.Model.RelationType.OneHasMany ||
(type === schema_1.Model.RelationType.OneHasOne && !('joiningColumn' in field))) {
errors.add('This relation type is not allowed on a view entity. Only one-has-one owning and many-has one are allowed.');
if (field.type === schema_1.Model.RelationType.ManyHasMany ||
field.type === schema_1.Model.RelationType.OneHasMany ||
(field.type === schema_1.Model.RelationType.OneHasOne && !('joiningColumn' in field))) {
return errors.add('This relation type is not allowed on a view entity. Only one-has-one owning and many-has one are allowed.');
}
}
if ((0, model_1.isInverseRelation)(field)) {
// todo
const ownedBy = field.ownedBy;
if (typeof ownedBy !== 'string') {
errors.add('Owned by is not defined');
return undefined;
}
const targetField = targetEntity.fields[ownedBy];
const relationDescription = `Target relation ${targetEntityName}::${ownedBy}:`;
if (!targetField) {
errors.add(`${relationDescription} not exists`);
return undefined;
return errors.add(`${relationDescription} not exists`);
}
if (!isRelation(targetField)) {
errors.add(`${relationDescription} not a relation`);
return undefined;
return errors.add(`${relationDescription} not a relation`);
}
if (targetField.target !== entityName) {
errors.add(`${relationDescription} back reference to entity ${entityName} expected, but ${targetField.target} given`);
return undefined;
return errors.add(`${relationDescription} back reference to entity ${entityName} expected, but ${targetField.target} given`);
}
if (!(0, model_1.isOwningRelation)(targetField)) {
errors.add(`${relationDescription} not an owning relation`);
return undefined;
return;
}
if (!targetField.inversedBy) {
errors.add(`${relationDescription} inverse relation is not set`);
return undefined;
return errors.add(`${relationDescription} inverse relation is not set`);
}
if (targetField.inversedBy !== field.name) {
errors.add(`${relationDescription} back reference ${entityName}::${field.name} exepcted, ${targetField.target}::${targetField.inversedBy} given`);
return undefined;
return errors.add(`${relationDescription} back reference ${entityName}::${field.name} exepcted, ${targetField.target}::${targetField.inversedBy} given`);
}
if (field.type === schema_1.Model.RelationType.OneHasOne && targetField.type !== schema_1.Model.RelationType.OneHasOne) {
errors.add(`${relationDescription} "OneHasOne" type expected, "${targetField.type}" given`);
return undefined;
return errors.add(`${relationDescription} "OneHasOne" type expected, "${targetField.type}" given`);
}
if (field.type === schema_1.Model.RelationType.OneHasMany && targetField.type !== schema_1.Model.RelationType.ManyHasOne) {
errors.add(`${relationDescription} "ManyHasOne" type expected, "${targetField.type}" given`);
return undefined;
return errors.add(`${relationDescription} "ManyHasOne" type expected, "${targetField.type}" given`);
}
if (field.type === schema_1.Model.RelationType.ManyHasMany && targetField.type !== schema_1.Model.RelationType.ManyHasMany) {
errors.add(`${relationDescription} "ManyHasMany" type expected, "${targetField.type}" given`);
return undefined;
return errors.add(`${relationDescription} "ManyHasMany" type expected, "${targetField.type}" given`);
}
}
else {
const inversedBy = field.inversedBy; // todo
const inversedBy = field.inversedBy;
if (targetEntity.view) {
if ('joiningColumn' in field) {
errors.add(`View entity ${targetEntity.name} cannot be referenced from an owning relation. Try switching the owning side.`);
return errors.add(`View entity ${targetEntity.name} cannot be referenced from an owning relation. Try switching the owning side.`);
}

@@ -312,51 +153,38 @@ }

if (!targetField) {
errors.add(`${relationDescription} not exists`);
return undefined;
return errors.add(`${relationDescription} not exists`);
}
if (!isRelation(targetField)) {
errors.add(`${relationDescription} not a relation`);
return undefined;
return errors.add(`${relationDescription} not a relation`);
}
if (targetField.target !== entityName) {
errors.add(`${relationDescription} back reference to entity ${entityName} expected, but ${targetField.target} given`);
return undefined;
return errors.add(`${relationDescription} back reference to entity ${entityName} expected, but ${targetField.target} given`);
}
if (!(0, model_1.isInverseRelation)(targetField)) {
errors.add(`${relationDescription} not an inverse relation`);
return undefined;
return errors.add(`${relationDescription} not an inverse relation`);
}
if (!targetField.ownedBy) {
errors.add(`${relationDescription} owning relation is not set`);
return undefined;
return errors.add(`${relationDescription} owning relation is not set`);
}
if (targetField.ownedBy !== field.name) {
errors.add(`${relationDescription} back reference ${entityName}::${field.name} exepcted, ${targetField.target}::${targetField.ownedBy} given`);
return undefined;
return errors.add(`${relationDescription} back reference ${entityName}::${field.name} exepcted, ${targetField.target}::${targetField.ownedBy} given`);
}
if (field.type === schema_1.Model.RelationType.OneHasOne && targetField.type !== schema_1.Model.RelationType.OneHasOne) {
errors.add(`${relationDescription} "OneHasOne" type expected, "${targetField.type}" given`);
return undefined;
return errors.add(`${relationDescription} "OneHasOne" type expected, "${targetField.type}" given`);
}
if (field.type === schema_1.Model.RelationType.ManyHasOne && targetField.type !== schema_1.Model.RelationType.OneHasMany) {
errors.add(`${relationDescription} "ManyHasOne" type expected, "${targetField.type}" given`);
return undefined;
return errors.add(`${relationDescription} "ManyHasOne" type expected, "${targetField.type}" given`);
}
if (field.type === schema_1.Model.RelationType.ManyHasMany && targetField.type !== schema_1.Model.RelationType.ManyHasMany) {
errors.add(`${relationDescription} "ManyHasMany" type expected, "${targetField.type}" given`);
return undefined;
return errors.add(`${relationDescription} "ManyHasMany" type expected, "${targetField.type}" given`);
}
}
}
return field; // todo
}
validateIdentifier(value, errorBuilder) {
if (!value.match(IDENTIFIER_PATTERN)) {
errorBuilder.add(`${value} must match pattern ${IDENTIFIER_PATTERN.source}`);
return false;
return errorBuilder.add(`${value} must match pattern ${IDENTIFIER_PATTERN.source}`);
}
if (RESERVED_WORDS.includes(value)) {
errorBuilder.add(`${value} is reserved word`);
return false;
}
return true;
}

@@ -386,3 +214,4 @@ validateCollisions(entities, errorBuilder) {

.for(relation.name)
.add(`Joining table name ${joiningTable.tableName} of ${description} collides with a table name of ${tableNames[joiningTable.tableName]}. Consider using plural for a relation name or change the joining table name using .joiningTable(...) in schema definition.`);
.add(`Joining table name ${joiningTable.tableName} of ${description} collides with a table name of ${tableNames[joiningTable.tableName]}.` +
'Consider using plural for a relation name or change the joining table name using .joiningTable(...) in schema definition.');
}

@@ -389,0 +218,0 @@ else {

@@ -7,25 +7,11 @@ "use strict";

const ValidationValidator_1 = require("./ValidationValidator");
const utils_1 = require("../utils");
class SchemaValidator {
static validate(schema) {
const modelValidator = new ModelValidator_1.ModelValidator(schema.model);
const modelErrors = modelValidator.validate();
const aclValidator = new AclValidator_1.AclValidator(schema.model);
const [acl, aclErrors] = aclValidator.validate(schema.acl);
const modelValidator = new ModelValidator_1.ModelValidator(schema.model);
const [model, modelErrors] = modelValidator.validate();
const aclErrors = aclValidator.validate(schema.acl);
const validationValidator = new ValidationValidator_1.ValidationValidator(schema.model);
const [validation, validationErrors] = validationValidator.validate(schema.validation);
const validSchema = { ...schema, acl, model, validation };
const errors = [...aclErrors, ...modelErrors, ...validationErrors];
if (errors.length === 0) {
const errors = (0, utils_1.deepCompare)(validSchema, schema, []);
if (errors.length) {
let message = 'There is something wrong with a schema validator:';
for (const err of errors) {
message += '\n\t' + err.path.join('.') + ': ' + err.message;
}
message += '\n\nPlease fill a bug report';
throw new Error(message);
}
}
return errors;
const validationErrors = validationValidator.validate(schema.validation);
return [...aclErrors, ...modelErrors, ...validationErrors];
}

@@ -32,0 +18,0 @@ }

@@ -6,12 +6,10 @@ import { Model, Validation } from '@contember/schema';

constructor(model: Model.Schema);
validate(schema: unknown): [Validation.Schema, ValidationError[]];
validate(schema: Validation.Schema): ValidationError[];
private validateEntityRules;
private validateFieldRules;
private validateFieldRule;
private validateFieldRuleMessage;
private validateValidator;
private validateValidatorArgument;
private validateLiteralArgument;
private validatePathArgument;
}
//# sourceMappingURL=ValidationValidator.d.ts.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ValidationValidator = void 0;
const schema_1 = require("@contember/schema");
const errors_1 = require("./errors");
const utils_1 = require("./utils");
const model_1 = require("../model");

@@ -14,28 +12,15 @@ class ValidationValidator {

const errorBuilder = new errors_1.ErrorBuilder([], ['validation']);
let validSchema;
if (!(0, utils_1.isObject)(schema)) {
errorBuilder.add('Must be an object');
validSchema = { roles: {} };
}
else {
validSchema = {};
for (const entityName in schema) {
const entity = this.model.entities[entityName];
const entityErrorBuilder = errorBuilder.for(entityName);
if (!entity) {
entityErrorBuilder.add('Entity not found');
}
else {
validSchema[entityName] = this.validateEntityRules(entityErrorBuilder, schema[entityName], entity);
}
for (const entityName in schema) {
const entity = this.model.entities[entityName];
const entityErrorBuilder = errorBuilder.for(entityName);
if (!entity) {
entityErrorBuilder.add('Entity not found');
}
else {
this.validateEntityRules(entityErrorBuilder, schema[entityName], entity);
}
}
return [validSchema, errorBuilder.errors];
return errorBuilder.errors;
}
validateEntityRules(errorBuilder, entitySchema, entity) {
if (!(0, utils_1.isObject)(entitySchema)) {
errorBuilder.add('Must be an object');
return {};
}
const validRules = {};
for (const fieldName in entitySchema) {

@@ -48,26 +33,12 @@ const field = entity.fields[fieldName];

else {
validRules[fieldName] = this.validateFieldRules(fieldErrorBuilder, entitySchema[fieldName], entity, field);
this.validateFieldRules(fieldErrorBuilder, entitySchema[fieldName], entity, field);
}
}
return validRules;
}
validateFieldRules(errorBuilder, fieldSchema, entity, field) {
if (!(0, utils_1.isArray)(fieldSchema)) {
errorBuilder.add('Must be an array');
return [];
}
const validRules = [];
for (const i in fieldSchema) {
const rule = this.validateFieldRule(errorBuilder.for(i), fieldSchema[i], entity, field);
if (rule !== undefined) {
validRules.push(rule);
}
this.validateFieldRule(errorBuilder.for(i), fieldSchema[i], entity, field);
}
return validRules;
}
validateFieldRule(errorBuilder, rule, entity, field) {
if (!(0, utils_1.isObject)(rule)) {
errorBuilder.add('Must be an object');
return undefined;
}
const errorMessage = (0, model_1.acceptFieldVisitor)(this.model, entity, field, {

@@ -81,110 +52,26 @@ visitColumn: () => null,

if (errorMessage) {
errorBuilder.add(errorMessage);
return undefined;
return errorBuilder.add(errorMessage);
}
const validMessage = this.validateFieldRuleMessage(errorBuilder.for('message'), rule.message);
if (!validMessage) {
return undefined;
}
const validValidator = this.validateValidator(errorBuilder.for('validator'), rule.validator, entity, field);
if (!validValidator) {
return undefined;
}
return { message: validMessage, validator: validValidator };
this.validateValidator(errorBuilder.for('validator'), rule.validator, entity, field);
}
validateFieldRuleMessage(errorBuilder, message) {
if (!(0, utils_1.isObject)(message)) {
errorBuilder.add('Must be an object');
return undefined;
}
if (!(0, utils_1.hasStringProperty)(message, 'text')) {
errorBuilder.for('text').add('Must be a string');
return undefined;
}
let validMessage = { text: message.text };
if (message.parameters) {
if (!(0, utils_1.hasArrayProperty)(message, 'parameters')) {
errorBuilder.for('parameters').add('Must be an array');
return undefined;
}
validMessage.parameters = message.parameters;
}
return validMessage;
}
validateValidator(errorBuilder, validator, entity, field) {
if (!(0, utils_1.isObject)(validator)) {
errorBuilder.add('Must be an object');
return undefined;
}
const validatorCast = validator;
switch (validatorCast.operation) {
case 'and':
const andArgs = validatorCast.args
.map((it, index) => this.validateValidatorArgument(errorBuilder.for(String(index)), it, entity, field))
.filter((it) => it !== undefined);
return { operation: 'and', args: andArgs };
case 'or':
const orArgs = validatorCast.args
.map((it, index) => this.validateValidatorArgument(errorBuilder.for(String(index)), it, entity, field))
.filter((it) => it !== undefined);
return { operation: 'or', args: orArgs };
return validatorCast.args
.forEach((it, index) => this.validateValidatorArgument(errorBuilder.for(String(index)), it, entity, field));
case 'conditional':
const argA = this.validateValidatorArgument(errorBuilder.for('condition'), validatorCast.args[0], entity, field);
const argB = this.validateValidatorArgument(errorBuilder.for('rule'), validatorCast.args[1], entity, field);
if (argA === undefined || argB === undefined) {
return undefined;
}
return { operation: 'conditional', args: [argA, argB] };
this.validateValidatorArgument(errorBuilder.for('condition'), validatorCast.args[0], entity, field);
this.validateValidatorArgument(errorBuilder.for('rule'), validatorCast.args[1], entity, field);
return;
case 'pattern':
const patternLiteral = this.validateLiteralArgument(errorBuilder, validatorCast.args[0]);
if (!patternLiteral) {
return undefined;
}
if (!(0, utils_1.isArray)(patternLiteral.value) ||
patternLiteral.value.length !== 2 ||
typeof patternLiteral.value[0] !== 'string' ||
typeof patternLiteral.value[1] !== 'string') {
errorBuilder.add('Invalid pattern value');
return undefined;
}
return { operation: 'pattern', args: [patternLiteral] };
case 'lengthRange':
const lengthArgA = this.validateLiteralArgument(errorBuilder.for('min'), validatorCast.args[0]);
const lengthArgB = this.validateLiteralArgument(errorBuilder.for('max'), validatorCast.args[1]);
if (lengthArgA === undefined || lengthArgB === undefined) {
return undefined;
}
return {
operation: 'lengthRange',
args: [lengthArgA, lengthArgB],
};
case 'range':
const rangeArgA = this.validateLiteralArgument(errorBuilder.for('min'), validatorCast.args[0]);
const rangeArgB = this.validateLiteralArgument(errorBuilder.for('max'), validatorCast.args[1]);
if (rangeArgA === undefined || rangeArgB === undefined) {
return undefined;
}
return {
operation: 'range',
args: [rangeArgA, rangeArgB],
};
case 'equals':
const eqArg = this.validateLiteralArgument(errorBuilder, validatorCast.args[0]);
if (eqArg === undefined) {
return undefined;
}
return {
operation: 'equals',
args: [eqArg],
};
case 'not':
const notArg = this.validateValidatorArgument(errorBuilder, validatorCast.args[0], entity, field);
if (notArg === undefined) {
return undefined;
}
return { operation: 'not', args: [notArg] };
case 'empty':
return { operation: 'empty', args: [] };
case 'defined':
return { operation: 'defined', args: [] };
return;
case 'not':
return this.validateValidatorArgument(errorBuilder, validatorCast.args[0], entity, field);
case 'inContext':

@@ -195,27 +82,10 @@ const pathArgResult = this.validatePathArgument(errorBuilder.for('path'), validatorCast.args[0], entity);

}
const [pathArg, inField] = pathArgResult;
const inValidatorArg = this.validateValidatorArgument(errorBuilder.for('validator'), validatorCast.args[1], entity, inField);
if (inValidatorArg === undefined) {
return undefined;
}
return { operation: 'inContext', args: [pathArg, inValidatorArg] };
return this.validateValidatorArgument(errorBuilder.for('validator'), validatorCast.args[1], entity, pathArgResult);
case 'every':
const everyArg = this.validateValidatorArgument(errorBuilder, validatorCast.args[0], entity, field);
if (everyArg === undefined) {
return undefined;
}
return { operation: 'every', args: [everyArg] };
case 'any':
const anyArg = this.validateValidatorArgument(errorBuilder, validatorCast.args[0], entity, field);
if (anyArg === undefined) {
return undefined;
}
return { operation: 'any', args: [anyArg] };
return this.validateValidatorArgument(errorBuilder, validatorCast.args[0], entity, field);
case 'filter':
const filterArgA = this.validateValidatorArgument(errorBuilder.for('filter'), validatorCast.args[0], entity, field);
const filterArgB = this.validateValidatorArgument(errorBuilder.for('validator'), validatorCast.args[1], entity, field);
if (filterArgA === undefined || filterArgB === undefined) {
return undefined;
}
return { operation: 'filter', args: [filterArgA, filterArgB] };
this.validateValidatorArgument(errorBuilder.for('filter'), validatorCast.args[0], entity, field);
this.validateValidatorArgument(errorBuilder.for('validator'), validatorCast.args[1], entity, field);
return;
default:

@@ -228,48 +98,5 @@ ((_) => {

validateValidatorArgument(errorBuilder, argument, entity, field) {
if (!(0, utils_1.isObject)(argument)) {
errorBuilder.add('Must be an object');
return undefined;
}
if (!(0, utils_1.hasStringProperty)(argument, 'type') || argument.type !== schema_1.Validation.ArgumentType.validator) {
errorBuilder.for('type').add(`Invalid value ${argument.type}`);
return undefined;
}
const validator = this.validateValidator(errorBuilder.for('validator'), argument.validator, entity, field);
if (!validator) {
return undefined;
}
return { validator, type: argument.type };
this.validateValidator(errorBuilder.for('validator'), argument.validator, entity, field);
}
validateLiteralArgument(errorBuilder, argument) {
if (!(0, utils_1.isObject)(argument)) {
errorBuilder.add('Must be an object');
return undefined;
}
if (!(0, utils_1.hasStringProperty)(argument, 'type') || argument.type !== schema_1.Validation.ArgumentType.literal) {
errorBuilder.for('type').add(`Invalid value ${argument.type}`);
return undefined;
}
if (!('value' in argument)) {
errorBuilder.for('value').add('Undefined literal value');
return undefined;
}
return { type: argument.type, value: argument.value };
}
validatePathArgument(errorBuilder, argument, entity) {
if (!(0, utils_1.isObject)(argument)) {
errorBuilder.add('Must be an object');
return undefined;
}
if (!(0, utils_1.hasStringProperty)(argument, 'type') || argument.type !== schema_1.Validation.ArgumentType.path) {
errorBuilder.for('type').add(`Invalid value ${argument.type}`);
return undefined;
}
if (!(0, utils_1.hasArrayProperty)(argument, 'path')) {
errorBuilder.add('Undefined path');
return undefined;
}
if (!(0, utils_1.everyIs)(argument.path, (it) => typeof it === 'string')) {
errorBuilder.add('Invalid path');
return undefined;
}
const isRelation = (0, model_1.acceptFieldVisitor)(this.model, entity, argument.path[0], {

@@ -283,3 +110,3 @@ visitColumn: () => false,

}
return [{ type: argument.type, path: argument.path }, entity.fields[argument.path[0]]];
return entity.fields[argument.path[0]];
}

@@ -286,0 +113,0 @@ }

{
"name": "@contember/schema-utils",
"version": "1.1.0-alpha.5",
"version": "1.1.0-alpha.6",
"license": "Apache-2.0",

@@ -11,3 +11,4 @@ "main": "dist/src/index.js",

"dependencies": {
"@contember/schema": "^1.1.0-alpha.5"
"@contember/schema": "^1.1.0-alpha.6",
"@contember/typesafe": "^1.1.0-alpha.6"
},

@@ -14,0 +15,0 @@ "devDependencies": {

@@ -1,2 +0,2 @@

import { Acl, Model } from '@contember/schema'
import { Acl, Model, Writable } from '@contember/schema'

@@ -14,3 +14,3 @@ export class AllowAllPermissionFactory {

create(schema: Pick<Model.Schema, 'entities'>, allowCustomPrimary = false): Acl.Permissions {
const permissions: Acl.Permissions = {}
const permissions: Writable<Acl.Permissions> = {}
for (let entityName in schema.entities) {

@@ -17,0 +17,0 @@ if (!schema.entities.hasOwnProperty(entityName)) {

@@ -40,7 +40,7 @@ import { Input, Model, Value } from '@contember/schema'

export const resolvePrimaryGenerator = (column: Model.AnyColumn, providers: Providers): (() => Input.PrimaryValue) => {
export const resolvePrimaryGenerator = (column: Model.AnyColumn, providers: Providers): (() => Input.PrimaryValue | undefined) => {
if (column.type === Model.ColumnType.Uuid) {
return providers.uuid
}
throw new Error('not implemented')
return () => undefined
}

@@ -59,3 +59,3 @@

providers: Providers,
): Value.AtomicValue => {
): Value.AtomicValue | undefined => {
if (input !== undefined) {

@@ -62,0 +62,0 @@ return input as Value.AtomicValue

import { Schema } from '@contember/schema'
import { emptyModelSchema } from './model'
import * as Typesafe from '@contember/typesafe'
import { aclSchema, modelSchema, validationSchema } from './type-schema'

@@ -17,1 +19,7 @@ export * from './model'

}
export const schemaType: Typesafe.Type<Schema> = Typesafe.object({
model: modelSchema,
acl: aclSchema,
validation: validationSchema,
})

@@ -13,3 +13,3 @@ import crypto from 'crypto'

public static createUniqueConstraintName = (entityName: string, fields: string[]): string => {
public static createUniqueConstraintName = (entityName: string, fields: readonly string[]): string => {
const uniqueSuffix = crypto

@@ -16,0 +16,0 @@ .createHash('sha256')

@@ -10,4 +10,7 @@ {

"path": "../../schema/src"
},
{
"path": "../../typesafe/src"
}
]
}
import { Acl, Model } from '@contember/schema'
import { checkExtraProperties, everyIs, hasStringProperty, isObject } from './utils'
import { PredicateDefinitionProcessor } from '../acl'

@@ -7,26 +6,13 @@ import { getEntity } from '../model'

export class AclValidator {
constructor(private readonly model: Model.Schema) {}
public validate(schema: unknown): [Acl.Schema, ValidationError[]] {
public validate(schema: Acl.Schema): ValidationError[] {
const errorBuilder = new ErrorBuilder([], ['acl'])
let validSchema: Acl.Schema
if (!isObject(schema)) {
errorBuilder.add('Must be an object')
validSchema = { roles: {} }
} else if (!schema.roles) {
errorBuilder.for('roles').add('Property is missing')
validSchema = { roles: {} }
} else {
validSchema = { roles: this.validateRoles(schema.roles, errorBuilder) }
}
return [validSchema, errorBuilder.errors]
this.validateRoles(schema.roles, errorBuilder)
return errorBuilder.errors
}
private validateRoles(roles: unknown, errorBuilder: ErrorBuilder): Acl.Schema['roles'] {
if (!isObject(roles)) {
errorBuilder.add('Must be an object')
return {}
}
const validRoles: Acl.Schema['roles'] = {}
private validateRoles(roles: Acl.Roles, errorBuilder: ErrorBuilder): void {
for (const role in roles) {

@@ -36,85 +22,30 @@ if (!roles.hasOwnProperty(role)) {

}
// if (role === 'admin') {
// errorBuilder.add('Role "admin" is reserved.')
// continue
// }
const rolePermissions = this.validateRolePermissions(roles[role], Object.keys(roles), errorBuilder.for(role))
if (rolePermissions !== undefined) {
validRoles[role] = rolePermissions
}
this.validateRolePermissions(roles[role], Object.keys(roles), errorBuilder.for(role))
}
return validRoles
}
private validateRolePermissions(
permissions: unknown,
permissions: Acl.RolePermissions,
roles: string[],
errorBuilder: ErrorBuilder,
): Acl.RolePermissions | undefined {
if (!isObject(permissions)) {
errorBuilder.add('Must be an object')
return
}
): void {
const {
inherits: inheritsIn,
variables: variablesIn,
stages: stagesIn,
entities: entitiesIn,
implicit: implicitIn,
...plugins
} = permissions
const inherits = this.validateInherits(inheritsIn, roles, errorBuilder.for('inherits'))
const variables = this.validateVariables(variablesIn, errorBuilder.for('variables'))
const stages = this.validateStagesDefinition(stagesIn, errorBuilder.for('stages'))
const entities = this.validatePermissions(
entitiesIn,
(permissions.variables as Acl.Variables) || {},
errorBuilder.for('entities'),
)
this.validateInherits(permissions.inherits, roles, errorBuilder.for('inherits'))
this.validateVariables(permissions.variables, errorBuilder.for('variables'))
this.validatePermissions(permissions.entities, (permissions.variables as Acl.Variables) || {}, errorBuilder.for('entities'))
// todo: plugins validation
const result: Acl.RolePermissions = { variables, stages, entities }
if (inherits !== undefined) {
result.inherits = inherits
}
if (implicitIn !== undefined) {
if (typeof implicitIn !== 'boolean') {
errorBuilder.for('implicit').add('Must be boolean')
return
}
result.implicit = implicitIn
}
return { ...plugins, ...result }
}
private validateInherits(inherits: unknown, roles: string[], errorBuilder: ErrorBuilder): string[] | undefined {
private validateInherits(inherits: Acl.RolePermissions['inherits'], roles: string[], errorBuilder: ErrorBuilder): void {
if (inherits === undefined) {
return undefined
}
if (!Array.isArray(inherits) || !everyIs(inherits, (it): it is string => typeof it === 'string')) {
errorBuilder.add('Must be an array of strings')
return []
} else {
const validRoles: string[] = []
for (const inheritsFrom of inherits) {
if (!roles.includes(inheritsFrom)) {
// todo: check recursion
errorBuilder.for(inheritsFrom).add('Referenced role not exists.')
} else {
validRoles.push(inheritsFrom)
}
for (const inheritsFrom of inherits) {
if (!roles.includes(inheritsFrom)) {
errorBuilder.for(inheritsFrom).add('Referenced role not exists.')
}
return validRoles
}
}
private validateVariables(variables: unknown, errorBuilder: ErrorBuilder): Acl.Variables {
let validVariables: Acl.Variables = {}
if (!isObject(variables)) {
errorBuilder.add('Must be an object')
return {}
}
private validateVariables(variables: Acl.Variables, errorBuilder: ErrorBuilder): void {
for (const varName in variables) {

@@ -124,25 +55,9 @@ if (!variables.hasOwnProperty(varName)) {

}
const validVariable = this.validateVariable(variables[varName], errorBuilder.for(varName))
if (validVariable) {
validVariables[varName] = validVariable
}
this.validateVariable(variables[varName], errorBuilder.for(varName))
}
return validVariables
}
private validateVariable(variable: unknown, errorBuilder: ErrorBuilder): Acl.Variable | undefined {
if (!isObject(variable)) {
errorBuilder.add('Must be an object')
return
}
if (!hasStringProperty(variable, 'type')) {
errorBuilder.add('Variable type is not defined')
return
}
private validateVariable(variable: Acl.Variable, errorBuilder: ErrorBuilder): void {
switch (variable.type) {
case Acl.VariableType.entity:
if (!hasStringProperty(variable, 'entityName')) {
errorBuilder.add('Entity name must be specified for this type of variable')
return
}
case 'entity':
if (!this.model.entities[variable.entityName]) {

@@ -152,24 +67,2 @@ errorBuilder.add(`Entity "${variable.entityName}" not found`)

}
const extra = checkExtraProperties(variable, ['entityName', 'type'])
if (extra.length) {
errorBuilder.add('Unsupported properties found: ' + extra.join(', '))
}
return { type: Acl.VariableType.entity, entityName: variable.entityName }
case Acl.VariableType.predefined:
if (!hasStringProperty(variable, 'value')) {
errorBuilder.add('Variable value type must be specified for this type of variable')
return
}
if (variable.value !== 'identityID' && variable.value !== 'personID') {
errorBuilder.add('Unknown predefined variable')
return
}
const extra2 = checkExtraProperties(variable, ['type', 'value'])
if (extra2.length) {
errorBuilder.add('Unsupported properties found: ' + extra2.join(', '))
}
return { type: variable.type, value: variable.value }
default:
errorBuilder.add(`Variable type "${variable.type}" is not supported.`)
}

@@ -179,24 +72,3 @@ return

private validateStagesDefinition(stages: unknown, errorBuilder: ErrorBuilder): Acl.StagesDefinition {
if (stages === '*') {
return '*'
}
if (!Array.isArray(stages) || !everyIs(stages, (it): it is string => typeof it === 'string')) {
errorBuilder.add('Stages must be either "*" or array of stage names')
return []
}
// todo validate stages ??
return stages
}
private validatePermissions(
permissions: unknown,
variables: Acl.Variables,
errorBuilder: ErrorBuilder,
): Acl.Permissions {
if (!isObject(permissions)) {
errorBuilder.add('Must be an object')
return {}
}
const validPermissions: Acl.Permissions = {}
private validatePermissions(permissions: Acl.Permissions, variables: Acl.Variables, errorBuilder: ErrorBuilder): void {
for (const entityName in permissions) {

@@ -212,3 +84,3 @@ if (!permissions.hasOwnProperty(entityName)) {

const entityPermissions = this.validateEntityPermissions(
this.validateEntityPermissions(
permissions[entityName],

@@ -219,24 +91,12 @@ entity,

)
if (entityPermissions) {
validPermissions[entityName] = entityPermissions
}
}
return validPermissions
}
private validateEntityPermissions(
entityPermissions: unknown,
entityPermissions: Acl.EntityPermissions,
entity: Model.Entity,
variables: Acl.Variables,
errorBuilder: ErrorBuilder,
): Acl.EntityPermissions | undefined {
if (!isObject(entityPermissions)) {
errorBuilder.add('Must be an object')
return
}
const extra = checkExtraProperties(entityPermissions, ['predicates', 'operations'])
if (extra.length) {
errorBuilder.add('Unsupported properties found: ' + extra.join(', '))
}
const predicates = this.validatePredicates(
): void {
this.validatePredicates(
entityPermissions.predicates,

@@ -247,3 +107,3 @@ entity,

)
const operations = this.validateOperations(
this.validateOperations(
entityPermissions.operations,

@@ -254,17 +114,5 @@ entity,

)
return { operations, predicates }
}
private validatePredicates(
predicates: unknown,
entity: Model.Entity,
variables: Acl.Variables,
errorBuilder: ErrorBuilder,
): Acl.PredicateMap {
if (!isObject(predicates)) {
errorBuilder.add('Must be an object')
return {}
}
const validPredicates: Acl.PredicateMap = {}
private validatePredicates(predicates: Acl.PredicateMap, entity: Model.Entity, variables: Acl.Variables, errorBuilder: ErrorBuilder): void {
for (const predicateName in predicates) {

@@ -274,3 +122,3 @@ if (!predicates.hasOwnProperty(predicateName)) {

}
const predicate = this.validatePredicateDefinition(
this.validatePredicateDefinition(
predicates[predicateName],

@@ -281,25 +129,15 @@ entity,

)
if (predicate) {
validPredicates[predicateName] = predicate
}
}
return validPredicates
}
private validatePredicateDefinition(
predicate: unknown,
predicate: Acl.PredicateDefinition,
entity: Model.Entity,
variables: Acl.Variables,
errorBuilder: ErrorBuilder,
): Acl.PredicateDefinition | undefined {
if (!isObject(predicate)) {
errorBuilder.add('Must be an object')
return
}
): void {
const processor = new PredicateDefinitionProcessor(this.model)
let valid = true
const result = processor.process(entity, predicate, {
processor.process(entity, predicate, {
handleColumn: ctx => {
if (typeof ctx.value === 'string' && !variables[ctx.value]) {
valid = false
errorBuilder.for(...ctx.path).add(`Undefined variable ${ctx.value}`)

@@ -311,3 +149,2 @@ }

if (typeof ctx.value === 'string' && !variables[ctx.value]) {
valid = false
errorBuilder.for(...ctx.path).add(`Undefined variable ${ctx.value}`)

@@ -319,57 +156,30 @@ }

errorBuilder.for(...ctx.path).add(`Undefined field ${ctx.name} on entity ${ctx.entity.name}`)
valid = false
return ctx.value
},
})
if (!valid) {
return
}
return result
}
private validateOperations(
operations: unknown,
operations: Acl.EntityOperations,
entity: Model.Entity,
predicates: Acl.PredicateDefinition,
errorBuilder: ErrorBuilder,
): Acl.EntityOperations {
if (!isObject(operations)) {
errorBuilder.add('Must be an object')
return {}
}
const extra = checkExtraProperties(operations, ['read', 'create', 'update', 'delete', 'customPrimary'])
if (extra.length) {
errorBuilder.add('Unsupported properties found: ' + extra.join(', '))
}
const validOperations: Acl.EntityOperations = {}
): void {
if (operations.delete !== undefined) {
validOperations.delete = this.validatePredicate(operations.delete, predicates, errorBuilder.for('delete'))
this.validatePredicate(operations.delete, predicates, errorBuilder.for('delete'))
}
for (const op of ['read', 'create', 'update'] as const) {
if (operations[op]) {
validOperations[op] = this.validateFieldPermissions(operations[op], entity, predicates, errorBuilder.for(op))
const operation = operations[op]
if (operation !== undefined) {
this.validateFieldPermissions(operation, entity, predicates, errorBuilder.for(op))
}
}
if (operations.customPrimary !== undefined) {
if (typeof operations.customPrimary !== 'boolean') {
errorBuilder.for('customPrimary').add('Must be boolean')
}
validOperations.customPrimary = operations.customPrimary as boolean
}
return validOperations
}
private validateFieldPermissions(
permissions: unknown,
permissions: Acl.FieldPermissions,
entity: Model.Entity,
predicates: Acl.PredicateDefinition,
errorBuilder: ErrorBuilder,
): Acl.FieldPermissions {
if (!isObject(permissions)) {
errorBuilder.add('Must be an object')
return {}
}
const valid: Acl.FieldPermissions = {}
): void {
for (const field in permissions) {

@@ -382,25 +192,18 @@ if (!permissions.hasOwnProperty(field)) {

}
valid[field] = this.validatePredicate(permissions[field], predicates, errorBuilder.for(field))
this.validatePredicate(permissions[field], predicates, errorBuilder.for(field))
}
return valid
}
private validatePredicate(
predicate: unknown,
predicate: Acl.Predicate,
predicates: Acl.PredicateDefinition,
errorBuilder: ErrorBuilder,
): Acl.Predicate {
): void {
if (predicate === false || predicate === true) {
return predicate
return
}
if (typeof predicate !== 'string') {
errorBuilder.add('Predicate must be either boolean or predicate reference')
return false
}
if (!predicates[predicate]) {
errorBuilder.add(`Predicate ${predicate} not found`)
return false
}
return predicate
}
}
export * from './AclValidator'
export * from './ModelValidator'
export * from './ValidationValidator'
export * from './errors'
export * from './SchemaValidator'
import { Model } from '@contember/schema'
import { ErrorBuilder, ValidationError } from './errors'
import { everyIs, isObject, UnknownObject } from './utils'
import { acceptEveryFieldVisitor, getTargetEntity, isInverseRelation, isOwningRelation } from '../model'

@@ -9,179 +8,53 @@

export class ModelValidator {
constructor(private readonly model: Model.Schema) {}
public validate(): [Model.Schema, ValidationError[]] {
const model = this.model as unknown
public validate(): ValidationError[] {
const errorBuilder = new ErrorBuilder([], [])
let validModel: Model.Schema
if (!isObject(model)) {
errorBuilder.add('Must be an object')
validModel = { entities: {}, enums: {} }
} else {
const enums = this.validateEnums(model.enums, errorBuilder.for('enums'))
const entities = this.validateEntities(model.entities, errorBuilder.for('entities'))
validModel = { enums, entities }
}
this.validateCollisions(Object.values(validModel.entities), errorBuilder.for('entities'))
return [validModel, errorBuilder.errors]
this.validateEnums(this.model.enums, errorBuilder.for('enums'))
this.validateEntities(this.model.entities, errorBuilder.for('entities'))
this.validateCollisions(Object.values(this.model.entities), errorBuilder.for('entities'))
return errorBuilder.errors
}
private validateEnums(enums: unknown, errors: ErrorBuilder): Model.Schema['enums'] {
if (!enums) {
errors.add('Enums definitions are missing')
return {}
}
if (!isObject(enums)) {
errors.add('Enums must be an object')
return {}
}
const validEnums: Model.Schema['enums'] = {}
enums: for (const [enumName, enumValues] of Object.entries(enums)) {
if (!Array.isArray(enumValues) || !everyIs(enumValues, (it): it is string => typeof it === 'string')) {
errors.for(enumName).add('Enum values must be an array of strings')
continue
}
if (enumValues.length === 0) {
errors.for(enumName).add('Enum must have at least one value')
}
private validateEnums(enums: Model.Schema['enums'], errors: ErrorBuilder): void {
for (const [enumName, enumValues] of Object.entries(enums)) {
for (const value of enumValues) {
const valueErrors = errors.for(value)
if (!this.validateIdentifier(value, valueErrors)) {
continue enums
}
const valueErrors = errors.for(enumName, value)
this.validateIdentifier(value, valueErrors)
}
validEnums[enumName] = enumValues
}
return validEnums
}
private validateEntities(entities: unknown, errors: ErrorBuilder): Model.Schema['entities'] {
if (!entities) {
errors.add('Entities definitions are missing')
return {}
}
if (!isObject(entities)) {
errors.add('Entities must be an object')
return {}
}
const validEntities: Model.Schema['entities'] = {}
private validateEntities(entities: Model.Schema['entities'], errors: ErrorBuilder): void {
for (const [entityName, entity] of Object.entries(entities)) {
const entityErrors = errors.for(entityName)
const validEntity = this.validateEntity(entity, entityErrors)
if (!validEntity) {
continue
if (entity.name !== entityName) {
entityErrors.add(`Entity name "${entity.name}" does not match the name in a map "${entityName}"`)
}
if (validEntity.name !== entityName) {
entityErrors.add(`Entity name "${validEntity.name}" does not match the name in a map "${entityName}"`)
continue
}
validEntities[entityName] = validEntity
this.validateEntity(entity, entityErrors)
}
return validEntities
}
private validateEntity(entity: unknown, errors: ErrorBuilder): Model.Entity | undefined {
if (!isObject(entity)) {
errors.add('Entity must be an object')
return undefined
}
if (typeof entity.name !== 'string') {
errors.for('name').add('Entity name must be a string')
return undefined
}
if (!this.validateIdentifier(entity.name, errors)) {
return undefined
}
if (typeof entity.primary !== 'string') {
errors.for('primary').add('Primary must be a string')
return undefined
}
if (typeof entity.primaryColumn !== 'string') {
errors.for('primaryColumn').add('Primary column must be a string')
return undefined
}
if (typeof entity.tableName !== 'string') {
errors.for('tableName').add('Table name must be a string')
return undefined
}
const fields = entity.fields
if (!isObject(fields)) {
errors.add('Fields must be an object')
return undefined
}
const partialEntity: Model.Entity = {
name: entity.name,
primary: entity.primary,
primaryColumn: entity.primaryColumn,
tableName: entity.tableName,
fields: {},
unique: {},
}
private validateEntity(entity: Model.Entity, errors: ErrorBuilder): void {
this.validateIdentifier(entity.name, errors)
if (entity.view) {
const viewTmp = entity.view
if (!isObject(viewTmp)) {
errors.for('view').add('View must be an object')
return undefined
}
if (typeof viewTmp.sql !== 'string') {
errors.for('view', 'sql').add('View SQL must be a string')
return undefined
}
partialEntity.view = entity.view as Model.View
}
const validFields: Model.Entity['fields'] = {}
for (const [fieldName, field] of Object.entries(fields)) {
for (const [fieldName, field] of Object.entries(entity.fields)) {
const fieldErrors = errors.for(fieldName)
const validField = this.validateField(partialEntity, field, fieldErrors)
if (!validField) {
continue
this.validateField(entity, field, fieldErrors)
if (field.name !== fieldName) {
fieldErrors.add(`Field name "${field.name}" does not match the name in a map "${fieldName}"`)
}
if (validField.name !== fieldName) {
fieldErrors.add(`Field name "${validField.name}" does not match the name in a map "${fieldName}"`)
continue
}
validFields[fieldName] = validField
}
const uniqueConstraints = entity.unique
if (!isObject(uniqueConstraints)) {
errors.add('Unique constraints must be an object')
return undefined
}
const validUniqueConstraints = this.validateUniqueConstraints(
uniqueConstraints,
new Set(Object.keys(validFields)),
this.validateUniqueConstraints(
entity.unique,
new Set(Object.keys(entity.fields)),
errors.for('unique'),
)
return {
...partialEntity,
fields: validFields,
unique: validUniqueConstraints,
}
}
private validateUniqueConstraints(
uniqueConstraints: unknown,
fields: Set<string>,
errors: ErrorBuilder,
): Model.Entity['unique'] {
if (!isObject(uniqueConstraints)) {
errors.add('Unique constraints must be an object')
return {}
}
const validUniqueConstraints: Model.Entity['unique'] = {}
constraint: for (const [constraintName, constraint] of Object.entries(uniqueConstraints)) {
private validateUniqueConstraints(uniqueConstraints: Model.Entity['unique'], fields: Set<string>, errors: ErrorBuilder): void {
for (const [constraintName, constraint] of Object.entries(uniqueConstraints)) {
const uniqueErrors = errors.for(constraintName)
if (!isObject(constraint)) {
uniqueErrors.add('Unique constraint must be an object')
continue
}
if (typeof constraint.name !== 'string') {
uniqueErrors.add('Constraint name is not defined')
continue
}
if (constraint.name !== constraintName) {

@@ -191,55 +64,25 @@ uniqueErrors.add(`Constraint name ${constraint.name} does not match the name in a map "${constraintName}"`)

}
if (
!Array.isArray(constraint.fields) ||
!everyIs(constraint.fields, (it): it is string => typeof it === 'string')
) {
uniqueErrors.add('Every field must be a string')
continue
}
for (const field of constraint.fields) {
if (!fields.has(field)) {
uniqueErrors.add(`Referenced field ${field} in a constraint does not exists`)
continue constraint
}
}
validUniqueConstraints[constraintName] = { name: constraint.name, fields: constraint.fields }
}
return validUniqueConstraints
}
private validateField(partialEntity: Model.Entity, field: unknown, errors: ErrorBuilder): Model.AnyField | undefined {
if (!isObject(field)) {
errors.add('Field must be an object')
return undefined
private validateField(partialEntity: Model.Entity, field: Model.AnyField, errors: ErrorBuilder): void {
this.validateIdentifier(field.name, errors)
if (isRelation(field)) {
this.validateRelation(partialEntity, field, errors)
}
if (typeof field.type !== 'string') {
errors.add('Field type must be a string')
return undefined
}
if (typeof field.name !== 'string') {
errors.for('name').add('Field name must be a string')
return undefined
}
if (!this.validateIdentifier(field.name, errors)) {
return undefined
}
if (isRelation(field as any)) {
return this.validateRelation(partialEntity, field, errors)
}
return field as unknown as Model.AnyColumn
}
private validateRelation(
partialEntity: Model.Entity,
field: UnknownObject,
errors: ErrorBuilder,
): Model.AnyRelation | undefined {
private validateRelation(partialEntity: Model.Entity, field: Model.AnyRelation, errors: ErrorBuilder): void {
const entityName = partialEntity.name
const targetEntityName = field.target as string // todo
const targetEntityName = field.target
const targetEntity = this.model.entities[targetEntityName] || undefined
if (!targetEntity) {
errors.add(`Target entity ${targetEntityName} not found`)
return undefined
return errors.add(`Target entity ${targetEntityName} not found`)
}
if (((it: Model.AnyRelation): it is Model.AnyRelation & Model.OrderableRelation => 'orderBy' in it)(field as any)) {
if (((it: Model.AnyRelation): it is Model.AnyRelation & Model.OrderableRelation => 'orderBy' in it)(field)) {
(field as Model.OrderableRelation).orderBy?.forEach(it => {

@@ -267,69 +110,48 @@ let entity = targetEntity

if (partialEntity.view) {
const type = (field as any as Model.Relation).type
if (
type === Model.RelationType.ManyHasMany ||
type === Model.RelationType.OneHasMany ||
(type === Model.RelationType.OneHasOne && !('joiningColumn' in field))
field.type === Model.RelationType.ManyHasMany ||
field.type === Model.RelationType.OneHasMany ||
(field.type === Model.RelationType.OneHasOne && !('joiningColumn' in field))
) {
errors.add(
'This relation type is not allowed on a view entity. Only one-has-one owning and many-has one are allowed.',
)
return errors.add('This relation type is not allowed on a view entity. Only one-has-one owning and many-has one are allowed.')
}
}
if (isInverseRelation(field as any as Model.Relation)) {
// todo
if (isInverseRelation(field)) {
const ownedBy = field.ownedBy
if (typeof ownedBy !== 'string') {
errors.add('Owned by is not defined')
return undefined
}
const targetField = targetEntity.fields[ownedBy]
const relationDescription = `Target relation ${targetEntityName}::${ownedBy}:`
if (!targetField) {
errors.add(`${relationDescription} not exists`)
return undefined
return errors.add(`${relationDescription} not exists`)
}
if (!isRelation(targetField)) {
errors.add(`${relationDescription} not a relation`)
return undefined
return errors.add(`${relationDescription} not a relation`)
}
if (targetField.target !== entityName) {
errors.add(
`${relationDescription} back reference to entity ${entityName} expected, but ${targetField.target} given`,
)
return undefined
return errors.add(`${relationDescription} back reference to entity ${entityName} expected, but ${targetField.target} given`)
}
if (!isOwningRelation(targetField)) {
errors.add(`${relationDescription} not an owning relation`)
return undefined
return
}
if (!targetField.inversedBy) {
errors.add(`${relationDescription} inverse relation is not set`)
return undefined
return errors.add(`${relationDescription} inverse relation is not set`)
}
if (targetField.inversedBy !== field.name) {
errors.add(
`${relationDescription} back reference ${entityName}::${field.name} exepcted, ${targetField.target}::${targetField.inversedBy} given`,
)
return undefined
return errors.add(`${relationDescription} back reference ${entityName}::${field.name} exepcted, ${targetField.target}::${targetField.inversedBy} given`)
}
if (field.type === Model.RelationType.OneHasOne && targetField.type !== Model.RelationType.OneHasOne) {
errors.add(`${relationDescription} "OneHasOne" type expected, "${targetField.type}" given`)
return undefined
return errors.add(`${relationDescription} "OneHasOne" type expected, "${targetField.type}" given`)
}
if (field.type === Model.RelationType.OneHasMany && targetField.type !== Model.RelationType.ManyHasOne) {
errors.add(`${relationDescription} "ManyHasOne" type expected, "${targetField.type}" given`)
return undefined
return errors.add(`${relationDescription} "ManyHasOne" type expected, "${targetField.type}" given`)
}
if (field.type === Model.RelationType.ManyHasMany && targetField.type !== Model.RelationType.ManyHasMany) {
errors.add(`${relationDescription} "ManyHasMany" type expected, "${targetField.type}" given`)
return undefined
return errors.add(`${relationDescription} "ManyHasMany" type expected, "${targetField.type}" given`)
}
} else {
const inversedBy = field.inversedBy as string // todo
const inversedBy = field.inversedBy
if (targetEntity.view) {
if ('joiningColumn' in field) {
errors.add(
`View entity ${targetEntity.name} cannot be referenced from an owning relation. Try switching the owning side.`,
)
return errors.add(`View entity ${targetEntity.name} cannot be referenced from an owning relation. Try switching the owning side.`)
}

@@ -341,44 +163,30 @@ }

if (!targetField) {
errors.add(`${relationDescription} not exists`)
return undefined
return errors.add(`${relationDescription} not exists`)
}
if (!isRelation(targetField)) {
errors.add(`${relationDescription} not a relation`)
return undefined
return errors.add(`${relationDescription} not a relation`)
}
if (targetField.target !== entityName) {
errors.add(
`${relationDescription} back reference to entity ${entityName} expected, but ${targetField.target} given`,
)
return undefined
return errors.add(`${relationDescription} back reference to entity ${entityName} expected, but ${targetField.target} given`)
}
if (!isInverseRelation(targetField)) {
errors.add(`${relationDescription} not an inverse relation`)
return undefined
return errors.add(`${relationDescription} not an inverse relation`)
}
if (!targetField.ownedBy) {
errors.add(`${relationDescription} owning relation is not set`)
return undefined
return errors.add(`${relationDescription} owning relation is not set`)
}
if (targetField.ownedBy !== field.name) {
errors.add(
`${relationDescription} back reference ${entityName}::${field.name} exepcted, ${targetField.target}::${targetField.ownedBy} given`,
)
return undefined
return errors.add(`${relationDescription} back reference ${entityName}::${field.name} exepcted, ${targetField.target}::${targetField.ownedBy} given`)
}
if (field.type === Model.RelationType.OneHasOne && targetField.type !== Model.RelationType.OneHasOne) {
errors.add(`${relationDescription} "OneHasOne" type expected, "${targetField.type}" given`)
return undefined
return errors.add(`${relationDescription} "OneHasOne" type expected, "${targetField.type}" given`)
}
if (field.type === Model.RelationType.ManyHasOne && targetField.type !== Model.RelationType.OneHasMany) {
errors.add(`${relationDescription} "ManyHasOne" type expected, "${targetField.type}" given`)
return undefined
return errors.add(`${relationDescription} "ManyHasOne" type expected, "${targetField.type}" given`)
}
if (field.type === Model.RelationType.ManyHasMany && targetField.type !== Model.RelationType.ManyHasMany) {
errors.add(`${relationDescription} "ManyHasMany" type expected, "${targetField.type}" given`)
return undefined
return errors.add(`${relationDescription} "ManyHasMany" type expected, "${targetField.type}" given`)
}
}
}
return field as any as Model.AnyRelation // todo
}

@@ -388,10 +196,7 @@

if (!value.match(IDENTIFIER_PATTERN)) {
errorBuilder.add(`${value} must match pattern ${IDENTIFIER_PATTERN.source}`)
return false
return errorBuilder.add(`${value} must match pattern ${IDENTIFIER_PATTERN.source}`)
}
if (RESERVED_WORDS.includes(value)) {
errorBuilder.add(`${value} is reserved word`)
return false
}
return true
}

@@ -407,7 +212,3 @@

.for(entity.name)
.add(
`Table name ${entity.tableName} of ${description} collides with a table name of ${
tableNames[entity.tableName]
}`,
)
.add(`Table name ${entity.tableName} of ${description} collides with a table name of ${tableNames[entity.tableName]}`)
} else {

@@ -427,5 +228,4 @@ tableNames[entity.tableName] = description

.add(
`Joining table name ${joiningTable.tableName} of ${description} collides with a table name of ${
tableNames[joiningTable.tableName]
}. Consider using plural for a relation name or change the joining table name using .joiningTable(...) in schema definition.`,
`Joining table name ${joiningTable.tableName} of ${description} collides with a table name of ${tableNames[joiningTable.tableName]}.` +
'Consider using plural for a relation name or change the joining table name using .joiningTable(...) in schema definition.',
)

@@ -432,0 +232,0 @@ } else {

@@ -5,33 +5,18 @@ import { Schema } from '@contember/schema'

import { ModelValidator } from './ModelValidator'
import { isDeepStrictEqual } from 'util'
import { ValidationValidator } from './ValidationValidator'
import { deepCompare } from '../utils'
export class SchemaValidator {
public static validate(schema: Schema): ValidationError[] {
const modelValidator = new ModelValidator(schema.model)
const modelErrors = modelValidator.validate()
const aclValidator = new AclValidator(schema.model)
const [acl, aclErrors] = aclValidator.validate(schema.acl)
const aclErrors = aclValidator.validate(schema.acl)
const modelValidator = new ModelValidator(schema.model)
const [model, modelErrors] = modelValidator.validate()
const validationValidator = new ValidationValidator(schema.model)
const [validation, validationErrors] = validationValidator.validate(schema.validation)
const validationErrors = validationValidator.validate(schema.validation)
const validSchema = { ...schema, acl, model, validation }
const errors = [...aclErrors, ...modelErrors, ...validationErrors]
if (errors.length === 0) {
const errors = deepCompare(validSchema, schema, [])
if (errors.length) {
let message = 'There is something wrong with a schema validator:'
for (const err of errors) {
message += '\n\t' + err.path.join('.') + ': ' + err.message
}
message += '\n\nPlease fill a bug report'
throw new Error(message)
}
}
return errors
return [...aclErrors, ...modelErrors, ...validationErrors]
}
}
import { Model, Validation } from '@contember/schema'
import { ErrorBuilder, ValidationError } from './errors'
import { everyIs, hasArrayProperty, hasStringProperty, isArray, isObject } from './utils'
import { acceptFieldVisitor } from '../model'

@@ -9,21 +8,14 @@

public validate(schema: unknown): [Validation.Schema, ValidationError[]] {
public validate(schema: Validation.Schema): ValidationError[] {
const errorBuilder = new ErrorBuilder([], ['validation'])
let validSchema: Validation.Schema
if (!isObject(schema)) {
errorBuilder.add('Must be an object')
validSchema = { roles: {} }
} else {
validSchema = {}
for (const entityName in schema) {
const entity = this.model.entities[entityName]
const entityErrorBuilder = errorBuilder.for(entityName)
if (!entity) {
entityErrorBuilder.add('Entity not found')
} else {
validSchema[entityName] = this.validateEntityRules(entityErrorBuilder, schema[entityName], entity)
}
for (const entityName in schema) {
const entity = this.model.entities[entityName]
const entityErrorBuilder = errorBuilder.for(entityName)
if (!entity) {
entityErrorBuilder.add('Entity not found')
} else {
this.validateEntityRules(entityErrorBuilder, schema[entityName], entity)
}
}
return [validSchema, errorBuilder.errors]
return errorBuilder.errors
}

@@ -33,10 +25,5 @@

errorBuilder: ErrorBuilder,
entitySchema: unknown,
entitySchema: Validation.EntityRules,
entity: Model.Entity,
): Validation.EntityRules {
if (!isObject(entitySchema)) {
errorBuilder.add('Must be an object')
return {}
}
const validRules: Validation.EntityRules = {}
): void {
for (const fieldName in entitySchema) {

@@ -48,6 +35,5 @@ const field = entity.fields[fieldName]

} else {
validRules[fieldName] = this.validateFieldRules(fieldErrorBuilder, entitySchema[fieldName], entity, field)
this.validateFieldRules(fieldErrorBuilder, entitySchema[fieldName], entity, field)
}
}
return validRules
}

@@ -57,18 +43,9 @@

errorBuilder: ErrorBuilder,
fieldSchema: unknown,
fieldSchema: readonly Validation.ValidationRule[],
entity: Model.Entity,
field: Model.AnyField,
): Validation.ValidationRule[] {
if (!isArray(fieldSchema)) {
errorBuilder.add('Must be an array')
return []
}
const validRules = []
): void {
for (const i in fieldSchema) {
const rule = this.validateFieldRule(errorBuilder.for(i), fieldSchema[i], entity, field)
if (rule !== undefined) {
validRules.push(rule)
}
this.validateFieldRule(errorBuilder.for(i), fieldSchema[i], entity, field)
}
return validRules
}

@@ -78,10 +55,6 @@

errorBuilder: ErrorBuilder,
rule: unknown,
rule: Validation.ValidationRule,
entity: Model.Entity,
field: Model.AnyField,
): Validation.ValidationRule | undefined {
if (!isObject(rule)) {
errorBuilder.add('Must be an object')
return undefined
}
): void {
const errorMessage = acceptFieldVisitor(this.model, entity, field, {

@@ -95,122 +68,33 @@ visitColumn: () => null,

if (errorMessage) {
errorBuilder.add(errorMessage)
return undefined
return errorBuilder.add(errorMessage)
}
const validMessage = this.validateFieldRuleMessage(errorBuilder.for('message'), rule.message)
if (!validMessage) {
return undefined
}
const validValidator = this.validateValidator(errorBuilder.for('validator'), rule.validator, entity, field)
if (!validValidator) {
return undefined
}
return { message: validMessage, validator: validValidator }
this.validateValidator(errorBuilder.for('validator'), rule.validator, entity, field)
}
private validateFieldRuleMessage(errorBuilder: ErrorBuilder, message: unknown): Validation.Message | undefined {
if (!isObject(message)) {
errorBuilder.add('Must be an object')
return undefined
}
if (!hasStringProperty(message, 'text')) {
errorBuilder.for('text').add('Must be a string')
return undefined
}
let validMessage: Validation.Message = { text: message.text }
if (message.parameters) {
if (!hasArrayProperty(message, 'parameters')) {
errorBuilder.for('parameters').add('Must be an array')
return undefined
}
validMessage.parameters = message.parameters as string[]
}
return validMessage
}
private validateValidator(
errorBuilder: ErrorBuilder,
validator: unknown,
validator: Validation.Validator,
entity: Model.Entity,
field: Model.AnyField,
): Validation.Validator | undefined {
if (!isObject(validator)) {
errorBuilder.add('Must be an object')
return undefined
}
): void {
const validatorCast = validator as Validation.Validator
switch (validatorCast.operation) {
case 'and':
const andArgs = validatorCast.args
.map((it, index) => this.validateValidatorArgument(errorBuilder.for(String(index)), it, entity, field))
.filter((it): it is Validation.ValidatorArgument => it !== undefined)
return { operation: 'and' as const, args: andArgs }
case 'or':
const orArgs = validatorCast.args
.map((it, index) => this.validateValidatorArgument(errorBuilder.for(String(index)), it, entity, field))
.filter((it): it is Validation.ValidatorArgument => it !== undefined)
return { operation: 'or' as const, args: orArgs }
return validatorCast.args
.forEach((it, index) => this.validateValidatorArgument(errorBuilder.for(String(index)), it, entity, field))
case 'conditional':
const argA = this.validateValidatorArgument(errorBuilder.for('condition'), validatorCast.args[0], entity, field)
const argB = this.validateValidatorArgument(errorBuilder.for('rule'), validatorCast.args[1], entity, field)
if (argA === undefined || argB === undefined) {
return undefined
}
return { operation: 'conditional', args: [argA, argB] }
this.validateValidatorArgument(errorBuilder.for('condition'), validatorCast.args[0], entity, field)
this.validateValidatorArgument(errorBuilder.for('rule'), validatorCast.args[1], entity, field)
return
case 'pattern':
const patternLiteral = this.validateLiteralArgument(errorBuilder, validatorCast.args[0])
if (!patternLiteral) {
return undefined
}
if (
!isArray(patternLiteral.value) ||
patternLiteral.value.length !== 2 ||
typeof patternLiteral.value[0] !== 'string' ||
typeof patternLiteral.value[1] !== 'string'
) {
errorBuilder.add('Invalid pattern value')
return undefined
}
return { operation: 'pattern', args: [patternLiteral as Validation.LiteralArgument<[string, string]>] }
case 'lengthRange':
const lengthArgA = this.validateLiteralArgument(errorBuilder.for('min'), validatorCast.args[0])
const lengthArgB = this.validateLiteralArgument(errorBuilder.for('max'), validatorCast.args[1])
if (lengthArgA === undefined || lengthArgB === undefined) {
return undefined
}
return {
operation: 'lengthRange',
args: [lengthArgA as Validation.LiteralArgument<number>, lengthArgB as Validation.LiteralArgument<number>],
}
case 'range':
const rangeArgA = this.validateLiteralArgument(errorBuilder.for('min'), validatorCast.args[0])
const rangeArgB = this.validateLiteralArgument(errorBuilder.for('max'), validatorCast.args[1])
if (rangeArgA === undefined || rangeArgB === undefined) {
return undefined
}
return {
operation: 'range',
args: [rangeArgA as Validation.LiteralArgument<number>, rangeArgB as Validation.LiteralArgument<number>],
}
case 'equals':
const eqArg = this.validateLiteralArgument(errorBuilder, validatorCast.args[0])
if (eqArg === undefined) {
return undefined
}
return {
operation: 'equals',
args: [eqArg],
}
case 'not':
const notArg = this.validateValidatorArgument(errorBuilder, validatorCast.args[0], entity, field)
if (notArg === undefined) {
return undefined
}
return { operation: 'not', args: [notArg] }
case 'empty':
return { operation: 'empty', args: [] }
case 'defined':
return { operation: 'defined', args: [] }
return
case 'not':
return this.validateValidatorArgument(errorBuilder, validatorCast.args[0], entity, field)
case 'inContext':

@@ -221,28 +105,13 @@ const pathArgResult = this.validatePathArgument(errorBuilder.for('path'), validatorCast.args[0], entity)

}
const [pathArg, inField] = pathArgResult
const inValidatorArg = this.validateValidatorArgument(
return this.validateValidatorArgument(
errorBuilder.for('validator'),
validatorCast.args[1],
entity,
inField,
pathArgResult,
)
if (inValidatorArg === undefined) {
return undefined
}
return { operation: 'inContext', args: [pathArg, inValidatorArg] }
case 'every':
const everyArg = this.validateValidatorArgument(errorBuilder, validatorCast.args[0], entity, field)
if (everyArg === undefined) {
return undefined
}
return { operation: 'every', args: [everyArg] }
case 'any':
const anyArg = this.validateValidatorArgument(errorBuilder, validatorCast.args[0], entity, field)
if (anyArg === undefined) {
return undefined
}
return { operation: 'any', args: [anyArg] }
return this.validateValidatorArgument(errorBuilder, validatorCast.args[0], entity, field)
case 'filter':
const filterArgA = this.validateValidatorArgument(
this.validateValidatorArgument(
errorBuilder.for('filter'),

@@ -253,3 +122,3 @@ validatorCast.args[0],

)
const filterArgB = this.validateValidatorArgument(
this.validateValidatorArgument(
errorBuilder.for('validator'),

@@ -260,7 +129,3 @@ validatorCast.args[1],

)
if (filterArgA === undefined || filterArgB === undefined) {
return undefined
}
return { operation: 'filter', args: [filterArgA, filterArgB] }
return
default:

@@ -275,61 +140,15 @@ ((_: never) => {

errorBuilder: ErrorBuilder,
argument: unknown,
argument: Validation.ValidatorArgument,
entity: Model.Entity,
field: Model.AnyField,
): Validation.ValidatorArgument | undefined {
if (!isObject(argument)) {
errorBuilder.add('Must be an object')
return undefined
}
if (!hasStringProperty(argument, 'type') || argument.type !== Validation.ArgumentType.validator) {
errorBuilder.for('type').add(`Invalid value ${argument.type}`)
return undefined
}
const validator = this.validateValidator(errorBuilder.for('validator'), argument.validator, entity, field)
if (!validator) {
return undefined
}
return { validator, type: argument.type }
): void {
this.validateValidator(errorBuilder.for('validator'), argument.validator, entity, field)
}
private validateLiteralArgument(
errorBuilder: ErrorBuilder,
argument: unknown,
): Validation.LiteralArgument<unknown> | undefined {
if (!isObject(argument)) {
errorBuilder.add('Must be an object')
return undefined
}
if (!hasStringProperty(argument, 'type') || argument.type !== Validation.ArgumentType.literal) {
errorBuilder.for('type').add(`Invalid value ${argument.type}`)
return undefined
}
if (!('value' in argument)) {
errorBuilder.for('value').add('Undefined literal value')
return undefined
}
return { type: argument.type, value: argument.value }
}
private validatePathArgument(
errorBuilder: ErrorBuilder,
argument: unknown,
argument: Validation.PathArgument,
entity: Model.Entity,
): [Validation.PathArgument, Model.AnyField] | undefined {
if (!isObject(argument)) {
errorBuilder.add('Must be an object')
return undefined
}
if (!hasStringProperty(argument, 'type') || argument.type !== Validation.ArgumentType.path) {
errorBuilder.for('type').add(`Invalid value ${argument.type}`)
return undefined
}
if (!hasArrayProperty(argument, 'path')) {
errorBuilder.add('Undefined path')
return undefined
}
if (!everyIs(argument.path, (it): it is string => typeof it === 'string')) {
errorBuilder.add('Invalid path')
return undefined
}
): Model.AnyField | undefined {
const isRelation = acceptFieldVisitor(this.model, entity, argument.path[0], {

@@ -343,4 +162,4 @@ visitColumn: () => false,

}
return [{ type: argument.type, path: argument.path }, entity.fields[argument.path[0]]]
return entity.fields[argument.path[0]]
}
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc