ResultKit integration for Fox.ConfigKit - Railway Oriented Programming for configuration validation. Extends Fox.ConfigKit with Result<T> pattern for functional error handling.
$ dotnet add package Fox.ConfigKit.ResultKitIntegration package combining Fox.ConfigKit configuration validation with Fox.ResultKit Railway Oriented Programming
This package bridges Fox.ConfigKit's configuration validation with Fox.ResultKit's Result pattern, enabling functional-style configuration validation workflows.
dotnet add package Fox.ConfigKit.ResultKit
NuGet Package Manager:
Install-Package Fox.ConfigKit.ResultKit
PackageReference:
<PackageReference Include="Fox.ConfigKit.ResultKit" Version="1.0.0" />
Validate configurations and return Result<T> for functional composition:
using Fox.ConfigKit.ResultKit;
using Fox.ResultKit;
// Standalone validation without dependency injection
var result = ConfigValidator.Validate<DatabaseConfig>()
.NotEmpty(c => c.ConnectionString, "Connection string is required")
.InRange(c => c.MaxPoolSize, 1, 1000, "Max pool size must be between 1 and 1000")
.ToResult(databaseConfig);
// Chain with other operations
var finalResult = result
.Bind(config => DatabaseService.Connect(config))
.Bind(connection => connection.ExecuteQuery())
.Match(
onSuccess: data => $"Query returned {data.Count} records",
onFailure: error => $"Failed: {error}"
);
Note: ConfigValidator.Validate<T>() is useful for:
For ASP.NET Core applications with DI, use AddConfigKit<T>() with ValidateOnStartup() instead.
Validate configurations at startup and handle failures gracefully:
var configValidation = ConfigValidator.Validate<ApiConfig>()
.NotEmpty(c => c.BaseUrl, "API URL is required")
.NotEmpty(c => c.ApiKey, "API key is required")
.ToResult(apiConfig);
if (configValidation.IsFailure)
{
var errors = configValidation.Error;
throw new InvalidOperationException($"Configuration validation failed: {errors}");
}
using Fox.ConfigKit.ResultKit;
using Fox.ResultKit;
public Result<AppConfig> ValidateAppConfiguration(AppConfig config)
{
return ConfigValidator.Validate<AppConfig>()
.NotEmpty(c => c.Name, "Application name is required")
.MatchesPattern(c => c.Version, @"^\d+\.\d+\.\d+$", "Version must be X.Y.Z format")
.InRange(c => c.Port, 1, 65535, "Port must be between 1 and 65535")
.ToResult(config);
}
// Usage with Railway Oriented Programming
var result = ValidateAppConfiguration(appConfig)
.Bind(config => InitializeApplication(config))
.Bind(app => app.Start())
.Match(
onSuccess: app => $"Application {app.Name} started successfully",
onFailure: error => $"Startup failed: {error}"
);
public Result<SecurityConfig> ValidateSecurityConfig(SecurityConfig config, string environment)
{
var builder = ConfigValidator.Validate<SecurityConfig>()
.NotEmpty(c => c.Environment, "Environment is required");
if (environment == "Production")
{
builder = builder
.NotEmpty(c => c.CertificatePath, "Certificate is required in production")
.FileExists(c => c.CertificatePath, message: "Certificate file not found");
}
return builder.ToResult(config);
}
public Result<AppSettings> ValidateAllConfigurations(
DatabaseConfig dbConfig,
ApiConfig apiConfig,
LoggingConfig loggingConfig)
{
var dbResult = ConfigValidator.Validate<DatabaseConfig>()
.NotEmpty(c => c.ConnectionString, "DB connection string is required")
.ToValidationResult(dbConfig);
var apiResult = ConfigValidator.Validate<ApiConfig>()
.NotEmpty(c => c.BaseUrl, "API URL is required")
.ToValidationResult(apiConfig);
var loggingResult = ConfigValidator.Validate<LoggingConfig>()
.DirectoryExists(c => c.LogDirectory, message: "Log directory not found")
.ToValidationResult(loggingConfig);
// Fail-fast: stop at first error
return dbResult
.Bind(() => apiResult)
.Bind(() => loggingResult)
.Bind(() => Result<AppSettings>.Success(new AppSettings(dbConfig, apiConfig, loggingConfig)));
}
var startupResult = ConfigValidator.Validate<DatabaseConfig>()
.NotEmpty(c => c.ConnectionString, "Connection string is required")
.InRange(c => c.MaxPoolSize, 1, 1000, "Max pool size must be between 1 and 1000")
.ToResult(dbConfig)
.BindAsync(config => DatabaseService.TestConnection(config))
.BindAsync(connection => MigrationService.EnsureSchemaUpToDate(connection))
.Match(
onSuccess: _ => "Database ready",
onFailure: error => throw new InvalidOperationException($"Startup failed: {error}")
);
public Result<SecurityConfig> ValidateForEnvironment(SecurityConfig config, IHostEnvironment env)
{
var validator = ConfigValidator.Validate<SecurityConfig>();
if (env.IsProduction())
{
validator = validator
.NotEmpty(c => c.CertificatePath, "Certificate is required in production")
.FileExists(c => c.CertificatePath, message: "Certificate not found")
.NoPlainTextSecrets(c => c.ApiKey, "Plain-text secrets not allowed in production");
}
else if (env.IsDevelopment())
{
validator = validator
.NotEmpty(c => c.ApiKey, "API key is required");
}
return validator.ToResult(config);
}
// Use ToErrorsResult() to collect all validation errors instead of stopping at first error
var validationResult = ConfigValidator.Validate<AppConfig>()
.NotEmpty(c => c.Name, "Name is required")
.InRange(c => c.Port, 1, 65535, "Invalid port")
.NotEmpty(c => c.ApiKey, "API key is required")
.ToErrorsResult(config);
if (validationResult.IsFailure)
{
foreach (var error in validationResult.Errors)
{
Console.WriteLine($"Validation error: {error}");
}
}
ValidateOnStartup)Use ConfigValidator.Validate<T>() for:
// Console applications
static void Main(string[] args)
{
var config = LoadConfiguration();
var result = ConfigValidator.Validate<AppConfig>()
.NotEmpty(c => c.Name, "Name is required")
.ToResult(config);
if (result.IsFailure)
{
Console.WriteLine($"Config error: {result.Error}");
return;
}
}
// Unit tests
[Fact]
public void Configuration_should_be_valid()
{
var config = new DatabaseConfig { ConnectionString = "..." };
var result = ConfigValidator.Validate<DatabaseConfig>()
.NotEmpty(c => c.ConnectionString, "Required")
.ToResult(config);
result.IsSuccess.Should().BeTrue();
}
// Functional composition
public Result<Database> InitializeDatabase(DatabaseConfig config)
{
return ConfigValidator.Validate<DatabaseConfig>()
.NotEmpty(c => c.ConnectionString, "Connection string required")
.ToResult(config)
.Bind(cfg => Database.Connect(cfg));
}
Use AddConfigKit<T>() with ValidateOnStartup() for ASP.NET Core:
// ASP.NET Core applications with dependency injection
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddConfigKit<DatabaseConfig>("Database")
.NotEmpty(c => c.ConnectionString, "Connection string is required")
.InRange(c => c.MaxPoolSize, 1, 1000, "Pool size must be between 1 and 1000")
.ValidateOnStartup();
var app = builder.Build(); // Validation happens here (fail-fast)
app.Run();
Key Differences:
| Feature | ConfigValidator.Validate<T>() | AddConfigKit<T>() |
|---|---|---|
| Use Case | Standalone, functional, testing | ASP.NET Core with DI |
| Returns | ConfigValidationBuilder<T> | ConfigValidationBuilder<T> |
| Validation Timing | Explicit (when you call .ToResult()) | Automatic at startup |
| Result Type | Result<T>, ErrorsResult | Exception on failure |
| DI Required | No | Yes |
| IOptions Integration | No | Yes |
For comprehensive documentation, advanced scenarios, and API reference, see the GitHub repository.
This project is licensed under the MIT License - see the LICENSE.txt file for details.
Károly Akácz
Inspired by: