NodePipeline/NodePipeline.Engine.CodeGeneration/NodeValidatorGenerator.cs
2026-01-02 20:55:25 +03:00

589 lines
28 KiB
C#

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<NodePortKey, NodePortInfo>? _allPorts;");
sb.AppendLine(" public Dictionary<Type, INodeValidator> NodeValidators { get; set; } = [];");
sb.AppendLine(" public Dictionary<Type, INodeFieldValidator> 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<NodeConfig> allNodes, Dictionary<string, object?> 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<string, InputSource>(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<string, InputSource> nodePortConnections, IReadOnlyDictionary<NodePortKey, NodePortInfo> ports, CultureInfo cultureInfo)");
sb.AppendLine(" {");
if (parameterValidatorsCount > 0)
sb.AppendLine($"{tab}var validationResult = ValidationResult.Valid;");
sb.Append($"{tab}var fieldResults = new Dictionary<string, List<NodeFieldValidationResult>>");
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<string, object>");
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<NodeFieldDescriptor> 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<NodePortKey, NodePortInfo> GetAllPorts(string pipelineId, INodeFactory nodeFactory, List<NodeConfig> 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<T> GetNodeFieldValidator<T>(Type validatorType)");
sb.AppendLine(" {");
sb.AppendLine(" if (!NodeFieldValidators.TryGetValue(validatorType, out var validator)");
sb.AppendLine(" || validator is not INodeFieldValidator<T> 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<string, InputSource> nodePortConnections, IReadOnlyDictionary<NodePortKey, NodePortInfo> ports,");
sb.AppendLine(
" out Dictionary<string, List<NodeFieldValidationResult>> inputPortValidationResults, CultureInfo cultureInfo)");
sb.AppendLine(" {");
sb.AppendLine(" var result = ValidationResult.Valid;");
sb.AppendLine(
" inputPortValidationResults = new Dictionary<string, List<NodeFieldValidationResult>>();");
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<NodePortKey, NodePortInfo> 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<T>(string nodeId, NodePipeline.Abstractions.Models.NodeField<T> 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<string, List<NodeFieldValidationResult>> fieldResults, Dictionary<string, List<NodeFieldValidationResult>> 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<string, List<NodeFieldValidationResult>> fieldResults, Dictionary<string, List<NodeFieldValidationResult>> 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.");
}
}