feat(lmxproxy): phase 1 — v2 protocol types and domain model
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Serilog;
|
||||
|
||||
namespace ZB.MOM.WW.LmxProxy.Host.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Validates LmxProxy configuration settings on startup.
|
||||
/// </summary>
|
||||
public static class ConfigurationValidator
|
||||
{
|
||||
private static readonly ILogger Logger = Log.ForContext(typeof(ConfigurationValidator));
|
||||
|
||||
/// <summary>
|
||||
/// Validates the provided configuration and returns a list of validation errors.
|
||||
/// </summary>
|
||||
/// <param name="configuration">The configuration to validate.</param>
|
||||
/// <returns>A list of validation error messages. Empty if configuration is valid.</returns>
|
||||
public static List<string> Validate(LmxProxyConfiguration configuration)
|
||||
{
|
||||
var errors = new List<string>();
|
||||
|
||||
if (configuration == null)
|
||||
{
|
||||
errors.Add("Configuration is null");
|
||||
return errors;
|
||||
}
|
||||
|
||||
// Validate gRPC port
|
||||
if (configuration.GrpcPort <= 0 || configuration.GrpcPort > 65535)
|
||||
{
|
||||
errors.Add($"Invalid gRPC port: {configuration.GrpcPort}. Must be between 1 and 65535.");
|
||||
}
|
||||
|
||||
// Validate API key configuration file
|
||||
if (string.IsNullOrWhiteSpace(configuration.ApiKeyConfigFile))
|
||||
{
|
||||
errors.Add("API key configuration file path is not specified.");
|
||||
}
|
||||
|
||||
// Validate Connection settings
|
||||
if (configuration.Connection != null)
|
||||
{
|
||||
ValidateConnectionConfiguration(configuration.Connection, errors);
|
||||
}
|
||||
else
|
||||
{
|
||||
errors.Add("Connection configuration is missing.");
|
||||
}
|
||||
|
||||
// Validate Subscription settings
|
||||
if (configuration.Subscription != null)
|
||||
{
|
||||
ValidateSubscriptionConfiguration(configuration.Subscription, errors);
|
||||
}
|
||||
|
||||
// Validate Service Recovery settings
|
||||
if (configuration.ServiceRecovery != null)
|
||||
{
|
||||
ValidateServiceRecoveryConfiguration(configuration.ServiceRecovery, errors);
|
||||
}
|
||||
|
||||
// Validate TLS settings
|
||||
if (configuration.Tls != null)
|
||||
{
|
||||
if (!configuration.Tls.Validate())
|
||||
{
|
||||
errors.Add("TLS configuration validation failed. Check the logs for details.");
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
private static void ValidateConnectionConfiguration(ConnectionConfiguration config, List<string> errors)
|
||||
{
|
||||
if (config.MonitorIntervalSeconds <= 0)
|
||||
{
|
||||
errors.Add(
|
||||
$"Invalid monitor interval: {config.MonitorIntervalSeconds} seconds. Must be greater than 0.");
|
||||
}
|
||||
|
||||
if (config.ConnectionTimeoutSeconds <= 0)
|
||||
{
|
||||
errors.Add(
|
||||
$"Invalid connection timeout: {config.ConnectionTimeoutSeconds} seconds. Must be greater than 0.");
|
||||
}
|
||||
|
||||
if (config.ReadTimeoutSeconds <= 0)
|
||||
{
|
||||
errors.Add($"Invalid read timeout: {config.ReadTimeoutSeconds} seconds. Must be greater than 0.");
|
||||
}
|
||||
|
||||
if (config.WriteTimeoutSeconds <= 0)
|
||||
{
|
||||
errors.Add($"Invalid write timeout: {config.WriteTimeoutSeconds} seconds. Must be greater than 0.");
|
||||
}
|
||||
|
||||
if (config.MaxConcurrentOperations.HasValue && config.MaxConcurrentOperations.Value <= 0)
|
||||
{
|
||||
errors.Add(
|
||||
$"Invalid max concurrent operations: {config.MaxConcurrentOperations}. Must be greater than 0.");
|
||||
}
|
||||
|
||||
// Validate node and galaxy names if provided
|
||||
if (!string.IsNullOrWhiteSpace(config.NodeName) && config.NodeName?.Length > 255)
|
||||
{
|
||||
errors.Add($"Node name is too long: {config.NodeName.Length} characters. Maximum is 255.");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(config.GalaxyName) && config.GalaxyName?.Length > 255)
|
||||
{
|
||||
errors.Add($"Galaxy name is too long: {config.GalaxyName.Length} characters. Maximum is 255.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateSubscriptionConfiguration(SubscriptionConfiguration config, List<string> errors)
|
||||
{
|
||||
if (config.ChannelCapacity <= 0)
|
||||
{
|
||||
errors.Add($"Invalid channel capacity: {config.ChannelCapacity}. Must be greater than 0.");
|
||||
}
|
||||
|
||||
if (config.ChannelCapacity > 100000)
|
||||
{
|
||||
errors.Add($"Channel capacity too large: {config.ChannelCapacity}. Maximum recommended is 100000.");
|
||||
}
|
||||
|
||||
string[] validChannelModes = { "DropOldest", "DropNewest", "Wait" };
|
||||
if (!validChannelModes.Contains(config.ChannelFullMode))
|
||||
{
|
||||
errors.Add(
|
||||
$"Invalid channel full mode: {config.ChannelFullMode}. Valid values are: {string.Join(", ", validChannelModes)}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateServiceRecoveryConfiguration(ServiceRecoveryConfiguration config,
|
||||
List<string> errors)
|
||||
{
|
||||
if (config.FirstFailureDelayMinutes < 0)
|
||||
{
|
||||
errors.Add(
|
||||
$"Invalid first failure delay: {config.FirstFailureDelayMinutes} minutes. Must be 0 or greater.");
|
||||
}
|
||||
|
||||
if (config.SecondFailureDelayMinutes < 0)
|
||||
{
|
||||
errors.Add(
|
||||
$"Invalid second failure delay: {config.SecondFailureDelayMinutes} minutes. Must be 0 or greater.");
|
||||
}
|
||||
|
||||
if (config.SubsequentFailureDelayMinutes < 0)
|
||||
{
|
||||
errors.Add(
|
||||
$"Invalid subsequent failure delay: {config.SubsequentFailureDelayMinutes} minutes. Must be 0 or greater.");
|
||||
}
|
||||
|
||||
if (config.ResetPeriodDays <= 0)
|
||||
{
|
||||
errors.Add($"Invalid reset period: {config.ResetPeriodDays} days. Must be greater than 0.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs validation results and returns whether the configuration is valid.
|
||||
/// </summary>
|
||||
/// <param name="configuration">The configuration to validate.</param>
|
||||
/// <returns>True if configuration is valid; otherwise, false.</returns>
|
||||
public static bool ValidateAndLog(LmxProxyConfiguration configuration)
|
||||
{
|
||||
List<string> errors = Validate(configuration);
|
||||
|
||||
if (errors.Any())
|
||||
{
|
||||
Logger.Error("Configuration validation failed with {ErrorCount} errors:", errors.Count);
|
||||
foreach (string? error in errors)
|
||||
{
|
||||
Logger.Error(" - {ValidationError}", error);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Logger.Information("Configuration validation successful");
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an exception if the configuration is invalid.
|
||||
/// </summary>
|
||||
/// <param name="configuration">The configuration to validate.</param>
|
||||
/// <exception cref="InvalidOperationException">Thrown when configuration is invalid.</exception>
|
||||
public static void ValidateOrThrow(LmxProxyConfiguration configuration)
|
||||
{
|
||||
List<string> errors = Validate(configuration);
|
||||
|
||||
if (errors.Any())
|
||||
{
|
||||
string message = $"Configuration validation failed with {errors.Count} error(s):\n" +
|
||||
string.Join("\n", errors.Select(e => $" - {e}"));
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user