Types and validation framework for Backstage's software catalog model with support for all entity kinds, relationships, and validation policies
93
Evaluation — 93%
↑ 1.05xAgent success when using this tile
Comprehensive validation system with schema-based validation, field format checking, and pluggable validator functions. The validation framework ensures data integrity and consistency across all catalog entities while providing extensible validation capabilities.
Core schema validation functions for validating entities and entity envelopes against JSON schemas.
/**
* Creates a validator function for entities based on a JSON schema
* @param schema - Optional JSON schema for validation
* @returns Validator function that validates and returns typed entity
*/
function entitySchemaValidator<T extends Entity>(
schema?: unknown
): (data: unknown) => T;
/**
* Creates a validator function for entity envelopes based on a JSON schema
* @param schema - Optional JSON schema for validation
* @returns Validator function that validates and returns typed entity envelope
*/
function entityEnvelopeSchemaValidator<T extends EntityEnvelope>(
schema?: unknown
): (data: unknown) => T;
/**
* Creates a kind-specific validator function based on a JSON schema
* @param schema - JSON schema for the specific entity kind
* @returns Validator function that returns entity or false if invalid
*/
function entityKindSchemaValidator<T extends Entity>(
schema: unknown
): (data: unknown) => T | false;Usage Examples:
import {
entitySchemaValidator,
entityKindSchemaValidator,
ComponentEntity
} from "@backstage/catalog-model";
// Create a general entity validator
const validator = entitySchemaValidator();
try {
const entity = validator(someUnknownData);
console.log("Valid entity:", entity);
} catch (error) {
console.error("Validation failed:", error);
}
// Create a component-specific validator
const componentValidator = entityKindSchemaValidator<ComponentEntity>(componentSchema);
const result = componentValidator(potentialComponentData);
if (result !== false) {
// result is now typed as ComponentEntity
console.log("Valid component:", result.spec.type);
} else {
console.error("Invalid component data");
}Interface defining validation functions for different entity field types with pluggable validation logic.
/**
* Interface defining validation functions for entity fields
*/
interface Validators {
/** Validate entity apiVersion field */
isValidApiVersion(value: unknown): boolean;
/** Validate entity kind field */
isValidKind(value: unknown): boolean;
/** Validate entity name field */
isValidEntityName(value: unknown): boolean;
/** Validate entity namespace field */
isValidNamespace(value: unknown): boolean;
/** Validate metadata label key */
isValidLabelKey(value: unknown): boolean;
/** Validate metadata label value */
isValidLabelValue(value: unknown): boolean;
/** Validate metadata annotation key */
isValidAnnotationKey(value: unknown): boolean;
/** Validate metadata annotation value */
isValidAnnotationValue(value: unknown): boolean;
/** Validate metadata tag value */
isValidTag(value: unknown): boolean;
}
/**
* Create a validator object with optional overrides
* @param overrides - Partial validator overrides
* @returns Complete validators object with defaults
*/
function makeValidator(overrides?: Partial<Validators>): Validators;Usage Examples:
import { makeValidator, Validators } from "@backstage/catalog-model";
// Use default validators
const validators = makeValidator();
const isValidName = validators.isValidEntityName("my-service");
const isValidLabel = validators.isValidLabelKey("app.kubernetes.io/name");
// Create custom validators with overrides
const customValidators = makeValidator({
isValidEntityName: (value) => {
// Custom name validation logic
return typeof value === 'string' && /^[a-z][a-z0-9-]*$/.test(value);
},
isValidTag: (value) => {
// Custom tag validation
return typeof value === 'string' && value.length <= 50;
}
});Static utility functions for common validation patterns used throughout the Backstage ecosystem.
/**
* Common validation utility functions
*/
class CommonValidatorFunctions {
/**
* Validate prefix and suffix patterns with separator
* @param value - Value to validate
* @param separator - Separator character
* @param isValidPrefix - Prefix validation function
* @param isValidSuffix - Suffix validation function
* @returns True if valid prefix/suffix pattern
*/
static isValidPrefixAndOrSuffix(
value: unknown,
separator: string,
isValidPrefix: (value: string) => boolean,
isValidSuffix: (value: string) => boolean
): boolean;
/** Check if value is safe for JSON serialization */
static isJsonSafe(value: unknown): boolean;
/** Validate DNS subdomain format */
static isValidDnsSubdomain(value: unknown): boolean;
/** Validate DNS label format */
static isValidDnsLabel(value: unknown): boolean;
/** Validate URL format */
static isValidUrl(value: unknown): boolean;
/** Check if value is a non-empty string */
static isNonEmptyString(value: unknown): value is string;
/** @deprecated Use isNonEmptyString instead */
static isValidString(value: unknown): boolean;
/** @deprecated Use validators.tag instead */
static isValidTag(value: unknown): boolean;
}Kubernetes-compatible validation functions that follow Kubernetes naming and format conventions.
/**
* Kubernetes-compatible validation functions
*/
class KubernetesValidatorFunctions {
/** Validate Kubernetes API version format */
static isValidApiVersion(value: unknown): boolean;
/** Validate Kubernetes kind format */
static isValidKind(value: unknown): boolean;
/** Validate Kubernetes object name format */
static isValidObjectName(value: unknown): boolean;
/** Validate Kubernetes namespace format */
static isValidNamespace(value: unknown): boolean;
/** Validate Kubernetes label key format */
static isValidLabelKey(value: unknown): boolean;
/** Validate Kubernetes label value format */
static isValidLabelValue(value: unknown): boolean;
/** Validate Kubernetes annotation key format */
static isValidAnnotationKey(value: unknown): boolean;
/** Validate Kubernetes annotation value format */
static isValidAnnotationValue(value: unknown): boolean;
}Usage Examples:
import {
CommonValidatorFunctions,
KubernetesValidatorFunctions
} from "@backstage/catalog-model";
// Common validation
const isValidDomain = CommonValidatorFunctions.isValidDnsSubdomain("my-service.example.com");
const isValidUrl = CommonValidatorFunctions.isValidUrl("https://example.com/api");
const isNonEmpty = CommonValidatorFunctions.isNonEmptyString("hello");
// Kubernetes validation
const isValidK8sName = KubernetesValidatorFunctions.isValidObjectName("my-service");
const isValidLabel = KubernetesValidatorFunctions.isValidLabelKey("app.kubernetes.io/name");
const isValidNamespace = KubernetesValidatorFunctions.isValidNamespace("default");
// Custom domain validation
function validateCustomDomain(domain: string): boolean {
return CommonValidatorFunctions.isValidDnsSubdomain(domain) &&
domain.endsWith('.mycompany.com');
}import {
Entity,
entitySchemaValidator,
makeValidator,
CommonValidatorFunctions
} from "@backstage/catalog-model";
async function validateEntity(data: unknown): Promise<Entity> {
// Schema validation
const schemaValidator = entitySchemaValidator<Entity>();
const entity = schemaValidator(data);
// Field validation
const validators = makeValidator();
if (!validators.isValidEntityName(entity.metadata.name)) {
throw new Error(`Invalid entity name: ${entity.metadata.name}`);
}
if (entity.metadata.namespace && !validators.isValidNamespace(entity.metadata.namespace)) {
throw new Error(`Invalid namespace: ${entity.metadata.namespace}`);
}
// Validate labels
if (entity.metadata.labels) {
for (const [key, value] of Object.entries(entity.metadata.labels)) {
if (!validators.isValidLabelKey(key)) {
throw new Error(`Invalid label key: ${key}`);
}
if (!validators.isValidLabelValue(value)) {
throw new Error(`Invalid label value: ${value}`);
}
}
}
// Validate annotations
if (entity.metadata.annotations) {
for (const [key, value] of Object.entries(entity.metadata.annotations)) {
if (!validators.isValidAnnotationKey(key)) {
throw new Error(`Invalid annotation key: ${key}`);
}
if (!validators.isValidAnnotationValue(value)) {
throw new Error(`Invalid annotation value: ${value}`);
}
}
}
return entity;
}import { Validators, makeValidator, KubernetesValidatorFunctions } from "@backstage/catalog-model";
function createCompanyValidator(): Validators {
return makeValidator({
isValidEntityName: (value) => {
// Company-specific naming rules
return typeof value === 'string' &&
/^[a-z][a-z0-9-]*[a-z0-9]$/.test(value) &&
value.length >= 3 &&
value.length <= 50;
},
isValidNamespace: (value) => {
// Allow Kubernetes namespaces plus company-specific ones
return KubernetesValidatorFunctions.isValidNamespace(value) ||
(typeof value === 'string' && /^(dev|staging|prod)-[a-z]+$/.test(value));
},
isValidLabelKey: (value) => {
// Require company domain in custom labels
if (typeof value === 'string' && value.includes('mycompany.com/')) {
return /^mycompany\.com\/[a-z][a-z0-9.-]*$/.test(value);
}
return KubernetesValidatorFunctions.isValidLabelKey(value);
}
});
}
// Usage
const companyValidators = createCompanyValidator();
const isValidName = companyValidators.isValidEntityName("user-service-v2");import { Entity, entitySchemaValidator } from "@backstage/catalog-model";
interface ValidationResult {
entity?: Entity;
error?: string;
}
async function validateEntities(entities: unknown[]): Promise<ValidationResult[]> {
const validator = entitySchemaValidator<Entity>();
return entities.map((data, index) => {
try {
const entity = validator(data);
return { entity };
} catch (error) {
return {
error: `Entity ${index}: ${error instanceof Error ? error.message : 'Unknown error'}`
};
}
});
}
// Usage
const results = await validateEntities(rawEntityData);
const validEntities = results
.filter(result => result.entity)
.map(result => result.entity!);
const errors = results
.filter(result => result.error)
.map(result => result.error!);Install with Tessl CLI
npx tessl i tessl/npm-backstage--catalog-modeldocs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10