CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-backstage--catalog-model

Types and validation framework for Backstage's software catalog model with support for all entity kinds, relationships, and validation policies

93

1.05x

Evaluation93%

1.05x

Agent success when using this tile

Overview
Eval results
Files

validation.mddocs/

Validation Framework

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.

Capabilities

Schema Validators

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");
}

Validator Interface

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;
  }
});

Common Validation Functions

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 Validation Functions

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');
}

Validation Patterns

Entity Validation Pipeline

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;
}

Custom Validator Factory

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");

Batch Entity Validation

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-model

docs

core-entities.md

entity-kinds.md

entity-policies.md

entity-references.md

entity-relations.md

index.md

location-management.md

validation.md

tile.json