Implement model prefix stripping and leading operator handling in utility functions

This commit introduces new functions to handle model prefix stripping and leading operator detection in the `utils.js` file. The `stripModelPrefixFromExpression` and `splitLeadingOperator` functions are added to enhance expression parsing. Additionally, existing functions are updated to utilize these new utilities, improving the handling of expressions in filters. Corresponding tests are added to ensure the correctness of the new functionality, particularly for equality and not-equal expressions.
This commit is contained in:
Tom Butcher 2026-09-01 18:44:38 +01:00
parent 1f679825df
commit a7b613c88b
2 changed files with 48 additions and 9 deletions

View File

@ -38,4 +38,16 @@ describe('parseFilter number fields', () => {
const result = await parseFilter('_reference', 'JOB*', jobModel);
expect(result._reference).toEqual({ $regex: '^JOB.*$', $options: 'i' });
});
it('strips model prefix from equality expressions', async () => {
await expect(parseFilter('_reference', 'GCF:EJX3EXUZYW2U', jobModel)).resolves.toEqual({
_reference: { $regex: '^EJX3EXUZYW2U$', $options: 'i' },
});
});
it('strips model prefix from not-equal expressions', async () => {
await expect(parseFilter('_reference', '<>GCF:EJX3EXUZYW2U', jobModel)).resolves.toEqual({
_reference: { $ne: 'EJX3EXUZYW2U' },
});
});
});

View File

@ -198,6 +198,26 @@ function parsePrefixedValue(value) {
return { prefix: null, suffix: trimmed, hadPrefix: false };
}
const FILTER_OPERATOR_PREFIXES = ['<>', '>=', '<=', '>', '<', '='];
function splitLeadingOperator(str) {
const text = String(str).trim();
for (const op of FILTER_OPERATOR_PREFIXES) {
if (text.startsWith(op)) {
return { operator: op, operand: text.slice(op.length).trim() };
}
}
return { operator: '', operand: text };
}
// Strips a model prefix (e.g. GCF:) from an expression, including after a leading operator.
function stripModelPrefixFromExpression(expression) {
const { operator, operand } = splitLeadingOperator(expression);
const { suffix, hadPrefix } = parsePrefixedValue(operand);
if (!hadPrefix) return expression;
return operator + suffix;
}
function buildRegexOp(pattern, useOptions = true) {
const op = { $regex: pattern };
if (useOptions) op.$options = 'i';
@ -242,12 +262,12 @@ function getRefModelEntryFromPrefix(prefix) {
}
function stripPrefixFromOperand(operand) {
const { suffix, hadPrefix } = parsePrefixedValue(String(operand).trim());
return hadPrefix ? suffix : String(operand).trim();
return stripModelPrefixFromExpression(operand);
}
function getRefModelEntryForToken(token, fallbackRefName) {
const { prefix, hadPrefix } = parsePrefixedValue(String(token).trim());
const { operand } = splitLeadingOperator(token);
const { prefix, hadPrefix } = parsePrefixedValue(operand);
if (hadPrefix) {
const entry = getRefModelEntryFromPrefix(prefix);
if (entry) return entry;
@ -741,9 +761,15 @@ function combineAndRefIds(children) {
}
async function resolveRefOperand(operand, refModelEntry, operator = 'eq') {
const lookupOperand = String(operand).trim();
const { prefix, suffix, hadPrefix } = parsePrefixedValue(lookupOperand);
if (hadPrefix) {
const prefixEntry = getRefModelEntryFromPrefix(prefix);
if (prefixEntry) refModelEntry = prefixEntry;
}
if (!refModelEntry?.model) return NO_MATCH_CONDITION;
const lookupOperand = String(operand).trim();
const objectId = extractObjectIdFromOperand(lookupOperand);
if (objectId) {
@ -752,14 +778,16 @@ async function resolveRefOperand(operand, refModelEntry, operator = 'eq') {
return NO_MATCH_CONDITION;
}
const expression = buildRefListExpression(operator, lookupOperand);
const ids = await listRefModelIds(refModelEntry, expression);
const expressionOperand = hadPrefix ? suffix : lookupOperand;
if (operator === 'ne') {
const ids = await listRefModelIds(refModelEntry, expressionOperand);
if (ids.length === 0) return { query: {} };
return { op: { $nin: ids } };
}
const expression = buildRefListExpression(operator, expressionOperand);
const ids = await listRefModelIds(refModelEntry, expression);
return idsToCondition(ids);
}
@ -850,9 +878,8 @@ async function parseFilter(property, value, model = null) {
}
let expression = trimmed;
if (fieldKind.kind === 'default' && expression.charAt(3) === ':') {
const afterColon = value.split(':')[1];
expression = afterColon != null ? afterColon.trim() : '';
if (fieldKind.kind === 'default') {
expression = stripModelPrefixFromExpression(expression);
}
const isDateField = looksLikeDateField(property);