diff --git a/.changeset/purple-impalas-listen.md b/.changeset/purple-impalas-listen.md new file mode 100644 index 0000000000..b2cc2f4a14 --- /dev/null +++ b/.changeset/purple-impalas-listen.md @@ -0,0 +1,8 @@ +--- +'@backstage/catalog-model': minor +--- + +**BREAKING**: Simplified the `parseEntityRef` function to _always_ either return +a complete `EntityName`, complete with both kind, namespace and name, or throw +an error if it for some reason did not have enough information to form that +result. This makes its usage and its type declaration vastly simpler. diff --git a/packages/catalog-model/api-report.md b/packages/catalog-model/api-report.md index a6264ab30d..210d0187e2 100644 --- a/packages/catalog-model/api-report.md +++ b/packages/catalog-model/api-report.md @@ -417,47 +417,11 @@ export function parseEntityRef( name: string; }, context?: { - defaultKind: string; - defaultNamespace: string; + defaultKind?: string; + defaultNamespace?: string; }, ): EntityName; -// @public -export function parseEntityRef( - ref: - | string - | { - kind?: string; - namespace?: string; - name: string; - }, - context?: { - defaultKind: string; - }, -): { - kind: string; - namespace?: string; - name: string; -}; - -// @public -export function parseEntityRef( - ref: - | string - | { - kind?: string; - namespace?: string; - name: string; - }, - context?: { - defaultNamespace: string; - }, -): { - kind?: string; - namespace: string; - name: string; -}; - // @public export function parseLocationRef(ref: string): { type: string; diff --git a/packages/catalog-model/src/entity/ref.test.ts b/packages/catalog-model/src/entity/ref.test.ts index ba0402cb71..b0c7068fae 100644 --- a/packages/catalog-model/src/entity/ref.test.ts +++ b/packages/catalog-model/src/entity/ref.test.ts @@ -187,21 +187,11 @@ describe('ref', () => { namespace: 'b', name: 'c', }); - expect(parseEntityRef('b/c')).toEqual({ - kind: undefined, - namespace: 'b', - name: 'c', - }); expect(parseEntityRef('a:c')).toEqual({ kind: 'a', namespace: 'default', name: 'c', }); - expect(parseEntityRef('c')).toEqual({ - kind: undefined, - namespace: 'default', - name: 'c', - }); }); it('rejects bad inputs', () => { diff --git a/packages/catalog-model/src/entity/ref.ts b/packages/catalog-model/src/entity/ref.ts index 3c77998eb8..66974f5a01 100644 --- a/packages/catalog-model/src/entity/ref.ts +++ b/packages/catalog-model/src/entity/ref.ts @@ -121,81 +121,51 @@ export function parseEntityName( */ export function parseEntityRef( ref: string | { kind?: string; namespace?: string; name: string }, - context?: { defaultKind: string; defaultNamespace: string }, -): EntityName; -/** - * parseEntityRef with optional Kind. - * - * @public - */ -export function parseEntityRef( - ref: string | { kind?: string; namespace?: string; name: string }, - context?: { defaultKind: string }, -): { - kind: string; - namespace?: string; - name: string; -}; -/** - * parseEntityRef with optional Namespace. - * - * @public - */ -export function parseEntityRef( - ref: string | { kind?: string; namespace?: string; name: string }, - context?: { defaultNamespace: string }, -): { - kind?: string; - namespace: string; - name: string; -}; -/** - * parseEntityRef with optional Kind and Namespace. - * - * @public - */ -export function parseEntityRef( - ref: string | { kind?: string; namespace?: string; name: string }, - context: { + context?: { /** The default kind, if none is given in the reference */ defaultKind?: string; /** The default namespace, if none is given in the reference */ defaultNamespace?: string; - } = {}, -): { - kind?: string; - namespace?: string; - name: string; -} { + }, +): EntityName { if (!ref) { throw new Error(`Entity reference must not be empty`); } - const defaultNamespace = context.defaultNamespace || DEFAULT_NAMESPACE; + const defaultKind = context?.defaultKind; + const defaultNamespace = context?.defaultNamespace || DEFAULT_NAMESPACE; + + let kind: string | undefined; + let namespace: string | undefined; + let name: string | undefined; if (typeof ref === 'string') { const parsed = parseRefString(ref); - return { - kind: parsed.kind ?? context.defaultKind, - namespace: parsed.namespace ?? defaultNamespace, - name: parsed.name, - }; + kind = parsed.kind ?? defaultKind; + namespace = parsed.namespace ?? defaultNamespace; + name = parsed.name; + } else { + kind = ref.kind ?? defaultKind; + namespace = ref.namespace ?? defaultNamespace; + name = ref.name; } - const { kind, namespace, name } = ref; - if (kind === '') { - throw new Error('Entity reference kinds must not be empty'); - } else if (namespace === '') { - throw new Error('Entity reference namespaces must not be empty'); + if (!kind) { + const textual = JSON.stringify(ref); + throw new Error( + `Entity reference ${textual} had missing or empty kind (e.g. did not start with "component:" or similar)`, + ); + } else if (!namespace) { + const textual = JSON.stringify(ref); + throw new Error( + `Entity reference ${textual} had missing or empty namespace`, + ); } else if (!name) { - throw new Error('Entity references must contain a name'); + const textual = JSON.stringify(ref); + throw new Error(`Entity reference ${textual} had missing or empty name`); } - return { - kind: kind ?? context.defaultKind, - namespace: namespace ?? defaultNamespace, - name, - }; + return { kind, namespace, name }; } /** diff --git a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts index 1b7739a772..2532293acf 100644 --- a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts @@ -187,7 +187,7 @@ describe('BuiltinKindsEntityProcessor', () => { await expect( processor.postProcessEntity(entity, location, emit), ).rejects.toThrowError( - 'Entity reference "r" did not specify a kind (e.g. starting with "Component:"), and has no default', + 'Entity reference "r" had missing or empty kind (e.g. did not start with "component:" or similar)', ); }); @@ -360,7 +360,7 @@ describe('BuiltinKindsEntityProcessor', () => { await expect( processor.postProcessEntity(entity, location, emit), ).rejects.toThrowError( - 'Entity reference "c" did not specify a kind (e.g. starting with "Component:"), and has no default', + 'Entity reference "c" had missing or empty kind (e.g. did not start with "component:" or similar)', ); }); diff --git a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts index 75a1241b8f..89b0bb1bb5 100644 --- a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts @@ -106,11 +106,6 @@ export class BuiltinKindsEntityProcessor implements CatalogProcessor { } for (const target of [targets].flat()) { const targetRef = parseEntityRef(target, context); - if (targetRef.kind === undefined) { - throw new Error( - `Entity reference "${target}" did not specify a kind (e.g. starting with "Component:"), and has no default`, - ); - } emit( result.relation({ source: selfRef,