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-layerprocessConfig()merge pipeline.CoreValidationService(window.lcards.core.validationService) does the actual structural (JSON-schema-style) validation viavalidate(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
| File | Role |
|---|---|
core/validation-service/index.js | CoreValidationService: structural validation (validate()), plus internal CoreSchemaRegistry (5 built-in generic schemas) and CoreErrorFormatter (used automatically inside validate()) |
core/validation-service/TokenValidator.js | Validates {theme:token.path} / token-path references against a ThemeManager; called manually via validationService.validateTokens() |
core/validation-service/DataSourceValidator.js | Validates source / data_source / sources fields on data-source-capable config types (currently lcards-chart/chart); called manually via validationService.validateDataSources() |
core/validation-service/ErrorFormatter.js | Standalone formatter that turns a validation result into a multi-line human-readable string, used by validationService.formatErrors() |
core/config-manager/index.js | CoreConfigManager: per-card-type schema registry (_cardSchemas), config merge pipeline, and the _validateConfig() step that calls validationService.validate() |
src/cards/schemas/*.js | Per-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-main.js after all core services initialise:
// 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 typeInside registerSchema(), the card calls configManager.registerCardSchema():
// 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:
- Add a schema file at
src/cards/schemas/my-card-schema.js - Add
static registerSchema()to your card class callingconfigManager.registerCardSchema('my-card', schema) - Call
MyCard.registerSchema()insrc/lcards-main.jsafter 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)
| Method | Returns | Description |
|---|---|---|
registerCardSchema(cardType, schema, options?) | void | Register a JSON schema for a card type. options.version defaults to '1.0' |
getCardSchema(cardType) | Object|null | The 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)
| Method | Returns | Description |
|---|---|---|
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) | void | Wires up validateTokens() |
setDataSourceManager(dataSourceManager) | void | Wires up validateDataSources() |
formatErrors(validationResult) | string | Multi-line human-readable summary via the standalone ErrorFormatter |
getStats() | Object | { validationsPerformed, errorsFound, warningsFound, cacheHits, entityChecks } |
clearCache() | void | Clears the validation-result cache and the entity-existence cache |
getDebugInfo() | Object | See Console Access below |
// 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
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']
// }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 typeNote: 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:
{
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 AssistantEntity 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:
- Browser console: structured log messages with field paths (via
lcardsLog.warn()at call sites) - Card editor:
LCARdSBaseEditor._validateConfig()callsvalidationService.validate()directly against the schema fromconfigManager.getCardSchema()and stores the result for the YAML tab's inline error markers