using System; using System.Collections.Generic; using System.Linq; using System.Text; using NodePipeline.Abstractions.Interfaces.Nodes; using NodePipeline.Engine.CodeGeneration.Abstractions; using NodePipeline.Engine.CodeGeneration.Abstractions.Models; namespace NodePipeline.Engine.CodeGeneration; internal static class NodeValidatorGenerator { private const string NullString = "null"; private static readonly TypeCode[] NumericTypeCodes = [ TypeCode.Byte, TypeCode.SByte, TypeCode.Int16, TypeCode.UInt16, TypeCode.Int32, TypeCode.UInt32, TypeCode.Int64, TypeCode.UInt64, TypeCode.Single, TypeCode.Double, TypeCode.Decimal ]; public static string Generate(NodeModelBuilder.NodesModel model) { var sb = new StringBuilder(); sb.AppendLine("// ReSharper disable RedundantUsingDirective"); sb.AppendLine("using System.Globalization;"); sb.AppendLine("using NodePipeline.Abstractions;"); sb.AppendLine("using NodePipeline.Abstractions.Exceptions.Validation;"); sb.AppendLine("using NodePipeline.Abstractions.Interfaces;"); sb.AppendLine("using NodePipeline.Abstractions.Interfaces.Nodes;"); sb.AppendLine("using NodePipeline.Abstractions.Interfaces.Validation;"); sb.AppendLine("using NodePipeline.Abstractions.Models.Validation;"); sb.AppendLine("using NodePipeline.Abstractions.Validators;"); sb.AppendLine("using NodePipeline.Configuration.Abstractions.Models.Execute;"); sb.AppendLine("using NodePipeline.Engine.Abstractions;"); sb.AppendLine("using NodePipeline.Engine.NodeFields;"); sb.AppendLine("using NodePipeline.Engine.Utils;"); sb.AppendLine("using NodePipeline.Engine.Abstractions.Validation;"); sb.AppendLine(); sb.AppendLine("// ReSharper disable CheckNamespace"); sb.AppendLine("// ReSharper disable RedundantNameQualifier"); sb.AppendLine("// ReSharper disable UnusedMember.Local"); sb.AppendLine("// ReSharper disable UnusedParameter.Local"); sb.AppendLine("// ReSharper disable UnusedVariable"); sb.AppendLine("#nullable enable"); sb.AppendLine(); sb.AppendLine("namespace NodePipeline.Engine.Generated;"); sb.AppendLine(); sb.AppendLine("public sealed class GeneratedNodeValidator : IPipelineNodeValidator"); sb.AppendLine("{"); sb.AppendLine(" private Dictionary? _allPorts;"); sb.AppendLine(" public Dictionary NodeValidators { get; set; } = [];"); sb.AppendLine(" public Dictionary NodeFieldValidators { get; set; } = [];"); sb.AppendLine( " public IPipelineLocalizationProvider PipelineLocalizationProvider { get; set; } = new PipelineLocalizationProvider();"); sb.AppendLine(); sb.AppendLine(BuildValidateNodeMethod(model)); sb.AppendLine(BuildValidateNodeSpecificMethods(model)); sb.AppendLine(BuildGetAllPortsMethod()); sb.AppendLine(BuildGetNodeFieldValidatorMethod()); sb.AppendLine(BuildValidateNodePortsMethod()); sb.AppendLine(BuildValidateInputPortMethod()); sb.AppendLine(BuildGetNodeInputKeyMethod()); sb.AppendLine(BuildGetNodeValidatorMethod()); sb.AppendLine(BuildCheckRequiredNodeParameterMethod()); sb.AppendLine(BuildSuccessNodeValidationResultMethod()); sb.AppendLine(BuildGetErrorValidationResultMethod()); sb.AppendLine(BuildSuccessNodeFieldValidationResultMethod()); sb.AppendLine(BuildUpdateValidationResultMethod()); sb.AppendLine("}"); sb.AppendLine("#nullable restore"); return sb.ToString(); } private static string GetSpecificValidateNodeMethodName(NodeDescriptor node) { return $"Validate__{node.TypeNameShortSanitized}__Node"; } private static string BuildValidateNodeMethod(NodeModelBuilder.NodesModel model) { var sb = new StringBuilder(); sb.AppendLine( " public NodeValidationResult ValidateNode(string pipelineId, INodeFactory nodeFactory, NodeConfig nodeConfig, List allNodes, Dictionary parameters, CultureInfo cultureInfo)"); sb.AppendLine(" {"); sb.AppendLine(" var node = nodeFactory.CreateNode(pipelineId, nodeConfig);"); sb.AppendLine(" nodeFactory.SetNodeParametersValues(pipelineId, node, nodeConfig.Id, parameters);"); sb.AppendLine(" var nodeConnections = allNodes.SelectMany(q => q.Inputs"); sb.AppendLine( " .Select(x => new KeyValuePair(GetNodeInputKey(q.Id, x.Key), x.Value))"); sb.AppendLine(" ).ToDictionary(q => q.Key, q => q.Value);"); sb.AppendLine(" var ports = GetAllPorts(pipelineId, nodeFactory, allNodes);"); sb.AppendLine(" switch (node)"); sb.AppendLine(" {"); var i = 1; foreach (var n in model.Nodes) { sb.AppendLine($" case {n.TypeNameFull} t{i}:"); sb.AppendLine( $" return {GetSpecificValidateNodeMethodName(n)}(pipelineId, nodeFactory, t{i}, nodeConfig.Id, nodeConnections, ports, cultureInfo);"); i++; } sb.AppendLine(" default:"); sb.AppendLine(" throw new UnknownNodeTypeException(nodeConfig.Type, nodeConfig.Id);"); sb.AppendLine(" }"); sb.AppendLine(" }"); return sb.ToString(); } private static string BuildValidateNodeSpecificMethods(NodeModelBuilder.NodesModel model) { var sb = new StringBuilder(); const string tab = " "; foreach (var n in model.Nodes) { var parameterValidatorsCode = CreateParameterValidatorsMethods(n, tab, out var parameterValidatorsCount); var nodeParameters = n.Fields .Where(f => f.Direction == FieldDirection.Parameter) .ToList(); sb.AppendLine( $" private NodeValidationResult {GetSpecificValidateNodeMethodName(n)}(string pipelineId, INodeFactory nodeFactory, {n.TypeNameFull} node, string nodeId, IReadOnlyDictionary nodePortConnections, IReadOnlyDictionary ports, CultureInfo cultureInfo)"); sb.AppendLine(" {"); if (parameterValidatorsCount > 0) sb.AppendLine($"{tab}var validationResult = ValidationResult.Valid;"); sb.Append($"{tab}var fieldResults = new Dictionary>"); if (nodeParameters.Count > 0) { sb.AppendLine(); sb.AppendLine(CreateFieldResultsContentCode(nodeParameters, " ")); } else { sb.AppendLine("();"); sb.AppendLine(); } if (parameterValidatorsCount > 0) sb.AppendLine(parameterValidatorsCode); var nodeName = NodeGeneratorHelper.GetNodeName(n); sb.AppendLine( $"{tab}var portsValidationResult = ValidateNodePorts(pipelineId, nodeFactory, {nodeName}, nodeId, nodePortConnections, ports, out var portResults, cultureInfo);"); sb.Append($"{tab}var isValid = "); if (parameterValidatorsCount > 0) sb.Append("validationResult != ValidationResult.HasErrors && "); sb.AppendLine("portsValidationResult != ValidationResult.HasErrors;"); if (parameterValidatorsCount > 0) sb.AppendLine($"{tab}UpdateValidationResult(portsValidationResult, ref validationResult);"); sb.AppendLine($"{tab}return !isValid"); sb.Append(" ? GetErrorValidationResult("); sb.Append(parameterValidatorsCount > 0 ? "validationResult" : "ValidationResult.HasErrors"); sb.AppendLine(", nodeId, fieldResults, portResults, cultureInfo)"); var nodeValidator = n.CustomValidators.FirstOrDefault(); if (nodeValidator == null) { sb.Append(" : SuccessNodeValidationResult("); if (parameterValidatorsCount > 0) sb.Append("validationResult == ValidationResult.HasWarnings || "); sb.AppendLine("portsValidationResult == ValidationResult.HasWarnings, fieldResults, portResults);"); } else { sb.AppendLine($" : GetNodeValidator(typeof({nodeValidator.ValidatorType})).Validate("); sb.AppendLine(" new Dictionary"); sb.AppendLine(" {"); foreach (var p in nodeParameters) sb.AppendLine($" {{ \"{p.FieldName}\", node.{p.PropertyName}.Value }},"); sb.AppendLine(" }, fieldResults, portResults);"); } sb.AppendLine(" }"); sb.AppendLine(); } return sb.ToString(); } private static string CreateParameterValidatorsMethods(NodeDescriptor n, string tab, out int parameterValidatorsCount) { var sb = new StringBuilder(); var currentValidatorNumber = 1; var nodeParameters = n.Fields.Where(f => f.Direction == FieldDirection.Parameter) .ToList(); foreach (var p in nodeParameters) { if (HasParameterValueLengthValidator(p, out var minLength, out var maxLength)) { EnsureParameterValueIsString(p.FieldName, n.Type, p.ValueType); var validatorCallText = $"new StringLengthValidator(PipelineLocalizationProvider, {minLength}, {maxLength})"; sb.AppendLine(GetValidatorString(p, validatorCallText, ref currentValidatorNumber, tab)); } if (HasParameterNumberBoundValidator(p, out var minValue, out var maxValue)) { EnsureParameterValueIsNumber(p.FieldName, n.Type, p.ValueType); var validatorCallText = $"new NumberRangeValidator<{p.ValueType}>(PipelineLocalizationProvider, {minValue}, {maxValue})"; sb.AppendLine(GetValidatorString(p, validatorCallText, ref currentValidatorNumber, tab)); } if (p.Metadata?.IsRequired == true) sb.AppendLine(AddRequiredValidatorCode(p, ref currentValidatorNumber, tab)); if (p.Metadata == null) continue; foreach (var validator in p.Metadata.CustomValidators) { var validatorCallText = validator.CanBeInitialized ? $"{validator.ValidatorType}()" : $"GetNodeFieldValidator<{p.ValueType}>(typeof({validator.ValidatorType}))"; sb.AppendLine(GetValidatorString(p, validatorCallText, ref currentValidatorNumber, tab)); } } parameterValidatorsCount = currentValidatorNumber - 1; return sb.ToString(); } private static string CreateFieldResultsContentCode(List nodeParameters, string tab) { var sb = new StringBuilder(); sb.AppendLine($"{tab}{{"); foreach (var p in nodeParameters) sb.AppendLine($" {{ \"{p.FieldName}\", [] }},"); sb.AppendLine($"{tab}}};"); return sb.ToString(); } private static string GetValidatorString(NodeFieldDescriptor parameter, string validatorCallText, ref int currentValidatorNumber, string tab) { var sb = new StringBuilder(); currentValidatorNumber++; sb.AppendLine( $"{tab}var r{currentValidatorNumber} = {validatorCallText}.Validate(node.{parameter.PropertyName});"); sb.AppendLine($"{tab}fieldResults[\"{parameter.FieldName}\"].Add(r{currentValidatorNumber});"); sb.AppendLine($"{tab}UpdateValidationResult(r{currentValidatorNumber}.Result, ref validationResult);"); return sb.ToString(); } private static bool HasParameterValueLengthValidator(NodeFieldDescriptor parameter, out string minLength, out string maxLength) { if (parameter.Metadata is null || (parameter.Metadata?.StringMaxLength is null && parameter.Metadata?.StringMinLength is null)) { minLength = NumberToString(null); maxLength = NumberToString(null); return false; } minLength = NumberToString(parameter.Metadata.StringMinLength); maxLength = NumberToString(parameter.Metadata.StringMaxLength); return true; } private static bool HasParameterNumberBoundValidator(NodeFieldDescriptor parameter, out string minValue, out string maxValue) { if (parameter.Metadata is null || (parameter.Metadata?.NumberMinBound is null && parameter.Metadata?.NumberMaxBound is null)) { minValue = NumberToString(null); maxValue = NumberToString(null); return false; } minValue = NumberToString(parameter.Metadata.NumberMinBound); maxValue = NumberToString(parameter.Metadata.NumberMaxBound); return true; } private static string NumberToString(int? value) { return value?.ToString() ?? NullString; } private static string NumberToString(decimal? value) { return value?.ToString() ?? NullString; } private static string AddRequiredValidatorCode(NodeFieldDescriptor parameter, ref int currentValidatorNumber, string tab) { var sb = new StringBuilder(); currentValidatorNumber++; sb.AppendLine( $"{tab}var r{currentValidatorNumber} = CheckRequiredNodeParameter<{parameter.ValueType}>(nodeId, node.{parameter.PropertyName}, \"{parameter.FieldName}\", cultureInfo);"); sb.AppendLine($"{tab}fieldResults[\"{parameter.FieldName}\"].Add(r{currentValidatorNumber});"); sb.AppendLine($"{tab}UpdateValidationResult(r{currentValidatorNumber}.Result, ref validationResult);"); return sb.ToString(); } private static string BuildGetAllPortsMethod() { var sb = new StringBuilder(); sb.AppendLine( " private IReadOnlyDictionary GetAllPorts(string pipelineId, INodeFactory nodeFactory, List allNodes)"); sb.AppendLine(" {"); sb.AppendLine(" var nodeTypes = allNodes.Select(q => q.Type)"); sb.AppendLine(" .Distinct()"); sb.AppendLine(" .ToHashSet();"); sb.AppendLine(); sb.AppendLine(" var nodeList = allNodes"); sb.AppendLine(" .GroupBy(q => q.Type,"); sb.AppendLine(" (type, g) => new { Type = type, NodeIds = g.Select(q => q.Id) })"); sb.AppendLine(" .ToDictionary(q => q.Type, q => q.NodeIds);"); sb.AppendLine(" _allPorts ??= nodeFactory.GetAllPorts(pipelineId, nodeTypes, nodeList);"); sb.AppendLine(" return _allPorts.AsReadOnly();"); sb.AppendLine(" }"); return sb.ToString(); } private static string BuildGetNodeFieldValidatorMethod() { var sb = new StringBuilder(); sb.AppendLine(" private INodeFieldValidator GetNodeFieldValidator(Type validatorType)"); sb.AppendLine(" {"); sb.AppendLine(" if (!NodeFieldValidators.TryGetValue(validatorType, out var validator)"); sb.AppendLine(" || validator is not INodeFieldValidator genericValidator)"); sb.AppendLine( " throw new UserDefinedNodeFieldValidatorNotFoundException(validatorType.FullName ?? validatorType.Name);"); sb.AppendLine(" return genericValidator;"); sb.AppendLine(" }"); return sb.ToString(); } private static string BuildValidateNodePortsMethod() { var sb = new StringBuilder(); sb.AppendLine( " private ValidationResult ValidateNodePorts(string pipelineId, INodeFactory nodeFactory, string nodeType, string nodeId,"); sb.AppendLine( " IReadOnlyDictionary nodePortConnections, IReadOnlyDictionary ports,"); sb.AppendLine( " out Dictionary> inputPortValidationResults, CultureInfo cultureInfo)"); sb.AppendLine(" {"); sb.AppendLine(" var result = ValidationResult.Valid;"); sb.AppendLine( " inputPortValidationResults = new Dictionary>();"); sb.AppendLine(); sb.AppendLine(" var inputPorts = nodeFactory.GetNodeInputPorts(pipelineId, nodeType, nodeId);"); sb.AppendLine(" foreach (var inputPort in inputPorts)"); sb.AppendLine(" {"); sb.AppendLine(" var code = NodeFieldCodeGenerator.GenerateFieldCode(nodeId, inputPort.Name,"); sb.AppendLine(" inputPort.Input ? FieldDirection.Input : FieldDirection.Output);"); sb.AppendLine(" inputPortValidationResults[code] = [];"); sb.AppendLine( " var hasConnection = nodePortConnections.TryGetValue(GetNodeInputKey(nodeId, inputPort.Name), out var connection);"); sb.AppendLine(" if (inputPort.Required && !hasConnection)"); sb.AppendLine(" {"); sb.AppendLine( " inputPortValidationResults[code].Add(new NodeFieldValidationResult(ValidationResult.HasErrors,"); sb.AppendLine( " PipelineLocalizationProvider.GetLocalizedString(Constants.ValidationResults.ErrorMessages.RequiredInputPortHasNoConnectionError, cultureInfo, inputPort.Name, nodeId)));"); //TODO: add error code sb.AppendLine(" UpdateValidationResult(ValidationResult.HasErrors, ref result);"); sb.AppendLine(" continue;"); sb.AppendLine(" }"); sb.AppendLine(" if (!hasConnection || connection == null) continue;"); sb.AppendLine(); sb.AppendLine( " var portValidationResult = ValidateInputPort(nodeId, inputPort, connection, ports, cultureInfo);"); sb.AppendLine(" inputPortValidationResults[code].Add(portValidationResult);"); sb.AppendLine(" UpdateValidationResult(portValidationResult.Result, ref result);"); sb.AppendLine(" }"); sb.AppendLine(); sb.AppendLine(" return result;"); sb.AppendLine(" }"); return sb.ToString(); } private static string BuildGetNodeInputKeyMethod() { var sb = new StringBuilder(); sb.AppendLine(" private static string GetNodeInputKey(string nodeId, string inputPortName)"); sb.AppendLine(" {"); sb.AppendLine(" return string.Concat(nodeId, \"_\", inputPortName);"); sb.AppendLine(" }"); return sb.ToString(); } private static string BuildValidateInputPortMethod() { var sb = new StringBuilder(); sb.AppendLine( " private NodeFieldValidationResult ValidateInputPort(string nodeId, NodePortInfo inputPort, InputSource connection,"); sb.AppendLine(" IReadOnlyDictionary ports, CultureInfo cultureInfo)"); sb.AppendLine(" {"); sb.AppendLine( " if (!ports.TryGetValue(new NodePortKey(connection.NodeId, connection.OutputName, false), out var outputPort))"); sb.AppendLine(" {"); sb.AppendLine( " return new NodeFieldValidationResult(ValidationResult.HasErrors, PipelineLocalizationProvider"); sb.AppendLine( " .GetLocalizedString(Constants.ValidationResults.ErrorMessages.OutputPortNotFoundError, cultureInfo, connection.OutputName,"); sb.AppendLine(" connection.NodeId, inputPort.Name, nodeId));"); sb.AppendLine(" }"); sb.AppendLine(); sb.AppendLine(" if (inputPort.Type != outputPort.Type)"); sb.AppendLine(" {"); sb.AppendLine( " return new NodeFieldValidationResult(ValidationResult.HasErrors, PipelineLocalizationProvider"); sb.AppendLine( " .GetLocalizedString(Constants.ValidationResults.ErrorMessages.PortsTypeMismatchError, cultureInfo,"); sb.AppendLine( " inputPort.Name, nodeId, inputPort.Type, connection.OutputName, connection.NodeId, outputPort.Type));"); sb.AppendLine(" }"); sb.AppendLine(); sb.AppendLine(" if (inputPort.IsNullable == outputPort.IsNullable)"); sb.AppendLine(" {"); sb.AppendLine(" return SuccessNodeFieldValidationResult();"); sb.AppendLine(" }"); sb.AppendLine(); sb.AppendLine(" if (!inputPort.IsNullable && outputPort.IsNullable)"); sb.AppendLine(" {"); sb.AppendLine(" return new NodeFieldValidationResult("); sb.AppendLine(" ValidationResult.HasWarnings, PipelineLocalizationProvider"); sb.AppendLine( " .GetLocalizedString(Constants.ValidationResults.ErrorMessages.NullableAndNonNullablePortsConnectionWarning, cultureInfo,"); sb.AppendLine( " inputPort.Name, nodeId, inputPort.Type, connection.OutputName, connection.NodeId, outputPort.Type));"); sb.AppendLine(" }"); sb.AppendLine(); sb.AppendLine(" return SuccessNodeFieldValidationResult();"); sb.AppendLine(" }"); return sb.ToString(); } private static string BuildGetNodeValidatorMethod() { var sb = new StringBuilder(); sb.AppendLine(" private INodeValidator GetNodeValidator(Type validatorType)"); sb.AppendLine(" {"); sb.AppendLine(" if (!NodeValidators.TryGetValue(validatorType, out var validator))"); sb.AppendLine( " throw new UserDefinedNodeValidatorNotFoundException(validatorType.FullName ?? validatorType.Name);"); sb.AppendLine(" return validator;"); sb.AppendLine(" }"); return sb.ToString(); } private static string BuildCheckRequiredNodeParameterMethod() { var sb = new StringBuilder(); sb.AppendLine( " private NodeFieldValidationResult CheckRequiredNodeParameter(string nodeId, NodePipeline.Abstractions.Models.NodeField nodeField, string nodeParameterName, CultureInfo cultureInfo)"); sb.AppendLine(" {"); sb.AppendLine(" return nodeField.Value is not null"); sb.AppendLine(" ? new NodeFieldValidationResult(ValidationResult.Valid, null)"); sb.AppendLine( " : new NodeFieldValidationResult(ValidationResult.HasErrors, PipelineLocalizationProvider"); sb.AppendLine( " .GetLocalizedString(Constants.ValidationResults.ErrorMessages.RequiredNodeParameterValidationError, cultureInfo, nodeParameterName, nodeId));"); sb.AppendLine(" }"); return sb.ToString(); } private static string BuildSuccessNodeValidationResultMethod() { var sb = new StringBuilder(); sb.AppendLine( " private static NodeValidationResult SuccessNodeValidationResult(bool hasWarnings, Dictionary> fieldResults, Dictionary> portResults) =>"); sb.AppendLine(" new(hasWarnings ? ValidationResult.HasWarnings : ValidationResult.Valid, string.Empty,"); sb.AppendLine(" fieldResults.AsReadOnly(), portResults.AsReadOnly());"); return sb.ToString(); } private static string BuildGetErrorValidationResultMethod() { var sb = new StringBuilder(); sb.AppendLine( " private NodeValidationResult GetErrorValidationResult(ValidationResult validationResult, string nodeId, Dictionary> fieldResults, Dictionary> portResults, CultureInfo cultureInfo) =>"); sb.AppendLine( " new(validationResult, PipelineLocalizationProvider.GetLocalizedString(Constants.ValidationResults.ErrorMessages.NodeValidationError, cultureInfo, nodeId), fieldResults.AsReadOnly(), portResults.AsReadOnly());"); return sb.ToString(); } private static string BuildSuccessNodeFieldValidationResultMethod() { var sb = new StringBuilder(); sb.AppendLine(" private static NodeFieldValidationResult SuccessNodeFieldValidationResult() =>"); sb.AppendLine(" new NodeFieldValidationResult(ValidationResult.Valid, null);"); return sb.ToString(); } private static string BuildUpdateValidationResultMethod() { var sb = new StringBuilder(); sb.AppendLine( " private static void UpdateValidationResult(ValidationResult validationResult, ref ValidationResult parametersValidationResult)"); sb.AppendLine(" {"); sb.AppendLine( " if (validationResult == parametersValidationResult || validationResult == ValidationResult.Valid) return;"); sb.AppendLine(); sb.AppendLine(" parametersValidationResult = validationResult switch"); sb.AppendLine(" {"); sb.AppendLine( " ValidationResult.HasWarnings when parametersValidationResult == ValidationResult.Valid => ValidationResult.HasWarnings,"); sb.AppendLine( " ValidationResult.HasErrors when parametersValidationResult != ValidationResult.HasErrors => ValidationResult.HasErrors,"); sb.AppendLine(" _ => parametersValidationResult"); sb.AppendLine(" };"); sb.AppendLine(" }"); return sb.ToString(); } private static void EnsureParameterValueIsString(string fieldName, string nodeName, string parameterValueType) { var shortTypeName = parameterValueType.Split('.').Last(); var isString = shortTypeName.Equals("string", StringComparison.OrdinalIgnoreCase) || shortTypeName.Equals("String", StringComparison.OrdinalIgnoreCase); if (isString) return; ThrowInvalidTypeException(fieldName, nodeName, "string", parameterValueType); } private static void EnsureParameterValueIsNumber(string fieldName, string nodeName, string parameterValueType) { var normalized = NormalizeTypeName(parameterValueType); var type = Type.GetType(normalized, false); var typeCode = Type.GetTypeCode(type); if (NumericTypeCodes.Contains(typeCode)) return; var expectedType = $"number ({string.Join(", ", NumericTypeCodes)})"; ThrowInvalidTypeException(fieldName, nodeName, expectedType, parameterValueType); } private static string NormalizeTypeName(string typeName) { if (string.IsNullOrWhiteSpace(typeName)) return typeName; typeName = typeName.Replace("global::", "").TrimEnd('?'); return typeName switch { "byte" => "System.Byte", "sbyte" => "System.SByte", "short" => "System.Int16", "ushort" => "System.UInt16", "int" => "System.Int32", "uint" => "System.UInt32", "long" => "System.Int64", "ulong" => "System.UInt64", "float" => "System.Single", "double" => "System.Double", "decimal" => "System.Decimal", _ => typeName }; } private static void ThrowInvalidTypeException(string fieldName, string nodeName, string expectedType, string realType) { throw new Exception( $"Parameter [{fieldName}] ([{nodeName}] node) value has [{realType}] type but [{expectedType}] is expected."); } }