Changing how negation key word is processed

Signed-off-by: Jordan Snow <jordans@spotify.com>
This commit is contained in:
Jordan Snow
2024-10-02 17:00:29 -04:00
parent d3c14e574a
commit 64b6492f9e
6 changed files with 34 additions and 50 deletions
@@ -16,7 +16,6 @@
import { InputError } from '@backstage/errors';
import { EntityMatcherFn } from './types';
import { Entity } from '@backstage/catalog-model';
const allowedMatchers: Record<string, EntityMatcherFn> = {
labels: entity => {
@@ -33,7 +32,6 @@ const allowedMatchers: Record<string, EntityMatcherFn> = {
export function createHasMatcher(
parameters: string[],
onParseError: (error: Error) => void,
negation?: boolean,
): EntityMatcherFn {
const matchers = parameters.flatMap(parameter => {
const matcher = allowedMatchers[parameter.toLocaleLowerCase('en-US')];
@@ -49,7 +47,6 @@ export function createHasMatcher(
return [matcher];
});
const isMatch = (entity: Entity) =>
return entity =>
matchers.length ? matchers.some(matcher => matcher(entity)) : true;
return negation ? entity => !isMatch(entity) : isMatch;
}
@@ -28,7 +28,6 @@ const allowedMatchers: Record<string, EntityMatcherFn> = {
export function createIsMatcher(
parameters: string[],
onParseError: (error: Error) => void,
negation?: boolean,
): EntityMatcherFn {
const matchers = parameters.flatMap(parameter => {
const matcher = allowedMatchers[parameter.toLocaleLowerCase('en-US')];
@@ -44,7 +43,6 @@ export function createIsMatcher(
return [matcher];
});
const isMatch = (entity: any) =>
return entity =>
matchers.length ? matchers.some(matcher => matcher(entity)) : true;
return negation ? entity => !isMatch(entity) : isMatch;
}
@@ -22,10 +22,7 @@ import { EntityMatcherFn } from './types';
export function createKindMatcher(
parameters: string[],
_onParseError: (error: Error) => void,
negation?: boolean,
): EntityMatcherFn {
const items = parameters.map(p => p.toLocaleLowerCase('en-US'));
const isMatch = (entity: any) =>
items.includes(entity.kind.toLocaleLowerCase('en-US'));
return negation ? entity => !isMatch(entity) : isMatch;
return entity => items.includes(entity.kind.toLocaleLowerCase('en-US'));
}
@@ -22,14 +22,13 @@ import { EntityMatcherFn } from './types';
export function createTypeMatcher(
parameters: string[],
_onParseError: (error: Error) => void,
negation?: boolean,
): EntityMatcherFn {
const items = parameters.map(p => p.toLocaleLowerCase('en-US'));
return entity => {
const value = entity.spec?.type;
const isMatch =
return (
typeof value === 'string' &&
items.includes(value.toLocaleLowerCase('en-US'));
return negation ? !isMatch : isMatch;
items.includes(value.toLocaleLowerCase('en-US'))
);
};
}
@@ -137,17 +137,21 @@ describe('splitFilterExpression', () => {
expect(run('')).toEqual([]);
expect(run(' ')).toEqual([]);
expect(run('kind:component')).toEqual([
{ key: 'kind', parameters: ['component'] },
{ key: 'kind', parameters: ['component'], negation: false },
]);
expect(run('kind:component,user')).toEqual([
{ key: 'kind', parameters: ['component', 'user'] },
{ key: 'kind', parameters: ['component', 'user'], negation: false },
]);
expect(run('kind:component,user not:type:foo')).toEqual([
{ key: 'kind', parameters: ['component', 'user'], negation: false },
{ key: 'type', parameters: ['foo'], negation: true },
]);
expect(run('kind:component,user type:foo')).toEqual([
{ key: 'kind', parameters: ['component', 'user'] },
{ key: 'type', parameters: ['foo'] },
{ key: 'kind', parameters: ['component', 'user'], negation: false },
{ key: 'type', parameters: ['foo'], negation: false },
]);
expect(run('with:multiple:colons')).toEqual([
{ key: 'with', parameters: ['multiple:colons'] },
{ key: 'with', parameters: ['multiple:colons'], negation: false },
]);
});
@@ -61,14 +61,10 @@ export function parseFilterExpression(expression: string): {
const parts = splitFilterExpression(expression, e =>
expressionParseErrors.push(e),
);
let negation = false;
const matchers = parts.flatMap(part => {
const factory = rootMatcherFactories[part.key];
const negation = part.negation;
if (!factory) {
if (isNegation(part.key)) {
negation = true;
return [];
}
const known = Object.keys(rootMatcherFactories).map(m => `'${m}'`);
expressionParseErrors.push(
new InputError(
@@ -78,13 +74,11 @@ export function parseFilterExpression(expression: string): {
return [];
}
const matcher = factory(
part.parameters,
e => expressionParseErrors.push(e),
negation,
const matcher = factory(part.parameters, e =>
expressionParseErrors.push(e),
);
negation = false;
return [matcher];
return [negation ? (entity: Entity) => !matcher(entity) : matcher];
});
const filterFn = (entity: Entity) =>
@@ -105,20 +99,20 @@ export function parseFilterExpression(expression: string): {
export function splitFilterExpression(
expression: string,
onParseError: (error: Error) => void,
): Array<{ key: string; parameters: string[] }> {
): Array<{ key: string; parameters: string[]; negation: boolean }> {
const words = expression
.split(' ')
.map(w => w.trim())
.filter(Boolean);
const result = new Array<{ key: string; parameters: string[] }>();
const result = new Array<{
key: string;
parameters: string[];
negation: boolean;
}>();
for (const word of words) {
let match = word.match(/^([^:]+):(.+)$/);
if (match && isNegation(match[1])) {
result.push({ key: 'not', parameters: [] });
match = match[2].match(/^([^:]+):(.+)$/) || null;
}
const match = word.match(/^(not:)?([^:]+):(.+)$/);
if (!match) {
onParseError(
new InputError(
@@ -127,19 +121,14 @@ export function splitFilterExpression(
);
continue;
}
const key = match[1];
const parameters = match[2].split(',').filter(Boolean); // silently ignore double commas
result.push({ key, parameters });
const key = match[2];
const parameters = match[3].split(',').filter(Boolean); // silently ignore double commas
if (match[1]) {
result.push({ key, parameters, negation: true });
} else {
result.push({ key, parameters, negation: false });
}
}
return result;
}
function isNegation(parameter: string): boolean {
if (parameter.toLocaleLowerCase('en-US') === 'not') {
return true;
}
return false;
}