private void CheckForValidityAndThrowIfSomethingIsWrong(ClientPropertyMetadata creationOptions) { var errorList = new ArrayList(); var warningList = new ArrayList(); var isMetadataOk = creationOptions.ValidateAndFix(ref errorList, ref warningList); if (isMetadataOk == false) { var errorMessage = $"Provided metadata is incorrect. Errors: "; foreach (var problem in errorList) { errorMessage += problem + " "; } throw new ArgumentException(errorMessage); } }
private static bool TryParseProperties(ref ArrayList unparsedTopicList, ref ArrayList parsedTopicList, string baseTopic, ref ClientDeviceMetadata candidateDevice, ref ArrayList errorList, ref ArrayList warningList) { var isParseSuccessful = false; for (var n = 0; n < candidateDevice.Nodes.Length; n++) { var candidatePropertyIds = ((string)candidateDevice.Nodes[n].AllAttributes["$properties"]).Split(','); var goodProperties = new ArrayList(); for (var p = 0; p < candidatePropertyIds.Length; p++) { var candidateProperty = new ClientPropertyMetadata() { NodeId = candidateDevice.Nodes[n].Id, PropertyId = candidatePropertyIds[p] }; // Parsing property attributes and value. var attributeRegex = new Regex($@"^({baseTopic})\/({candidateDevice.Id})\/({candidateDevice.Nodes[n].Id})\/({candidateProperty.PropertyId})(\/\$[a-z0-9][a-z0-9-]+)?(:)(.+)$"); var setRegex = new Regex($@"^({baseTopic})\/({candidateDevice.Id})\/({candidateDevice.Nodes[n].Id})\/({candidateProperty.PropertyId})(\/set)(:)(.+)$"); var valueRegex = new Regex($@"^({baseTopic})\/({candidateDevice.Id})\/({candidateDevice.Nodes[n].Id})\/({candidateProperty.PropertyId})()(:)(.+)$"); var isSettable = false; var isRetained = false; var isNameReceived = false; var isDataTypeReceived = false; var isSettableReceived = false; var isRetainedReceived = false; foreach (string inputString in unparsedTopicList) { var attributeMatch = attributeRegex.Match(inputString); if (attributeMatch.Success) { var key = attributeMatch.Groups[5].Value; var value = attributeMatch.Groups[7].Value; if (key == "/$name") { candidateProperty.Name = value; isNameReceived = true; } if (key == "/$datatype") { if (Helpers.TryParseHomieDataType(value, out var parsedType)) { candidateProperty.DataType = parsedType; isDataTypeReceived = true; } ; } if (key == "/$format") { candidateProperty.Format = value; } if (key == "/$settable") { if (Helpers.TryParseBool(value, out isSettable)) { isSettableReceived = true; } } if (key == "/$retained") { if (Helpers.TryParseBool(value, out isRetained)) { isRetainedReceived = true; } ; } if (key == "/$unit") { candidateProperty.Unit = value; } parsedTopicList.Add(inputString); } var setMatch = setRegex.Match(inputString); if (setMatch.Success) { // Discarding this one. This is a historically cached command, and these should not execute during initialization. Besides, it shouldn't be retained, // so the fact that we're here means something is wrong with the host side. warningList.Add($"{candidateDevice.Id}/{candidateProperty} has /set topic assigned, which means /set message is published as retained. This is against Homie convention."); } var valueMatch = valueRegex.Match(inputString); if (valueMatch.Success) { var value = attributeMatch.Groups[7].Value; candidateProperty.InitialValue = value; } } foreach (var parsedTopic in parsedTopicList) { unparsedTopicList.Remove(parsedTopic); } // Basic data extraction is done. Now we'll validate if values of the fields are compatible with each other and also YAHI itself. var isOk = isNameReceived & isDataTypeReceived & isSettableReceived & isRetainedReceived; if (isOk) { // Setting property type. if ((isSettable == false) && (isRetained == true)) { candidateProperty.PropertyType = PropertyType.State; } if ((isSettable == true) && (isRetained == false)) { candidateProperty.PropertyType = PropertyType.Command; } if ((isSettable == true) && (isRetained == true)) { candidateProperty.PropertyType = PropertyType.Parameter; } if ((isSettable == false) && (isRetained == false)) { errorList.Add($"{candidateDevice.Id}/{candidateProperty.NodeId}/{candidateProperty.PropertyId} has all mandatory fields set, but this retainability and settability configuration is not supported by YAHI. Skipping this property entirely."); isOk = false; } } else { // Some of the mandatory topic were not received. Can't let this property through. errorList.Add($"{candidateDevice.Id}/{candidateProperty.NodeId}/{candidateProperty.PropertyId} is defined, but mandatory attributes are missing. Skipping this property entirely."); isOk = false; } // Validating by property data type, because rules are very different for each of those. if (isOk) { var tempErrorList = new ArrayList(); var tempWarningList = new ArrayList(); // ValidateAndFix method does not know anythin about device it is parsing, thus doing some wrapping around error and warning lists (because I want device info in those). isOk = candidateProperty.ValidateAndFix(ref tempErrorList, ref tempWarningList); foreach (var error in tempErrorList) { errorList.Add($"{candidateDevice.Id}/{error}"); } foreach (var warning in tempWarningList) { warningList.Add($"{candidateDevice.Id}/{warning}"); } } if (isOk) { goodProperties.Add(candidateProperty); } } // Converting local temporary property lists to final arrays. candidateDevice.Nodes[n].Properties = new ClientPropertyMetadata[goodProperties.Count]; for (var i = 0; i < goodProperties.Count; i++) { candidateDevice.Nodes[n].Properties[i] = (ClientPropertyMetadata)goodProperties[i]; } } // Converting local temporary node lists to final arrays and returning. foreach (var node in candidateDevice.Nodes) { if (node.Properties.Length > 0) { isParseSuccessful = true; } } return(isParseSuccessful); }