catalog-model: implement matchEntityWithRef for client side filtering

This commit is contained in:
Fredrik Adelöw
2021-02-03 11:40:25 +01:00
parent dac74ddaca
commit 11cb5ef94e
4 changed files with 403 additions and 11 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/catalog-model': patch
---
Implement matchEntityWithRef for client side filtering of entities by ref matching
@@ -27,6 +27,7 @@ export type {
} from './Entity';
export * from './policies';
export {
compareEntityToRef,
getEntityName,
parseEntityName,
parseEntityRef,
+322 -1
View File
@@ -16,7 +16,12 @@
import { ENTITY_DEFAULT_NAMESPACE } from './constants';
import { Entity } from './Entity';
import { parseEntityName, parseEntityRef, serializeEntityRef } from './ref';
import {
compareEntityToRef,
parseEntityName,
parseEntityRef,
serializeEntityRef,
} from './ref';
describe('ref', () => {
describe('parseEntityName', () => {
@@ -381,4 +386,320 @@ describe('ref', () => {
).toEqual({ kind: 'a', namespace: 'b', name: 'c/x' });
});
});
describe('compareEntityToRef', () => {
const entityWithNamespace: Entity = {
apiVersion: 'a',
kind: 'K',
metadata: {
name: 'n',
namespace: 'ns',
},
};
const entityWithoutNamespace: Entity = {
apiVersion: 'a',
kind: 'K',
metadata: {
name: 'n',
},
};
it('handles matching string refs', () => {
expect(compareEntityToRef(entityWithNamespace, 'K:ns/n')).toBe(true);
expect(compareEntityToRef(entityWithNamespace, 'k:nS/N')).toBe(true);
expect(
compareEntityToRef(entityWithNamespace, 'K:n', {
defaultNamespace: 'ns',
}),
).toBe(true);
expect(
compareEntityToRef(entityWithNamespace, 'K:n', {
defaultNamespace: 'Ns',
}),
).toBe(true);
expect(
compareEntityToRef(entityWithNamespace, 'ns/n', { defaultKind: 'K' }),
).toBe(true);
expect(
compareEntityToRef(entityWithNamespace, 'n', {
defaultKind: 'K',
defaultNamespace: 'ns',
}),
).toBe(true);
expect(
compareEntityToRef(entityWithNamespace, 'N', {
defaultKind: 'k',
defaultNamespace: 'nS',
}),
).toBe(true);
expect(compareEntityToRef(entityWithoutNamespace, 'K:default/n')).toBe(
true,
);
expect(compareEntityToRef(entityWithoutNamespace, 'K:deFault/n')).toBe(
true,
);
expect(
compareEntityToRef(entityWithoutNamespace, 'K:n', {
defaultNamespace: 'default',
}),
).toBe(true);
expect(
compareEntityToRef(entityWithoutNamespace, 'K:n', {
defaultNamespace: 'deFault',
}),
).toBe(true);
expect(compareEntityToRef(entityWithoutNamespace, 'K:default/n')).toBe(
true,
);
expect(compareEntityToRef(entityWithoutNamespace, 'K:n')).toBe(true);
expect(
compareEntityToRef(entityWithoutNamespace, 'default/n', {
defaultKind: 'K',
}),
).toBe(true);
expect(
compareEntityToRef(entityWithoutNamespace, 'n', {
defaultKind: 'K',
defaultNamespace: 'default',
}),
).toBe(true);
expect(
compareEntityToRef(entityWithoutNamespace, 'n', {
defaultKind: 'K',
}),
).toBe(true);
});
it('handles mismatching string refs', () => {
expect(compareEntityToRef(entityWithNamespace, 'X:ns/n')).toBe(false);
expect(
compareEntityToRef(entityWithoutNamespace, 'ns/n', {
defaultKind: 'X',
}),
).toBe(false);
expect(compareEntityToRef(entityWithNamespace, 'K:xx/n')).toBe(false);
expect(
compareEntityToRef(entityWithoutNamespace, 'K:n', {
defaultNamespace: 'xx',
}),
).toBe(false);
expect(compareEntityToRef(entityWithNamespace, 'K:ns/x')).toBe(false);
expect(
compareEntityToRef(entityWithoutNamespace, 'x', {
defaultKind: 'K',
defaultNamespace: 'ns',
}),
).toBe(false);
});
it('handles matching compound refs', () => {
expect(
compareEntityToRef(entityWithNamespace, {
kind: 'K',
namespace: 'ns',
name: 'n',
}),
).toBe(true);
expect(
compareEntityToRef(entityWithNamespace, {
kind: 'k',
namespace: 'Ns',
name: 'N',
}),
).toBe(true);
expect(
compareEntityToRef(
entityWithNamespace,
{ kind: 'K', name: 'n' },
{
defaultNamespace: 'ns',
},
),
).toBe(true);
expect(
compareEntityToRef(
entityWithNamespace,
{ namespace: 'ns', name: 'n' },
{ defaultKind: 'K' },
),
).toBe(true);
expect(
compareEntityToRef(entityWithNamespace, 'n', {
defaultKind: 'K',
defaultNamespace: 'ns',
}),
).toBe(true);
expect(
compareEntityToRef(entityWithNamespace, 'N', {
defaultKind: 'k',
defaultNamespace: 'nS',
}),
).toBe(true);
expect(
compareEntityToRef(entityWithoutNamespace, {
kind: 'K',
namespace: 'default',
name: 'n',
}),
).toBe(true);
expect(
compareEntityToRef(entityWithoutNamespace, {
kind: 'k',
namespace: 'deFault',
name: 'N',
}),
).toBe(true);
expect(
compareEntityToRef(
entityWithoutNamespace,
{ kind: 'K', name: 'n' },
{
defaultNamespace: 'default',
},
),
).toBe(true);
expect(
compareEntityToRef(entityWithoutNamespace, { kind: 'K', name: 'n' }),
).toBe(true);
expect(
compareEntityToRef(
entityWithoutNamespace,
{ namespace: 'default', name: 'n' },
{
defaultKind: 'K',
},
),
).toBe(true);
expect(
compareEntityToRef(
entityWithoutNamespace,
{ name: 'n' },
{
defaultKind: 'K',
defaultNamespace: 'default',
},
),
).toBe(true);
expect(
compareEntityToRef(
entityWithoutNamespace,
{ name: 'N' },
{
defaultKind: 'k',
defaultNamespace: 'defAult',
},
),
).toBe(true);
expect(
compareEntityToRef(
entityWithoutNamespace,
{ name: 'n' },
{
defaultKind: 'K',
},
),
).toBe(true);
});
it('handles mismatching compound refs', () => {
expect(
compareEntityToRef(entityWithNamespace, {
kind: 'X',
namespace: 'ns',
name: 'n',
}),
).toBe(false);
expect(
compareEntityToRef(
entityWithNamespace,
{
namespace: 'ns',
name: 'n',
},
{ defaultKind: 'X' },
),
).toBe(false);
expect(
compareEntityToRef(entityWithoutNamespace, {
kind: 'X',
namespace: 'default',
name: 'n',
}),
).toBe(false);
expect(
compareEntityToRef(
entityWithoutNamespace,
{
namespace: 'default',
name: 'n',
},
{ defaultKind: 'X' },
),
).toBe(false);
expect(
compareEntityToRef(entityWithNamespace, {
kind: 'K',
namespace: 'xx',
name: 'n',
}),
).toBe(false);
expect(
compareEntityToRef(
entityWithNamespace,
{
kind: 'K',
name: 'n',
},
{ defaultNamespace: 'xx' },
),
).toBe(false);
expect(
compareEntityToRef(entityWithoutNamespace, {
kind: 'K',
namespace: 'xx',
name: 'n',
}),
).toBe(false);
expect(
compareEntityToRef(
entityWithoutNamespace,
{
kind: 'K',
name: 'n',
},
{ defaultNamespace: 'xx' },
),
).toBe(false);
expect(
compareEntityToRef(entityWithNamespace, {
kind: 'K',
namespace: 'ns',
name: 'x',
}),
).toBe(false);
expect(
compareEntityToRef(entityWithoutNamespace, {
kind: 'K',
namespace: 'default',
name: 'x',
}),
).toBe(false);
expect(
compareEntityToRef(
entityWithoutNamespace,
{
kind: 'K',
name: 'x',
},
{ defaultNamespace: 'default' },
),
).toBe(false);
});
});
});
+75 -10
View File
@@ -18,6 +18,27 @@ import { EntityName, EntityRef } from '../types';
import { ENTITY_DEFAULT_NAMESPACE } from './constants';
import { Entity } from './Entity';
function parseRefString(
ref: string,
): {
kind?: string;
namespace?: string;
name: string;
} {
const match = /^([^:/]+:)?([^:/]+\/)?([^:/]+)$/.exec(ref.trim());
if (!match) {
throw new TypeError(
`Entity reference "${ref}" was not on the form [<kind>:][<namespace>/]<name>`,
);
}
return {
kind: match[1]?.slice(0, -1),
namespace: match[2]?.slice(0, -1),
name: match[3],
};
}
/**
* Extracts the kind, namespace and name that form the name triplet of the
* given entity.
@@ -121,17 +142,11 @@ export function parseEntityRef(
}
if (typeof ref === 'string') {
const match = /^([^:/]+:)?([^:/]+\/)?([^:/]+)$/.exec(ref.trim());
if (!match) {
throw new Error(
`Entity reference "${ref}" was not on the form [<kind>:][<namespace>/]<name>`,
);
}
const parsed = parseRefString(ref);
return {
kind: match[1]?.slice(0, -1) ?? context.defaultKind,
namespace: match[2]?.slice(0, -1) ?? context.defaultNamespace,
name: match[3],
kind: parsed.kind ?? context.defaultKind,
namespace: parsed.namespace ?? context.defaultNamespace,
name: parsed.name,
};
}
@@ -196,3 +211,53 @@ export function serializeEntityRef(
return `${kind ? `${kind}:` : ''}${namespace ? `${namespace}/` : ''}${name}`;
}
/**
* Compares an entity to either a string reference or a compound reference.
*
* The comparison is case insensitive, and all of kind, namespace, and name
* must match (after applying the optional context to the ref).
*
* @param entity The entity to match
* @param ref A string or compound entity ref
* @param context An optional context of default kind and namespace, that apply
* to the ref if given
* @returns True if matching, false otherwise
*/
export function compareEntityToRef(
entity: Entity,
ref: EntityRef | EntityName,
context?: EntityRefContext,
): boolean {
const entityKind = entity.kind;
const entityNamespace = entity.metadata.namespace || ENTITY_DEFAULT_NAMESPACE;
const entityName = entity.metadata.name;
let refKind: string | undefined;
let refNamespace: string | undefined;
let refName: string;
if (typeof ref === 'string') {
const parsed = parseRefString(ref);
refKind = parsed.kind || context?.defaultKind;
refNamespace =
parsed.namespace || context?.defaultNamespace || ENTITY_DEFAULT_NAMESPACE;
refName = parsed.name;
} else {
refKind = ref.kind || context?.defaultKind;
refNamespace =
ref.namespace || context?.defaultNamespace || ENTITY_DEFAULT_NAMESPACE;
refName = ref.name;
}
if (!refKind || !refNamespace) {
throw new Error(
`Entity reference or context did not contain kind and namespace`,
);
}
return (
entityKind.toLowerCase() === refKind.toLowerCase() &&
entityNamespace.toLowerCase() === refNamespace.toLowerCase() &&
entityName.toLowerCase() === refName.toLowerCase()
);
}