From aa64cc917cb0ade6bb6b005b8b5456f92b7c8003 Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Mon, 20 Jan 2020 16:29:01 -0700 Subject: [PATCH] Adds draft schema document and initial logic to validate config against schema --- .../Config/DeviceConfig.cs | 85 +- .../Config/Essentials/ConfigReader.cs | 20 + .../Config/Schema/EssentialsConfigSchema.json | 744 ++++++++++++++++++ .../Devices/ReconfigurableDevice.cs | 4 +- .../PepperDash_Essentials_Core.csproj | 1 + essentials-framework/pepperdashcore-builds | 2 +- 6 files changed, 849 insertions(+), 7 deletions(-) create mode 100644 essentials-framework/Essentials Core/PepperDashEssentialsBase/Config/Schema/EssentialsConfigSchema.json diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Config/DeviceConfig.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Config/DeviceConfig.cs index bb95da01..f3a00d43 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Config/DeviceConfig.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Config/DeviceConfig.cs @@ -11,9 +11,9 @@ using PepperDash.Essentials.Core; namespace PepperDash.Essentials.Core.Config { - public class DeviceConfig + public class DeviceConfig : PropertiesConfigBase { - [JsonProperty("key")] + [JsonProperty("key", Required = Required.Always)] public string Key { get; set; } [JsonProperty("uid")] @@ -25,12 +25,87 @@ namespace PepperDash.Essentials.Core.Config [JsonProperty("group")] public string Group { get; set; } - [JsonProperty("type")] + [JsonProperty("type", Required = Required.Always)] public string Type { get; set; } - [JsonProperty("properties")] + [JsonProperty("properties", Required = Required.Always)] [JsonConverter(typeof(DevicePropertiesConverter))] public JToken Properties { get; set; } + + public DeviceConfig() + { + SchemaJson = @" +{ + 'definitions': {}, + '$schema': 'http://json-schema.org/draft-07/schema#', + '$id': 'http://example.com/root.json', + 'type': 'object', + 'title': 'The Root Schema', + 'properties': { + 'name': { + '$id': '#/properties/name', + 'type': 'string', + 'title': 'The Name Schema', + 'default': '', + 'examples': [ + 'App Server' + ], + 'pattern': '^(.*)$' + }, + 'group': { + '$id': '#/properties/group', + 'type': 'string', + 'title': 'The Group Schema', + 'default': '', + 'examples': [ + 'appServer' + ], + 'pattern': '^(.*)$' + }, + 'properties': { + '$id': '#/properties/properties', + 'type': 'object', + 'title': 'The Properties Schema' + }, + 'uid': { + '$id': '#/properties/uid', + 'type': 'integer', + 'title': 'The Uid Schema', + 'default': 0, + 'examples': [ + 4 + ] + }, + 'key': { + '$id': '#/properties/key', + 'type': 'string', + 'title': 'The Key Schema', + 'default': '', + 'examples': [ + 'display-1' + ], + 'pattern': '^(.*)$' + }, + 'type': { + '$id': '#/properties/type', + 'type': 'string', + 'title': 'The Type Schema', + 'default': '', + 'examples': [ + 'appServer' + ], + 'pattern': '^(.*)$' + } + }, + 'required': [ + 'group', + 'properties', + 'key', + 'type' + ] +} +"; + } } /// @@ -59,7 +134,7 @@ namespace PepperDash.Essentials.Core.Config public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { - throw new NotImplementedException("SOD OFF HOSER"); + throw new NotImplementedException("Not Supported"); } } } \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Config/Essentials/ConfigReader.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Config/Essentials/ConfigReader.cs index 0a288944..8c601210 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Config/Essentials/ConfigReader.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Config/Essentials/ConfigReader.cs @@ -4,6 +4,7 @@ using Crestron.SimplSharp; using Crestron.SimplSharp.CrestronIO; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Schema; using PepperDash.Core; using PepperDash.Core.Config; @@ -91,6 +92,9 @@ namespace PepperDash.Essentials.Core.Config { Debug.Console(0, Debug.ErrorLogLevel.Notice, "Loading config file: '{0}'", filePath); + // Attempt to validate config against schema + ValidateSchema(fs.ReadToEnd()); + if (localConfigFound) { ConfigObject = JObject.Parse(fs.ReadToEnd()).ToObject(); @@ -129,6 +133,22 @@ namespace PepperDash.Essentials.Core.Config } } + public static void ValidateSchema(string json) + { + JToken config = JToken.Parse(json); + + var deviceConfig = new DeviceConfig(); + + JsonSchema schema = JsonSchema.Parse(deviceConfig.SchemaJson); + + config.Validate(schema, _ValidationEventHandler); + } + + public static void _ValidationEventHandler(object sender, ValidationEventArgs args) + { + Debug.Console(0, "{0}", args.Message); + } + /// /// Returns all the files from the directory specified. /// diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Config/Schema/EssentialsConfigSchema.json b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Config/Schema/EssentialsConfigSchema.json new file mode 100644 index 00000000..f3a53c12 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Config/Schema/EssentialsConfigSchema.json @@ -0,0 +1,744 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Essentials Configuration File Schema", + "description": "", + "type": "object", + "$ref":"#/definitions/EssentialsConfig", + "definitions": { + "EssentialsConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "template": { + "$ref": "#/definitions/BasicConfig" + }, + "system_url": { + "type": "string", + "format": "uri", + "qt-uri-protocols": [ + "https" + ] + }, + "system": { + "$ref": "#/definitions/BasicConfig" + }, + "template_url": { + "type": "string", + "format": "uri", + "qt-uri-protocols": [ + "https" + ] + } + }, + "required": [ + "system", + "template" + ], + "title": "Essentials Configuration" + }, + "Info": { + "type": "object", + "additionalProperties": false, + "properties": { + "name":{ + "type":"string" + }, + "date":{ + "type":"string", + "format": "date-time" + }, + "version":{ + "type":"string" + }, + "runtimeInfo":{ + "type":"object" + }, + "comment":{ + "type":"string" + }, + "hostname":{ + "type":"string" + }, + "appNumber":{ + "type":"integer" + }, + "lastModifiedDate": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "lastModifiedDate" + ], + "title": "SystemInfo" + }, + "BasicConfig": { + "type": "object", + "additionalProperties": false, + "properties": { + "devices": { + "type": "array", + "items": { + "$ref": "#/definitions/Device" + } + }, + "rooms": { + "type": "array", + "items": { + "$ref": "#/definitions/Room" + } + }, + "info": { + "$ref": "#/definitions/Info" + }, + "sourceLists": { + "$ref": "#/definitions/SourceLists" + } + }, + "required": [ + "devices", + "info", + "rooms", + "sourceLists", + "tieLines" + ], + "title": "Template" + }, + "Device": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "supportedConfigModes": { + "type": "array", + "items": { + "type": "string" + } + }, + "group": { + "type": "string" + }, + "properties": { + "$ref": "#/definitions/DeviceProperties" + }, + "uid": { + "type": "integer" + }, + "key": { + "type": "string" + }, + "type": { + "type": "string" + }, + "supportsCompliance": { + "type": "boolean" + }, + "supportedSystemTypes": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "group", + "key", + "name", + "properties", + "type", + "uid" + ], + "title": "Device" + }, + "DeviceProperties": { + "type": "object", + "additionalProperties": false, + "properties": { + "numberOfRelays": { + "type": "integer" + }, + "numberOfComPorts": { + "type": "integer" + }, + "numberOfDIOPorts": { + "type": "integer" + }, + "numberOfIrPorts": { + "type": "integer" + }, + "hasControls": { + "type": "boolean" + }, + "isDefault": { + "type": "boolean" + }, + "hasAudio": { + "type": "boolean" + }, + "hasDpad": { + "type": "boolean" + }, + "hasNumeric": { + "type": "boolean" + }, + "hasDvr": { + "type": "boolean" + }, + "disableCodecSharing": { + "type": "boolean" + }, + "control": { + "$ref": "#/definitions/Control" + }, + "serverUrl": { + "type": "string" + }, + "communicationMonitorProperties": { + "$ref": "#/definitions/CommunicationMonitorProperties" + }, + "phonebookMode": { + "type": "string" + }, + "occupancy": { + "$ref": "#/definitions/Occupancy" + }, + "favorites": { + "type": "array", + "items": { + "$ref": "#/definitions/Favorite" + } + }, + "sgdFile": { + "type": "string" + }, + "showTime": { + "type": "boolean" + }, + "showVolumeGauge": { + "type": "boolean" + }, + "sourcesOverflowCount": { + "type": "integer" + }, + "showDate": { + "type": "boolean" + }, + "headerStyle": { + "type": "string" + }, + "usesSplashPage": { + "type": "boolean" + }, + "roomListKey": { + "type": "string" + }, + "defaultRoomKey": { + "type": "string" + } + }, + "required": [], + "title": "DeviceProperties" + }, + "CommunicationMonitorProperties": { + "type": "object", + "additionalProperties": false, + "properties": { + "timeToError": { + "type": "integer" + }, + "timeToWarning": { + "type": "integer" + }, + "pollInterval": { + "type": "integer" + }, + "pollString": { + "type": "string" + } + }, + "required": [ + "pollInterval", + "pollString", + "timeToError", + "timeToWarning" + ], + "title": "CommunicationMonitorProperties" + }, + "Control": { + "type": "object", + "additionalProperties": false, + "properties": { + "irFile": { + "type": "string" + }, + "controlPortNumber": { + "type": "integer" + }, + "controlPortDevKey": { + "type": "string" + }, + "method": { + "type": "string" + }, + "tcpSshProperties": { + "$ref": "#/definitions/TCPSSHProperties" + }, + "controlPortName": { + "type": "string" + }, + "ipid": { + "type": "string", + "format": "integer" + }, + "endOfLineChar": { + "type": "string" + }, + "endOfLineString": { + "type": "string" + }, + "deviceReadyResponsePattern": { + "type": "string" + }, + "params": { + "$ref": "#/definitions/Params" + } + }, + "required": [], + "title": "Control" + }, + "Params": { + "type": "object", + "additionalProperties": false, + "properties": { + "deviceReadyResponsePattern": { + "type": "string" + }, + "endOfLineString": { + "type": "string" + } + }, + "required": [ + "deviceReadyResponsePattern", + "endOfLineString" + ], + "title": "Params" + }, + "TCPSSHProperties": { + "type": "object", + "additionalProperties": false, + "properties": { + "username": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "address": { + "type": "string" + }, + "password": { + "type": "string" + }, + "autoReconnect": { + "type": "boolean" + }, + "autoReconnectIntervalMs": { + "type": "integer" + }, + "bufferSize": { + "type": "integer" + } + }, + "required": [ + "address", + "autoReconnect", + "autoReconnectIntervalMs", + "bufferSize", + "password", + "port", + "username" + ], + "title": "TCPSSHProperties" + }, + "Favorite": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "number": { + "type": "string", + "format": "integer" + } + }, + "required": [ + "name", + "number" + ], + "title": "Favorite" + }, + "Occupancy": { + "type": "object", + "additionalProperties": false, + "properties": { + "available": { + "type": "boolean" + } + }, + "required": [ + "available" + ], + "title": "Occupancy" + }, + "TemplateInfo": { + "type": "object", + "additionalProperties": false, + "properties": { + "lastUid": { + "type": "integer" + }, + "processorType": { + "type": "string" + }, + "systemType": { + "type": "string" + }, + "lastModifiedDate": { + "type": "string", + "format": "date-time" + }, + "requiredControlSofwareVersion": { + "type": "string" + }, + "comment": { + "type": "string" + } + }, + "required": [ + "comment", + "lastModifiedDate", + "lastUid", + "processorType", + "requiredControlSofwareVersion", + "systemType" + ], + "title": "TemplateInfo" + }, + "Room": { + "type": "object", + "additionalProperties": false, + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "properties": { + "$ref": "#/definitions/RoomProperties" + }, + "type": { + "type": "string" + } + }, + "required": [ + "key", + "name", + "properties", + "type" + ], + "title": "Room" + }, + "RoomProperties": { + "type": "object", + "additionalProperties": false, + "properties": { + "description": { + "type": "string" + }, + "volumes": { + "$ref": "#/definitions/Volumes" + }, + "defaultDisplayKey": { + "type": "string" + }, + "defaultAudioKey": { + "type": "string" + }, + "helpMessage": { + "type": "string" + }, + "hasDsp": { + "type": "boolean" + }, + "defaultSourceItem": { + "type": "string" + }, + "logo": { + "$ref": "#/definitions/Logo" + }, + "environment": { + "$ref": "#/definitions/Environment" + }, + "defaultVideoBehavior": { + "type": "string" + }, + "videoCodecKey": { + "type": "string" + }, + "sourceListKey": { + "type": "string" + } + }, + "required": [ + "defaultAudioKey", + "defaultDisplayKey", + "defaultSourceItem", + "defaultVideoBehavior", + "description", + "environment", + "hasDsp", + "helpMessage", + "logo", + "sourceListKey", + "videoCodecKey", + "volumes" + ], + "title": "RoomProperties" + }, + "Environment": { + "type": "object", + "additionalProperties": false, + "properties": { + "deviceKeys": { + "type": "array", + "items": {} + } + }, + "required": [ + "deviceKeys" + ], + "title": "Environment" + }, + "Logo": { + "type": "object", + "additionalProperties": false, + "properties": { + "url": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type", + "url" + ], + "title": "Logo" + }, + "Volumes": { + "type": "object", + "additionalProperties": false, + "properties": { + "master": { + "$ref": "#/definitions/Master" + } + }, + "required": [ + "master" + ], + "title": "Volumes" + }, + "Master": { + "type": "object", + "additionalProperties": false, + "properties": { + "level": { + "type": "integer" + }, + "deviceKey": { + "type": "string" + }, + "label": { + "type": "string" + } + }, + "required": [ + "deviceKey", + "label", + "level" + ], + "title": "Master" + }, + "SourceLists": { + "type": "object", + "additionalProperties": false, + "properties": { + "default": { + "$ref": "#/definitions/Default" + } + }, + "required": [ + "default" + ], + "title": "SourceLists" + }, + "Default": { + "type": "object", + "additionalProperties": false, + "properties": { + "codecOsd": { + "$ref": "#/definitions/CodecOsd" + }, + "source-1": { + "$ref": "#/definitions/Source" + }, + "roomOff": { + "$ref": "#/definitions/RoomOff" + }, + "source-2": { + "$ref": "#/definitions/Source" + } + }, + "required": [ + "codecOsd", + "roomOff", + "source-1", + "source-2" + ], + "title": "Default" + }, + "CodecOsd": { + "type": "object", + "additionalProperties": false, + "properties": { + "sourceKey": { + "type": "string" + }, + "name": { + "type": "string" + }, + "routeList": { + "type": "array", + "items": { + "$ref": "#/definitions/RouteList" + } + }, + "includeInSourceList": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "order": { + "type": "integer" + } + }, + "required": [ + "includeInSourceList", + "name", + "order", + "routeList", + "sourceKey", + "type" + ], + "title": "CodecOsd" + }, + "RouteList": { + "type": "object", + "additionalProperties": false, + "properties": { + "destinationKey": { + "type": "string" + }, + "sourceKey": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "destinationKey", + "sourceKey", + "type" + ], + "title": "RouteList" + }, + "RoomOff": { + "type": "object", + "additionalProperties": false, + "properties": { + "sourceKey": { + "type": "string" + }, + "routeList": { + "type": "array", + "items": { + "$ref": "#/definitions/RouteList" + } + }, + "type": { + "type": "string" + } + }, + "required": [ + "routeList", + "sourceKey", + "type" + ], + "title": "RoomOff" + }, + "Source": { + "type": "object", + "additionalProperties": false, + "properties": { + "icon": { + "type": "string" + }, + "volumeControlKey": { + "type": "string" + }, + "sourceKey": { + "type": "string" + }, + "routeList": { + "type": "array", + "items": { + "$ref": "#/definitions/RouteList" + } + }, + "includeInSourceList": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "altIcon": { + "type": "string" + }, + "order": { + "type": "integer" + }, + "disableCodecSharing": { + "type": "boolean" + } + }, + "required": [ + "altIcon", + "icon", + "includeInSourceList", + "order", + "routeList", + "sourceKey", + "type", + "volumeControlKey" + ], + "title": "Source" + } + } +} diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/ReconfigurableDevice.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/ReconfigurableDevice.cs index 3acfcac0..b3dacd1d 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/ReconfigurableDevice.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Devices/ReconfigurableDevice.cs @@ -10,7 +10,9 @@ using PepperDash.Essentials.Core.Config; namespace PepperDash.Essentials.Core.Devices { /// - /// + /// This class should be inherited from when the configuration can be modified at runtime from another source other than the configuration file. + /// It contains the necessary properties, methods and events to allot the initial device configuration to be overridden and then notifies the + /// ConfigWriter to write out the new values to a local file to be read on next boot. /// public abstract class ReconfigurableDevice : Device { diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj b/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj index c6915164..72ca8bab 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj @@ -271,6 +271,7 @@ + diff --git a/essentials-framework/pepperdashcore-builds b/essentials-framework/pepperdashcore-builds index 2d9c383d..aa2cbb96 160000 --- a/essentials-framework/pepperdashcore-builds +++ b/essentials-framework/pepperdashcore-builds @@ -1 +1 @@ -Subproject commit 2d9c383db02ef3ef69dee8d42201c746fdfe79ea +Subproject commit aa2cbb965d2777370358e749e9608dbbafccc466