fix: Typescript type and Zod parser were not the same.

Signed-off-by: Sandor Karpf <sandor.karpf@lastpass.com>
This commit is contained in:
Sandor Karpf
2025-12-08 18:57:33 +01:00
parent 210406dfc0
commit c51c901779
4 changed files with 96 additions and 73 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-react': patch
---
$contains may have string value in an entity filter. Typescript type and Zod parser were not the same.
+3 -1
View File
@@ -478,6 +478,7 @@ export const EntityIconLinkBlueprint: ExtensionBlueprint<{
export type EntityPredicate =
| EntityPredicateExpression
| EntityPredicatePrimitive
| EntityPredicatePrimitive[]
| {
$all: EntityPredicate[];
}
@@ -506,6 +507,7 @@ export function entityPredicateToFilterFunction<T extends JsonValue>(
// @alpha (undocumented)
export type EntityPredicateValue =
| EntityPredicatePrimitive
| EntityPredicatePrimitive[]
| {
$exists: boolean;
}
@@ -513,7 +515,7 @@ export type EntityPredicateValue =
$in: EntityPredicatePrimitive[];
}
| {
$contains: EntityPredicateExpression;
$contains: EntityPredicate;
};
// @alpha
@@ -16,84 +16,98 @@
import { z } from 'zod';
import { createEntityPredicateSchema } from './createEntityPredicateSchema';
import { EntityPredicate } from './types';
describe('createEntityPredicateSchema', () => {
const schema = createEntityPredicateSchema(z);
it.each([
'string',
'',
[],
1,
{ kind: 'component', 'spec.type': 'service' },
{ 'metadata.tags': { $in: ['java'] } },
{
$all: [
{ 'metadata.tags': { $contains: 'java' } },
{ 'metadata.tags': { $contains: 'spring' } },
],
},
{ 'metadata.tags': ['java', 'spring'] },
{ 'metadata.tags': { $in: ['go'] } },
{ 'metadata.tags.0': 'java' },
{ $not: { 'metadata.tags': { $in: ['java'] } } },
{
$any: [{ kind: 'component', 'spec.type': 'service' }, { kind: 'group' }],
},
{
relations: {
$contains: { type: 'ownedBy', targetRef: 'group:default/g' },
describe('valid predicates', () => {
const predicates: EntityPredicate[] = [
'string',
'',
[],
1,
{ kind: 'component', 'spec.type': 'service' },
{ 'metadata.tags': { $in: ['java'] } },
{
$all: [
{ 'metadata.tags': { $contains: 'java' } },
{ 'metadata.tags': { $contains: 'spring' } },
],
},
},
{
metadata: { $contains: { name: 'a' } },
},
{ kind: 'component', 'spec.type': { $in: ['service', 'website'] } },
{
$any: [
{
$all: [
{
kind: 'component',
'spec.type': { $in: ['service', 'website'] },
},
],
{ 'metadata.tags': ['java', 'spring'] },
{ 'metadata.tags': { $in: ['go'] } },
{ 'metadata.tags.0': 'java' },
{ $not: { 'metadata.tags': { $in: ['java'] } } },
{
$any: [
{ kind: 'component', 'spec.type': 'service' },
{ kind: 'group' },
],
},
{
relations: {
$contains: { type: 'ownedBy', targetRef: 'group:default/g' },
},
{ $all: [{ kind: 'api', 'spec.type': 'grpc' }] },
],
},
{ kind: 'component', 'spec.type': { $in: ['service'] } },
{ 'spec.owner': { $exists: true } },
{ 'spec.owner': { $exists: false } },
{ 'spec.type': 'service' },
{ $not: { 'spec.type': 'service' } },
{
kind: 'component',
'metadata.annotations.github.com/repo': { $exists: true },
},
{ $all: [{ x: { $exists: true } }] },
{ $any: [{ x: { $exists: true } }] },
{ $not: { x: { $exists: true } } },
{ $not: { $all: [{ x: { $exists: true } }] } },
])('should accept valid predicate %j', predicate => {
expect(schema.parse(predicate)).toEqual(predicate);
},
{
metadata: { $contains: { name: 'a' } },
},
{ kind: 'component', 'spec.type': { $in: ['service', 'website'] } },
{
$any: [
{
$all: [
{
kind: 'component',
'spec.type': { $in: ['service', 'website'] },
},
],
},
{ $all: [{ kind: 'api', 'spec.type': 'grpc' }] },
],
},
{ kind: 'component', 'spec.type': { $in: ['service'] } },
{ 'spec.owner': { $exists: true } },
{ 'spec.owner': { $exists: false } },
{ 'spec.type': 'service' },
{ $not: { 'spec.type': 'service' } },
{
kind: 'component',
'metadata.annotations.github.com/repo': { $exists: true },
},
{ $all: [{ x: { $exists: true } }] },
{ $any: [{ x: { $exists: true } }] },
{ $not: { x: { $exists: true } } },
{ $not: { $all: [{ x: { $exists: true } }] } },
];
it.each(predicates)('should accept valid predicate %j', predicate => {
expect(schema.parse(predicate)).toEqual(predicate);
});
});
it.each([
{ kind: { 1: 'foo' } },
{ kind: { foo: 'bar' } },
{ kind: { $unknown: 'foo' } },
{ kind: { $in: 'foo' } },
{ kind: { $in: [{ x: 'foo' }] } },
{ kind: { $in: [{ x: 'foo' }] } },
{ 'spec.type': null },
{ $all: [{ x: { $unknown: true } }] },
{ $any: [{ x: { $unknown: true } }] },
{ $not: { x: { $unknown: true } } },
{ $not: { $all: [{ x: { $unknown: true } }] } },
{ $unknown: 'foo' },
])('should reject invalid predicate %j', predicate => {
const result = schema.safeParse(predicate);
expect(result.success).toBe(false);
describe('invalid predicates', () => {
const predicates: Array<
Exclude<EntityPredicate | unknown, EntityPredicate>
> = [
{ kind: { 1: 'foo' } },
{ kind: { foo: 'bar' } },
{ kind: { $unknown: 'foo' } },
{ kind: { $in: 'foo' } },
{ kind: { $in: [{ x: 'foo' }] } },
{ kind: { $in: [{ x: 'foo' }] } },
{ 'spec.type': null },
{ $all: [{ x: { $unknown: true } }] },
{ $any: [{ x: { $unknown: true } }] },
{ $not: { x: { $unknown: true } } },
{ $not: { $all: [{ x: { $unknown: true } }] } },
{ $unknown: 'foo' },
];
it.each(predicates)('should reject invalid predicate %j', predicate => {
const result = schema.safeParse(predicate);
expect(result.success).toBe(false);
});
});
});
@@ -18,6 +18,7 @@
export type EntityPredicate =
| EntityPredicateExpression
| EntityPredicatePrimitive
| EntityPredicatePrimitive[]
| { $all: EntityPredicate[] }
| { $any: EntityPredicate[] }
| { $not: EntityPredicate };
@@ -32,9 +33,10 @@ export type EntityPredicateExpression = {
/** @alpha */
export type EntityPredicateValue =
| EntityPredicatePrimitive
| EntityPredicatePrimitive[]
| { $exists: boolean }
| { $in: EntityPredicatePrimitive[] }
| { $contains: EntityPredicateExpression };
| { $contains: EntityPredicate };
/** @alpha */
export type EntityPredicatePrimitive = string | number | boolean;