/// <summary> /// Checks for all elements with a based-on field that the referenced ids exist and have the /// same type /// </summary> /// <exception cref="LoadException"> /// if base id does not exist or base element does not have the same type /// </exception> private void ValidateBaseIdsExist() { var errors = ( from element in this._tles where element.BasedOnId != null && this._tles.All(elem => elem.Id != element.BasedOnId) select element ).ToList(); if (errors.Any()) { throw LoadException.BaseElementNotFound(errors); } var typeErrors = ( from element in this._tles where element.BasedOnId != null let baseElem = this._tles.First(e => e.Id == element.BasedOnId) where !baseElem.GetType().IsInstanceOfType(element) select( Base: baseElem, Target: element ) ).ToList(); if (typeErrors.Any()) { throw LoadException.BaseElementHasDifferentType(typeErrors); } }
/// <summary> /// process 'based-on' fields /// </summary> /// <exception cref="LoadException"></exception> private void RealizeInheritance() { var realisationQueue = new Queue <BasicElement>(this._tles); int stepsBeforeAbort = realisationQueue.Count; while (realisationQueue.Count > 0 && stepsBeforeAbort > 0) { stepsBeforeAbort--; bool processedElement = false; var targetElem = realisationQueue.Dequeue(); if (targetElem.BasedOnId == null) { processedElement = true; } else { var baseElem = this._tles.First(e => e.Id == targetElem.BasedOnId); // if base element is not done yet, postpone target element if (realisationQueue.Contains(baseElem)) { realisationQueue.Enqueue(targetElem); } else { // base element done, do the actual work // properties contains all properties of baseElem type that have the YamlMember attribute var properties = from prop in baseElem.GetType().GetProperties() where prop.IsDefined(typeof(YamlMemberAttribute), true) select prop; // for each of those properties foreach (var prop in properties) { // set the target value to the base value if target value is not set var targetValue = prop.GetValue(targetElem); if (targetValue == null) { var baseValue = prop.GetValue(baseElem); prop.SetValue(targetElem, baseValue); } } processedElement = true; } } if (processedElement) { stepsBeforeAbort = realisationQueue.Count; } } if (realisationQueue.Count > 0) { throw LoadException.InheritanceLoopAborted(realisationQueue); } }
/// <summary> /// checks for duplicate ids and whether all ids are well-formed /// </summary> /// <exception cref="LoadException">if one or more elements does not fufill these rules</exception> private void ValidateUniqueWellformedIds() { var duplicates = ( from tle in this._tles group tle by tle.Id into grouped where grouped.Count() > 1 select( grouped.Key, grouped.AsEnumerable() ) ).ToList(); if (duplicates.Any()) { string msg = "The project contains duplicate element ids, which is not allowed.\n"; foreach ((string id, var elements) in duplicates) { msg += $"- id '{id}', defined in:\n"; msg = elements.Aggregate(msg, (current, elem) => current + $" - '{elem.OriginalFilePath}'\n"); } throw new LoadException(msg); } // matches 'id', 'some-id', 'id-9-test', but not ' id ', '%KHGSI' var idRegex = new Regex("[a-z][a-z]+(-([a-z]|[0-9])+)*"); // good regex tool: regexr.com var mismatches = this._tles.Where(tle => !idRegex.IsMatch(tle.Id)).ToList(); if (mismatches.Any()) { throw LoadException.MalformedId(mismatches); } }
/// <summary> /// Checks if all properties with the [YamlProperties] attribute and Required set to true /// have a value for all of the loaded elements /// </summary> /// <exception cref="LoadException">if a required property is null</exception> private void ValidateRequiredFields() { var errors = ( from element in this._tles from property in element.GetType().GetProperties() // ignore properties that have [YamlIgnore] where !property.IsDefined(typeof(YamlIgnoreAttribute)) && property.GetValue(element) == null && property.IsDefined(typeof(YamlPropertiesAttribute), true) let yamlPropsAttr = property.GetCustomAttribute <YamlPropertiesAttribute>() where yamlPropsAttr.Required let yamlMember = property.GetCustomAttribute <YamlMemberAttribute>() select( Element: element, PropYamlName: yamlMember.Alias, PropCsName: property.Name ) ).ToList(); if (errors.Any()) { throw LoadException.RequiredPropertyNull(errors); } }