Skip to content

Commit

Permalink
Validate attribute on filter or select (#163)
Browse files Browse the repository at this point in the history
* validate attribute on filter or select

* validate attribute name

* format error inner message
  • Loading branch information
kbarbounakis authored Sep 30, 2024
1 parent 388bf26 commit 16dc40b
Show file tree
Hide file tree
Showing 11 changed files with 169 additions and 34 deletions.
5 changes: 3 additions & 2 deletions data-attribute-resolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ var {DataError} = require('@themost/common');
var Symbol = require('symbol');
var {hasOwnProperty} = require('./has-own-property');
var aliasProperty = Symbol('alias');
var {UnknownAttributeError} = require('./data-errors');
/**
* @class
* @constructor
Expand Down Expand Up @@ -172,8 +173,8 @@ DataAttributeResolver.prototype.resolveNestedAttributeJoin = function(memberExpr
//if the specified member contains '/' e.g. user/name then prepare join
var arrMember = memberExprString.split('/');
var attrMember = self.field(arrMember[0]);
if (_.isNil(attrMember)) {
throw new Error(sprintf('The target model does not have an attribute named as %s',arrMember[0]));
if (attrMember == null) {
throw new UnknownAttributeError(self.name, arrMember[0]);
}
//search for field mapping
var mapping = self.inferMapping(arrMember[0]);
Expand Down
5 changes: 5 additions & 0 deletions data-errors.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { DataError } from '@themost/common';

export declare class UnknownAttributeError extends DataError {
constructor(model?: string, attribute?: string);
}
31 changes: 31 additions & 0 deletions data-errors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const { DataError } = require('@themost/common');
const { sprintf } = require('sprintf-js');

/**
* @private
* @param {string=} entityType
* @param {string=} attribute
* @returns
*/
function formatUnknownAttributeMessage(entityType, attribute) {
if (typeof entityType === 'string' || typeof attribute === 'string') {
return sprintf('Attribute "%s" does not exist on entity type "%s"', attribute, entityType);
}
return null;
}

class UnknownAttributeError extends DataError {
/**
*
* @param {string=} entityType
* @param {string=} attribute
*/
constructor(entityType, attribute) {
super('ERR_ATTR_UNKNOWN','The specified attribute does not exist on target entity type', formatUnknownAttributeMessage(entityType, attribute), entityType, attribute);
}

}

module.exports = {
UnknownAttributeError
}
14 changes: 6 additions & 8 deletions data-filter-resolver.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
// MOST Web Framework 2.0 Codename Blueshift BSD-3-Clause license Copyright (c) 2017-2022, THEMOST LP All rights reserved
var {FunctionContext} = require('./functions');

/**
* @module @themost/data/data-filter-resolver
* @ignore
*/
var { UnknownAttributeError } = require('./data-errors');

/**
* @ignore
Expand All @@ -20,11 +16,13 @@ function DataFilterResolver() {
DataFilterResolver.prototype.resolveMember = function(member, callback) {
if (/\//.test(member)) {
var arr = member.split('/');
callback(null, arr.slice(arr.length-2).join('.'));
return callback(null, arr.slice(arr.length-2).join('.'));
}
else {
callback(null, this.viewAdapter.concat('.', member))
var attribute = this.getAttribute(member);
if (attribute == null) {
return callback(new UnknownAttributeError(this.name, member));
}
return callback(null, this.viewAdapter.concat('.', member))
};

DataFilterResolver.prototype.resolveMethod = function(name, args, callback) {
Expand Down
27 changes: 20 additions & 7 deletions data-queryable.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ var Q = require('q');
var aliasProperty = Symbol('alias');
var {hasOwnProperty} = require('./has-own-property');
var {isObjectDeep} = require('./is-object');
var { UnknownAttributeError } = require('./data-errors');

/**
* @param {DataQueryable} target
Expand Down Expand Up @@ -313,16 +314,18 @@ DataAttributeResolver.prototype.resolveNestedAttributeJoin = function(memberExpr
//if the specified member contains '/' e.g. user/name then prepare join
var arrMember = memberExprString.split('/');
var attrMember = self.field(arrMember[0]);
if (_.isNil(attrMember)) {
throw new Error(sprintf('The target model does not have an attribute named as %s',arrMember[0]));
if (attrMember == null) {
throw new UnknownAttributeError(self.name, arrMember[0]);
}
//search for field mapping
var mapping = self.inferMapping(arrMember[0]);
if (_.isNil(mapping)) {
throw new Error(sprintf('The target model does not have an association defined for attribute named %s',arrMember[0]));
}
if (mapping.childModel===self.name && mapping.associationType==='association') {
//get parent model
/**
* @type {import('./data-model').DataModel}
*/
var parentModel = self.context.model(mapping.parentModel);
if (_.isNil(parentModel)) {
throw new Error(sprintf('Association parent model (%s) cannot be found.', mapping.parentModel));
Expand Down Expand Up @@ -357,6 +360,10 @@ DataAttributeResolver.prototype.resolveNestedAttributeJoin = function(memberExpr
parentModel[aliasProperty] = mapping.childField;
expr = DataAttributeResolver.prototype.resolveNestedAttributeJoin.call(parentModel, arrMember.slice(1).join('/'));
return [].concat(res.$expand).concat(expr);
} else {
// validate attribute name
var attribute = parentModel.getAttribute(arrMember[1]);
Args.check(attribute != null, new UnknownAttributeError(parentModel.name, arrMember[1]));
}
//--set active field
return res.$expand;
Expand Down Expand Up @@ -407,6 +414,8 @@ DataAttributeResolver.prototype.resolveNestedAttributeJoin = function(memberExpr
memberExpr.name = arrMember.join('/');
}
}
} else {
throw new UnknownAttributeError(childModel.name, arrMember[1]);
}
}
return res.$expand;
Expand Down Expand Up @@ -667,6 +676,8 @@ DataAttributeResolver.prototype.resolveJunctionAttributeJoin = function(attr) {
if (_.isNil(childModel)) {
throw new DataError('EJUNC','The associated model cannot be found.');
}
// validate attribute name
Args.check(childModel.getAttribute(member[1]) != null, new UnknownAttributeError(childModel.name, member[1]));
//create new join
var alias = field.name + '_' + childModel.name;
entity = new QueryEntity(childModel.viewAdapter).as(alias);
Expand Down Expand Up @@ -983,6 +994,7 @@ DataQueryable.prototype.join = function(model)
});
*/
DataQueryable.prototype.and = function(attr) {
Args.check(this.query.$where != null, new Error('The where expression has not been initialized.'));
if (typeof attr === 'string' && /\//.test(attr)) {
this.query.and(DataAttributeResolver.prototype.resolveNestedAttribute.call(this, attr));
return this;
Expand All @@ -1005,6 +1017,7 @@ DataQueryable.prototype.and = function(attr) {
});
*/
DataQueryable.prototype.or = function(attr) {
Args.check(this.query.$where != null, new Error('The where expression has not been initialized.'));
if (typeof attr === 'string' && /\//.test(attr)) {
this.query.or(DataAttributeResolver.prototype.resolveNestedAttribute.call(this, attr));
return this;
Expand Down Expand Up @@ -1659,7 +1672,7 @@ DataQueryable.prototype.fieldOf = function(attr, alias) {
}
}
if (typeof field === 'undefined' || field === null)
throw new Error(sprintf('The specified field %s cannot be found in target model.', matches[2]));
throw new UnknownAttributeError(this.model.name, matches[2]);
if (_.isNil(alias)) {
matches = /as\s([\u0020-\u007F\u0080-\uFFFF]+)$/i.exec(attr);
if (matches) {
Expand Down Expand Up @@ -1687,7 +1700,7 @@ DataQueryable.prototype.fieldOf = function(attr, alias) {
field = this.model.field(matches[2]);
aggr = matches[1];
if (typeof field === 'undefined' || field === null)
throw new Error(sprintf('The specified field %s cannot be found in target model.', matches[2]));
throw new UnknownAttributeError(this.model.name, matches[2]);
if (_.isNil(alias)) {
matches = /as\s([\u0021-\u007F\u0080-\uFFFF]+)$/i.exec(attr);
if (matches) {
Expand All @@ -1704,7 +1717,7 @@ DataQueryable.prototype.fieldOf = function(attr, alias) {
if (matches) {
field = this.model.field(matches[1]);
if (typeof field === 'undefined' || field === null)
throw new Error(sprintf('The specified field %s cannot be found in target model.', attr));
throw new UnknownAttributeError(this.model.name, matches[1]);
alias = matches[2];
prop = alias || field.property || field.name;
return QueryField.select(field.name).from(this.model.viewAdapter).as(prop);
Expand All @@ -1713,7 +1726,7 @@ DataQueryable.prototype.fieldOf = function(attr, alias) {
//try to match field with expression [field] as [alias] or [nested]/[field] as [alias]
field = this.model.field(attr);
if (typeof field === 'undefined' || field === null)
throw new Error(sprintf('The specified field %s cannot be found in target model.', attr));
throw new UnknownAttributeError(this.model.name, attr);
var f = QueryField.select(field.name).from(this.model.viewAdapter);
if (alias) {
return f.as(alias);
Expand Down
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ export * from './has-parent-junction';
export * from './data-listeners';
export * from './data-associations';
export * from './data-application';
export * from './data-errors';
5 changes: 4 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ var { DataObjectAssociationListener,
DataObjectMultiAssociationError } = require('./data-associations');
var { DataApplication } = require('./data-application');

var { UknonwnAttributeError } = require('./data-errors');

module.exports = {
TypeParser,
PrivilegeType,
Expand Down Expand Up @@ -165,6 +167,7 @@ module.exports = {
EntitySetKind,
ODataModelBuilder,
ODataConventionModelBuilder,
EntitySetSchemaLoaderStrategy
EntitySetSchemaLoaderStrategy,
UknonwnAttributeError
};

3 changes: 2 additions & 1 deletion most-data-resources.en-us.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,6 @@
"Data model cannot be found.":"Data model cannot be found.",
"Object type cannot be empty during remove operation.":"Object type cannot be empty during remove operation.",
"The associated model cannot be found.":"The associated model cannot be found.",
"The target model does not have a many to many association defined by the given attribute.":"The target model does not have a many to many association defined by the given attribute."
"The target model does not have a many to many association defined by the given attribute.":"The target model does not have a many to many association defined by the given attribute.",
"Attribute \"%s\" does not exist on entity type \"%s\"": "Attribute \"%s\" does not exist on entity type \"%s\""
}
29 changes: 16 additions & 13 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
},
"peerDependencies": {
"@themost/common": "^2.5.11",
"@themost/query": "^2.5.24",
"@themost/query": "^2.5.27",
"@themost/xml": "^2.5.2"
},
"engines": {
Expand Down Expand Up @@ -43,7 +43,7 @@
"@themost/common": "^2.5.11",
"@themost/json-logger": "^1.1.0",
"@themost/peers": "^1.0.2",
"@themost/query": "^2.5.24",
"@themost/query": "^2.5.27",
"@themost/sqlite": "^2.6.16",
"@themost/xml": "^2.5.2",
"@types/core-js": "^2.5.0",
Expand Down
Loading

0 comments on commit 16dc40b

Please sign in to comment.