stabilizaiton work - projects rework for maintenanceability and ui livening

This commit is contained in:
master
2026-02-03 23:40:04 +02:00
parent 074ce117ba
commit 557feefdc3
3305 changed files with 186813 additions and 107843 deletions

View File

@@ -0,0 +1,42 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace StellaOps.DependencyInjection.Validation;
public static partial class FailFastOptionsExtensions
{
/// <summary>
/// Adds options with data annotation validation and configures fail-fast startup validation.
/// </summary>
/// <typeparam name="TOptions">The options type to configure. Must have DataAnnotations attributes.</typeparam>
/// <param name="services">The service collection.</param>
/// <param name="sectionName">The configuration section name.</param>
/// <returns>The service collection for chaining.</returns>
public static IServiceCollection AddOptionsWithDataAnnotations<TOptions>(
this IServiceCollection services,
string sectionName)
where TOptions : class
{
services.AddOptions<TOptions>()
.BindConfiguration(sectionName)
.ValidateDataAnnotations()
.ValidateOnStart();
return services;
}
/// <summary>
/// Registers an existing options configuration to validate on start.
/// Use when options are already registered but need fail-fast validation added.
/// </summary>
/// <typeparam name="TOptions">The options type.</typeparam>
/// <param name="services">The service collection.</param>
/// <returns>The service collection for chaining.</returns>
public static IServiceCollection ValidateOptionsOnStart<TOptions>(this IServiceCollection services)
where TOptions : class
{
// Force options validation at startup by resolving IOptions<TOptions>
services.AddHostedService<OptionsValidationHostedService<TOptions>>();
return services;
}
}

View File

@@ -10,7 +10,7 @@ namespace StellaOps.DependencyInjection.Validation;
/// Fail-fast validation ensures configuration errors are detected immediately at startup
/// rather than at first use, following the principle of "fail early, fail loudly."
/// </remarks>
public static class FailFastOptionsExtensions
public static partial class FailFastOptionsExtensions
{
/// <summary>
/// Adds options with a validator and configures fail-fast startup validation.
@@ -84,38 +84,4 @@ public static class FailFastOptionsExtensions
return services;
}
/// <summary>
/// Adds options with data annotation validation and configures fail-fast startup validation.
/// </summary>
/// <typeparam name="TOptions">The options type to configure. Must have DataAnnotations attributes.</typeparam>
/// <param name="services">The service collection.</param>
/// <param name="sectionName">The configuration section name.</param>
/// <returns>The service collection for chaining.</returns>
public static IServiceCollection AddOptionsWithDataAnnotations<TOptions>(
this IServiceCollection services,
string sectionName)
where TOptions : class
{
services.AddOptions<TOptions>()
.BindConfiguration(sectionName)
.ValidateDataAnnotations()
.ValidateOnStart();
return services;
}
/// <summary>
/// Registers an existing options configuration to validate on start.
/// Use when options are already registered but need fail-fast validation added.
/// </summary>
/// <typeparam name="TOptions">The options type.</typeparam>
/// <param name="services">The service collection.</param>
/// <returns>The service collection for chaining.</returns>
public static IServiceCollection ValidateOptionsOnStart<TOptions>(this IServiceCollection services)
where TOptions : class
{
// Force options validation at startup by resolving IOptions<TOptions>
services.AddHostedService<OptionsValidationHostedService<TOptions>>();
return services;
}
}

View File

@@ -0,0 +1,76 @@
namespace StellaOps.DependencyInjection.Validation;
public abstract partial class OptionsValidatorBase<TOptions>
where TOptions : class
{
protected sealed partial class ValidationContext
{
/// <summary>
/// Validates that a string property is not null or whitespace.
/// </summary>
/// <param name="value">The value to check.</param>
/// <param name="propertyName">The property name.</param>
/// <returns>This context for chaining.</returns>
public ValidationContext RequireNotEmpty(string? value, string propertyName)
{
if (string.IsNullOrWhiteSpace(value))
{
AddError(propertyName, "is required and cannot be empty.");
}
return this;
}
/// <summary>
/// Validates that a numeric property is greater than zero.
/// </summary>
/// <param name="value">The value to check.</param>
/// <param name="propertyName">The property name.</param>
/// <returns>This context for chaining.</returns>
public ValidationContext RequirePositive(int value, string propertyName)
{
if (value <= 0)
{
AddError(propertyName, "must be greater than zero.");
}
return this;
}
/// <summary>
/// Validates that a TimeSpan property is greater than zero.
/// </summary>
/// <param name="value">The value to check.</param>
/// <param name="propertyName">The property name.</param>
/// <returns>This context for chaining.</returns>
public ValidationContext RequirePositive(TimeSpan value, string propertyName)
{
if (value <= TimeSpan.Zero)
{
AddError(propertyName, "must be greater than zero.");
}
return this;
}
/// <summary>
/// Validates that a value is within a specified range.
/// </summary>
/// <typeparam name="T">The value type.</typeparam>
/// <param name="value">The value to check.</param>
/// <param name="propertyName">The property name.</param>
/// <param name="min">The minimum allowed value (inclusive).</param>
/// <param name="max">The maximum allowed value (inclusive).</param>
/// <returns>This context for chaining.</returns>
public ValidationContext RequireInRange<T>(T value, string propertyName, T min, T max)
where T : IComparable<T>
{
if (value.CompareTo(min) < 0 || value.CompareTo(max) > 0)
{
AddError(propertyName, $"must be between {min} and {max}.");
}
return this;
}
}
}

View File

@@ -0,0 +1,82 @@
namespace StellaOps.DependencyInjection.Validation;
public abstract partial class OptionsValidatorBase<TOptions>
where TOptions : class
{
/// <summary>
/// Provides a fluent interface for collecting validation errors.
/// </summary>
protected sealed partial class ValidationContext
{
private readonly List<string> _errors = new();
private readonly string _sectionPrefix;
internal ValidationContext(string sectionPrefix)
{
_sectionPrefix = sectionPrefix;
}
/// <summary>
/// Gets the collected errors.
/// </summary>
public IReadOnlyList<string> Errors => _errors;
/// <summary>
/// Returns true if any errors have been added.
/// </summary>
public bool HasErrors => _errors.Count > 0;
/// <summary>
/// Adds a validation error for a specific property.
/// </summary>
/// <param name="propertyName">The property name (e.g., "MaxRetries").</param>
/// <param name="message">The error message.</param>
/// <returns>This context for chaining.</returns>
public ValidationContext AddError(string propertyName, string message)
{
_errors.Add($"{_sectionPrefix}:{propertyName} {message}");
return this;
}
/// <summary>
/// Adds a validation error for a nested property.
/// </summary>
/// <param name="parentProperty">The parent property name.</param>
/// <param name="childProperty">The child property name.</param>
/// <param name="message">The error message.</param>
/// <returns>This context for chaining.</returns>
public ValidationContext AddError(string parentProperty, string childProperty, string message)
{
_errors.Add($"{_sectionPrefix}:{parentProperty}:{childProperty} {message}");
return this;
}
/// <summary>
/// Adds a general validation error.
/// </summary>
/// <param name="message">The error message.</param>
/// <returns>This context for chaining.</returns>
public ValidationContext AddGeneralError(string message)
{
_errors.Add($"{_sectionPrefix}: {message}");
return this;
}
/// <summary>
/// Conditionally adds an error if the condition is true.
/// </summary>
/// <param name="condition">The condition to check.</param>
/// <param name="propertyName">The property name.</param>
/// <param name="message">The error message.</param>
/// <returns>This context for chaining.</returns>
public ValidationContext AddErrorIf(bool condition, string propertyName, string message)
{
if (condition)
{
AddError(propertyName, message);
}
return this;
}
}
}

View File

@@ -6,7 +6,7 @@ namespace StellaOps.DependencyInjection.Validation;
/// Base class for implementing options validators with a fluent error collection pattern.
/// </summary>
/// <typeparam name="TOptions">The options type to validate.</typeparam>
public abstract class OptionsValidatorBase<TOptions> : IValidateOptions<TOptions>
public abstract partial class OptionsValidatorBase<TOptions> : IValidateOptions<TOptions>
where TOptions : class
{
/// <summary>
@@ -35,149 +35,4 @@ public abstract class OptionsValidatorBase<TOptions> : IValidateOptions<TOptions
/// <param name="options">The options to validate.</param>
/// <param name="context">The validation context for collecting errors.</param>
protected abstract void ValidateOptions(TOptions options, ValidationContext context);
/// <summary>
/// Provides a fluent interface for collecting validation errors.
/// </summary>
protected sealed class ValidationContext
{
private readonly List<string> _errors = new();
private readonly string _sectionPrefix;
internal ValidationContext(string sectionPrefix)
{
_sectionPrefix = sectionPrefix;
}
/// <summary>
/// Gets the collected errors.
/// </summary>
public IReadOnlyList<string> Errors => _errors;
/// <summary>
/// Returns true if any errors have been added.
/// </summary>
public bool HasErrors => _errors.Count > 0;
/// <summary>
/// Adds a validation error for a specific property.
/// </summary>
/// <param name="propertyName">The property name (e.g., "MaxRetries").</param>
/// <param name="message">The error message.</param>
/// <returns>This context for chaining.</returns>
public ValidationContext AddError(string propertyName, string message)
{
_errors.Add($"{_sectionPrefix}:{propertyName} {message}");
return this;
}
/// <summary>
/// Adds a validation error for a nested property.
/// </summary>
/// <param name="parentProperty">The parent property name.</param>
/// <param name="childProperty">The child property name.</param>
/// <param name="message">The error message.</param>
/// <returns>This context for chaining.</returns>
public ValidationContext AddError(string parentProperty, string childProperty, string message)
{
_errors.Add($"{_sectionPrefix}:{parentProperty}:{childProperty} {message}");
return this;
}
/// <summary>
/// Adds a general validation error.
/// </summary>
/// <param name="message">The error message.</param>
/// <returns>This context for chaining.</returns>
public ValidationContext AddGeneralError(string message)
{
_errors.Add($"{_sectionPrefix}: {message}");
return this;
}
/// <summary>
/// Conditionally adds an error if the condition is true.
/// </summary>
/// <param name="condition">The condition to check.</param>
/// <param name="propertyName">The property name.</param>
/// <param name="message">The error message.</param>
/// <returns>This context for chaining.</returns>
public ValidationContext AddErrorIf(bool condition, string propertyName, string message)
{
if (condition)
{
AddError(propertyName, message);
}
return this;
}
/// <summary>
/// Validates that a string property is not null or whitespace.
/// </summary>
/// <param name="value">The value to check.</param>
/// <param name="propertyName">The property name.</param>
/// <returns>This context for chaining.</returns>
public ValidationContext RequireNotEmpty(string? value, string propertyName)
{
if (string.IsNullOrWhiteSpace(value))
{
AddError(propertyName, "is required and cannot be empty.");
}
return this;
}
/// <summary>
/// Validates that a numeric property is greater than zero.
/// </summary>
/// <param name="value">The value to check.</param>
/// <param name="propertyName">The property name.</param>
/// <returns>This context for chaining.</returns>
public ValidationContext RequirePositive(int value, string propertyName)
{
if (value <= 0)
{
AddError(propertyName, "must be greater than zero.");
}
return this;
}
/// <summary>
/// Validates that a TimeSpan property is greater than zero.
/// </summary>
/// <param name="value">The value to check.</param>
/// <param name="propertyName">The property name.</param>
/// <returns>This context for chaining.</returns>
public ValidationContext RequirePositive(TimeSpan value, string propertyName)
{
if (value <= TimeSpan.Zero)
{
AddError(propertyName, "must be greater than zero.");
}
return this;
}
/// <summary>
/// Validates that a value is within a specified range.
/// </summary>
/// <typeparam name="T">The value type.</typeparam>
/// <param name="value">The value to check.</param>
/// <param name="propertyName">The property name.</param>
/// <param name="min">The minimum allowed value (inclusive).</param>
/// <param name="max">The maximum allowed value (inclusive).</param>
/// <returns>This context for chaining.</returns>
public ValidationContext RequireInRange<T>(T value, string propertyName, T min, T max)
where T : IComparable<T>
{
if (value.CompareTo(min) < 0 || value.CompareTo(max) > 0)
{
AddError(propertyName, $"must be between {min} and {max}.");
}
return this;
}
}
}