From 74e833a7fbc1eb2203dda0536430ddde37484a4b Mon Sep 17 00:00:00 2001 From: gaosen <0x5e@sina.cn> Date: Mon, 10 Apr 2023 01:47:15 +0800 Subject: [PATCH] Strict config validate for `deviceOverrides` --- CHANGELOG.md | 1 + src/config.ts | 22 +++++++-------- src/platform.ts | 71 +++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 72 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f66d1feb..b5bd7e39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ - Support Fan light temperature and color. (#184) - Support Humidifier light. (#184) - Expose energy usage for outlets/switches. (#190) Thanks @lstrojny for the contribution +- Strict config validate for `deviceOverrides`. ## [1.6.0] - (2022.12.3) diff --git a/src/config.ts b/src/config.ts index a6b14c93..ab3c0e66 100644 --- a/src/config.ts +++ b/src/config.ts @@ -3,18 +3,18 @@ import { TuyaDeviceSchemaProperty, TuyaDeviceSchemaType } from './device/TuyaDev export interface TuyaPlatformDeviceSchemaConfig { code: string; - newCode: string; - type: TuyaDeviceSchemaType; - property: TuyaDeviceSchemaProperty; - onGet: string; - onSet: string; - hidden: boolean; + newCode?: string; + type?: TuyaDeviceSchemaType; + property?: TuyaDeviceSchemaProperty; + onGet?: string; + onSet?: string; + hidden?: boolean; } export interface TuyaPlatformDeviceConfig { id: string; - category: string; - schema: Array; + category?: string; + schema?: Array; } export interface TuyaPlatformCustomConfigOptions { @@ -24,7 +24,7 @@ export interface TuyaPlatformCustomConfigOptions { accessKey: string; username: string; password: string; - deviceOverrides: Array; + deviceOverrides?: Array; } export interface TuyaPlatformHomeConfigOptions { @@ -36,8 +36,8 @@ export interface TuyaPlatformHomeConfigOptions { username: string; password: string; appSchema: string; - homeWhitelist: Array; - deviceOverrides: Array; + homeWhitelist?: Array; + deviceOverrides?: Array; } export type TuyaPlatformConfigOptions = TuyaPlatformCustomConfigOptions | TuyaPlatformHomeConfigOptions; diff --git a/src/platform.ts b/src/platform.ts index 47ed1e54..bb8fb9b9 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -32,21 +32,70 @@ export class TuyaPlatform implements DynamicPlatformPlugin { public deviceManager?: TuyaDeviceManager; public accessoryHandlers: BaseAccessory[] = []; - validate(config) { + validate() { let result; - if (!config.options) { - this.log.warn('Not configured, exit.'); + if (!this.options) { + this.log.error('Not configured, exit.'); return false; - } else if (config.options.projectType === '1') { - result = new Validator().validate(config.options, customOptionsSchema); - } else if (config.options.projectType === '2') { - result = new Validator().validate(config.options, homeOptionsSchema); + } else if (this.options.projectType === '1') { + result = new Validator().validate(this.options, customOptionsSchema); + } else if (this.options.projectType === '2') { + result = new Validator().validate(this.options, homeOptionsSchema); } else { - this.log.warn(`Unsupported projectType: ${config.options.projectType}, exit.`); + this.log.error(`Unsupported projectType: ${this.options['projectType']}, exit.`); return false; } result.errors.forEach(error => this.log.error(error.stack)); - return result.errors.length === 0; + if (result.errors.length > 0) { + return false; + } + + if (!this.validateDeviceOverrides() || !this.validateSchema()) { + return false; + } + + return true; + } + + validateDeviceOverrides() { + const idMap = new Map(); + for (const item of this.options.deviceOverrides!) { + if (idMap.has(item.id)) { + idMap.get(item.id)?.push(item); + } else { + idMap.set(item.id, [item]); + } + } + for (const items of idMap.values()) { + if (items.length > 1) { + this.log.error('"deviceOverrides" conflict, "id" must be unique: %o.', items); + return false; + } + } + return true; + } + + validateSchema() { + for (const deviceOverride of this.options.deviceOverrides!) { + if (!deviceOverride.schema) { + continue; + } + const idMap = new Map(); + for (const item of deviceOverride.schema) { + if (idMap.has(item.code)) { + idMap.get(item.code)?.push(item); + } else { + idMap.set(item.code, [item]); + } + } + for (const items of idMap.values()) { + if (items.length > 1) { + this.log.error('"schema" conflict, "code" must be unique: %o.', items); + return false; + } + } + } + return true; } constructor( @@ -55,7 +104,7 @@ export class TuyaPlatform implements DynamicPlatformPlugin { public readonly api: API, ) { - if (!this.validate(config)) { + if (!this.validate()) { return; } @@ -66,7 +115,7 @@ export class TuyaPlatform implements DynamicPlatformPlugin { // in order to ensure they weren't added to homebridge already. This event can also be used // to start discovery of new accessories. this.api.on('didFinishLaunching', async () => { - log.debug('Executed didFinishLaunching callback'); + this.log.debug('Executed didFinishLaunching callback'); // run the method to discover / register your devices as accessories await this.initDevices(); });