diff --git a/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.ts b/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.ts index 20a3724079..7193c1a89b 100644 --- a/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.ts +++ b/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.ts @@ -15,7 +15,12 @@ */ import { EntityPolicy } from '../../types'; -import { makeValidator, Validators } from '../../validation'; +import { + CommonValidatorFunctions, + KubernetesValidatorFunctions, + makeValidator, + Validators, +} from '../../validation'; import { Entity } from '../Entity'; /** @@ -50,7 +55,47 @@ export class FieldFormatEntityPolicy implements EntityPolicy { } if (!isValid) { - throw new Error(`${field} "${value}" is not valid`); + let expectation; + switch ( + validator.name as + | keyof typeof KubernetesValidatorFunctions + | keyof typeof CommonValidatorFunctions + ) { + case 'isValidLabelValue': + case 'isValidObjectName': + expectation = + 'a string that is sequences of [a-zA-Z0-9] separated by any of [-_.], at most 63 characters in total'; + break; + case 'isValidLabelKey': + case 'isValidApiVersion': + case 'isValidAnnotationKey': + expectation = 'a valid prefix and/or suffix'; + break; + case 'isValidNamespace': + case 'isValidDnsLabel': + expectation = + 'a string that is sequences of [a-zA-Z0-9] separated by [-], at most 63 characters in total'; + break; + case 'isValidAnnotationValue': + expectation = 'a string'; + break; + case 'isValidKind': + expectation = + 'a string that is a sequence of [a-zA-Z][a-z0-9A-Z], at most 63 characters in total'; + break; + default: + expectation = undefined; + break; + } + + // ensure that if there are other/future validators, the error message defaults to a general "is not valid, visit link" + const message = expectation + ? ` expected ${expectation} but found "${value}".` + : ''; + + throw new Error( + `"${field}" is not valid;${message} To learn more about catalog file format, visit: https://github.com/spotify/backstage/blob/master/docs/architecture-decisions/adr002-default-catalog-file-format.md`, + ); } } diff --git a/plugins/catalog-backend/src/ingestion/HigherOrderOperations.ts b/plugins/catalog-backend/src/ingestion/HigherOrderOperations.ts index 23f35a4945..cb161d1ccf 100644 --- a/plugins/catalog-backend/src/ingestion/HigherOrderOperations.ts +++ b/plugins/catalog-backend/src/ingestion/HigherOrderOperations.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { InputError } from '@backstage/backend-common'; import { Location, LocationSpec } from '@backstage/catalog-model'; import { v4 as uuidv4 } from 'uuid'; import { Logger } from 'winston'; @@ -81,9 +80,7 @@ export class HigherOrderOperations implements HigherOrderOperation { const readerOutput = await this.locationReader.read(spec); if (!(spec.presence === 'optional') && readerOutput.errors.length) { const item = readerOutput.errors[0]; - throw new InputError( - `Failed to read location ${item.location.type}:${item.location.target}, ${item.error}`, - ); + throw item.error; } // TODO(freben): At this point, we could detect orphaned entities, by way diff --git a/plugins/catalog-backend/src/ingestion/LocationReaders.ts b/plugins/catalog-backend/src/ingestion/LocationReaders.ts index 215bfc9dc1..11ae45cae9 100644 --- a/plugins/catalog-backend/src/ingestion/LocationReaders.ts +++ b/plugins/catalog-backend/src/ingestion/LocationReaders.ts @@ -225,7 +225,7 @@ export class LocationReaders implements LocationReader { ); } catch (e) { const message = `Processor ${processor.constructor.name} threw an error while preprocessing entity ${kind}:${namespace}/${name} at ${item.location.type} ${item.location.target}, ${e}`; - emit(result.generalError(item.location, message)); + emit(result.generalError(item.location, e.message)); logger.warn(message); return undefined; } @@ -243,7 +243,7 @@ export class LocationReaders implements LocationReader { current = next; } catch (e) { const message = `Policy check failed while analyzing entity ${kind}:${namespace}/${name} at ${item.location.type} ${item.location.target}, ${e}`; - emit(result.inputError(item.location, message)); + emit(result.inputError(item.location, e.message)); logger.warn(message); return undefined; }