Skip to content

Validation Service

Config schema validation for all LCARdS card types — window.lcards.core.validationService


Overview

Config validation is split across two singletons:

  • CoreConfigManager (window.lcards.core.configManager) owns the per-card-type schema registry (registerCardSchema() / getCardSchema()) and calls validation automatically as the last step of its four/five-layer processConfig() merge pipeline.
  • CoreValidationService (window.lcards.core.validationService) does the actual structural (JSON-schema-style) validation via validate(data, schema, context). It also owns optional token-reference and data-source-reference validators, and a small legacy registry of generic schemas (card-config, entity-reference, action-config, position, size) used internally.

Key Files

FileRole
core/validation-service/index.jsCoreValidationService — structural validation (validate()), plus internal CoreSchemaRegistry (5 built-in generic schemas) and CoreErrorFormatter (used automatically inside validate())
core/validation-service/TokenValidator.jsValidates {theme:token.path} / token-path references against a ThemeManager — called manually via validationService.validateTokens()
core/validation-service/DataSourceValidator.jsValidates source / data_source / sources fields on data-source-capable config types (currently lcards-chart/chart) — called manually via validationService.validateDataSources()
core/validation-service/ErrorFormatter.jsStandalone formatter that turns a validation result into a multi-line human-readable string, used by validationService.formatErrors()
core/config-manager/index.jsCoreConfigManager — per-card-type schema registry (_cardSchemas), config merge pipeline, and the _validateConfig() step that calls validationService.validate()
src/cards/schemas/*.jsPer-card JSON schema definitions (e.g. button-schema.js, msd-schema.js)

Schema Registration

Each card class exposes a static registerSchema() method. These are called in src/lcards.js after all core services initialise:

javascript
// lcards.js (called once at startup, after core is ready)
if (LCARdSButton.registerSchema) LCARdSButton.registerSchema();
if (LCARdSElbow.registerSchema) LCARdSElbow.registerSchema();
if (LCARdSMSDCard.registerSchema) LCARdSMSDCard.registerSchema();
// ... one call per card type

Inside registerSchema(), the card calls configManager.registerCardSchema():

javascript
// Inside your card class
static registerSchema() {
    const configManager = window.lcards?.core?.configManager;
    if (!configManager) return;

    const schema = getMyCardSchema({ /* runtime options, e.g. available presets */ });
    configManager.registerCardSchema('my-card', schema, { version: __LCARDS_VERSION__ });
}

To add a new card type to validation:

  1. Add a schema file at src/cards/schemas/my-card-schema.js
  2. Add static registerSchema() to your card class calling configManager.registerCardSchema('my-card', schema)
  3. Call MyCard.registerSchema() in src/lcards.js after core initialisation

Once registered, configManager.processConfig(userConfig, 'my-card', context) automatically validates the merged config against that schema on every call — there's no separate opt-in step.


Public API

CoreConfigManager (schema registry + orchestration)

MethodReturnsDescription
registerCardSchema(cardType, schema, options?)voidRegister a JSON schema for a card type. options.version defaults to '1.0'
getCardSchema(cardType)Object|nullThe raw registered schema, or null
processConfig(userConfig, cardType, context?)Promise<{ valid, mergedConfig, errors, warnings, provenance }>Runs the full merge pipeline, then validates the merged result against the registered schema (skipped silently if no schema is registered for cardType)
getDebugInfo()Object{ initialized, stats, registeredCards: { schemas, defaults }, dependencies }

CoreValidationService (structural validation)

MethodReturnsDescription
validate(data, schema, context?){ valid, errors, warnings, data, schema }Validate data against a schema object, or a string name from the internal generic registry ('card-config', 'entity-reference', 'action-config', 'position', 'size')
validateTokens(config, context?){ errors, warnings }Runs TokenValidator — requires setThemeManager() to have been called first; returns { errors: [], warnings: [] } otherwise. Not invoked automatically by validate() or by configManager — call it explicitly
validateDataSources(config, context?){ errors, warnings }Runs DataSourceValidator — requires setDataSourceManager() first. Not invoked automatically either
setThemeManager(themeManager)voidWires up validateTokens()
setDataSourceManager(dataSourceManager)voidWires up validateDataSources()
formatErrors(validationResult)stringMulti-line human-readable summary via the standalone ErrorFormatter
getStats()Object{ validationsPerformed, errorsFound, warningsFound, cacheHits, entityChecks }
clearCache()voidClears the validation-result cache and the entity-existence cache
getDebugInfo()ObjectSee Console Access below
javascript
// Card-type validation (the common path) goes through configManager, not validationService directly:
const configManager = window.lcards.core.configManager;
const result = await configManager.processConfig(this.config, 'lcards-button', { hass: this.hass });
if (!result.valid) {
  lcardsLog.warn('[MyCard] Config validation failed:', result.errors);
}

// Direct structural validation against an arbitrary schema object:
const vs = window.lcards.core.validationService;
const result2 = vs.validate({ entity: 'light.bedroom' }, myJsonSchema);

The config, editor, and MSD pipeline code paths all call CoreValidationService.validate() directly. TokenValidator/DataSourceValidator work correctly once wired up via setThemeManager()/setDataSourceManager(), but validateTokens()/validateDataSources() must be called explicitly — validate() runs structural and entity validation only.


Console Access

javascript
window.lcards.debug.singleton('validationService')
// → {
//   initialized: true,
//   hasHASS: true,
//   config: {
//     strict: false, validateEntities: true, cacheResults: true, debug: false,
//     validateTokens: true, validateDataSources: true, stopOnError: false
//   },
//   stats: { validationsPerformed: 42, errorsFound: 3, warningsFound: 5, cacheHits: 18, entityChecks: 12 },
//   cacheSize: 24,
//   entityCacheSize: 12,
//   availableSchemas: ['card-config', 'entity-reference', 'action-config', 'position', 'size']
// }
javascript
const vs = window.lcards.core.validationService

vs.validate(config, schema, context)     // run structural validation directly
vs.getStats()                            // { validationsPerformed, errorsFound, warningsFound, cacheHits, entityChecks }
vs.clearCache()                          // clear validation + entity caches
vs.formatErrors(result)                  // human-readable multi-line summary

const cm = window.lcards.core.configManager
cm.getCardSchema('lcards-button')        // registered JSON schema for a card type

Note: availableSchemas in the snapshot lists the legacy generic schemas built into CoreValidationService (card-config, entity-reference, action-config, position, size) — it does not include per-card-type schemas (lcards-button, msd, etc.), which live in configManager instead. Use cm.getCardSchema('lcards-button') for those.


Error Output Format

CoreValidationService.validate() (and CoreConfigManager.processConfig(), which wraps it) returns:

javascript
{
  valid: boolean,
  errors: Array<{ type, field, message, formattedMessage, suggestion, severity, context }>,
  warnings: Array<{ type, field, message, formattedMessage, suggestion, severity, context }>
}

errors/warnings are objects, not plain strings — each entry carries a machine-readable type (e.g. 'required_field', 'invalid_type', 'invalid_enum', 'out_of_range', 'missing_entity'), the field path, and a formattedMessage produced automatically by the internal CoreErrorFormatter. Example formattedMessage values:

Required field "entity" is missing
Field "style.color" must be string, got number
Field "preset" must be one of: lozenge, pill, bullet, rectangle
Field "style.color" value 999 is out of valid range
Entity "light.bedroom_lamp" not found in Home Assistant

Entity references are checked only as a warning (missing_entity) when validateEntities is enabled and HASS is available — an unknown entity never blocks a save.


TokenValidator

TokenValidator (constructor(themeManager), validate(config, context)) walks a config object looking for theme-token-shaped string values and checks that each resolves against the active theme via ThemeTokenResolver. It's available on validationService.tokenValidator once setThemeManager() has been called, but — as noted above — nothing in the current codebase calls validationService.validateTokens() automatically; a card or pipeline stage that wants token validation must call it explicitly.


Error Display

Validation errors surface in two places:

  1. Browser console — structured log messages with field paths (via lcardsLog.warn() at call sites)
  2. Card editorLCARdSBaseEditor._validateConfig() calls validationService.validate() directly against the schema from configManager.getCardSchema() and stores the result for the YAML tab's inline error markers

See Also